diff --git a/argh/parser.go b/argh/parser.go index 8c5d0dc..72c15d9 100644 --- a/argh/parser.go +++ b/argh/parser.go @@ -107,7 +107,9 @@ func (p *parser) parseCommand(cCfg *CommandConfig) Node { tracef("parseCommand(...) for=%d nodes=%+#v", i, nodes) tracef("parseCommand(...) for=%d tok=%s lit=%q pos=%v", i, p.tok, p.lit, p.pos) - if subCfg, ok := cCfg.Commands[p.lit]; ok { + tracef("parseCommand(...) cCfg=%+#v", cCfg) + + if subCfg, ok := cCfg.GetCommandConfig(p.lit); ok { subCommand := p.lit nodes = append(nodes, p.parseCommand(&subCfg)) @@ -186,26 +188,26 @@ func (p *parser) parseIdent() Node { return node } -func (p *parser) parseFlag(flCfgMap map[string]FlagConfig) Node { +func (p *parser) parseFlag(flags *Flags) Node { switch p.tok { case SHORT_FLAG: - tracef("parseFlag(...) parsing short flag with config=%+#v", flCfgMap) - return p.parseShortFlag(flCfgMap) + tracef("parseFlag(...) parsing short flag with config=%+#v", flags) + return p.parseShortFlag(flags) case LONG_FLAG: - tracef("parseFlag(...) parsing long flag with config=%+#v", flCfgMap) - return p.parseLongFlag(flCfgMap) + tracef("parseFlag(...) parsing long flag with config=%+#v", flags) + return p.parseLongFlag(flags) case COMPOUND_SHORT_FLAG: - tracef("parseFlag(...) parsing compound short flag with config=%+#v", flCfgMap) - return p.parseCompoundShortFlag(flCfgMap) + tracef("parseFlag(...) parsing compound short flag with config=%+#v", flags) + return p.parseCompoundShortFlag(flags) } panic(fmt.Sprintf("token %v cannot be parsed as flag", p.tok)) } -func (p *parser) parseShortFlag(flCfgMap map[string]FlagConfig) Node { +func (p *parser) parseShortFlag(flags *Flags) Node { node := &Flag{Name: string(p.lit[1])} - flCfg, ok := flCfgMap[node.Name] + flCfg, ok := flags.Get(node.Name) if !ok { p.addError(fmt.Sprintf("unknown flag %q", node.Name)) @@ -215,10 +217,10 @@ func (p *parser) parseShortFlag(flCfgMap map[string]FlagConfig) Node { return p.parseConfiguredFlag(node, flCfg) } -func (p *parser) parseLongFlag(flCfgMap map[string]FlagConfig) Node { +func (p *parser) parseLongFlag(flags *Flags) Node { node := &Flag{Name: string(p.lit[2:])} - flCfg, ok := flCfgMap[node.Name] + flCfg, ok := flags.Get(node.Name) if !ok { p.addError(fmt.Sprintf("unknown flag %q", node.Name)) @@ -228,7 +230,7 @@ func (p *parser) parseLongFlag(flCfgMap map[string]FlagConfig) Node { return p.parseConfiguredFlag(node, flCfg) } -func (p *parser) parseCompoundShortFlag(flCfgMap map[string]FlagConfig) Node { +func (p *parser) parseCompoundShortFlag(flags *Flags) Node { flagNodes := []Node{} withoutFlagPrefix := p.lit[1:] @@ -237,7 +239,7 @@ func (p *parser) parseCompoundShortFlag(flCfgMap map[string]FlagConfig) Node { node := &Flag{Name: string(r)} if i == len(withoutFlagPrefix)-1 { - flCfg, ok := flCfgMap[node.Name] + flCfg, ok := flags.Get(node.Name) if !ok { p.addError(fmt.Sprintf("unknown flag %q", node.Name)) diff --git a/argh/parser_config.go b/argh/parser_config.go index 62b209e..e77638e 100644 --- a/argh/parser_config.go +++ b/argh/parser_config.go @@ -36,11 +36,62 @@ type ParserConfig struct { type CommandConfig struct { NValue NValue ValueNames []string - Flags map[string]FlagConfig - Commands map[string]CommandConfig + Flags *Flags + Commands *Commands +} + +func (cCfg *CommandConfig) GetCommandConfig(name string) (CommandConfig, bool) { + if cCfg.Commands == nil { + cCfg.Commands = &Commands{Map: map[string]CommandConfig{}} + } + + return cCfg.Commands.Get(name) +} + +func (cCfg *CommandConfig) GetFlagConfig(name string) (FlagConfig, bool) { + if cCfg.Flags == nil { + cCfg.Flags = &Flags{Map: map[string]FlagConfig{}} + } + + return cCfg.Flags.Get(name) } type FlagConfig struct { NValue NValue + Persist bool ValueNames []string } + +type Flags struct { + Parent *Flags + Map map[string]FlagConfig +} + +func (fl *Flags) Get(name string) (FlagConfig, bool) { + if fl.Map == nil { + fl.Map = map[string]FlagConfig{} + } + + flCfg, ok := fl.Map[name] + if !ok && fl.Parent != nil { + flCfg, ok = fl.Parent.Get(name) + return flCfg, ok && flCfg.Persist + } + + return flCfg, ok +} + +type Commands struct { + Map map[string]CommandConfig +} + +func (cmd *Commands) Get(name string) (CommandConfig, bool) { + tracef("Get(%q)", name) + + if cmd.Map == nil { + cmd.Map = map[string]CommandConfig{} + } + + cmdCfg, ok := cmd.Map[name] + return cmdCfg, ok +} diff --git a/argh/parser_test.go b/argh/parser_test.go index fe57c77..bea803f 100644 --- a/argh/parser_test.go +++ b/argh/parser_test.go @@ -25,16 +25,20 @@ func TestParser(t *testing.T) { }, cfg: &argh.ParserConfig{ Prog: argh.CommandConfig{ - Flags: map[string]argh.FlagConfig{ - "e": {}, - "a": {}, - "t": {}, - "wat": {}, + Flags: &argh.Flags{ + Map: map[string]argh.FlagConfig{ + "e": {}, + "a": {}, + "t": {}, + "wat": {}, + }, }, - Commands: map[string]argh.CommandConfig{ - "hello": argh.CommandConfig{ - NValue: 1, - ValueNames: []string{"name"}, + Commands: &argh.Commands{ + Map: map[string]argh.CommandConfig{ + "hello": argh.CommandConfig{ + NValue: 1, + ValueNames: []string{"name"}, + }, }, }, }, @@ -88,6 +92,89 @@ func TestParser(t *testing.T) { }, }, }, + { + name: "persistent flags", + args: []string{ + "pies", "--wat", "hello", "mario", "-eat", + }, + cfg: &argh.ParserConfig{ + Prog: func() argh.CommandConfig { + cmdCfg := argh.CommandConfig{ + Flags: &argh.Flags{ + Map: map[string]argh.FlagConfig{ + "e": {Persist: true}, + "a": {Persist: true}, + "t": {Persist: true}, + "wat": {}, + }, + }, + } + + cmdCfg.Commands = &argh.Commands{ + Map: map[string]argh.CommandConfig{ + "hello": argh.CommandConfig{ + NValue: 1, + ValueNames: []string{"name"}, + Flags: &argh.Flags{ + Parent: cmdCfg.Flags, + Map: map[string]argh.FlagConfig{}, + }, + }, + }, + } + + return cmdCfg + }(), + }, + expPT: []argh.Node{ + &argh.Command{ + Name: "pies", + Nodes: []argh.Node{ + &argh.ArgDelimiter{}, + &argh.Flag{Name: "wat"}, + &argh.ArgDelimiter{}, + &argh.Command{ + Name: "hello", + Values: map[string]string{ + "name": "mario", + }, + Nodes: []argh.Node{ + &argh.ArgDelimiter{}, + &argh.Ident{Literal: "mario"}, + &argh.ArgDelimiter{}, + &argh.CompoundShortFlag{ + Nodes: []argh.Node{ + &argh.Flag{Name: "e"}, + &argh.Flag{Name: "a"}, + &argh.Flag{Name: "t"}, + }, + }, + }, + }, + }, + }, + }, + expAST: []argh.Node{ + &argh.Command{ + Name: "pies", + Nodes: []argh.Node{ + &argh.Flag{Name: "wat"}, + &argh.Command{ + Name: "hello", + Values: map[string]string{ + "name": "mario", + }, + Nodes: []argh.Node{ + &argh.Ident{Literal: "mario"}, + &argh.Flag{Name: "e"}, + &argh.Flag{Name: "a"}, + &argh.Flag{Name: "t"}, + }, + }, + }, + }, + }, + }, { name: "bare", args: []string{"pizzas"}, @@ -181,10 +268,12 @@ func TestParser(t *testing.T) { args: []string{"pizzas", "--tasty", "--fresh", "--super-hot-right-now"}, cfg: &argh.ParserConfig{ Prog: argh.CommandConfig{ - Flags: map[string]argh.FlagConfig{ - "tasty": {}, - "fresh": {}, - "super-hot-right-now": {}, + Flags: &argh.Flags{ + Map: map[string]argh.FlagConfig{ + "tasty": {}, + "fresh": {}, + "super-hot-right-now": {}, + }, }, }, }, @@ -224,13 +313,15 @@ func TestParser(t *testing.T) { }, cfg: &argh.ParserConfig{ Prog: argh.CommandConfig{ - Commands: map[string]argh.CommandConfig{}, - Flags: map[string]argh.FlagConfig{ - "tasty": {}, - "fresh": argh.FlagConfig{NValue: 1}, - "super-hot-right-now": {}, - "box": argh.FlagConfig{NValue: argh.OneOrMoreValue}, - "please": {}, + Commands: &argh.Commands{Map: map[string]argh.CommandConfig{}}, + Flags: &argh.Flags{ + Map: map[string]argh.FlagConfig{ + "tasty": {}, + "fresh": argh.FlagConfig{NValue: 1}, + "super-hot-right-now": {}, + "box": argh.FlagConfig{NValue: argh.OneOrMoreValue}, + "please": {}, + }, }, }, }, @@ -301,10 +392,12 @@ func TestParser(t *testing.T) { args: []string{"pizzas", "-t", "-f", "-s"}, cfg: &argh.ParserConfig{ Prog: argh.CommandConfig{ - Flags: map[string]argh.FlagConfig{ - "t": {}, - "f": {}, - "s": {}, + Flags: &argh.Flags{ + Map: map[string]argh.FlagConfig{ + "t": {}, + "f": {}, + "s": {}, + }, }, }, }, @@ -337,12 +430,14 @@ func TestParser(t *testing.T) { args: []string{"pizzas", "-aca", "-blol"}, cfg: &argh.ParserConfig{ Prog: argh.CommandConfig{ - Flags: map[string]argh.FlagConfig{ - "a": {}, - "b": {}, - "c": {}, - "l": {}, - "o": {}, + Flags: &argh.Flags{ + Map: map[string]argh.FlagConfig{ + "a": {}, + "b": {}, + "c": {}, + "l": {}, + "o": {}, + }, }, }, }, @@ -390,13 +485,15 @@ func TestParser(t *testing.T) { args: []string{"pizzas", "-a", "--ca", "-b", "1312", "-lol"}, cfg: &argh.ParserConfig{ Prog: argh.CommandConfig{ - Commands: map[string]argh.CommandConfig{}, - Flags: map[string]argh.FlagConfig{ - "a": {}, - "b": argh.FlagConfig{NValue: 1}, - "ca": {}, - "l": {}, - "o": {}, + Commands: &argh.Commands{Map: map[string]argh.CommandConfig{}}, + Flags: &argh.Flags{ + Map: map[string]argh.FlagConfig{ + "a": {}, + "b": argh.FlagConfig{NValue: 1}, + "ca": {}, + "l": {}, + "o": {}, + }, }, }, }, @@ -453,18 +550,24 @@ func TestParser(t *testing.T) { args: []string{"pizzas", "fly", "freely", "sometimes", "and", "other", "times", "fry", "deeply", "--forever"}, cfg: &argh.ParserConfig{ Prog: argh.CommandConfig{ - Commands: map[string]argh.CommandConfig{ - "fly": argh.CommandConfig{ - Commands: map[string]argh.CommandConfig{ - "fry": argh.CommandConfig{ - Flags: map[string]argh.FlagConfig{ - "forever": {}, + Commands: &argh.Commands{ + Map: map[string]argh.CommandConfig{ + "fly": argh.CommandConfig{ + Commands: &argh.Commands{ + Map: map[string]argh.CommandConfig{ + "fry": argh.CommandConfig{ + Flags: &argh.Flags{ + Map: map[string]argh.FlagConfig{ + "forever": {}, + }, + }, + }, }, }, }, }, }, - Flags: map[string]argh.FlagConfig{}, + Flags: &argh.Flags{Map: map[string]argh.FlagConfig{}}, }, }, expPT: []argh.Node{ @@ -530,14 +633,16 @@ func TestParser(t *testing.T) { args: []string{"pizzas", "-need", "sauce", "heat", "love", "-also", "over9000"}, cfg: &argh.ParserConfig{ Prog: argh.CommandConfig{ - Flags: map[string]argh.FlagConfig{ - "a": {NValue: argh.ZeroOrMoreValue}, - "d": {NValue: argh.OneOrMoreValue}, - "e": {}, - "l": {}, - "n": {}, - "o": {NValue: 1, ValueNames: []string{"level"}}, - "s": {NValue: argh.ZeroOrMoreValue}, + Flags: &argh.Flags{ + Map: map[string]argh.FlagConfig{ + "a": {NValue: argh.ZeroOrMoreValue}, + "d": {NValue: argh.OneOrMoreValue}, + "e": {}, + "l": {}, + "n": {}, + "o": {NValue: 1, ValueNames: []string{"level"}}, + "s": {NValue: argh.ZeroOrMoreValue}, + }, }, }, }, @@ -631,24 +736,32 @@ func TestParser(t *testing.T) { args: []string{"pizzas", "fly", "--freely", "fry", "--deeply", "-wAt", "hugs"}, cfg: &argh.ParserConfig{ Prog: argh.CommandConfig{ - Commands: map[string]argh.CommandConfig{ - "fly": argh.CommandConfig{ - Flags: map[string]argh.FlagConfig{ - "freely": {}, - }, - Commands: map[string]argh.CommandConfig{ - "fry": argh.CommandConfig{ - Flags: map[string]argh.FlagConfig{ - "deeply": {}, - "w": {}, - "A": {}, - "t": argh.FlagConfig{NValue: 1}, + Commands: &argh.Commands{ + Map: map[string]argh.CommandConfig{ + "fly": argh.CommandConfig{ + Flags: &argh.Flags{ + Map: map[string]argh.FlagConfig{ + "freely": {}, + }, + }, + Commands: &argh.Commands{ + Map: map[string]argh.CommandConfig{ + "fry": argh.CommandConfig{ + Flags: &argh.Flags{ + Map: map[string]argh.FlagConfig{ + "deeply": {}, + "w": {}, + "A": {}, + "t": argh.FlagConfig{NValue: 1}, + }, + }, + }, }, }, }, }, }, - Flags: map[string]argh.FlagConfig{}, + Flags: &argh.Flags{Map: map[string]argh.FlagConfig{}}, }, }, expPT: []argh.Node{ @@ -723,19 +836,25 @@ func TestParser(t *testing.T) { args: []string{"PIZZAs", "^wAT@golf", "^^hecKing", "goose", "bonk", "^^FIERCENESS@-2"}, cfg: &argh.ParserConfig{ Prog: argh.CommandConfig{ - Commands: map[string]argh.CommandConfig{ - "goose": argh.CommandConfig{ - NValue: 1, - Flags: map[string]argh.FlagConfig{ - "FIERCENESS": argh.FlagConfig{NValue: 1}, + Commands: &argh.Commands{ + Map: map[string]argh.CommandConfig{ + "goose": argh.CommandConfig{ + NValue: 1, + Flags: &argh.Flags{ + Map: map[string]argh.FlagConfig{ + "FIERCENESS": argh.FlagConfig{NValue: 1}, + }, + }, }, }, }, - Flags: map[string]argh.FlagConfig{ - "w": argh.FlagConfig{}, - "A": argh.FlagConfig{}, - "T": argh.FlagConfig{NValue: 1}, - "hecKing": argh.FlagConfig{}, + Flags: &argh.Flags{ + Map: map[string]argh.FlagConfig{ + "w": argh.FlagConfig{}, + "A": argh.FlagConfig{}, + "T": argh.FlagConfig{NValue: 1}, + "hecKing": argh.FlagConfig{}, + }, }, }, ScannerConfig: &argh.ScannerConfig{ @@ -792,13 +911,17 @@ func TestParser(t *testing.T) { args: []string{"hotdog", "/f", "/L", "/o:ppy", "hats"}, cfg: &argh.ParserConfig{ Prog: argh.CommandConfig{ - Flags: map[string]argh.FlagConfig{ - "f": {}, - "L": {}, - "o": argh.FlagConfig{NValue: 1}, + Flags: &argh.Flags{ + Map: map[string]argh.FlagConfig{ + "f": {}, + "L": {}, + "o": argh.FlagConfig{NValue: 1}, + }, }, - Commands: map[string]argh.CommandConfig{ - "hats": {}, + Commands: &argh.Commands{ + Map: map[string]argh.CommandConfig{ + "hats": {}, + }, }, }, ScannerConfig: &argh.ScannerConfig{ @@ -835,8 +958,10 @@ func TestParser(t *testing.T) { args: []string{"pizzas", "=", "--wat"}, cfg: &argh.ParserConfig{ Prog: argh.CommandConfig{ - Flags: map[string]argh.FlagConfig{ - "wat": {}, + Flags: &argh.Flags{ + Map: map[string]argh.FlagConfig{ + "wat": {}, + }, }, }, }, diff --git a/argh/querier_test.go b/argh/querier_test.go index bb3b767..962d869 100644 --- a/argh/querier_test.go +++ b/argh/querier_test.go @@ -20,10 +20,14 @@ func TestQuerier_Program(t *testing.T) { args: []string{"pizzas", "ahoy", "--treatsa", "fun"}, cfg: &argh.ParserConfig{ Prog: argh.CommandConfig{ - Commands: map[string]argh.CommandConfig{ - "ahoy": argh.CommandConfig{ - Flags: map[string]argh.FlagConfig{ - "treatsa": argh.FlagConfig{NValue: 1}, + Commands: &argh.Commands{ + Map: map[string]argh.CommandConfig{ + "ahoy": argh.CommandConfig{ + Flags: &argh.Flags{ + Map: map[string]argh.FlagConfig{ + "treatsa": argh.FlagConfig{NValue: 1}, + }, + }, }, }, },