Skip to content

Commit

Permalink
Begin introducing alternate runtime "argh mode"
Browse files Browse the repository at this point in the history
Connected to #1583
  • Loading branch information
meatballhat committed Jul 3, 2023
1 parent bafe0ce commit 6f53044
Show file tree
Hide file tree
Showing 8 changed files with 143 additions and 1 deletion.
3 changes: 2 additions & 1 deletion cli.go
Expand Up @@ -30,7 +30,8 @@ import (
)

var (
isTracingOn = os.Getenv("URFAVE_CLI_TRACING") == "on"
isTracingOn = os.Getenv("URFAVE_CLI_TRACING") == "on"
isArghModeOn = os.Getenv("URFAVE_CLI_ARGH_MODE") == "on"
)

func tracef(format string, a ...any) {
Expand Down
124 changes: 124 additions & 0 deletions command.go
Expand Up @@ -2,6 +2,7 @@ package cli

import (
"context"
"encoding/json"
"flag"
"fmt"
"io"
Expand All @@ -10,6 +11,8 @@ import (
"reflect"
"sort"
"strings"

"github.com/urfave/argh"
)

const (
Expand Down Expand Up @@ -139,6 +142,12 @@ type Command struct {
isInError bool
// track state of defaults
didSetupDefaults bool

// fields used by argh-based parsing {{
parserCfg *argh.ParserConfig
cfg *argh.CommandConfig
stringValues map[string]string
// }}
}

// FullName returns the full name of the command.
Expand Down Expand Up @@ -175,6 +184,16 @@ func (cmd *Command) setupDefaults(osArgs []string) {
isRoot := cmd.parent == nil
tracef("isRoot? %[1]v (cmd=%[2]q)", isRoot, cmd.Name)

if isArghModeOn {
if isRoot {
cmd.parserCfg = argh.NewParserConfig()
cmd.cfg = cmd.parserCfg.Prog
cmd.cfg.NValue = argh.ZeroOrMoreValue
}

Check warning on line 192 in command.go

View check run for this annotation

Codecov / codecov/patch

command.go#L188-L192

Added lines #L188 - L192 were not covered by tests

cmd.stringValues = map[string]string{}

Check warning on line 194 in command.go

View check run for this annotation

Codecov / codecov/patch

command.go#L194

Added line #L194 was not covered by tests
}

if cmd.ShellComplete == nil {
tracef("setting default ShellComplete (cmd=%[1]q)", cmd.Name)
cmd.ShellComplete = DefaultCompleteWithFlags(cmd)
Expand Down Expand Up @@ -287,6 +306,49 @@ func (cmd *Command) setupDefaults(osArgs []string) {
disableSliceFlagSeparator = cmd.DisableSliceFlagSeparator
}

func (cmd *Command) setupParser() {
tracef("setting up parser (cmd=%[1]q)", cmd.Name)

// TODO: add support for named positional args {{
cmd.cfg.NValue = 1
cmd.cfg.ValueNames = []string{}
// }}

for _, loopFlag := range cmd.Flags {
fl := loopFlag

// TODO: add support for flags with value counts other than 0 and 1
flagNValue := argh.NValue(0)
canonicalName := fl.Names()[0]

if v, ok := fl.(DocGenerationFlag); ok && v.TakesValue() {
flagNValue = argh.NValue(1)
canonicalName = v.CanonicalName()
}

Check warning on line 327 in command.go

View check run for this annotation

Codecov / codecov/patch

command.go#L309-L327

Added lines #L309 - L327 were not covered by tests

flCfg := &argh.FlagConfig{
NValue: flagNValue,
On: func(cf argh.CommandFlag) {
tracef("flag %[1]q set (cmd=%[2]q)", fl.Names(), cmd.Name)

for k, v := range cf.Values {
cmd.stringValues[fmt.Sprintf("%[1]v.%[2]v", canonicalName, cf.Name, k)] = v
}

Check warning on line 336 in command.go

View check run for this annotation

Codecov / codecov/patch

command.go#L329-L336

Added lines #L329 - L336 were not covered by tests
},
}

for _, name := range fl.Names() {
tracef("setting flag config in parser config name=%[1]q cfg=%+#[2]v flCfg=%+#[3]v (cmd=%[4]q)", name, cmd.cfg, flCfg, cmd.Name)

cmd.cfg.SetFlagConfig(name, flCfg)
}

Check warning on line 344 in command.go

View check run for this annotation

Codecov / codecov/patch

command.go#L340-L344

Added lines #L340 - L344 were not covered by tests
}

for _, subCmd := range cmd.Commands {
subCmd.setupParser()
}

Check warning on line 349 in command.go

View check run for this annotation

Codecov / codecov/patch

command.go#L347-L349

Added lines #L347 - L349 were not covered by tests
}

func (cmd *Command) setupCommandGraph() {
tracef("setting up command graph (cmd=%[1]q)", cmd.Name)

Expand All @@ -300,6 +362,21 @@ func (cmd *Command) setupCommandGraph() {
func (cmd *Command) setupSubcommand() {
tracef("setting up self as sub-command (cmd=%[1]q)", cmd.Name)

if isArghModeOn {
cfg, ok := cmd.parent.cfg.GetCommandConfig(cmd.Name)
if !ok {
cfg = argh.CommandConfig{}
}

Check warning on line 369 in command.go

View check run for this annotation

Codecov / codecov/patch

command.go#L366-L369

Added lines #L366 - L369 were not covered by tests

cfg.On = func(cf argh.CommandFlag) {
tracef("received command flag=%+#[1]v (cmd=%[2]q)", cmd.Name)
}

Check warning on line 373 in command.go

View check run for this annotation

Codecov / codecov/patch

command.go#L371-L373

Added lines #L371 - L373 were not covered by tests

tracef("setting parser config on parent cfg=%+#[1]v (cmd=%[2]q)", &cfg, cmd.Name)
cmd.parent.cfg.SetCommandConfig(cmd.Name, &cfg)
cmd.cfg = &cfg

Check warning on line 377 in command.go

View check run for this annotation

Codecov / codecov/patch

command.go#L375-L377

Added lines #L375 - L377 were not covered by tests
}

cmd.ensureHelp()

tracef("setting command categories (cmd=%[1]q)", cmd.Name)
Expand Down Expand Up @@ -338,6 +415,15 @@ func (cmd *Command) ensureHelp() {
// arguments are parsed according to the Flag and Command
// definitions and the matching Action functions are run.
func (cmd *Command) Run(ctx context.Context, osArgs []string) (deferErr error) {
if isArghModeOn {
if err := cmd.runWithArgh(ctx, osArgs); err != nil {
tracef("setting deferErr from argh error: %[1]v", err)
deferErr = err
}

Check warning on line 422 in command.go

View check run for this annotation

Codecov / codecov/patch

command.go#L419-L422

Added lines #L419 - L422 were not covered by tests

return

Check warning on line 424 in command.go

View check run for this annotation

Codecov / codecov/patch

command.go#L424

Added line #L424 was not covered by tests
}

tracef("running with arguments %[1]q (cmd=%[2]q)", osArgs, cmd.Name)
cmd.setupDefaults(osArgs)

Expand Down Expand Up @@ -524,6 +610,44 @@ func (cmd *Command) Run(ctx context.Context, osArgs []string) (deferErr error) {
return deferErr
}

func (cmd *Command) runWithArgh(ctx context.Context, osArgs []string) error {
cmd.setupParser()

parseTree, parseErr := argh.ParseArgs(osArgs, cmd.Root().parserCfg)
if parseErr != nil {
tracef("argh parse error: %[1]v", parseErr)

return parseErr
}

Check warning on line 621 in command.go

View check run for this annotation

Codecov / codecov/patch

command.go#L613-L621

Added lines #L613 - L621 were not covered by tests

if isTracingOn {
if b, err := json.Marshal(argh.ToAST(parseTree.Nodes)); err == nil {
tracef("argh ast: %[1]s", string(b))
}

Check warning on line 626 in command.go

View check run for this annotation

Codecov / codecov/patch

command.go#L623-L626

Added lines #L623 - L626 were not covered by tests

if v, ok := os.LookupEnv("URFAVE_CLI_ARGH_DUMP_JSON"); ok {
if fd, err := os.Create(v); err == nil {
if err := json.NewEncoder(fd).Encode(
map[string]any{
"cfg": cmd.cfg,
"string_values": cmd.stringValues,
"ast": argh.ToAST(parseTree.Nodes),
"err": parseErr,
},
); err != nil {
tracef("URFAVE_CLI_ARGH_DUMP_JSON err: %[1]v", err)
}

Check warning on line 639 in command.go

View check run for this annotation

Codecov / codecov/patch

command.go#L628-L639

Added lines #L628 - L639 were not covered by tests

if err := fd.Close(); err != nil {
tracef("URFAVE_CLI_ARGH_DUMP_JSON err: %[1]v", err)
}

Check warning on line 643 in command.go

View check run for this annotation

Codecov / codecov/patch

command.go#L641-L643

Added lines #L641 - L643 were not covered by tests
}
}
}

return nil

Check warning on line 648 in command.go

View check run for this annotation

Codecov / codecov/patch

command.go#L648

Added line #L648 was not covered by tests
}

func (cmd *Command) checkHelp() bool {
tracef("checking if help is wanted (cmd=%[1]q)", cmd.Name)

Expand Down
2 changes: 2 additions & 0 deletions flag.go
Expand Up @@ -123,6 +123,8 @@ type DocGenerationFlag interface {
// TakesValue returns true if the flag takes a value, otherwise false
TakesValue() bool

CanonicalName() string

// GetUsage returns the usage string for the flag
GetUsage() string

Expand Down
4 changes: 4 additions & 0 deletions flag_impl.go
Expand Up @@ -210,6 +210,10 @@ func (f *FlagBase[T, C, V]) IsSet() bool {
return f.hasBeenSet
}

func (f *FlagBase[T, C, V]) CanonicalName() string {
return f.Name

Check warning on line 214 in flag_impl.go

View check run for this annotation

Codecov / codecov/patch

flag_impl.go#L213-L214

Added lines #L213 - L214 were not covered by tests
}

// Names returns the names of the flag
func (f *FlagBase[T, C, V]) Names() []string {
return FlagNames(f.Name, f.Aliases)
Expand Down
1 change: 1 addition & 0 deletions go.mod
Expand Up @@ -4,6 +4,7 @@ go 1.18

require (
github.com/stretchr/testify v1.8.4
github.com/urfave/argh v0.2.1-0.20230702123329-da1ca8be8db5
github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673
)

Expand Down
2 changes: 2 additions & 0 deletions go.sum
Expand Up @@ -4,6 +4,8 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/urfave/argh v0.2.1-0.20230702123329-da1ca8be8db5 h1:Wlo6nZHECi5IysPUZKOq8KN8AoT/6KsmdGBRidH6n1M=
github.com/urfave/argh v0.2.1-0.20230702123329-da1ca8be8db5/go.mod h1:NUm2NMpNFfFIzbpD9qe98ZL7G3TYWiH1bynhvbJw2pY=
github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 h1:bAn7/zixMGCfxrRTfdpNzjtPYqr8smhKouy9mxVdGPU=
github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673/go.mod h1:N3UwUGtsrSj3ccvlPHLoLsHnpR27oXr4ZE984MbSER8=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
Expand Down
4 changes: 4 additions & 0 deletions godoc-current.txt
Expand Up @@ -516,6 +516,8 @@ type DocGenerationFlag interface {
// TakesValue returns true if the flag takes a value, otherwise false
TakesValue() bool

CanonicalName() string

// GetUsage returns the usage string for the flag
GetUsage() string

Expand Down Expand Up @@ -645,6 +647,8 @@ type FlagBase[T any, C any, VC ValueCreator[T, C]] struct {
func (f *FlagBase[T, C, V]) Apply(set *flag.FlagSet) error
Apply populates the flag given the flag set and environment

func (f *FlagBase[T, C, V]) CanonicalName() string

func (f *FlagBase[T, C, V]) Get(cmd *Command) T
Get returns the flag’s value in the given Command.

Expand Down
4 changes: 4 additions & 0 deletions testdata/godoc-v3.x.txt
Expand Up @@ -516,6 +516,8 @@ type DocGenerationFlag interface {
// TakesValue returns true if the flag takes a value, otherwise false
TakesValue() bool

CanonicalName() string

// GetUsage returns the usage string for the flag
GetUsage() string

Expand Down Expand Up @@ -645,6 +647,8 @@ type FlagBase[T any, C any, VC ValueCreator[T, C]] struct {
func (f *FlagBase[T, C, V]) Apply(set *flag.FlagSet) error
Apply populates the flag given the flag set and environment

func (f *FlagBase[T, C, V]) CanonicalName() string

func (f *FlagBase[T, C, V]) Get(cmd *Command) T
Get returns the flag’s value in the given Command.

Expand Down

0 comments on commit 6f53044

Please sign in to comment.