Implementing long flag values in parser2

arghing
Dan Buch 2 years ago
parent d1ffbe25a3
commit 03edacc8ec
Signed by: meatballhat
GPG Key ID: A12F782281063434

@ -3,20 +3,20 @@ package argh
type Node interface{} type Node interface{}
type TypedNode struct { type TypedNode struct {
Type string `json:"type"` Type string
Node Node `json:"node"` Node Node
} }
type PassthroughArgs struct { type PassthroughArgs struct {
Nodes []Node `json:"nodes"` Nodes []Node
} }
type CompoundShortFlag struct { type CompoundShortFlag struct {
Nodes []Node `json:"nodes"` Nodes []Node
} }
type Ident struct { type Ident struct {
Literal string `json:"literal"` Literal string
} }
type BadArg struct { type BadArg struct {
@ -26,14 +26,15 @@ type BadArg struct {
} }
type Command struct { type Command struct {
Name string `json:"name"` Name string
Values map[string]string `json:"values"` Values map[string]string
Nodes []Node `json:"nodes"` Nodes []Node
} }
type Flag struct { type Flag struct {
Name string `json:"name"` Name string
Values map[string]string `json:"values"` Values map[string]string
Nodes []Node
} }
type StdinFlag struct{} type StdinFlag struct{}

@ -16,6 +16,8 @@ type parser2 struct {
tok Token tok Token
lit string lit string
pos Pos pos Pos
buffered bool
} }
func ParseArgs2(args []string, pCfg *ParserConfig) (*ParseTree, error) { func ParseArgs2(args []string, pCfg *ParserConfig) (*ParseTree, error) {
@ -25,7 +27,7 @@ func ParseArgs2(args []string, pCfg *ParserConfig) (*ParseTree, error) {
pCfg, pCfg,
) )
tracef("ParseArgs2 parser=%+#v", parser) tracef("ParseArgs2(...) parser=%+#v", parser)
return parser.parseArgs() return parser.parseArgs()
} }
@ -46,81 +48,93 @@ func (p *parser2) init(r io.Reader, pCfg *ParserConfig) {
func (p *parser2) parseArgs() (*ParseTree, error) { func (p *parser2) parseArgs() (*ParseTree, error) {
if p.errors.Len() != 0 { if p.errors.Len() != 0 {
tracef("parseArgs bailing due to initial error") tracef("parseArgs() bailing due to initial error")
return nil, p.errors.Err() 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) prog := p.parseCommand(&p.cfg.Prog)
nodes := []Node{prog} nodes := []Node{prog}
if v := p.parsePassthrough(); v != nil { if v := p.parsePassthrough(); v != nil {
tracef("parseArgs() appending passthrough argument %v", v)
nodes = append(nodes, v) nodes = append(nodes, v)
} }
return &ParseTree{ tracef("parseArgs() returning ParseTree")
Nodes: nodes,
}, nil return &ParseTree{Nodes: nodes}, nil
} }
func (p *parser2) next() { 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() 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 { func (p *parser2) parseCommand(cCfg *CommandConfig) Node {
tracef("parseCommand cfg=%+#v", cCfg) tracef("parseCommand(%+#v)", cCfg)
node := &Command{ node := &Command{
Name: p.lit, Name: p.lit,
Values: map[string]string{},
Nodes: []Node{},
} }
values := map[string]string{}
nodes := []Node{}
identIndex := 0 identIndex := 0
for i := 0; p.tok != EOL; i++ { 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 values=%+#v", i, values)
tracef("parseCommand for=%d node.Nodes=%+#v", i, node.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 { if subCfg, ok := cCfg.Commands[p.lit]; ok {
subCommand := p.lit 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 break
} }
switch p.tok { switch p.tok {
case ARG_DELIMITER: 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 continue
case IDENT, STDIN_FLAG: case IDENT, STDIN_FLAG:
tracef("parseCommand handling %s", p.tok) tracef("parseCommand(...) handling %s", p.tok)
if !cCfg.NValue.Contains(identIndex) { 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 break
} }
name := fmt.Sprintf("%d", identIndex) 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 { if len(cCfg.ValueNames) > identIndex {
name = 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) { } else if len(cCfg.ValueNames) == 1 && (cCfg.NValue == OneOrMoreValue || cCfg.NValue == ZeroOrMoreValue) {
name = fmt.Sprintf("%s.%d", cCfg.ValueNames[0], identIndex) 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 node.Values[name] = p.lit
@ -128,61 +142,132 @@ func (p *parser2) parseCommand(cCfg *CommandConfig) Node {
identIndex++ identIndex++
case LONG_FLAG, SHORT_FLAG, COMPOUND_SHORT_FLAG: case LONG_FLAG, SHORT_FLAG, COMPOUND_SHORT_FLAG:
tok := p.tok 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: default:
tracef("parseCommand breaking on %s", p.tok) tracef("parseCommand(...) breaking on %s", p.tok)
break 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 return node
} }
func (p *parser2) parseIdent() Node { func (p *parser2) parseIdent() Node {
defer p.next()
node := &Ident{Literal: p.lit} node := &Ident{Literal: p.lit}
return node return node
} }
func (p *parser2) parseFlag() Node { func (p *parser2) parseFlag(flCfgMap map[string]FlagConfig) Node {
defer p.next()
switch p.tok { switch p.tok {
case SHORT_FLAG: case SHORT_FLAG:
return p.parseShortFlag() return p.parseShortFlag(flCfgMap)
case LONG_FLAG: case LONG_FLAG:
return p.parseLongFlag() return p.parseLongFlag(flCfgMap)
case COMPOUND_SHORT_FLAG: case COMPOUND_SHORT_FLAG:
return p.parseCompoundShortFlag() return p.parseCompoundShortFlag(flCfgMap)
} }
panic(fmt.Sprintf("token %v cannot be parsed as flag", p.tok)) panic(fmt.Sprintf("token %v cannot be parsed as flag", p.tok))
} }
func (p *parser2) parseShortFlag() Node { func (p *parser2) parseShortFlag(flCfgMap map[string]FlagConfig) Node {
node := &Flag{Name: string(p.lit[1])} name := string(p.lit[1])
// TODO: moar stuff node := &Flag{Name: name}
tracef("parseShortFlag(...) TODO capture flag value(s)")
return node return node
} }
func (p *parser2) parseLongFlag() Node { func (p *parser2) parseLongFlag(flCfgMap map[string]FlagConfig) Node {
node := &Flag{Name: string(p.lit[2:])} 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 return node
} }
func (p *parser2) parseCompoundShortFlag() Node { func (p *parser2) parseCompoundShortFlag(flCfgMap map[string]FlagConfig) Node {
flagNodes := []Node{} flagNodes := []Node{}
withoutFlagPrefix := p.lit[1:] 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)}) flagNodes = append(flagNodes, &Flag{Name: string(r)})
} }

@ -35,8 +35,7 @@ func TestParser2(t *testing.T) {
}, },
expPT: []argh.Node{ expPT: []argh.Node{
&argh.Command{ &argh.Command{
Name: "pies", Name: "pies",
Values: map[string]string{},
Nodes: []argh.Node{ Nodes: []argh.Node{
&argh.ArgDelimiter{}, &argh.ArgDelimiter{},
&argh.CompoundShortFlag{ &argh.CompoundShortFlag{
@ -46,7 +45,9 @@ func TestParser2(t *testing.T) {
&argh.Flag{Name: "t"}, &argh.Flag{Name: "t"},
}, },
}, },
&argh.ArgDelimiter{},
&argh.Flag{Name: "wat"}, &argh.Flag{Name: "wat"},
&argh.ArgDelimiter{},
&argh.Command{ &argh.Command{
Name: "hello", Name: "hello",
Values: map[string]string{ Values: map[string]string{
@ -61,8 +62,7 @@ func TestParser2(t *testing.T) {
}, },
expAST: []argh.Node{ expAST: []argh.Node{
&argh.Command{ &argh.Command{
Name: "pies", Name: "pies",
Values: map[string]string{},
Nodes: []argh.Node{ Nodes: []argh.Node{
&argh.Flag{Name: "e"}, &argh.Flag{Name: "e"},
&argh.Flag{Name: "a"}, &argh.Flag{Name: "a"},
@ -73,7 +73,6 @@ func TestParser2(t *testing.T) {
Values: map[string]string{ Values: map[string]string{
"name": "mario", "name": "mario",
}, },
Nodes: []argh.Node{},
}, },
}, },
}, },
@ -84,37 +83,38 @@ func TestParser2(t *testing.T) {
args: []string{"pizzas"}, args: []string{"pizzas"},
expPT: []argh.Node{ expPT: []argh.Node{
&argh.Command{ &argh.Command{
Name: "pizzas", Name: "pizzas",
Values: map[string]string{},
Nodes: []argh.Node{},
}, },
}, },
expAST: []argh.Node{ expAST: []argh.Node{
&argh.Command{ &argh.Command{
Name: "pizzas", Name: "pizzas",
Values: map[string]string{},
Nodes: []argh.Node{},
}, },
}, },
}, },
{ {
skip: true,
name: "one positional arg", name: "one positional arg",
args: []string{"pizzas", "excel"}, args: []string{"pizzas", "excel"},
cfg: &argh.ParserConfig{ cfg: &argh.ParserConfig{
Prog: argh.CommandConfig{NValue: 1}, Prog: argh.CommandConfig{NValue: 1},
}, },
expPT: []argh.Node{ 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{ 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", name: "many positional args",
args: []string{"pizzas", "excel", "wildly", "when", "feral"}, args: []string{"pizzas", "excel", "wildly", "when", "feral"},
cfg: &argh.ParserConfig{ cfg: &argh.ParserConfig{
@ -124,7 +124,7 @@ func TestParser2(t *testing.T) {
}, },
}, },
expPT: []argh.Node{ expPT: []argh.Node{
argh.Command{ &argh.Command{
Name: "pizzas", Name: "pizzas",
Values: map[string]string{ Values: map[string]string{
"word": "excel", "word": "excel",
@ -132,10 +132,16 @@ func TestParser2(t *testing.T) {
"word.2": "when", "word.2": "when",
"word.3": "feral", "word.3": "feral",
}, },
Nodes: []argh.Node{
&argh.ArgDelimiter{},
&argh.ArgDelimiter{},
&argh.ArgDelimiter{},
&argh.ArgDelimiter{},
},
}, },
}, },
expAST: []argh.Node{ expAST: []argh.Node{
argh.Command{ &argh.Command{
Name: "pizzas", Name: "pizzas",
Values: map[string]string{ Values: map[string]string{
"word": "excel", "word": "excel",
@ -147,29 +153,33 @@ func TestParser2(t *testing.T) {
}, },
}, },
{ {
skip: true,
name: "long value-less flags", name: "long value-less flags",
args: []string{"pizzas", "--tasty", "--fresh", "--super-hot-right-now"}, args: []string{"pizzas", "--tasty", "--fresh", "--super-hot-right-now"},
expPT: []argh.Node{ expPT: []argh.Node{
argh.Command{Name: "pizzas"}, &argh.Command{
argh.ArgDelimiter{}, Name: "pizzas",
argh.Flag{Name: "tasty"}, Nodes: []argh.Node{
argh.ArgDelimiter{}, &argh.ArgDelimiter{},
argh.Flag{Name: "fresh"}, &argh.Flag{Name: "tasty"},
argh.ArgDelimiter{}, &argh.ArgDelimiter{},
argh.Flag{Name: "super-hot-right-now"}, &argh.Flag{Name: "fresh"},
&argh.ArgDelimiter{},
&argh.Flag{Name: "super-hot-right-now"},
},
},
}, },
expAST: []argh.Node{ expAST: []argh.Node{
argh.Command{Name: "pizzas"}, &argh.Command{
argh.Flag{Name: "tasty"}, Name: "pizzas",
argh.Flag{Name: "fresh"}, Nodes: []argh.Node{
argh.Flag{Name: "super-hot-right-now"}, &argh.Flag{Name: "tasty"},
&argh.Flag{Name: "fresh"},
&argh.Flag{Name: "super-hot-right-now"},
},
},
}, },
}, },
{ {
skip: true,
name: "long flags mixed", name: "long flags mixed",
args: []string{ args: []string{
"pizzas", "pizzas",
@ -189,25 +199,47 @@ func TestParser2(t *testing.T) {
}, },
}, },
expPT: []argh.Node{ expPT: []argh.Node{
argh.Command{Name: "pizzas"}, &argh.Command{
argh.ArgDelimiter{}, Name: "pizzas",
argh.Flag{Name: "tasty"}, Nodes: []argh.Node{
argh.ArgDelimiter{}, &argh.ArgDelimiter{},
argh.Flag{Name: "fresh", Values: map[string]string{"0": "soon"}}, &argh.Flag{Name: "tasty"},
argh.ArgDelimiter{}, &argh.ArgDelimiter{},
argh.Flag{Name: "super-hot-right-now"}, &argh.Flag{
argh.ArgDelimiter{}, Name: "fresh",
argh.Flag{Name: "box", Values: map[string]string{"0": "square", "1": "shaped", "2": "hot"}}, Values: map[string]string{"0": "soon"},
argh.ArgDelimiter{}, Nodes: []argh.Node{
argh.Flag{Name: "please"}, &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{ expAST: []argh.Node{
argh.Command{Name: "pizzas"}, &argh.Command{
argh.Flag{Name: "tasty"}, Name: "pizzas",
argh.Flag{Name: "fresh", Values: map[string]string{"0": "soon"}}, Nodes: []argh.Node{
argh.Flag{Name: "super-hot-right-now"}, &argh.Flag{Name: "tasty"},
argh.Flag{Name: "box", Values: map[string]string{"0": "square", "1": "shaped", "2": "hot"}}, &argh.Flag{Name: "fresh", Values: map[string]string{"0": "soon"}},
argh.Flag{Name: "please"}, &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"},
},
},
}, },
}, },
{ {

@ -37,18 +37,44 @@ func (dq *defaultQuerier) AST() []Node {
} }
if v, ok := node.(*CompoundShortFlag); ok { 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 continue
} }
if v, ok := node.(*Command); ok { if v, ok := node.(*Command); ok {
astNodes := NewQuerier(v.Nodes).AST()
if len(astNodes) == 0 {
astNodes = nil
}
ret = append( ret = append(
ret, ret,
&Command{ &Command{
Name: v.Name, Name: v.Name,
Values: v.Values, 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 continue

Loading…
Cancel
Save