This commit is contained in:
a 2024-06-18 21:57:46 -05:00
parent edf4168c8e
commit 7bc7e1680e
No known key found for this signature in database
GPG key ID: 374BC539FE795AF0
4 changed files with 76 additions and 46 deletions

View file

@ -8,9 +8,10 @@ import (
"github.com/caddyserver/caddy/v2" "github.com/caddyserver/caddy/v2"
) )
var rootCmd = &cobra.Command{ var defaultFactory = NewRootCommandFactory(func() *cobra.Command {
Use: "caddy", return &cobra.Command{
Long: `Caddy is an extensible server platform written in Go. Use: "caddy",
Long: `Caddy is an extensible server platform written in Go.
At its core, Caddy merely manages configuration. Modules are plugged At its core, Caddy merely manages configuration. Modules are plugged
in statically at compile-time to provide useful functionality. Caddy's in statically at compile-time to provide useful functionality. Caddy's
@ -91,23 +92,26 @@ package installers: https://caddyserver.com/docs/install
Instructions for running Caddy in production are also available: Instructions for running Caddy in production are also available:
https://caddyserver.com/docs/running https://caddyserver.com/docs/running
`, `,
Example: ` $ caddy run Example: ` $ caddy run
$ caddy run --config caddy.json $ caddy run --config caddy.json
$ caddy reload --config caddy.json $ caddy reload --config caddy.json
$ caddy stop`, $ caddy stop`,
// kind of annoying to have all the help text printed out if // kind of annoying to have all the help text printed out if
// caddy has an error provisioning its modules, for instance... // caddy has an error provisioning its modules, for instance...
SilenceUsage: true, SilenceUsage: true,
Version: onlyVersionText(), Version: onlyVersionText(),
} }
})
const fullDocsFooter = `Full documentation is available at: const fullDocsFooter = `Full documentation is available at:
https://caddyserver.com/docs/command-line` https://caddyserver.com/docs/command-line`
func init() { func init() {
rootCmd.SetVersionTemplate("{{.Version}}\n") defaultFactory.Use(func(cmd *cobra.Command) {
rootCmd.SetHelpTemplate(rootCmd.HelpTemplate() + "\n" + fullDocsFooter + "\n") cmd.SetVersionTemplate("{{.Version}}\n")
cmd.SetHelpTemplate(cmd.HelpTemplate() + "\n" + fullDocsFooter + "\n")
})
} }
func onlyVersionText() string { func onlyVersionText() string {

28
cmd/commandfactory.go Normal file
View file

@ -0,0 +1,28 @@
package caddycmd
import (
"github.com/spf13/cobra"
)
type RootCommandFactory struct {
constructor func() *cobra.Command
options []func(*cobra.Command)
}
func NewRootCommandFactory(fn func() *cobra.Command) *RootCommandFactory {
return &RootCommandFactory{
constructor: fn,
}
}
func (f *RootCommandFactory) Use(fn func(cmd *cobra.Command)) {
f.options = append(f.options, fn)
}
func (f *RootCommandFactory) Build() *cobra.Command {
o := f.constructor()
for _, v := range f.options {
v(o)
}
return o
}

View file

@ -459,7 +459,8 @@ argument of --directory. If the directory does not exist, it will be created.
if err := os.MkdirAll(dir, 0o755); err != nil { if err := os.MkdirAll(dir, 0o755); err != nil {
return caddy.ExitCodeFailedQuit, err return caddy.ExitCodeFailedQuit, err
} }
if err := doc.GenManTree(rootCmd, &doc.GenManHeader{ ccmd := defaultFactory.Build()
if err := doc.GenManTree(ccmd, &doc.GenManHeader{
Title: "Caddy", Title: "Caddy",
Section: "8", // https://en.wikipedia.org/wiki/Man_page#Manual_sections Section: "8", // https://en.wikipedia.org/wiki/Man_page#Manual_sections
}, dir); err != nil { }, dir); err != nil {
@ -471,10 +472,11 @@ argument of --directory. If the directory does not exist, it will be created.
}) })
// source: https://github.com/spf13/cobra/blob/main/shell_completions.md // source: https://github.com/spf13/cobra/blob/main/shell_completions.md
rootCmd.AddCommand(&cobra.Command{ defaultFactory.Use(func(ccmd *cobra.Command) {
Use: "completion [bash|zsh|fish|powershell]", ccmd.AddCommand(&cobra.Command{
Short: "Generate completion script", Use: "completion [bash|zsh|fish|powershell]",
Long: fmt.Sprintf(`To load completions: Short: "Generate completion script",
Long: fmt.Sprintf(`To load completions:
Bash: Bash:
@ -512,24 +514,25 @@ argument of --directory. If the directory does not exist, it will be created.
# To load completions for every new session, run: # To load completions for every new session, run:
PS> %[1]s completion powershell > %[1]s.ps1 PS> %[1]s completion powershell > %[1]s.ps1
# and source this file from your PowerShell profile. # and source this file from your PowerShell profile.
`, rootCmd.Root().Name()), `, defaultFactory.constructor().Name()),
DisableFlagsInUseLine: true, DisableFlagsInUseLine: true,
ValidArgs: []string{"bash", "zsh", "fish", "powershell"}, ValidArgs: []string{"bash", "zsh", "fish", "powershell"},
Args: cobra.MatchAll(cobra.ExactArgs(1), cobra.OnlyValidArgs), Args: cobra.MatchAll(cobra.ExactArgs(1), cobra.OnlyValidArgs),
RunE: func(cmd *cobra.Command, args []string) error { RunE: func(cmd *cobra.Command, args []string) error {
switch args[0] { switch args[0] {
case "bash": case "bash":
return cmd.Root().GenBashCompletion(os.Stdout) return cmd.Root().GenBashCompletion(os.Stdout)
case "zsh": case "zsh":
return cmd.Root().GenZshCompletion(os.Stdout) return cmd.Root().GenZshCompletion(os.Stdout)
case "fish": case "fish":
return cmd.Root().GenFishCompletion(os.Stdout, true) return cmd.Root().GenFishCompletion(os.Stdout, true)
case "powershell": case "powershell":
return cmd.Root().GenPowerShellCompletionWithDesc(os.Stdout) return cmd.Root().GenPowerShellCompletionWithDesc(os.Stdout)
default: default:
return fmt.Errorf("unrecognized shell: %s", args[0]) return fmt.Errorf("unrecognized shell: %s", args[0])
} }
}, },
})
}) })
} }
@ -563,7 +566,9 @@ func RegisterCommand(cmd Command) {
if !commandNameRegex.MatchString(cmd.Name) { if !commandNameRegex.MatchString(cmd.Name) {
panic("invalid command name") panic("invalid command name")
} }
rootCmd.AddCommand(caddyCmdToCobra(cmd)) defaultFactory.Use(func(ccmd *cobra.Command) {
ccmd.AddCommand(caddyCmdToCobra(cmd))
})
} }
var commandNameRegex = regexp.MustCompile(`^[a-z0-9]$|^([a-z0-9]+-?[a-z0-9]*)+[a-z0-9]$`) var commandNameRegex = regexp.MustCompile(`^[a-z0-9]$|^([a-z0-9]+-?[a-z0-9]*)+[a-z0-9]$`)

