tool.go 4.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281
  1. package mtool
  2. import (
  3. "flag"
  4. "fmt"
  5. "io"
  6. "os"
  7. "sort"
  8. "strings"
  9. "text/tabwriter"
  10. )
  11. type Tool struct {
  12. name string
  13. handler Handler
  14. desc, ldesc, usage string
  15. subs ToolMap
  16. parent *Tool
  17. }
  18. // Returns new empty tool with specified name.
  19. func T(name string) *Tool {
  20. ret := &Tool{}
  21. ret.name = name
  22. return ret
  23. }
  24. func (t *Tool) Handler(h Handler) *Tool {
  25. t.handler = h
  26. return t
  27. }
  28. func (t *Tool) Func(fn HandlerFunc) *Tool {
  29. t.handler = fn
  30. return t
  31. }
  32. func (t *Tool) Desc(d string) *Tool {
  33. t.desc = d
  34. return t
  35. }
  36. func (t *Tool) Ldesc(d string) *Tool {
  37. t.ldesc = d
  38. return t
  39. }
  40. func (t *Tool) Usage(u string) *Tool {
  41. t.usage = u
  42. return t
  43. }
  44. func (t *Tool) Subs(tools ...*Tool) *Tool {
  45. if t.subs == nil {
  46. t.subs = ToolMap{}
  47. }
  48. for _, tool := range tools {
  49. tool.parent = t
  50. t.subs[tool.name] = tool
  51. }
  52. return t
  53. }
  54. func (t *Tool) Name() string {
  55. return t.name
  56. }
  57. func (t *Tool) FullName() string {
  58. ret := ""
  59. for t != nil {
  60. ret = t.name + func() string {
  61. if ret != "" {
  62. return " "
  63. }
  64. return ""
  65. }() + ret
  66. t = t.parent
  67. }
  68. return ret
  69. }
  70. func (t *Tool) IsRoot() bool {
  71. return t.parent == nil
  72. }
  73. func (t *Tool) ProgName() string {
  74. for t.parent != nil {
  75. t = t.parent
  76. }
  77. return t.name
  78. }
  79. func (t *Tool) PrintSubs(out io.Writer) {
  80. w := new(tabwriter.Writer)
  81. w.Init(out, 0, 0, 1, ' ', 0)
  82. defer w.Flush()
  83. keys := make([]string, len(t.subs))
  84. i := 0
  85. for k, _ := range t.subs {
  86. keys[i] = k
  87. i++
  88. }
  89. sort.Strings(keys)
  90. for _, k := range keys {
  91. tool := t.subs[k]
  92. fmt.Fprintf(w, " %s\t%s\n", k, tool.desc)
  93. }
  94. }
  95. func (t *Tool) Run(args []string, customArgs ...any) {
  96. var(
  97. usageTool *Tool
  98. )
  99. // Should implement the shit later
  100. //binBase := path.Base(arg0) ;
  101. //binBase = binBase[:len(binBase)-len(path.Ext(binBase))]
  102. flagSet := flag.NewFlagSet(t.FullName(), flag.ExitOnError)
  103. flags := &Flags{
  104. FlagSet : flagSet,
  105. envMap: make(map[string]string),
  106. envNameMap: make(map[string] []string),
  107. customArgs: customArgs,
  108. }
  109. out := flags.Output()
  110. flags.Usage = func() {
  111. nflags := 0
  112. flags.VisitAll(func(f *flag.Flag){
  113. nflags++
  114. f.Usage = FormatInCode(f.Usage, false)
  115. varNames, ok := flags.envNameMap[f.Name]
  116. if !ok || len(varNames) == 0 {
  117. return
  118. }
  119. f.Usage += "\n("
  120. for i, name := range varNames {
  121. f.Usage += "$"+name
  122. if i < len(varNames) - 1 {
  123. f.Usage += ", "
  124. }
  125. }
  126. f.Usage += ")"
  127. })
  128. hasOptions := nflags != 0
  129. // Name
  130. if usageTool.desc != "" {
  131. fmt.Fprintf(
  132. out, "Name:\n %s - %s\n\n",
  133. usageTool.FullName(), FormatInCode(t.desc, true),
  134. )
  135. }
  136. // Usage
  137. fmt.Fprintf(
  138. out, "Usage:\n %s",
  139. usageTool.FullName(),
  140. )
  141. if hasOptions {
  142. fmt.Fprintf(out, " [options]")
  143. }
  144. if usageTool.usage != "" {
  145. fmt.Fprintf(
  146. out,
  147. " %s",
  148. usageTool.usage,
  149. )
  150. }
  151. fmt.Fprintln(out, "")
  152. if usageTool.ldesc != "" {
  153. fmt.Fprintf(
  154. out,
  155. "\nDescription:\n %s\n",
  156. FormatInCode(usageTool.ldesc, true),
  157. )
  158. }
  159. // Options
  160. if hasOptions {
  161. fmt.Fprintln(out, "\nOptions:")
  162. fmt.Fprintln(out, " -- option terminator")
  163. flags.PrintDefaults()
  164. }
  165. }
  166. flags.args = args
  167. // If the tool has its own handler run it.
  168. if t.handler != nil {
  169. usageTool = t
  170. t.handler.Handle(flags)
  171. return
  172. }
  173. // Print available sub commands if
  174. // got no arguments.
  175. if len(args) == 0 || func() bool {
  176. toolName := args[0]
  177. _, ok := t.subs[toolName]
  178. if ok {
  179. return false
  180. }
  181. if toolName != "" && toolName[0]=='-' {
  182. return true
  183. }
  184. return false
  185. }() {
  186. if t.desc != "" {
  187. fmt.Fprintf(
  188. out, "Name:\n %s - %s\n\n",
  189. t.FullName(), FormatInCode(t.desc, true),
  190. )
  191. }
  192. fmt.Fprintf(out, "Usage:\n"+
  193. " %s <command>\n", t.FullName())
  194. if t.ldesc != "" {
  195. fmt.Fprintf(
  196. out,
  197. "\nDescription:\n %s\n",
  198. FormatInCode(t.ldesc, true),
  199. )
  200. }
  201. if len(t.subs) > 0 {
  202. fmt.Fprint(out, "\nCommands:\n")
  203. t.PrintSubs(out)
  204. }
  205. os.Exit(1)
  206. }
  207. toolName := args[0]
  208. args = args[1:]
  209. if _, ok := t.subs[toolName] ; !ok {
  210. fmt.Fprintf(
  211. out,
  212. "%s: No such util %q'\n" +
  213. "use ' %s ' to see available commands\n",
  214. t.FullName(), toolName,
  215. t.FullName(),
  216. )
  217. os.Exit(1)
  218. }
  219. sub := t.subs[toolName]
  220. usageTool = sub
  221. sub.Run(args, customArgs...)
  222. }
  223. // Returns the built-in
  224. // string in a more printable format.
  225. // Is used in printing descriptions.
  226. func FormatInCode(
  227. desc string,
  228. addSpaces bool,
  229. ) string {
  230. if desc == "" {
  231. return desc
  232. }
  233. desc = strings.ReplaceAll(desc, "\t", "")
  234. if desc[0] == '\n' {
  235. desc = desc[1:]
  236. }
  237. if desc[len(desc)-1] == '\n' {
  238. desc = desc[:len(desc)-1]
  239. }
  240. if addSpaces {
  241. desc = strings.ReplaceAll(desc, "\n", "\n ")
  242. }
  243. return desc
  244. }