From 6f7bea24d0cca642ad74ede0da2c22adf42cfc31 Mon Sep 17 00:00:00 2001 From: surdeus Date: Thu, 9 Nov 2023 23:44:57 +0300 Subject: [PATCH] Implemented more unified way for usage, description and options. --- cmd/test/main.go | 54 +++++++----- main.go | 136 ----------------------------- mtool/main.go | 217 +++++++++++++++++++++++++++++++++++++++++++++++ taskfile.yml | 7 ++ 4 files changed, 258 insertions(+), 156 deletions(-) delete mode 100644 main.go create mode 100644 mtool/main.go create mode 100644 taskfile.yml diff --git a/cmd/test/main.go b/cmd/test/main.go index 920cf0f..f7a420b 100644 --- a/cmd/test/main.go +++ b/cmd/test/main.go @@ -1,40 +1,54 @@ package main import ( - "github.com/omnipunk/cli" + "github.com/omnipunk/cli/mtool" "strconv" "fmt" + "os" ) var ( - tools = mtool.Tools{ - "echo": mtool.Tool{ - func(flags *mtool.Flags) { + root = mtool.T("test").Subs( + mtool.T("echo").Func(func(flags *mtool.Flags) { var b bool flags.BoolVar(&b, "b", false, "the check flag") - flags.Parse() - - args := flags.Args() - + args := flags.Parse() fmt.Println(args) - }, - "print string to standard output string", - "[str1 str2 ... strN]", - }, - "sum": mtool.Tool{ - func(flags *mtool.Flags) { + }).Desc( + "print string to standard output string", + ).Usage( + "[str1 str2 ... strN]", + ), + mtool.T("sum").Func(func(flags *mtool.Flags) { flags.Parse() args := flags.Args() one, _ := strconv.Atoi(args[1]) two, _ := strconv.Atoi(args[2]) fmt.Println(one + two) - }, - "add one value to another", - " ", - }, - } + }).Desc( + "add one value to another", + ).Usage( + " ", + ), + mtool.T("sub").Subs( + mtool.T("first").Func(func(flags *mtool.Flags) { + fmt.Println("called the first", flags.Parse()) + }).Desc( + "description", + ).Usage( + "[nothing here]", + ), + mtool.T("second").Func(func(flags *mtool.Flags){ + fmt.Println("called the second", flags.Parse()) + }).Desc( + "description", + ).Usage( + "[nothing here]", + ), + ), + ) ) func main() { - mtool.Main("test", tools) + root.Run(os.Args[1:]) } diff --git a/main.go b/main.go deleted file mode 100644 index a0fc154..0000000 --- a/main.go +++ /dev/null @@ -1,136 +0,0 @@ -package mtool - -import( - "fmt" - "os" - path "path/filepath" - "flag" - "sort" -) - -type Flags struct { - *flag.FlagSet - progName string - utilName string - args []string - parsedArgs []string -} - -type Handler func(*Flags) - -type Tool struct { - Handler Handler - Desc, Usage string -} - -type Tools map[string] Tool - -func (flags *Flags) Parse() []string { - flags.FlagSet.Parse(flags.args) - flags.parsedArgs = flags.FlagSet.Args() - return flags.parsedArgs -} - -func (flags *Flags) AllArgs() []string { - return flags.args -} - -func (flags *Flags) Args() []string { - return flags.parsedArgs -} - -func (flags *Flags) ProgName() string { - return flags.progName -} - -func (flags *Flags) UtilName() string { - return flags.utilName -} - -func Main(name string, m Tools) { - var( - utilName string - args []string - ) - - arg0 := os.Args[0] - binBase := path.Base(arg0) ; - binBase = binBase[:len(binBase)-len(path.Ext(binBase))] - if binBase != name { - utilName = binBase - args = os.Args - } else { - if len(os.Args)<2 { - keys := make([]string, len(m)) - i := 0 - for k, _ := range m { - keys[i] = k - i++ - } - sort.Strings(keys) - - for _, k := range keys { - tool := m[k] - fmt.Printf("%s: %s\n", k, tool.Desc) - } - - os.Exit(0) - } - utilName = os.Args[1] - args = os.Args[1:] - } - - if _, ok := m[utilName] ; !ok { - fmt.Printf("%s: No such uitl as '%s'.\n", arg0, utilName ) - os.Exit(1) - } - - util := m[utilName] - - flagSet := flag.NewFlagSet(utilName, flag.ExitOnError) - flags := &Flags{ - FlagSet : flagSet, - } - flags.Usage = func() { - out := flags.Output() - n := 0 - flags.VisitAll(func(f *flag.Flag){ - n++ - }) - - hasOptions := n != 0 - - fmt.Fprintf( - out, - "Usage of %s:\n\t%s", - utilName, utilName, - ) - if hasOptions { - fmt.Fprintf(out, " [options]") - } - - if util.Usage != "" { - fmt.Fprintf( - out, - " %s", - util.Usage, - ) - } - - fmt.Fprintln(out, "") - - if hasOptions { - fmt.Fprintln(out, "Options:") - flags.PrintDefaults() - } - - os.Exit(1) - } - - flags.progName = name - flags.utilName = utilName - flags.args = args[1:] - - util.Handler(flags) -} - diff --git a/mtool/main.go b/mtool/main.go new file mode 100644 index 0000000..a073d5d --- /dev/null +++ b/mtool/main.go @@ -0,0 +1,217 @@ +package mtool + +// The package implements fast way +// to make multitool CLI applications. + +import ( + "fmt" + "os" + //path "path/filepath" + "flag" + "sort" +) + + +type Flags struct { + *flag.FlagSet + tool *Tool + args []string + parsedArgs []string +} + +type Handler interface { + Handle(*Flags) +} + +type HandlerFunc func(*Flags) +func (fn HandlerFunc) Handle(flags *Flags) { + fn(flags) +} + +type Tool struct { + name string + handler Handler + desc, usage string + subs ToolMap + parent *Tool +} + +// Returns new empty tool with specified name. +func T(name string) *Tool { + ret := &Tool{} + ret.name = name + return ret +} + +func (t *Tool) Handler(h Handler) *Tool { + t.handler = h + return t +} + +func (t *Tool) Func(fn HandlerFunc) *Tool { + t.handler = fn + return t +} + +func (t *Tool) Desc(d string) *Tool { + t.desc = d + return t +} + +func (t *Tool) Usage(u string) *Tool { + t.usage = u + return t +} + +func (t *Tool) Subs(tools ...*Tool) *Tool { + if t.subs == nil { + t.subs = ToolMap{} + } + for _, tool := range tools { + tool.parent = t + t.subs[tool.name] = tool + } + return t +} + +func (t *Tool) Name() string { + return t.name +} + +func (t *Tool) FullName() string { + ret := "" + for t != nil { + ret = t.name + func() string { + if ret != "" { + return " " + } + return "" + }() + ret + t = t.parent + } + return ret +} + +func (t *Tool) ProgName() string { + for t.parent != nil { + t = t.parent + } + return t.name +} + +func (t *Tool) Run(args []string) { + var( + usageTool *Tool + ) + // Should implement the shit later + //binBase := path.Base(arg0) ; + //binBase = binBase[:len(binBase)-len(path.Ext(binBase))] + + flagSet := flag.NewFlagSet(t.FullName(), flag.ExitOnError) + flags := &Flags{ + FlagSet : flagSet, + } + out := flags.Output() + flags.Usage = func() { + n := 0 + // We can visit the flags since the + // function will be called after + // parsing. + flags.VisitAll(func(f *flag.Flag){ + n++ + }) + + hasOptions := n != 0 + + fmt.Fprintf( + out, "Usage:\n %s", + usageTool.FullName(), + ) + if hasOptions { + fmt.Fprintf(out, " [options]") + } + + if usageTool.usage != "" { + fmt.Fprintf( + out, + " %s", + usageTool.usage, + ) + } + + fmt.Fprintln(out, "") + + if hasOptions { + fmt.Fprintln(out, "\nOptions:") + flags.PrintDefaults() + } + + os.Exit(1) + } + + flags.args = args + + // If the tool has its own handler run it. + if t.handler != nil { + usageTool = t + t.handler.Handle(flags) + return + } + + // Print available sub commands if + // got no arguments. + if len(args) == 0 { + keys := make([]string, len(t.subs)) + i := 0 + for k, _ := range t.subs { + keys[i] = k + i++ + } + sort.Strings(keys) + + fmt.Fprintf(out, "Usage:\n"+ + " %s [options] [arguments]\n\n" + + "Commands:\n", t.FullName()) + for i, k := range keys { + tool := t.subs[k] + fmt.Fprintf(out, " %s\t%s\n", k, tool.desc) + if i != len(keys) - 1 { + fmt.Fprintln(out, "") + } + } + + os.Exit(1) + } + toolName := args[0] + args = args[1:] + + if _, ok := t.subs[toolName] ; !ok { + fmt.Printf("%s: No such util %q'\n", t.ProgName(), toolName) + os.Exit(1) + } + + sub := t.subs[toolName] + usageTool = sub + sub.Run(args) +} + +type ToolMap map[string] *Tool + +func (flags *Flags) Parse() []string { + flags.FlagSet.Parse(flags.args) + flags.parsedArgs = flags.FlagSet.Args() + return flags.parsedArgs +} + +func (flags *Flags) AllArgs() []string { + return flags.args +} + +func (flags *Flags) Args() []string { + return flags.parsedArgs +} + +func (flags *Flags) Tool() *Tool { + return flags.tool +} + diff --git a/taskfile.yml b/taskfile.yml new file mode 100644 index 0000000..13095cb --- /dev/null +++ b/taskfile.yml @@ -0,0 +1,7 @@ +version: '3' + +tasks: + btest: + cmds: + - go build ./cmd/test +