refactor: Move from global state to functions (#53)

This commit represents a few experiments of features I've used in Cobra

1. Uses cli.GenericFlag to encapsulate parsing and validation of flag
   values at parse time. This removes the burden from the individual
   CLI commands to parse and validate args and options.

2. Add influxid.ID that may be used by any flag that requires an
   Influx ID. influxid.ID parses and validates string value is a valid
   ID, removing this burden from individual commands and ensuring valid
   values before the command actions begins.

3. Binds cli.Flags directly to params structures to directly capture
   the values when parsing flags.

4. Moves from global state to local builder functions for the majority
   of the commands. This allows the commands to bind to flag variables
   reducing the repeated ctx.String(), ctx.Int(), etc

5. Leverages the BeforeFunc to create middleware and inject the CLI and
   API client into commands, saving the repeated boilerplate across
   all of the instantiated commands. This is extensible, so additional
   middleware can be appends using the middleware.WithBeforeFns
This commit is contained in:
Stuart Carnie
2021-05-03 23:31:45 +10:00
committed by GitHub
parent 0b4d753728
commit 3414e1a983
13 changed files with 1058 additions and 569 deletions

View File

@ -14,6 +14,7 @@ import (
"github.com/influxdata/influx-cli/v2/internal/api"
"github.com/influxdata/influx-cli/v2/internal/config"
"github.com/influxdata/influx-cli/v2/internal/stdio"
"github.com/influxdata/influx-cli/v2/pkg/cli/middleware"
"github.com/influxdata/influx-cli/v2/pkg/signals"
"github.com/urfave/cli/v2"
)
@ -185,26 +186,75 @@ func newApiClient(ctx *cli.Context, cli *internal.CLI, injectToken bool) (*api.A
return api.NewAPIClient(apiConfig), nil
}
// standardCtx returns a context that will cancel on SIGINT and SIGTERM.
func standardCtx(ctx *cli.Context) context.Context {
return signals.WithStandardSignals(ctx.Context)
}
var app = cli.App{
Name: "influx",
Usage: "Influx Client",
UsageText: "influx [command]",
Commands: []*cli.Command{
&versionCmd,
&pingCmd,
&setupCmd,
&writeCmd,
&bucketCmd,
newVersionCmd(),
newPingCmd(),
newSetupCmd(),
newWriteCmd(),
newBucketCmd(),
},
}
func withCli() cli.BeforeFunc {
return func(ctx *cli.Context) error {
c, err := newCli(ctx)
if err != nil {
return err
}
ctx.App.Metadata["cli"] = c
return nil
}
}
func getCLI(ctx *cli.Context) *internal.CLI {
i, ok := ctx.App.Metadata["cli"].(*internal.CLI)
if !ok {
panic("missing CLI")
}
return i
}
func withApi(injectToken bool) cli.BeforeFunc {
key := "api-no-token"
if injectToken {
key = "api"
}
makeFn := func(ctx *cli.Context) error {
c := getCLI(ctx)
apiClient, err := newApiClient(ctx, c, injectToken)
if err != nil {
return err
}
ctx.App.Metadata[key] = apiClient
return nil
}
return middleware.WithBeforeFns(makeFn)
}
func getAPI(ctx *cli.Context) *api.APIClient {
i, ok := ctx.App.Metadata["api"].(*api.APIClient)
if !ok {
panic("missing APIClient with token")
}
return i
}
func getAPINoToken(ctx *cli.Context) *api.APIClient {
i, ok := ctx.App.Metadata["api-no-token"].(*api.APIClient)
if !ok {
panic("missing APIClient without token")
}
return i
}
func main() {
if err := app.Run(os.Args); err != nil {
ctx := signals.WithStandardSignals(context.Background())
if err := app.RunContext(ctx, os.Args); err != nil {
_, _ = fmt.Fprintf(os.Stderr, "Error: %v\n", err)
os.Exit(1)
}