help.go 7.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271
  1. // License: GPLv3 Copyright: 2022, Kovid Goyal, <kovid at kovidgoyal.net>
  2. package cli
  3. import (
  4. "fmt"
  5. "io"
  6. "os"
  7. "os/exec"
  8. "slices"
  9. "strings"
  10. "time"
  11. "golang.org/x/sys/unix"
  12. "kitty"
  13. "kitty/tools/cli/markup"
  14. "kitty/tools/tty"
  15. "kitty/tools/utils"
  16. "kitty/tools/utils/style"
  17. )
  18. var _ = fmt.Print
  19. func ShowError(err error) {
  20. formatter := markup.New(tty.IsTerminal(os.Stderr.Fd()))
  21. msg := formatter.Prettify(err.Error())
  22. fmt.Fprintln(os.Stderr, formatter.Err("Error")+":", msg)
  23. }
  24. func (self *Command) version_string(formatter *markup.Context) string {
  25. return fmt.Sprintln(formatter.Italic(self.CommandStringForUsage()), formatter.Opt(kitty.VersionString), "created by", formatter.Title("Kovid Goyal"))
  26. }
  27. func (self *Command) ShowVersion() {
  28. formatter := markup.New(tty.IsTerminal(os.Stdout.Fd()))
  29. fmt.Fprint(os.Stdout, self.version_string(formatter))
  30. }
  31. func format_with_indent(output io.Writer, text string, indent string, screen_width int) {
  32. indented := style.WrapText(text, screen_width, style.WrapOptions{Indent: indent, Ignore_lines_containing: "#placeholder_for_formatting#", Trim_whitespace: true})
  33. io.WriteString(output, indented)
  34. io.WriteString(output, "\n")
  35. }
  36. func (self *Command) FormatSubCommands(output io.Writer, formatter *markup.Context, screen_width int) {
  37. for _, g := range self.SubCommandGroups {
  38. if !g.HasVisibleSubCommands() {
  39. continue
  40. }
  41. title := g.Title
  42. if title == "" {
  43. title = "Commands"
  44. }
  45. fmt.Fprintln(output)
  46. fmt.Fprintln(output, formatter.Title(title)+":")
  47. for _, c := range g.SubCommands {
  48. if c.Hidden {
  49. continue
  50. }
  51. fmt.Fprintln(output, " ", formatter.Opt(c.Name))
  52. format_with_indent(output, formatter.Prettify(c.ShortDescription), " ", screen_width)
  53. }
  54. }
  55. }
  56. func (self *Option) FormatOptionForMan(output io.Writer) {
  57. fmt.Fprintln(output, ".TP")
  58. fmt.Fprint(output, ".BI \"")
  59. for i, a := range self.Aliases {
  60. fmt.Fprint(output, a.String())
  61. if i != len(self.Aliases)-1 {
  62. fmt.Fprint(output, ", ")
  63. }
  64. }
  65. fmt.Fprint(output, "\" ")
  66. defval := self.Default
  67. switch self.OptionType {
  68. case StringOption:
  69. if self.IsList {
  70. defval = ""
  71. }
  72. case BoolOption, CountOption:
  73. defval = ""
  74. }
  75. if defval != "" {
  76. fmt.Fprintf(output, "\" [=%s]\"", escape_text_for_man(defval))
  77. }
  78. fmt.Fprintln(output)
  79. fmt.Fprintln(output, escape_help_for_man(self.Help))
  80. if self.Choices != nil {
  81. fmt.Fprintln(output)
  82. fmt.Fprintln(output, "Choices: "+strings.Join(self.Choices, ", "))
  83. }
  84. }
  85. func (self *Option) FormatOption(output io.Writer, formatter *markup.Context, screen_width int) {
  86. fmt.Fprint(output, " ")
  87. for i, a := range self.Aliases {
  88. fmt.Fprint(output, formatter.Opt(a.String()))
  89. if i != len(self.Aliases)-1 {
  90. fmt.Fprint(output, ", ")
  91. }
  92. }
  93. defval := self.Default
  94. switch self.OptionType {
  95. case StringOption:
  96. if self.IsList {
  97. defval = ""
  98. }
  99. case BoolOption, CountOption:
  100. defval = ""
  101. }
  102. if defval != "" {
  103. fmt.Fprintf(output, " [=%s]", formatter.Italic(defval))
  104. }
  105. fmt.Fprintln(output)
  106. format_with_indent(output, formatter.Prettify(prepare_help_text_for_display(self.Help)), " ", screen_width)
  107. if self.Choices != nil {
  108. format_with_indent(output, "Choices: "+strings.Join(self.Choices, ", "), " ", screen_width)
  109. }
  110. }
  111. func (self *Command) ShowHelp() {
  112. self.ShowHelpWithCommandString(strings.TrimSpace(self.CommandStringForUsage()))
  113. }
  114. func ShowHelpInPager(text string) {
  115. pager := exec.Command(kitty.DefaultPager[0], kitty.DefaultPager[1:]...)
  116. pager.Stdin = strings.NewReader(text)
  117. pager.Stdout = os.Stdout
  118. pager.Stderr = os.Stderr
  119. _ = pager.Run()
  120. }
  121. func (self *Command) GenerateManPages(level int, recurse bool) (err error) {
  122. var names []string
  123. p := self
  124. for p != nil {
  125. names = append(names, p.Name)
  126. p = p.Parent
  127. }
  128. slices.Reverse(names)
  129. name := strings.Join(names, "-")
  130. outf, err := os.Create(fmt.Sprintf("%s.%d", name, level))
  131. if err != nil {
  132. return err
  133. }
  134. defer outf.Close()
  135. fmt.Fprintf(outf, `.TH "%s" "1" "%s" "%s" "%s"`, name, time.Now().Format("Jan 02, 2006"), kitty.VersionString, "kitten Manual")
  136. fmt.Fprintln(outf)
  137. fmt.Fprintln(outf, ".SH Name")
  138. fmt.Fprintln(outf, name, "\\-", escape_text_for_man(self.ShortDescription))
  139. fmt.Fprintln(outf, ".SH Usage")
  140. fmt.Fprintln(outf, ".SY", `"`+self.CommandStringForUsage()+` `+self.Usage+`"`)
  141. fmt.Fprintln(outf, ".YS")
  142. if self.HelpText != "" {
  143. fmt.Fprintln(outf, ".SH Description")
  144. fmt.Fprintln(outf, escape_help_for_man(self.HelpText))
  145. }
  146. if self.HasVisibleSubCommands() {
  147. for _, g := range self.SubCommandGroups {
  148. if !g.HasVisibleSubCommands() {
  149. continue
  150. }
  151. title := g.Title
  152. if title == "" {
  153. title = "Commands"
  154. }
  155. fmt.Fprintln(outf, ".SH", title)
  156. for _, c := range utils.Sort(g.SubCommands, func(a, b *Command) int { return strings.Compare(a.Name, b.Name) }) {
  157. if c.Hidden {
  158. continue
  159. }
  160. if recurse {
  161. if err = c.GenerateManPages(level, recurse); err != nil {
  162. return err
  163. }
  164. }
  165. fmt.Fprintln(outf, ".TP", "2")
  166. fmt.Fprintln(outf, c.Name)
  167. fmt.Fprintln(outf, escape_text_for_man(c.ShortDescription)+".", "See: ")
  168. fmt.Fprintf(outf, ".MR %s %d\n", name+"-"+c.Name, level)
  169. }
  170. }
  171. fmt.Fprintln(outf, ".PP")
  172. fmt.Fprintln(outf, "Get help for an individual command by running:")
  173. fmt.Fprintln(outf, ".SY", self.CommandStringForUsage())
  174. fmt.Fprintln(outf, "command", "-h")
  175. fmt.Fprintln(outf, ".YS")
  176. }
  177. group_titles, gmap := self.GetVisibleOptions()
  178. if len(group_titles) > 0 {
  179. for _, title := range group_titles {
  180. ptitle := title
  181. if title == "" {
  182. ptitle = "Options"
  183. }
  184. fmt.Fprintln(outf, ".SH", ptitle)
  185. for _, opt := range gmap[title] {
  186. opt.FormatOptionForMan(outf)
  187. }
  188. }
  189. }
  190. return
  191. }
  192. func (self *Command) ShowHelpWithCommandString(cs string) {
  193. formatter := markup.New(tty.IsTerminal(os.Stdout.Fd()))
  194. screen_width := 80
  195. if formatter.EscapeCodesAllowed() {
  196. var sz *unix.Winsize
  197. var tty_size_err error
  198. for {
  199. sz, tty_size_err = unix.IoctlGetWinsize(int(os.Stdout.Fd()), unix.TIOCGWINSZ)
  200. if tty_size_err != unix.EINTR {
  201. break
  202. }
  203. }
  204. if tty_size_err == nil && sz.Col < 80 {
  205. screen_width = int(sz.Col)
  206. }
  207. }
  208. var output strings.Builder
  209. fmt.Fprintln(&output, formatter.Title("Usage")+":", formatter.Exe(cs), strings.TrimSpace(formatter.Prettify(self.Usage)))
  210. fmt.Fprintln(&output)
  211. if self.HelpText != "" {
  212. format_with_indent(&output, formatter.Prettify(prepare_help_text_for_display(self.HelpText)), "", screen_width)
  213. } else if self.ShortDescription != "" {
  214. format_with_indent(&output, formatter.Prettify(self.ShortDescription), "", screen_width)
  215. }
  216. if self.HasVisibleSubCommands() {
  217. self.FormatSubCommands(&output, formatter, screen_width)
  218. fmt.Fprintln(&output)
  219. format_with_indent(&output, "Get help for an individual command by running:", "", screen_width)
  220. fmt.Fprintln(&output, " ", strings.TrimSpace(self.CommandStringForUsage()), formatter.Italic("command"), "-h")
  221. }
  222. group_titles, gmap := self.GetVisibleOptions()
  223. if len(group_titles) > 0 {
  224. fmt.Fprintln(&output)
  225. for _, title := range group_titles {
  226. ptitle := title
  227. if title == "" {
  228. ptitle = "Options"
  229. }
  230. fmt.Fprintln(&output, formatter.Title(ptitle)+":")
  231. for _, opt := range gmap[title] {
  232. opt.FormatOption(&output, formatter, screen_width)
  233. fmt.Fprintln(&output)
  234. }
  235. }
  236. }
  237. output.WriteString(self.version_string(formatter))
  238. output_text := output.String()
  239. // fmt.Printf("%#v\n", output_text)
  240. if formatter.EscapeCodesAllowed() {
  241. ShowHelpInPager(output_text)
  242. } else {
  243. os.Stdout.WriteString(output_text)
  244. }
  245. }