From 5519d5890856780bef287d86b7d211a535a1a100 Mon Sep 17 00:00:00 2001 From: Evan Fiordeliso Date: Fri, 10 Nov 2023 13:17:01 -0500 Subject: [PATCH] Add help menu --- cmd/test.go | 46 ++++++++++++++++++++++++++++++++++++++++++++ command.go | 55 +++++++++++++++++++++++++++++++++++------------------ help.go | 53 +++++++++++++++++++++++++++++++++++++++++++++++++++ option.go | 32 +++++++++++++++++++++---------- 4 files changed, 158 insertions(+), 28 deletions(-) create mode 100644 cmd/test.go create mode 100644 help.go diff --git a/cmd/test.go b/cmd/test.go new file mode 100644 index 0000000..1805b2b --- /dev/null +++ b/cmd/test.go @@ -0,0 +1,46 @@ +package main + +import ( + "fmt" + "os" + + "go.fifitido.net/cmd" +) + +var root = cmd.NewRoot( + cmd.WithShortDescription("Example command"), + cmd.WithLongDescription(`An example command to show how to use go.fifitido.net/cmd + +this example is just a simple hello world program to show +the basics of the library.`), + cmd.WithSubcommand(subcmd), + cmd.WithArgument("name", false), + cmd.WithRunFunc(func(args []string) { + if len(args) == 0 { + fmt.Println("Hello World!") + } else { + fmt.Printf("Hello %s!\n", args[0]) + } + }), +) + +var subcmd = cmd.New( + "test", + cmd.WithShortDescription("Example command"), + cmd.WithLongDescription(`An example command to show how to use go.fifitido.net/cmd + +this example is just a simple hello world program to show +the basics of the library.`), + cmd.WithArgument("name", false), + cmd.WithRunFunc(func(args []string) { + if len(args) == 0 { + fmt.Println("Hello World!") + } else { + fmt.Printf("Hello %s!\n", args[0]) + } + }), +) + +func main() { + root.Execute(os.Args) +} diff --git a/command.go b/command.go index 7717868..ca200a1 100644 --- a/command.go +++ b/command.go @@ -1,20 +1,22 @@ package cmd -import "go.fifitido.net/cmd/flags" +import ( + "os" + "path/filepath" -type RunFunc func(args []string) -type RunErrFunc func(args []string) error + "go.fifitido.net/cmd/flags" +) type Command struct { Name string ShortDescription string LongDescription string - Aliases []string - Arguments []*Argument - Flags []flags.Flag - Subcommands []*Command - Run RunFunc - RunE RunErrFunc + aliases []string + arguments []*Argument + flags []flags.Flag + subcommands []*Command + parent *Command + run func(args []string) isRoot bool } @@ -36,36 +38,53 @@ func (c *Command) ApplyOptions(options ...Option) { } } +func (c *Command) CommandPath() string { + if c.parent == nil { + return filepath.Base(os.Args[0]) + } + + return c.parent.CommandPath() + " " + c.Name +} + +func (c *Command) CanRun() bool { + return c.run != nil +} + +func (c *Command) Run(args []string) { + c.run(args) +} + func (c *Command) Execute(args []string) { if c.isRoot { args = args[1:] } if len(args) > 0 { - for _, subcommand := range c.Subcommands { + for _, subcommand := range c.subcommands { if subcommand.Name == args[0] { subcommand.Execute(args[1:]) return } - for _, alias := range subcommand.Aliases { + for _, alias := range subcommand.aliases { if alias == args[0] { subcommand.Execute(args[1:]) return } } } + + // TODO: remove when done with flag parsing + if args[0] == "--help" { + c.ShowHelp() + return + } } - if c.Run != nil { + if c.CanRun() { c.Run(args) return } - if c.RunE != nil { - if err := c.RunE(args); err != nil { - panic(err) - } - return - } + c.ShowHelp() } diff --git a/help.go b/help.go new file mode 100644 index 0000000..1d540db --- /dev/null +++ b/help.go @@ -0,0 +1,53 @@ +package cmd + +import ( + "fmt" +) + +func (c *Command) ShowHelp() { + cmdPath := c.CommandPath() + + fmt.Println(c.LongDescription) + fmt.Println() + fmt.Println("Usage: ") + fmt.Printf(" %s ", cmdPath) + + if len(c.subcommands) > 0 { + if c.CanRun() { + fmt.Print("[command] ") + } else { + fmt.Print(" ") + } + } + + fmt.Println("[flags]") + + if len(c.subcommands) > 0 { + fmt.Println() + + if c.isRoot { + fmt.Println("Available commands:") + } else { + fmt.Println("Available subcommands:") + } + + for _, s := range c.subcommands { + fmt.Println(" " + s.Name + " " + s.ShortDescription) + } + } + + fmt.Println() + fmt.Println("Available flags:") + + for _, s := range c.flags { + fmt.Println(" " + s.Name() + " " + s.Description()) + } + + fmt.Println(" -h, --help Show the help menu") + fmt.Println(" -v, --version Show the app version") + + if len(c.subcommands) > 0 { + fmt.Println() + fmt.Println("Run 'go-cli --help' for more information about a command.") + } +} diff --git a/option.go b/option.go index 27317b6..5a1b3e4 100644 --- a/option.go +++ b/option.go @@ -18,48 +18,60 @@ func WithLongDescription(s string) Option { func WithArgument(name string, required bool) Option { return func(c *Command) { - c.Arguments = append(c.Arguments, &Argument{name, required}) + c.arguments = append(c.arguments, &Argument{name, required}) } } func WithArguments(args []*Argument) Option { return func(c *Command) { - c.Arguments = append(c.Arguments, args...) + c.arguments = append(c.arguments, args...) } } func WithFlag(f flags.Flag) Option { return func(c *Command) { - c.Flags = append(c.Flags, f) + c.flags = append(c.flags, f) } } func WithFlags(fs []flags.Flag) Option { return func(c *Command) { - c.Flags = append(c.Flags, fs...) + c.flags = append(c.flags, fs...) } } func WithSubcommand(s *Command) Option { return func(c *Command) { - c.Subcommands = append(c.Subcommands, s) + c.subcommands = append(c.subcommands, s) + s.parent = c } } func WithSubcommands(ss []*Command) Option { return func(c *Command) { - c.Subcommands = append(c.Subcommands, ss...) + c.subcommands = append(c.subcommands, ss...) } } -func WithRunFunc(r RunFunc) Option { +func WithParent(p *Command) Option { return func(c *Command) { - c.Run = r + c.parent = p + p.subcommands = append(p.subcommands, c) } } -func WithRunErrFunc(r RunErrFunc) Option { +func WithRunFunc(r func(args []string)) Option { return func(c *Command) { - c.RunE = r + c.run = r + } +} + +func WithRunErrFunc(r func(args []string) error) Option { + return func(c *Command) { + c.run = func(args []string) { + if err := r(args); err != nil { + panic(err) + } + } } }