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
This commit is contained in:
Mohammed Al Sahaf 2022-08-31 01:38:38 +03:00 committed by GitHub
parent 8cb3cf540c
commit 258bc82b69
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 123 additions and 126 deletions

33
cmd/cobra.go Normal file
View file

@ -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
}

View file

@ -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 <command> [<args...>]
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 <command>' 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

View file

@ -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: "<command>",
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 <directory path>",
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]$`)

View file

@ -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)
if err := rootCmd.Execute(); err != nil {
os.Exit(1)
}
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)
}
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 {