From 03edacc8ec44b2b8ff9778dc640d0254f024c81c Mon Sep 17 00:00:00 2001 From: Dan Buch Date: Wed, 25 May 2022 21:55:16 -0400 Subject: [PATCH] Implementing long flag values in parser2 --- argh/node.go | 21 +++--- argh/parser2.go | 171 ++++++++++++++++++++++++++++++++----------- argh/parser2_test.go | 134 ++++++++++++++++++++------------- argh/querier.go | 30 +++++++- 4 files changed, 250 insertions(+), 106 deletions(-) diff --git a/argh/node.go b/argh/node.go index c8fccda..a18e7b0 100644 --- a/argh/node.go +++ b/argh/node.go @@ -3,20 +3,20 @@ package argh type Node interface{} type TypedNode struct { - Type string `json:"type"` - Node Node `json:"node"` + Type string + Node Node } type PassthroughArgs struct { - Nodes []Node `json:"nodes"` + Nodes []Node } type CompoundShortFlag struct { - Nodes []Node `json:"nodes"` + Nodes []Node } type Ident struct { - Literal string `json:"literal"` + Literal string } type BadArg struct { @@ -26,14 +26,15 @@ type BadArg struct { } type Command struct { - Name string `json:"name"` - Values map[string]string `json:"values"` - Nodes []Node `json:"nodes"` + Name string + Values map[string]string + Nodes []Node } type Flag struct { - Name string `json:"name"` - Values map[string]string `json:"values"` + Name string + Values map[string]string + Nodes []Node } type StdinFlag struct{} diff --git a/argh/parser2.go b/argh/parser2.go index 1efb242..9aab14d 100644 --- a/argh/parser2.go +++ b/argh/parser2.go @@ -16,6 +16,8 @@ type parser2 struct { tok Token lit string pos Pos + + buffered bool } func ParseArgs2(args []string, pCfg *ParserConfig) (*ParseTree, error) { @@ -25,7 +27,7 @@ func ParseArgs2(args []string, pCfg *ParserConfig) (*ParseTree, error) { pCfg, ) - tracef("ParseArgs2 parser=%+#v", parser) + tracef("ParseArgs2(...) parser=%+#v", parser) return parser.parseArgs() } @@ -46,81 +48,93 @@ func (p *parser2) init(r io.Reader, pCfg *ParserConfig) { func (p *parser2) parseArgs() (*ParseTree, error) { if p.errors.Len() != 0 { - tracef("parseArgs bailing due to initial error") + tracef("parseArgs() bailing due to initial error") return nil, p.errors.Err() } + tracef("parseArgs() parsing %q as program command; cfg=%+#v", p.lit, p.cfg.Prog) prog := p.parseCommand(&p.cfg.Prog) nodes := []Node{prog} if v := p.parsePassthrough(); v != nil { + tracef("parseArgs() appending passthrough argument %v", v) nodes = append(nodes, v) } - return &ParseTree{ - Nodes: nodes, - }, nil + tracef("parseArgs() returning ParseTree") + + return &ParseTree{Nodes: nodes}, nil } func (p *parser2) next() { - tracef("parser2.next() current: %v %q %v", p.tok, p.lit, p.pos) + tracef("next() before scan: %v %q %v", p.tok, p.lit, p.pos) p.tok, p.lit, p.pos = p.s.Scan() - tracef("parser2.next() next: %v %q %v", p.tok, p.lit, p.pos) + tracef("next() after scan: %v %q %v", p.tok, p.lit, p.pos) } func (p *parser2) parseCommand(cCfg *CommandConfig) Node { - tracef("parseCommand cfg=%+#v", cCfg) + tracef("parseCommand(%+#v)", cCfg) node := &Command{ - Name: p.lit, - Values: map[string]string{}, - Nodes: []Node{}, + Name: p.lit, } + values := map[string]string{} + nodes := []Node{} identIndex := 0 for i := 0; p.tok != EOL; i++ { - p.next() + if !p.buffered { + tracef("parseCommand(...) buffered=false; scanning next") + p.next() + } + + p.buffered = false - tracef("parseCommand for=%d node.Values=%+#v", i, node.Values) - tracef("parseCommand for=%d node.Nodes=%+#v", i, node.Values) + tracef("parseCommand(...) for=%d values=%+#v", i, values) + 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 { subCommand := p.lit - node.Nodes = append(node.Nodes, p.parseCommand(&subCfg)) + nodes = append(nodes, p.parseCommand(&subCfg)) - tracef("parseCommand breaking after sub-command=%v", subCommand) + tracef("parseCommand(...) breaking after sub-command=%v", subCommand) break } switch p.tok { case ARG_DELIMITER: - tracef("parseCommand handling %s", p.tok) + tracef("parseCommand(...) handling %s", p.tok) - node.Nodes = append(node.Nodes, &ArgDelimiter{}) + nodes = append(nodes, &ArgDelimiter{}) continue case IDENT, STDIN_FLAG: - tracef("parseCommand handling %s", p.tok) + tracef("parseCommand(...) handling %s", p.tok) if !cCfg.NValue.Contains(identIndex) { - tracef("parseCommand identIndex=%d exceeds expected=%s; breaking", identIndex, cCfg.NValue) + tracef("parseCommand(...) identIndex=%d exceeds expected=%s; breaking", identIndex, cCfg.NValue) break } name := fmt.Sprintf("%d", identIndex) - tracef("parseCommand checking for name of identIndex=%d", identIndex) + tracef("parseCommand(...) checking for name of identIndex=%d", identIndex) if len(cCfg.ValueNames) > identIndex { name = cCfg.ValueNames[identIndex] - tracef("parseCommand setting name=%s from config value names", name) + tracef("parseCommand(...) setting name=%s from config value names", name) } else if len(cCfg.ValueNames) == 1 && (cCfg.NValue == OneOrMoreValue || cCfg.NValue == ZeroOrMoreValue) { name = fmt.Sprintf("%s.%d", cCfg.ValueNames[0], identIndex) - tracef("parseCommand setting name=%s from repeating value name", name) + tracef("parseCommand(...) setting name=%s from repeating value name", name) + } + + if node.Values == nil { + node.Values = map[string]string{} } node.Values[name] = p.lit @@ -128,61 +142,132 @@ func (p *parser2) parseCommand(cCfg *CommandConfig) Node { identIndex++ case LONG_FLAG, SHORT_FLAG, COMPOUND_SHORT_FLAG: tok := p.tok - flagNode := p.parseFlag() - tracef("parseCommand appending %s node=%+#v", tok, flagNode) + flagNode := p.parseFlag(cCfg.Flags) + + tracef("parseCommand(...) appending %s node=%+#v", tok, flagNode) - node.Nodes = append(node.Nodes, flagNode) + nodes = append(nodes, flagNode) default: - tracef("parseCommand breaking on %s", p.tok) + tracef("parseCommand(...) breaking on %s", p.tok) break } } - tracef("parseCommand returning node=%+#v", node) + if len(nodes) > 0 { + node.Nodes = nodes + } + + if len(values) > 0 { + node.Values = values + } + + tracef("parseCommand(...) returning node=%+#v", node) return node } func (p *parser2) parseIdent() Node { - defer p.next() - node := &Ident{Literal: p.lit} return node } -func (p *parser2) parseFlag() Node { - defer p.next() - +func (p *parser2) parseFlag(flCfgMap map[string]FlagConfig) Node { switch p.tok { case SHORT_FLAG: - return p.parseShortFlag() + return p.parseShortFlag(flCfgMap) case LONG_FLAG: - return p.parseLongFlag() + return p.parseLongFlag(flCfgMap) case COMPOUND_SHORT_FLAG: - return p.parseCompoundShortFlag() + return p.parseCompoundShortFlag(flCfgMap) } panic(fmt.Sprintf("token %v cannot be parsed as flag", p.tok)) } -func (p *parser2) parseShortFlag() Node { - node := &Flag{Name: string(p.lit[1])} - // TODO: moar stuff +func (p *parser2) parseShortFlag(flCfgMap map[string]FlagConfig) Node { + name := string(p.lit[1]) + node := &Flag{Name: name} + tracef("parseShortFlag(...) TODO capture flag value(s)") return node } -func (p *parser2) parseLongFlag() Node { +func (p *parser2) parseLongFlag(flCfgMap map[string]FlagConfig) Node { node := &Flag{Name: string(p.lit[2:])} - // TODO: moar stuff + values := map[string]string{} + nodes := []Node{} + + flCfg, ok := flCfgMap[node.Name] + if !ok { + return node + } + + identIndex := 0 + + for i := 0; p.tok != EOL; i++ { + if !flCfg.NValue.Contains(identIndex) { + tracef("parseLongFlag(...) identIndex=%d exceeds expected=%s; breaking") + break + } + + p.next() + + switch p.tok { + case ARG_DELIMITER: + nodes = append(nodes, &ArgDelimiter{}) + + continue + case IDENT, STDIN_FLAG: + name := fmt.Sprintf("%d", identIndex) + + tracef("parseLongFlag(...) checking for name of identIndex=%d", identIndex) + + if len(flCfg.ValueNames) > identIndex { + name = flCfg.ValueNames[identIndex] + tracef("parseLongFlag(...) setting name=%s from config value names", name) + } else if len(flCfg.ValueNames) == 1 && (flCfg.NValue == OneOrMoreValue || flCfg.NValue == ZeroOrMoreValue) { + name = fmt.Sprintf("%s.%d", flCfg.ValueNames[0], identIndex) + tracef("parseLongFlag(...) setting name=%s from repeating value name", name) + } + + values[name] = p.lit + + identIndex++ + default: + tracef("parseLongFlag(...) breaking on %s %q %v; setting buffered=true", p.tok, p.lit, p.pos) + p.buffered = true + + if len(nodes) > 0 { + node.Nodes = nodes + } + + if len(values) > 0 { + node.Values = values + } + + return node + } + } + + if len(nodes) > 0 { + node.Nodes = nodes + } + + if len(values) > 0 { + node.Values = values + } + return node } -func (p *parser2) parseCompoundShortFlag() Node { +func (p *parser2) parseCompoundShortFlag(flCfgMap map[string]FlagConfig) Node { flagNodes := []Node{} withoutFlagPrefix := p.lit[1:] - for _, r := range withoutFlagPrefix { + for i, r := range withoutFlagPrefix { + if i == len(withoutFlagPrefix)-1 { + tracef("parseCompoundShortFlag(...) TODO capture flag value(s)") + } flagNodes = append(flagNodes, &Flag{Name: string(r)}) } diff --git a/argh/parser2_test.go b/argh/parser2_test.go index 4549e94..83742a2 100644 --- a/argh/parser2_test.go +++ b/argh/parser2_test.go @@ -35,8 +35,7 @@ func TestParser2(t *testing.T) { }, expPT: []argh.Node{ &argh.Command{ - Name: "pies", - Values: map[string]string{}, + Name: "pies", Nodes: []argh.Node{ &argh.ArgDelimiter{}, &argh.CompoundShortFlag{ @@ -46,7 +45,9 @@ func TestParser2(t *testing.T) { &argh.Flag{Name: "t"}, }, }, + &argh.ArgDelimiter{}, &argh.Flag{Name: "wat"}, + &argh.ArgDelimiter{}, &argh.Command{ Name: "hello", Values: map[string]string{ @@ -61,8 +62,7 @@ func TestParser2(t *testing.T) { }, expAST: []argh.Node{ &argh.Command{ - Name: "pies", - Values: map[string]string{}, + Name: "pies", Nodes: []argh.Node{ &argh.Flag{Name: "e"}, &argh.Flag{Name: "a"}, @@ -73,7 +73,6 @@ func TestParser2(t *testing.T) { Values: map[string]string{ "name": "mario", }, - Nodes: []argh.Node{}, }, }, }, @@ -84,37 +83,38 @@ func TestParser2(t *testing.T) { args: []string{"pizzas"}, expPT: []argh.Node{ &argh.Command{ - Name: "pizzas", - Values: map[string]string{}, - Nodes: []argh.Node{}, + Name: "pizzas", }, }, expAST: []argh.Node{ &argh.Command{ - Name: "pizzas", - Values: map[string]string{}, - Nodes: []argh.Node{}, + Name: "pizzas", }, }, }, { - skip: true, - name: "one positional arg", args: []string{"pizzas", "excel"}, cfg: &argh.ParserConfig{ Prog: argh.CommandConfig{NValue: 1}, }, expPT: []argh.Node{ - argh.Command{Name: "pizzas", Values: map[string]string{"0": "excel"}}, + &argh.Command{ + Name: "pizzas", + Values: map[string]string{"0": "excel"}, + Nodes: []argh.Node{ + &argh.ArgDelimiter{}, + }, + }, }, expAST: []argh.Node{ - argh.Command{Name: "pizzas", Values: map[string]string{"0": "excel"}}, + &argh.Command{ + Name: "pizzas", + Values: map[string]string{"0": "excel"}, + }, }, }, { - skip: true, - name: "many positional args", args: []string{"pizzas", "excel", "wildly", "when", "feral"}, cfg: &argh.ParserConfig{ @@ -124,7 +124,7 @@ func TestParser2(t *testing.T) { }, }, expPT: []argh.Node{ - argh.Command{ + &argh.Command{ Name: "pizzas", Values: map[string]string{ "word": "excel", @@ -132,10 +132,16 @@ func TestParser2(t *testing.T) { "word.2": "when", "word.3": "feral", }, + Nodes: []argh.Node{ + &argh.ArgDelimiter{}, + &argh.ArgDelimiter{}, + &argh.ArgDelimiter{}, + &argh.ArgDelimiter{}, + }, }, }, expAST: []argh.Node{ - argh.Command{ + &argh.Command{ Name: "pizzas", Values: map[string]string{ "word": "excel", @@ -147,29 +153,33 @@ func TestParser2(t *testing.T) { }, }, { - skip: true, - name: "long value-less flags", args: []string{"pizzas", "--tasty", "--fresh", "--super-hot-right-now"}, expPT: []argh.Node{ - argh.Command{Name: "pizzas"}, - argh.ArgDelimiter{}, - argh.Flag{Name: "tasty"}, - argh.ArgDelimiter{}, - argh.Flag{Name: "fresh"}, - argh.ArgDelimiter{}, - argh.Flag{Name: "super-hot-right-now"}, + &argh.Command{ + Name: "pizzas", + Nodes: []argh.Node{ + &argh.ArgDelimiter{}, + &argh.Flag{Name: "tasty"}, + &argh.ArgDelimiter{}, + &argh.Flag{Name: "fresh"}, + &argh.ArgDelimiter{}, + &argh.Flag{Name: "super-hot-right-now"}, + }, + }, }, expAST: []argh.Node{ - argh.Command{Name: "pizzas"}, - argh.Flag{Name: "tasty"}, - argh.Flag{Name: "fresh"}, - argh.Flag{Name: "super-hot-right-now"}, + &argh.Command{ + Name: "pizzas", + Nodes: []argh.Node{ + &argh.Flag{Name: "tasty"}, + &argh.Flag{Name: "fresh"}, + &argh.Flag{Name: "super-hot-right-now"}, + }, + }, }, }, { - skip: true, - name: "long flags mixed", args: []string{ "pizzas", @@ -189,25 +199,47 @@ func TestParser2(t *testing.T) { }, }, expPT: []argh.Node{ - argh.Command{Name: "pizzas"}, - argh.ArgDelimiter{}, - argh.Flag{Name: "tasty"}, - argh.ArgDelimiter{}, - argh.Flag{Name: "fresh", Values: map[string]string{"0": "soon"}}, - argh.ArgDelimiter{}, - argh.Flag{Name: "super-hot-right-now"}, - argh.ArgDelimiter{}, - argh.Flag{Name: "box", Values: map[string]string{"0": "square", "1": "shaped", "2": "hot"}}, - argh.ArgDelimiter{}, - argh.Flag{Name: "please"}, + &argh.Command{ + Name: "pizzas", + Nodes: []argh.Node{ + &argh.ArgDelimiter{}, + &argh.Flag{Name: "tasty"}, + &argh.ArgDelimiter{}, + &argh.Flag{ + Name: "fresh", + Values: map[string]string{"0": "soon"}, + Nodes: []argh.Node{ + &argh.ArgDelimiter{}, + }, + }, + &argh.ArgDelimiter{}, + &argh.Flag{Name: "super-hot-right-now"}, + &argh.ArgDelimiter{}, + &argh.Flag{ + Name: "box", + Values: map[string]string{"0": "square", "1": "shaped", "2": "hot"}, + Nodes: []argh.Node{ + &argh.ArgDelimiter{}, + &argh.ArgDelimiter{}, + &argh.ArgDelimiter{}, + &argh.ArgDelimiter{}, + }, + }, + &argh.Flag{Name: "please"}, + }, + }, }, expAST: []argh.Node{ - argh.Command{Name: "pizzas"}, - argh.Flag{Name: "tasty"}, - argh.Flag{Name: "fresh", Values: map[string]string{"0": "soon"}}, - argh.Flag{Name: "super-hot-right-now"}, - argh.Flag{Name: "box", Values: map[string]string{"0": "square", "1": "shaped", "2": "hot"}}, - argh.Flag{Name: "please"}, + &argh.Command{ + Name: "pizzas", + Nodes: []argh.Node{ + &argh.Flag{Name: "tasty"}, + &argh.Flag{Name: "fresh", Values: map[string]string{"0": "soon"}}, + &argh.Flag{Name: "super-hot-right-now"}, + &argh.Flag{Name: "box", Values: map[string]string{"0": "square", "1": "shaped", "2": "hot"}}, + &argh.Flag{Name: "please"}, + }, + }, }, }, { diff --git a/argh/querier.go b/argh/querier.go index f2f6c8a..154914b 100644 --- a/argh/querier.go +++ b/argh/querier.go @@ -37,18 +37,44 @@ func (dq *defaultQuerier) AST() []Node { } if v, ok := node.(*CompoundShortFlag); ok { - ret = append(ret, NewQuerier(v.Nodes).AST()...) + if v.Nodes != nil { + ret = append(ret, NewQuerier(v.Nodes).AST()...) + } continue } if v, ok := node.(*Command); ok { + astNodes := NewQuerier(v.Nodes).AST() + + if len(astNodes) == 0 { + astNodes = nil + } + ret = append( ret, &Command{ Name: v.Name, Values: v.Values, - Nodes: NewQuerier(v.Nodes).AST(), + Nodes: astNodes, + }) + + continue + } + + if v, ok := node.(*Flag); ok { + astNodes := NewQuerier(v.Nodes).AST() + + if len(astNodes) == 0 { + astNodes = nil + } + + ret = append( + ret, + &Flag{ + Name: v.Name, + Values: v.Values, + Nodes: astNodes, }) continue