fix: copy-on-wrap commands and typed errors
Clone commands before wrapping Before hooks, export ErrLifecycleNotInContext and ErrNoDependencies, add Run helper, split command.go and deps.go. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
+104
@@ -0,0 +1,104 @@
|
||||
package appcli
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"gitea.auvem.com/go-toolkit/app"
|
||||
"github.com/urfave/cli/v3"
|
||||
)
|
||||
|
||||
// NewCommand wraps cmd with dependency loading in Before. The input command is
|
||||
// not mutated; a shallow copy is returned.
|
||||
func NewCommand(cmdcfg *cli.Command, depfn ...DepFn) *cli.Command {
|
||||
if len(depfn) == 0 {
|
||||
return cmdcfg
|
||||
}
|
||||
|
||||
cmd := cloneCommand(cmdcfg)
|
||||
originalBefore := cmdcfg.Before
|
||||
cmd.Before = func(ctx context.Context, cmd *cli.Command) (context.Context, error) {
|
||||
if originalBefore != nil {
|
||||
var err error
|
||||
ctx, err = originalBefore(ctx, cmd)
|
||||
if err != nil {
|
||||
return ctx, err
|
||||
}
|
||||
}
|
||||
|
||||
lifecycle := app.LifecycleFromContext(ctx)
|
||||
if lifecycle == nil {
|
||||
return ctx, ErrLifecycleNotInContext
|
||||
}
|
||||
|
||||
deps := make([]*app.Module, 0)
|
||||
for _, fn := range depfn {
|
||||
if fn == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
modules, err := fn(lifecycle, cmd)
|
||||
if err != nil {
|
||||
return ctx, err
|
||||
}
|
||||
deps = append(deps, modules...)
|
||||
}
|
||||
|
||||
if len(deps) > 0 {
|
||||
if err := lifecycle.Require(deps...); err != nil {
|
||||
return ctx, err
|
||||
}
|
||||
}
|
||||
|
||||
return ctx, nil
|
||||
}
|
||||
|
||||
return cmd
|
||||
}
|
||||
|
||||
// NewRootCommand wraps a root command, adds a global --verbose / -v flag, and
|
||||
// applies dependency loading via [NewCommand].
|
||||
func NewRootCommand(cmdcfg *cli.Command, depfn ...DepFn) *cli.Command {
|
||||
root := cloneCommand(cmdcfg)
|
||||
if !hasFlag(root, "verbose") {
|
||||
root.Flags = append(root.Flags, &cli.BoolFlag{
|
||||
Name: "verbose",
|
||||
Aliases: []string{"v"},
|
||||
Usage: "Enable verbose output",
|
||||
})
|
||||
}
|
||||
return NewCommand(root, depfn...)
|
||||
}
|
||||
|
||||
// VerboseFromCommand reports whether --verbose was set on the command or an ancestor.
|
||||
func VerboseFromCommand(cmd *cli.Command) bool {
|
||||
if cmd == nil {
|
||||
return false
|
||||
}
|
||||
return cmd.Bool("verbose")
|
||||
}
|
||||
|
||||
func cloneCommand(src *cli.Command) *cli.Command {
|
||||
if src == nil {
|
||||
return nil
|
||||
}
|
||||
dup := *src
|
||||
if len(src.Flags) > 0 {
|
||||
dup.Flags = append([]cli.Flag(nil), src.Flags...)
|
||||
}
|
||||
if len(src.Commands) > 0 {
|
||||
dup.Commands = append([]*cli.Command(nil), src.Commands...)
|
||||
}
|
||||
if len(src.Arguments) > 0 {
|
||||
dup.Arguments = append([]cli.Argument(nil), src.Arguments...)
|
||||
}
|
||||
return &dup
|
||||
}
|
||||
|
||||
func hasFlag(cmd *cli.Command, name string) bool {
|
||||
for _, f := range cmd.Flags {
|
||||
if bf, ok := f.(*cli.BoolFlag); ok && bf.Name == name {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
Reference in New Issue
Block a user