View file

@ -34,7 +34,6 @@ import (
"time" "time"
"github.com/caddyserver/certmagic" "github.com/caddyserver/certmagic"
"github.com/spf13/cobra"
"github.com/spf13/pflag" "github.com/spf13/pflag"
"go.uber.org/automaxprocs/maxprocs" "go.uber.org/automaxprocs/maxprocs"
"go.uber.org/zap" "go.uber.org/zap"
@ -72,7 +71,7 @@ func Main() {
if err != nil { if err != nil {
caddy.Log().Warn("failed to set GOMAXPROCS", zap.Error(err)) caddy.Log().Warn("failed to set GOMAXPROCS", zap.Error(err))
} }
rootCmd := defaultFactory.Build()
if err := rootCmd.Execute(); err != nil { if err := rootCmd.Execute(); err != nil {
var exitError *exitError var exitError *exitError
if errors.As(err, &exitError) { if errors.As(err, &exitError) {
@ -86,15 +85,9 @@ func Main() {
func MainForTesting(args ...string) error { func MainForTesting(args ...string) error {
// create a root command for testing which will not pollute the global namespace, and does not // create a root command for testing which will not pollute the global namespace, and does not
// call os.Exit(). // call os.Exit().
tmpRootCmp := cobra.Command{ rootCmd := defaultFactory.Build()
Use: rootCmd.Use, rootCmd.SetArgs(args)
Long: rootCmd.Long, if err := rootCmd.Execute(); err != nil {
Example: rootCmd.Example,
SilenceUsage: rootCmd.SilenceUsage,
Version: rootCmd.Version,
}
tmpRootCmp.SetArgs(args)
if err := tmpRootCmp.Execute(); err != nil {
return err return err
} }
return nil return nil