Support persistent flags

This commit is contained in:
Dan Buch 2022-05-31 08:24:58 -04:00
parent 395006cdb8
commit 92e3d6fe5b
Signed by: meatballhat
GPG Key ID: A12F782281063434
4 changed files with 285 additions and 103 deletions

View File

@ -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))

View File

@ -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
}

View File

@ -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": {},
},
},
},
},

View File

@ -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},
},
},
},
},
},