From 258bc82b69ccb0f514fc62ec8ecd7273458ab2e4 Mon Sep 17 00:00:00 2001 From: Mohammed Al Sahaf Date: Wed, 31 Aug 2022 01:38:38 +0300 Subject: cmd: Migrate to `spf13/cobra`, remove single-dash arg support (#4565) * cmd: migrate to spf13/cobra * add `manpage` command * limit Caddy tagline to root `help` only * hard-code the manpage section to 8 --- cmd/cobra.go | 33 +++++++++++++++++++++++++++ cmd/commandfuncs.go | 65 ----------------------------------------------------- cmd/commands.go | 57 ++++++++++++++++++++++++++++++++++++---------- cmd/main.go | 49 ++++------------------------------------ 4 files changed, 82 insertions(+), 122 deletions(-) create mode 100644 cmd/cobra.go (limited to 'cmd') diff --git a/cmd/cobra.go b/cmd/cobra.go new file mode 100644 index 0000000..ad95ec0 --- /dev/null +++ b/cmd/cobra.go @@ -0,0 +1,33 @@ +package caddycmd + +import ( + "github.com/spf13/cobra" +) + +var rootCmd = &cobra.Command{ + Use: "caddy", +} + +const docsHeader = "{{if not .HasParent}} Caddy is an extensible server platform.\n\n{{end}}" +const fullDocsFooter = `Full documentation is available at: +https://caddyserver.com/docs/command-line +` + +func init() { + rootCmd.SetHelpTemplate(docsHeader + rootCmd.HelpTemplate() + "\n" + fullDocsFooter) +} + +func caddyCmdToCoral(caddyCmd Command) *cobra.Command { + cmd := &cobra.Command{ + Use: caddyCmd.Name, + Short: caddyCmd.Short, + Long: caddyCmd.Long, + RunE: func(cmd *cobra.Command, _ []string) error { + fls := cmd.Flags() + _, err := caddyCmd.Func(Flags{fls}) + return err + }, + } + cmd.Flags().AddGoFlagSet(caddyCmd.Flags) + return cmd +} diff --git a/cmd/commandfuncs.go b/cmd/commandfuncs.go index 67015f7..874cc6f 100644 --- a/cmd/commandfuncs.go +++ b/cmd/commandfuncs.go @@ -29,7 +29,6 @@ import ( "os/exec" "runtime" "runtime/debug" - "sort" "strings" "github.com/aryann/difflib" @@ -580,70 +579,6 @@ func cmdFmt(fl Flags) (int, error) { return caddy.ExitCodeSuccess, nil } -func cmdHelp(fl Flags) (int, error) { - const fullDocs = `Full documentation is available at: -https://caddyserver.com/docs/command-line` - - args := fl.Args() - if len(args) == 0 { - s := `Caddy is an extensible server platform. - -usage: - caddy [] - -commands: -` - keys := make([]string, 0, len(commands)) - for k := range commands { - keys = append(keys, k) - } - sort.Strings(keys) - for _, k := range keys { - cmd := commands[k] - short := strings.TrimSuffix(cmd.Short, ".") - s += fmt.Sprintf(" %-15s %s\n", cmd.Name, short) - } - - s += "\nUse 'caddy help ' for more information about a command.\n" - s += "\n" + fullDocs + "\n" - - fmt.Print(s) - - return caddy.ExitCodeSuccess, nil - } else if len(args) > 1 { - return caddy.ExitCodeFailedStartup, fmt.Errorf("can only give help with one command") - } - - subcommand, ok := commands[args[0]] - if !ok { - return caddy.ExitCodeFailedStartup, fmt.Errorf("unknown command: %s", args[0]) - } - - helpText := strings.TrimSpace(subcommand.Long) - if helpText == "" { - helpText = subcommand.Short - if !strings.HasSuffix(helpText, ".") { - helpText += "." - } - } - - result := fmt.Sprintf("%s\n\nusage:\n caddy %s %s\n", - helpText, - subcommand.Name, - strings.TrimSpace(subcommand.Usage), - ) - - if help := flagHelp(subcommand.Flags); help != "" { - result += fmt.Sprintf("\nflags: (NOTE: prefix flags with `--` instead of `-`)\n%s", help) - } - - result += "\n" + fullDocs + "\n" - - fmt.Print(result) - - return caddy.ExitCodeSuccess, nil -} - // AdminAPIRequest makes an API request according to the CLI flags given, // with the given HTTP method and request URI. If body is non-nil, it will // be assumed to be Content-Type application/json. The caller should close diff --git a/cmd/commands.go b/cmd/commands.go index e454f7b..2085dfc 100644 --- a/cmd/commands.go +++ b/cmd/commands.go @@ -16,7 +16,13 @@ package caddycmd import ( "flag" + "fmt" + "os" "regexp" + "strings" + + "github.com/caddyserver/caddy/v2" + "github.com/spf13/cobra/doc" ) // Command represents a subcommand. Name, Func, @@ -70,13 +76,6 @@ func Commands() map[string]Command { var commands = make(map[string]Command) func init() { - RegisterCommand(Command{ - Name: "help", - Func: cmdHelp, - Usage: "", - Short: "Shows help for a Caddy subcommand", - }) - RegisterCommand(Command{ Name: "start", Func: cmdStart, @@ -346,16 +345,50 @@ EXPERIMENTAL: May be changed or removed. }(), }) + RegisterCommand(Command{ + Name: "manpage", + Func: func(fl Flags) (int, error) { + dir := strings.TrimSpace(fl.String("directory")) + if dir == "" { + return caddy.ExitCodeFailedQuit, fmt.Errorf("designated output directory and specified section are required") + } + if err := os.MkdirAll(dir, 0755); err != nil { + return caddy.ExitCodeFailedQuit, err + } + if err := doc.GenManTree(rootCmd, &doc.GenManHeader{ + Title: "Caddy", + Section: "8", + }, dir); err != nil { + return caddy.ExitCodeFailedQuit, err + } + return caddy.ExitCodeSuccess, nil + }, + Usage: "--directory ", + Short: "Generates the manual pages of Caddy commands", + Long: ` +Generates the manual pages of Caddy commands into the designated directory tagged into the specified section. + +The manual page files are generated into the directory specified by the argument of --directory. If the directory does not exist, it will be created. + +The manual pages are sorted into the section specified by the argument of --section. +`, + Flags: func() *flag.FlagSet { + fs := flag.NewFlagSet("manpage", flag.ExitOnError) + fs.String("directory", "", "The output directory where the manpages are generated") + fs.String("section", "", "The section number of the generated manual pages") + return fs + }(), + }) } // RegisterCommand registers the command cmd. // cmd.Name must be unique and conform to the // following format: // -// - lowercase -// - alphanumeric and hyphen characters only -// - cannot start or end with a hyphen -// - hyphen cannot be adjacent to another hyphen +// - lowercase +// - alphanumeric and hyphen characters only +// - cannot start or end with a hyphen +// - hyphen cannot be adjacent to another hyphen // // This function panics if the name is already registered, // if the name does not meet the described format, or if @@ -378,7 +411,7 @@ func RegisterCommand(cmd Command) { if !commandNameRegex.MatchString(cmd.Name) { panic("invalid command name") } - commands[cmd.Name] = cmd + rootCmd.AddCommand(caddyCmdToCoral(cmd)) } var commandNameRegex = regexp.MustCompile(`^[a-z0-9]$|^([a-z0-9]+-?[a-z0-9]*)+[a-z0-9]$`) diff --git a/cmd/main.go b/cmd/main.go index 09246f4..44e339a 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -33,6 +33,7 @@ import ( "github.com/caddyserver/caddy/v2" "github.com/caddyserver/caddy/v2/caddyconfig" "github.com/caddyserver/certmagic" + "github.com/spf13/pflag" "go.uber.org/zap" ) @@ -58,35 +59,9 @@ func Main() { os.Args = append(os.Args, "help") } - subcommandName := os.Args[1] - subcommand, ok := commands[subcommandName] - if !ok { - if strings.HasPrefix(os.Args[1], "-") { - // user probably forgot to type the subcommand - fmt.Println("[ERROR] first argument must be a subcommand; see 'caddy help'") - } else { - fmt.Printf("[ERROR] '%s' is not a recognized subcommand; see 'caddy help'\n", os.Args[1]) - } - os.Exit(caddy.ExitCodeFailedStartup) - } - - fs := subcommand.Flags - if fs == nil { - fs = flag.NewFlagSet(subcommand.Name, flag.ExitOnError) - } - - err := fs.Parse(os.Args[2:]) - if err != nil { - fmt.Println(err) - os.Exit(caddy.ExitCodeFailedStartup) + if err := rootCmd.Execute(); err != nil { + os.Exit(1) } - - exitCode, err := subcommand.Func(Flags{fs}) - if err != nil { - fmt.Fprintf(os.Stderr, "%s: %v\n", subcommand.Name, err) - } - - os.Exit(exitCode) } // handlePingbackConn reads from conn and ensures it matches @@ -280,7 +255,7 @@ func watchConfigFile(filename, adapterName string) { // Flags wraps a FlagSet so that typed values // from flags can be easily retrieved. type Flags struct { - *flag.FlagSet + *pflag.FlagSet } // String returns the string representation of the @@ -326,22 +301,6 @@ func (f Flags) Duration(name string) time.Duration { return val } -// flagHelp returns the help text for fs. -func flagHelp(fs *flag.FlagSet) string { - if fs == nil { - return "" - } - - // temporarily redirect output - out := fs.Output() - defer fs.SetOutput(out) - - buf := new(bytes.Buffer) - fs.SetOutput(buf) - fs.PrintDefaults() - return buf.String() -} - func loadEnvFromFile(envFile string) error { file, err := os.Open(envFile) if err != nil { -- cgit v1.2.3