From a34fb7a9cde8200a0700821426f5dee43f7ebed8 Mon Sep 17 00:00:00 2001 From: Dan Buch Date: Sun, 15 May 2022 20:55:54 -0400 Subject: [PATCH] Ensure program and commands can also receive positional arg values --- node.go | 7 ++-- parser.go | 94 +++++++++++++++++++++++++++++++++----------------- parser_test.go | 62 +++++++++++++++++++++++++++++++++ 3 files changed, 129 insertions(+), 34 deletions(-) diff --git a/node.go b/node.go index 01e3c5a..f51f48c 100644 --- a/node.go +++ b/node.go @@ -16,7 +16,8 @@ type CompoundShortFlag struct { } type Program struct { - Name string `json:"name"` + Name string `json:"name"` + Values []string `json:"values"` } type Ident struct { @@ -24,8 +25,8 @@ type Ident struct { } type Command struct { - Name string `json:"name"` - // Values []string `json:"values" + Name string `json:"name"` + Values []string `json:"values"` } type Flag struct { diff --git a/parser.go b/parser.go index cc42a92..87707b1 100644 --- a/parser.go +++ b/parser.go @@ -41,8 +41,7 @@ type Parser struct { buf []ScanEntry - commands map[string]NValue - valueFlags map[string]NValue + cfg *ParserConfig nodes []Node stopSeen bool @@ -55,25 +54,21 @@ type ScanEntry struct { } type ParserConfig struct { + ProgValues NValue Commands map[string]NValue Flags map[string]NValue ScannerConfig *ScannerConfig } -type parseDirective struct { - Break bool -} - func NewParser(r io.Reader, pCfg *ParserConfig) *Parser { if pCfg == nil { pCfg = DefaultParserConfig } parser := &Parser{ - buf: []ScanEntry{}, - s: NewScanner(r, pCfg.ScannerConfig), - commands: pCfg.Commands, - valueFlags: pCfg.Flags, + buf: []ScanEntry{}, + s: NewScanner(r, pCfg.ScannerConfig), + cfg: pCfg, } tracef("NewParser parser=%+#v", parser) @@ -86,12 +81,12 @@ func (p *Parser) Parse() (*Argh, error) { p.nodes = []Node{} for { - pd, err := p.parseArg() + br, err := p.parseArg() if err != nil { return nil, err } - if pd != nil && pd.Break { + if br { break } } @@ -99,14 +94,14 @@ func (p *Parser) Parse() (*Argh, error) { return &Argh{ParseTree: &ParseTree{Nodes: p.nodes}}, nil } -func (p *Parser) parseArg() (*parseDirective, error) { +func (p *Parser) parseArg() (bool, error) { tok, lit, pos := p.scan() if tok == ILLEGAL { - return nil, errors.Wrapf(errSyntax, "illegal value %q at pos=%v", lit, pos) + return false, errors.Wrapf(errSyntax, "illegal value %q at pos=%v", lit, pos) } if tok == EOL { - return &parseDirective{Break: true}, nil + return true, nil } p.unscan(tok, lit, pos) @@ -116,14 +111,14 @@ func (p *Parser) parseArg() (*parseDirective, error) { tracef("parseArg node=%+#v err=%+#v", node, err) if err != nil { - return nil, errors.Wrapf(err, "value %q at pos=%v", lit, pos) + return false, errors.Wrapf(err, "value %q at pos=%v", lit, pos) } if node != nil { p.nodes = append(p.nodes, node) } - return nil, nil + return false, nil } func (p *Parser) nodify() (Node, error) { @@ -134,11 +129,21 @@ func (p *Parser) nodify() (Node, error) { switch tok { case IDENT: if len(p.nodes) == 0 { - return Program{Name: lit}, nil + values, err := p.scanValues(lit, pos, p.cfg.ProgValues) + if err != nil { + return nil, err + } + + return Program{Name: lit, Values: values}, nil } - if n, ok := p.commands[lit]; ok { - return p.scanValueCommand(lit, pos, n) + if n, ok := p.cfg.Commands[lit]; ok { + values, err := p.scanValues(lit, pos, n) + if err != nil { + return nil, err + } + + return Command{Name: lit, Values: values}, nil } return Ident{Literal: lit}, nil @@ -147,7 +152,24 @@ func (p *Parser) nodify() (Node, error) { case COMPOUND_SHORT_FLAG: flagNodes := []Node{} - for _, r := range lit[1:] { + withoutFlagPrefix := lit[1:] + + for i, r := range withoutFlagPrefix { + if i == len(withoutFlagPrefix)-1 { + flagName := string(r) + + if n, ok := p.cfg.Flags[flagName]; ok { + values, err := p.scanValues(flagName, pos, n) + if err != nil { + return nil, err + } + + flagNodes = append(flagNodes, Flag{Name: flagName, Values: values}) + + continue + } + } + flagNodes = append( flagNodes, Flag{ @@ -159,15 +181,25 @@ func (p *Parser) nodify() (Node, error) { return CompoundShortFlag{Nodes: flagNodes}, nil case SHORT_FLAG: flagName := string(lit[1:]) - if n, ok := p.valueFlags[flagName]; ok { - return p.scanValueFlag(flagName, pos, n) + if n, ok := p.cfg.Flags[flagName]; ok { + values, err := p.scanValues(flagName, pos, n) + if err != nil { + return nil, err + } + + return Flag{Name: flagName, Values: values}, nil } return Flag{Name: flagName}, nil case LONG_FLAG: flagName := string(lit[2:]) - if n, ok := p.valueFlags[flagName]; ok { - return p.scanValueFlag(flagName, pos, n) + if n, ok := p.cfg.Flags[flagName]; ok { + values, err := p.scanValues(flagName, pos, n) + if err != nil { + return nil, err + } + + return Flag{Name: flagName, Values: values}, nil } return Flag{Name: flagName}, nil @@ -177,8 +209,8 @@ func (p *Parser) nodify() (Node, error) { return Ident{Literal: lit}, nil } -func (p *Parser) scanValueFlag(flagName string, pos int, n NValue) (Node, error) { - tracef("scanValueFlag flagName=%q pos=%v n=%v", flagName, pos, n) +func (p *Parser) scanValues(lit string, pos int, n NValue) ([]string, error) { + tracef("scanValues lit=%q pos=%v n=%v", lit, pos, n) values, err := func() ([]string, error) { if n == ZeroValue { @@ -213,11 +245,11 @@ func (p *Parser) scanValueFlag(flagName string, pos int, n NValue) (Node, error) return nil, err } - return Flag{Name: flagName, Values: values}, nil -} + if len(values) == 0 { + return nil, nil + } -func (p *Parser) scanValueCommand(lit string, pos int, n NValue) (Node, error) { - return Command{Name: lit}, nil + return values, nil } func (p *Parser) scanIdent() (string, error) { diff --git a/parser_test.go b/parser_test.go index 68315cc..6432105 100644 --- a/parser_test.go +++ b/parser_test.go @@ -31,6 +31,32 @@ func TestParser(t *testing.T) { argh.Program{Name: "pizzas"}, }, }, + { + name: "one positional arg", + args: []string{"pizzas", "excel"}, + cfg: &argh.ParserConfig{ + ProgValues: argh.OneValue, + }, + expPT: []argh.Node{ + argh.Program{Name: "pizzas", Values: []string{"excel"}}, + }, + expAST: []argh.Node{ + argh.Program{Name: "pizzas", Values: []string{"excel"}}, + }, + }, + { + name: "many positional args", + args: []string{"pizzas", "excel", "wildly", "when", "feral"}, + cfg: &argh.ParserConfig{ + ProgValues: argh.OneOrMoreValue, + }, + expPT: []argh.Node{ + argh.Program{Name: "pizzas", Values: []string{"excel", "wildly", "when", "feral"}}, + }, + expAST: []argh.Node{ + argh.Program{Name: "pizzas", Values: []string{"excel", "wildly", "when", "feral"}}, + }, + }, { name: "long value-less flags", args: []string{"pizzas", "--tasty", "--fresh", "--super-hot-right-now"}, @@ -191,6 +217,42 @@ func TestParser(t *testing.T) { argh.Command{Name: "fry"}, }, }, + { + name: "total weirdo", + args: []string{"PIZZAs", "^wAT@golf", "^^hecKing", "goose", "bonk", "^^FIERCENESS@-2"}, + cfg: &argh.ParserConfig{ + Commands: map[string]argh.NValue{"goose": argh.OneValue}, + Flags: map[string]argh.NValue{ + "w": argh.ZeroValue, + "A": argh.ZeroValue, + "T": argh.OneValue, + "hecking": argh.ZeroValue, + "FIERCENESS": argh.OneValue, + }, + ScannerConfig: &argh.ScannerConfig{ + AssignmentOperator: '@', + FlagPrefix: '^', + MultiValueDelim: ',', + }, + }, + expPT: []argh.Node{ + argh.Program{Name: "PIZZAs"}, + argh.ArgDelimiter{}, + argh.CompoundShortFlag{ + Nodes: []argh.Node{ + argh.Flag{Name: "w"}, + argh.Flag{Name: "A"}, + argh.Flag{Name: "T", Values: []string{"golf"}}, + }, + }, + argh.ArgDelimiter{}, + argh.Flag{Name: "hecKing"}, + argh.ArgDelimiter{}, + argh.Command{Name: "goose", Values: []string{"bonk"}}, + argh.ArgDelimiter{}, + argh.Flag{Name: "FIERCENESS", Values: []string{"-2"}}, + }, + }, } { if tc.skip { continue