Implementing long flag values in parser2

This commit is contained in:
Dan Buch 2022-05-25 21:55:16 -04:00
parent d1ffbe25a3
commit 03edacc8ec
Signed by: meatballhat
GPG Key ID: A12F782281063434
4 changed files with 250 additions and 106 deletions

View File

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

View File

@ -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()
}
tracef("parseCommand for=%d node.Values=%+#v", i, node.Values)
tracef("parseCommand for=%d node.Nodes=%+#v", i, node.Values)
p.buffered = false
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)
node.Nodes = append(node.Nodes, flagNode)
tracef("parseCommand(...) appending %s node=%+#v", tok, 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)})
}

View File

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

View File

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