initial commit
This commit is contained in:
3
README.md
Normal file
3
README.md
Normal file
@@ -0,0 +1,3 @@
|
||||
# appcli
|
||||
|
||||
appcli provides boilerplate dependency handling for command line apps based on [urfave/cli](https://github.com/urfave/cli) that utilize [app](https://gitea.auvem.com/go-toolkit/app).
|
||||
124
appcli.go
Normal file
124
appcli.go
Normal file
@@ -0,0 +1,124 @@
|
||||
package appcli
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
|
||||
"gitea.auvem.com/go-toolkit/app"
|
||||
"github.com/urfave/cli/v3"
|
||||
)
|
||||
|
||||
// NewCommand creates a new CLI command with the specified configuration and
|
||||
// and dependencies. Returns a standard *cli.Command that can be used directly
|
||||
// with urfave/cli/v3 types. If any dependencies are provided, the Before method
|
||||
// is overriden to ensure that all dependencies are satisfied before the command
|
||||
// is executed. Requires an app.Lifecycle to be present in the context when the
|
||||
// command is executed.
|
||||
func NewCommand(cmdcfg *cli.Command, depends ...*app.Module) *cli.Command {
|
||||
if len(depends) == 0 {
|
||||
return cmdcfg
|
||||
}
|
||||
|
||||
// Override the Before method to handle dependencies
|
||||
originalBefore := cmdcfg.Before
|
||||
cmdcfg.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, errors.New("lifecycle not found in context, cannot run command with dependencies")
|
||||
}
|
||||
|
||||
if err := lifecycle.Require(depends...); err != nil {
|
||||
return ctx, err
|
||||
}
|
||||
return ctx, nil
|
||||
}
|
||||
|
||||
return cmdcfg
|
||||
}
|
||||
|
||||
// RootCommandOpts defines the options for creating a root command.
|
||||
type RootCommandOpts struct {
|
||||
// Command is the base command configuration.
|
||||
Command *cli.Command
|
||||
|
||||
// Dependencies are the modules that this command depends on. Note: All
|
||||
// root dependencies will be inherited by all subcommands.
|
||||
Dependencies []*app.Module
|
||||
}
|
||||
|
||||
// NewRootCommand creates a new root CLI command with the specified configuration.
|
||||
// Override Before and After to take over app.Lifecycle setup and teardown. Adds
|
||||
// a verbose flag. See NewCommand for more details on how dependencies are handled.
|
||||
// Requires an app.Lifecycle to be present in the context when the command is executed.
|
||||
func NewRootCommand(rootcfg *RootCommandOpts) *cli.Command {
|
||||
cmdcfg := rootcfg.Command
|
||||
|
||||
cmdcfg.Flags = append(cmdcfg.Flags, &cli.BoolFlag{
|
||||
Name: "verbose",
|
||||
Aliases: []string{"v"},
|
||||
Usage: "Enable verbose output",
|
||||
})
|
||||
|
||||
// Override the before method to handle logger and lifecycle
|
||||
originalBefore := cmdcfg.Before
|
||||
cmdcfg.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, errors.New("lifecycle not found in context, run root command")
|
||||
}
|
||||
|
||||
if err := lifecycle.Setup(); err != nil {
|
||||
return ctx, err
|
||||
}
|
||||
|
||||
return ctx, nil
|
||||
}
|
||||
|
||||
// Override the After method to handle cleanup if needed
|
||||
originalAfter := cmdcfg.After
|
||||
cmdcfg.After = func(ctx context.Context, cmd *cli.Command) error {
|
||||
if originalAfter != nil {
|
||||
if err := originalAfter(ctx, cmd); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
lifecycle := app.LifecycleFromContext(ctx)
|
||||
if lifecycle == nil {
|
||||
return errors.New("lifecycle not found in context, cannot clean up root command")
|
||||
}
|
||||
|
||||
if err := lifecycle.Teardown(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
return NewCommand(cmdcfg, rootcfg.Dependencies...)
|
||||
}
|
||||
|
||||
// VerboseFromCommand checks if the verbose flag is set in the command context.
|
||||
func VerboseFromCommand(cmd *cli.Command) bool {
|
||||
if cmd == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
return cmd.Bool("verbose")
|
||||
}
|
||||
8
go.mod
Normal file
8
go.mod
Normal file
@@ -0,0 +1,8 @@
|
||||
module gitea.auvem.com/go-toolkit/appcli
|
||||
|
||||
go 1.24.0
|
||||
|
||||
require (
|
||||
gitea.auvem.com/go-toolkit/app v0.0.0-20250603235859-6f9e3731acf9
|
||||
github.com/urfave/cli/v3 v3.3.3
|
||||
)
|
||||
12
go.sum
Normal file
12
go.sum
Normal file
@@ -0,0 +1,12 @@
|
||||
gitea.auvem.com/go-toolkit/app v0.0.0-20250603235859-6f9e3731acf9 h1:MYOI+bB4IBAqoL1tyIUFnu0S+NSq0OX88J3K/PUR7lI=
|
||||
gitea.auvem.com/go-toolkit/app v0.0.0-20250603235859-6f9e3731acf9/go.mod h1:a7ENpOxndUdONE6oZ9MZAvG1ba2uq01x/LtcnDkpOj8=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
|
||||
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||
github.com/urfave/cli/v3 v3.3.3 h1:byCBaVdIXuLPIDm5CYZRVG6NvT7tv1ECqdU4YzlEa3I=
|
||||
github.com/urfave/cli/v3 v3.3.3/go.mod h1:FJSKtM/9AiiTOJL4fJ6TbMUkxBXn7GO9guZqoZtpYpo=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
Reference in New Issue
Block a user