diff --git a/.gitignore b/.gitignore index 36c6ae2..f738de7 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,4 @@ *.o *.out *.so +/.local/ diff --git a/Makefile b/Makefile index 4da2687..c365264 100644 --- a/Makefile +++ b/Makefile @@ -1,12 +1,21 @@ BENCHTIME ?= 10s +STRINGER := .local/bin/stringer .PHONY: all -all: test +all: generate test .PHONY: clean clean: rm -f coverage.out +.PHONY: distclean +distclean: clean + rm -f $(STRINGER) + +.PHONY: generate +generate: $(STRINGER) + PATH=$(PWD)/.local/bin:$(PATH) go generate ./... + .PHONY: test test: go test -v -coverprofile=coverage.out ./... @@ -18,3 +27,6 @@ bench: .PHONY: show-cover show-cover: go tool cover -func=coverage.out + +$(STRINGER): + GOBIN=$(PWD)/.local/bin go install golang.org/x/tools/cmd/stringer@latest diff --git a/argh.go b/argh.go index 805c45c..ec069ed 100644 --- a/argh.go +++ b/argh.go @@ -10,22 +10,22 @@ import ( ) var ( - tracingEnabled = os.Getenv("ARGH_TRACING") == "enabled" - traceLogger *log.Logger + isTracingOn = os.Getenv("ARGH_TRACING") == "on" + traceLogger *log.Logger Error = errors.New("argh error") ) func init() { - if !tracingEnabled { + if !isTracingOn { return } - traceLogger = log.New(os.Stderr, "ARGH TRACING: ", 0) + traceLogger = log.New(os.Stderr, "## ARGH TRACE ", 0) } func tracef(format string, v ...any) { - if !tracingEnabled { + if !isTracingOn { return } diff --git a/example_test.go b/example_test.go index 74d636a..2508d18 100644 --- a/example_test.go +++ b/example_test.go @@ -1,6 +1,7 @@ package argh_test import ( + "encoding/json" "fmt" "log" "os" @@ -9,31 +10,57 @@ import ( ) func ExampleParserConfig_simple() { + state := map[string]argh.CommandFlag{} + pCfg := argh.NewParserConfig() - pCfg.Prog = &argh.CommandConfig{ - NValue: argh.OneOrMoreValue, - ValueNames: []string{"val"}, - Flags: &argh.Flags{ - Map: map[string]argh.FlagConfig{ - "a": { - NValue: 2, - On: func(cf argh.CommandFlag) { - fmt.Printf("prog -a Name: %[1]q\n", cf.Name) - fmt.Printf("prog -a Values: %[1]q\n", cf.Values) - fmt.Printf("prog -a len(Nodes): %[1]v\n", len(cf.Nodes)) - }, - }, - }, + pCfg.Prog.NValue = argh.OneOrMoreValue + pCfg.Prog.ValueNames = []string{"val"} + pCfg.Prog.On = func(cf argh.CommandFlag) { + state["prog"] = cf + + fmt.Printf("prog Name: %[1]q\n", cf.Name) + fmt.Printf("prog Values: %[1]q\n", cf.Values) + fmt.Printf("prog len(Nodes): %[1]v\n", len(cf.Nodes)) + } + + pCfg.Prog.SetFlagConfig("a", &argh.FlagConfig{ + NValue: 2, + On: func(cf argh.CommandFlag) { + state["a"] = cf + + fmt.Printf("prog -a Name: %[1]q\n", cf.Name) + fmt.Printf("prog -a Values: %[1]q\n", cf.Values) + fmt.Printf("prog -a len(Nodes): %[1]v\n", len(cf.Nodes)) }, + }) + + pCfg.Prog.SetFlagConfig("b", &argh.FlagConfig{ + Persist: true, On: func(cf argh.CommandFlag) { - fmt.Printf("prog Name: %[1]q\n", cf.Name) - fmt.Printf("prog Values: %[1]q\n", cf.Values) - fmt.Printf("prog len(Nodes): %[1]v\n", len(cf.Nodes)) + state["b"] = cf + + fmt.Printf("prog -b Name: %[1]q\n", cf.Name) + fmt.Printf("prog -b Values: %[1]q\n", cf.Values) + fmt.Printf("prog -b len(Nodes): %[1]v\n", len(cf.Nodes)) }, - } + }) + + pCfg.Prog.SetCommandConfig("sub", &argh.CommandConfig{ + NValue: 3, + ValueNames: []string{"pilot", "navigator", "comms"}, + On: func(cf argh.CommandFlag) { + state["sub"] = cf + + fmt.Printf("prog sub Name: %[1]q\n", cf.Name) + fmt.Printf("prog sub Values: %[1]q\n", cf.Values) + fmt.Printf("prog sub len(Nodes): %[1]v\n", len(cf.Nodes)) + }, + }) // simulate command line args - os.Args = []string{"hello", "-a=from", "the", "ether"} + os.Args = []string{ + "hello", "-a=from", "the", "ether", "sub", "marge", "patty", "selma", "-b", + } pt, err := argh.ParseArgs(os.Args, pCfg) if err != nil { @@ -46,11 +73,137 @@ func ExampleParserConfig_simple() { log.Fatal("no parse tree?") } + enc := json.NewEncoder(os.Stdout) + enc.SetIndent("", " ") + + fmt.Printf("state: ") + + if err := enc.Encode(state); err != nil { + log.Fatalf("failed to jsonify: %v", err) + } + // Output: // prog -a Name: "a" // prog -a Values: map["0":"from" "1":"the"] // prog -a len(Nodes): 4 + // prog -b Name: "b" + // prog -b Values: map[] + // prog -b len(Nodes): 0 + // prog sub Name: "sub" + // prog sub Values: map["comms":"selma" "navigator":"patty" "pilot":"marge"] + // prog sub len(Nodes): 8 // prog Name: "hello" // prog Values: map["val":"ether"] - // prog len(Nodes): 4 + // prog len(Nodes): 6 + // state: { + // "a": { + // "Name": "a", + // "Values": { + // "0": "from", + // "1": "the" + // }, + // "Nodes": [ + // {}, + // { + // "Literal": "from" + // }, + // {}, + // { + // "Literal": "the" + // } + // ] + // }, + // "b": { + // "Name": "b", + // "Values": null, + // "Nodes": null + // }, + // "prog": { + // "Name": "hello", + // "Values": { + // "val": "ether" + // }, + // "Nodes": [ + // {}, + // { + // "Name": "a", + // "Values": { + // "0": "from", + // "1": "the" + // }, + // "Nodes": [ + // {}, + // { + // "Literal": "from" + // }, + // {}, + // { + // "Literal": "the" + // } + // ] + // }, + // {}, + // { + // "Literal": "ether" + // }, + // {}, + // { + // "Name": "sub", + // "Values": { + // "comms": "selma", + // "navigator": "patty", + // "pilot": "marge" + // }, + // "Nodes": [ + // {}, + // { + // "Literal": "marge" + // }, + // {}, + // { + // "Literal": "patty" + // }, + // {}, + // { + // "Literal": "selma" + // }, + // {}, + // { + // "Name": "b", + // "Values": null, + // "Nodes": null + // } + // ] + // } + // ] + // }, + // "sub": { + // "Name": "sub", + // "Values": { + // "comms": "selma", + // "navigator": "patty", + // "pilot": "marge" + // }, + // "Nodes": [ + // {}, + // { + // "Literal": "marge" + // }, + // {}, + // { + // "Literal": "patty" + // }, + // {}, + // { + // "Literal": "selma" + // }, + // {}, + // { + // "Name": "b", + // "Values": null, + // "Nodes": null + // } + // ] + // } + // } } diff --git a/parser_config.go b/parser_config.go index f446be4..2cdf0a2 100644 --- a/parser_config.go +++ b/parser_config.go @@ -99,6 +99,19 @@ func (cCfg *CommandConfig) GetCommandConfig(name string) (CommandConfig, bool) { return cCfg.Commands.Get(name) } +func (cCfg *CommandConfig) SetCommandConfig(name string, sCfg *CommandConfig) { + tracef("CommandConfig.SetCommandConfig(%q, ...)", name) + + if cCfg.Commands == nil { + cCfg.Commands = &Commands{Map: map[string]CommandConfig{}} + } + + sCfg.init() + sCfg.Flags.Parent = cCfg.Flags + + cCfg.Commands.Set(name, sCfg) +} + func (cCfg *CommandConfig) GetFlagConfig(name string) (FlagConfig, bool) { tracef("CommandConfig.GetFlagConfig(%q)", name) @@ -109,6 +122,16 @@ func (cCfg *CommandConfig) GetFlagConfig(name string) (FlagConfig, bool) { return cCfg.Flags.Get(name) } +func (cCfg *CommandConfig) SetFlagConfig(name string, flCfg *FlagConfig) { + tracef("CommandConfig.SetFlagConfig(%q, ...)", name) + + if cCfg.Flags == nil { + cCfg.Flags = &Flags{Map: map[string]FlagConfig{}} + } + + cCfg.Flags.Set(name, flCfg) +} + type FlagConfig struct { NValue NValue Persist bool @@ -146,6 +169,16 @@ func (fl *Flags) Get(name string) (FlagConfig, bool) { return flCfg, ok } +func (fl *Flags) Set(name string, flCfg *FlagConfig) { + tracef("Flags.Get(%q)", name) + + if fl.Map == nil { + fl.Map = map[string]FlagConfig{} + } + + fl.Map[name] = *flCfg +} + type Commands struct { Map map[string]CommandConfig } @@ -160,3 +193,15 @@ func (cmd *Commands) Get(name string) (CommandConfig, bool) { cmdCfg, ok := cmd.Map[name] return cmdCfg, ok } + +func (cmd *Commands) Set(name string, cCfg *CommandConfig) { + tracef("Commands.Set(%q, ...)", name) + + if cmd.Map == nil { + cmd.Map = map[string]CommandConfig{} + } + + cCfg.init() + + cmd.Map[name] = *cCfg +} diff --git a/token.go b/token.go index 2fd1456..240571b 100644 --- a/token.go +++ b/token.go @@ -1,3 +1,4 @@ +//go:generate stringer -type Token . package argh import "fmt" diff --git a/token_string.go b/token_string.go new file mode 100644 index 0000000..e3b73e2 --- /dev/null +++ b/token_string.go @@ -0,0 +1,35 @@ +// Code generated by "stringer -type Token ."; DO NOT EDIT. + +package argh + +import "strconv" + +func _() { + // An "invalid array index" compiler error signifies that the constant values have changed. + // Re-run the stringer command to generate them again. + var x [1]struct{} + _ = x[ILLEGAL-0] + _ = x[EOL-1] + _ = x[EMPTY-2] + _ = x[BS-3] + _ = x[IDENT-4] + _ = x[ARG_DELIMITER-5] + _ = x[ASSIGN-6] + _ = x[MULTI_VALUE_DELIMITER-7] + _ = x[LONG_FLAG-8] + _ = x[SHORT_FLAG-9] + _ = x[COMPOUND_SHORT_FLAG-10] + _ = x[STDIN_FLAG-11] + _ = x[STOP_FLAG-12] +} + +const _Token_name = "ILLEGALEOLEMPTYBSIDENTARG_DELIMITERASSIGNMULTI_VALUE_DELIMITERLONG_FLAGSHORT_FLAGCOMPOUND_SHORT_FLAGSTDIN_FLAGSTOP_FLAG" + +var _Token_index = [...]uint8{0, 7, 10, 15, 17, 22, 35, 41, 62, 71, 81, 100, 110, 119} + +func (i Token) String() string { + if i < 0 || i >= Token(len(_Token_index)-1) { + return "Token(" + strconv.FormatInt(int64(i), 10) + ")" + } + return _Token_name[_Token_index[i]:_Token_index[i+1]] +}