Work on separate querier + cleanups

arghing
Dan Buch 2 years ago
parent de6e907c60
commit 1080737931
Signed by: meatballhat
GPG Key ID: A12F782281063434

@ -21,18 +21,6 @@ func init() {
traceLogger = log.New(os.Stderr, "ARGH TRACING: ", 0) traceLogger = log.New(os.Stderr, "ARGH TRACING: ", 0)
} }
type Argh struct {
ParseTree *ParseTree `json:"parse_tree"`
}
func (a *Argh) TypedAST() []TypedNode {
return a.ParseTree.typedAST()
}
func (a *Argh) AST() []Node {
return a.ParseTree.ast()
}
func tracef(format string, v ...any) { func tracef(format string, v ...any) {
if !tracingEnabled { if !tracingEnabled {
return return

@ -16,8 +16,9 @@ type CompoundShortFlag struct {
} }
type Program struct { type Program struct {
Name string `json:"name"` Name string `json:"name"`
Values []string `json:"values"` Values map[string]string `json:"values"`
Nodes []Node `json:"nodes"`
} }
type Ident struct { type Ident struct {
@ -25,13 +26,14 @@ type Ident struct {
} }
type Command struct { type Command struct {
Name string `json:"name"` Name string `json:"name"`
Values []string `json:"values"` Values map[string]string `json:"values"`
Nodes []Node `json:"nodes"`
} }
type Flag struct { type Flag struct {
Name string `json:"name"` Name string `json:"name"`
Values []string `json:"values"` Values map[string]string `json:"values"`
} }
type StdinFlag struct{} type StdinFlag struct{}

@ -3,6 +3,7 @@
package argh package argh
import ( import (
"fmt"
"io" "io"
"strings" "strings"
@ -19,15 +20,15 @@ var (
ErrSyntax = errors.New("syntax error") ErrSyntax = errors.New("syntax error")
DefaultParserConfig = &ParserConfig{ DefaultParserConfig = &ParserConfig{
Commands: map[string]NValue{}, Commands: map[string]CommandConfig{},
Flags: map[string]NValue{}, Flags: map[string]FlagConfig{},
ScannerConfig: DefaultScannerConfig, ScannerConfig: DefaultScannerConfig,
} }
) )
type NValue int type NValue int
func ParseArgs(args []string, pCfg *ParserConfig) (*Argh, error) { func ParseArgs(args []string, pCfg *ParserConfig) (*ParseTree, error) {
reEncoded := strings.Join(args, string(nul)) reEncoded := strings.Join(args, string(nul))
return NewParser( return NewParser(
@ -39,38 +40,50 @@ func ParseArgs(args []string, pCfg *ParserConfig) (*Argh, error) {
type Parser struct { type Parser struct {
s *Scanner s *Scanner
buf []ScanEntry buf []scanEntry
cfg *ParserConfig cfg *ParserConfig
nodes []Node nodes []Node
stopSeen bool node Node
} }
type ScanEntry struct { type ParseTree struct {
Nodes []Node `json:"nodes"`
}
type scanEntry struct {
tok Token tok Token
lit string lit string
pos int pos int
} }
type ParserConfig struct { type ParserConfig struct {
ProgValues NValue Prog CommandConfig
Commands map[string]NValue Commands map[string]CommandConfig
Flags map[string]NValue Flags map[string]FlagConfig
OnUnknownFlag func(string) error
OnUnknownCommand func(string) error
ScannerConfig *ScannerConfig ScannerConfig *ScannerConfig
} }
type CommandConfig struct {
NValue NValue
ValueNames []string
Flags map[string]FlagConfig
}
type FlagConfig struct {
NValue NValue
ValueNames []string
}
func NewParser(r io.Reader, pCfg *ParserConfig) *Parser { func NewParser(r io.Reader, pCfg *ParserConfig) *Parser {
if pCfg == nil { if pCfg == nil {
pCfg = DefaultParserConfig pCfg = DefaultParserConfig
} }
parser := &Parser{ parser := &Parser{
buf: []ScanEntry{}, buf: []scanEntry{},
s: NewScanner(r, pCfg.ScannerConfig), s: NewScanner(r, pCfg.ScannerConfig),
cfg: pCfg, cfg: pCfg,
} }
@ -81,7 +94,7 @@ func NewParser(r io.Reader, pCfg *ParserConfig) *Parser {
return parser return parser
} }
func (p *Parser) Parse() (*Argh, error) { func (p *Parser) Parse() (*ParseTree, error) {
p.nodes = []Node{} p.nodes = []Node{}
for { for {
@ -95,7 +108,7 @@ func (p *Parser) Parse() (*Argh, error) {
} }
} }
return &Argh{ParseTree: &ParseTree{Nodes: p.nodes}}, nil return &ParseTree{Nodes: p.nodes}, nil
} }
func (p *Parser) parseArg() (bool, error) { func (p *Parser) parseArg() (bool, error) {
@ -110,7 +123,7 @@ func (p *Parser) parseArg() (bool, error) {
p.unscan(tok, lit, pos) p.unscan(tok, lit, pos)
node, err := p.nodify() node, err := p.scanNode()
tracef("parseArg node=%+#v err=%+#v", node, err) tracef("parseArg node=%+#v err=%+#v", node, err)
@ -125,10 +138,10 @@ func (p *Parser) parseArg() (bool, error) {
return false, nil return false, nil
} }
func (p *Parser) nodify() (Node, error) { func (p *Parser) scanNode() (Node, error) {
tok, lit, pos := p.scan() tok, lit, pos := p.scan()
tracef("nodify tok=%s lit=%q pos=%v", tok, lit, pos) tracef("scanNode tok=%s lit=%q pos=%v", tok, lit, pos)
switch tok { switch tok {
case ARG_DELIMITER: case ARG_DELIMITER:
@ -136,94 +149,120 @@ func (p *Parser) nodify() (Node, error) {
case ASSIGN: case ASSIGN:
return nil, errors.Wrapf(ErrSyntax, "bare assignment operator at pos=%v", pos) return nil, errors.Wrapf(ErrSyntax, "bare assignment operator at pos=%v", pos)
case IDENT: case IDENT:
if len(p.nodes) == 0 { p.unscan(tok, lit, pos)
values, err := p.scanValues(lit, pos, p.cfg.ProgValues) return p.scanCommandOrIdent()
if err != nil { case COMPOUND_SHORT_FLAG:
return nil, err p.unscan(tok, lit, pos)
} return p.scanCompoundShortFlag()
case SHORT_FLAG, LONG_FLAG:
p.unscan(tok, lit, pos)
return p.scanFlag()
default:
}
return Program{Name: lit, Values: values}, nil return Ident{Literal: lit}, nil
} }
if n, ok := p.cfg.Commands[lit]; ok { func (p *Parser) scanCommandOrIdent() (Node, error) {
values, err := p.scanValues(lit, pos, n) tok, lit, pos := p.scan()
if err != nil {
return nil, err
}
return Command{Name: lit, Values: values}, nil if len(p.nodes) == 0 {
p.unscan(tok, lit, pos)
values, err := p.scanValues(p.cfg.Prog.NValue, p.cfg.Prog.ValueNames)
if err != nil {
return nil, err
} }
return Ident{Literal: lit}, nil return Program{Name: lit, Values: values}, nil
case COMPOUND_SHORT_FLAG: }
flagNodes := []Node{}
withoutFlagPrefix := lit[1:] if cfg, ok := p.cfg.Commands[lit]; ok {
p.unscan(tok, lit, pos)
values, err := p.scanValues(cfg.NValue, cfg.ValueNames)
if err != nil {
return nil, err
}
for i, r := range withoutFlagPrefix { return Command{Name: lit, Values: values}, nil
if i == len(withoutFlagPrefix)-1 { }
flagName := string(r)
if n, ok := p.cfg.Flags[flagName]; ok { return Ident{Literal: lit}, nil
values, err := p.scanValues(flagName, pos, n) }
if err != nil {
return nil, err
}
flagNodes = append(flagNodes, Flag{Name: flagName, Values: values}) func (p *Parser) scanFlag() (Node, error) {
tok, lit, pos := p.scan()
continue flagName := string(lit[1:])
} if tok == LONG_FLAG {
} flagName = string(lit[2:])
}
flagNodes = append( if cfg, ok := p.cfg.Flags[flagName]; ok {
flagNodes, p.unscan(tok, flagName, pos)
Flag{
Name: string(r), values, err := p.scanValues(cfg.NValue, cfg.ValueNames)
}, if err != nil {
) return nil, err
} }
return CompoundShortFlag{Nodes: flagNodes}, nil return Flag{Name: flagName, Values: values}, nil
case SHORT_FLAG: }
flagName := string(lit[1:])
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
} }
return Flag{Name: flagName}, nil func (p *Parser) scanCompoundShortFlag() (Node, error) {
case LONG_FLAG: tok, lit, pos := p.scan()
flagName := string(lit[2:])
if n, ok := p.cfg.Flags[flagName]; ok { flagNodes := []Node{}
values, err := p.scanValues(flagName, pos, n)
if err != nil { withoutFlagPrefix := lit[1:]
return nil, err
}
return Flag{Name: flagName, Values: values}, nil for i, r := range withoutFlagPrefix {
if i == len(withoutFlagPrefix)-1 {
flagName := string(r)
if cfg, ok := p.cfg.Flags[flagName]; ok {
p.unscan(tok, flagName, pos)
values, err := p.scanValues(cfg.NValue, cfg.ValueNames)
if err != nil {
return nil, err
}
flagNodes = append(flagNodes, Flag{Name: flagName, Values: values})
continue
}
} }
return Flag{Name: flagName}, nil flagNodes = append(
default: flagNodes,
Flag{
Name: string(r),
},
)
} }
return Ident{Literal: lit}, nil return CompoundShortFlag{Nodes: flagNodes}, nil
}
func (p *Parser) scanValuesAndFlags() (map[string]string, []Node, error) {
return nil, nil, nil
} }
func (p *Parser) scanValues(lit string, pos int, n NValue) ([]string, error) { func (p *Parser) scanValues(n NValue, valueNames []string) (map[string]string, error) {
tracef("scanValues lit=%q pos=%v n=%v", lit, pos, n) _, lit, pos := p.scan()
tracef("scanValues lit=%q pos=%v n=%v valueNames=%+v", lit, pos, n, valueNames)
values, err := func() ([]string, error) { values, err := func() (map[string]string, error) {
if n == ZeroValue { if n == ZeroValue {
return []string{}, nil return map[string]string{}, nil
} }
ret := []string{} ret := map[string]string{}
i := 0
for { for {
lit, err := p.scanIdent() lit, err := p.scanIdent()
@ -237,11 +276,20 @@ func (p *Parser) scanValues(lit string, pos int, n NValue) ([]string, error) {
} }
} }
ret = append(ret, lit) name := fmt.Sprintf("%d", i)
if len(valueNames)-1 >= i {
name = valueNames[i]
} else if len(valueNames) > 0 && strings.HasSuffix(valueNames[len(valueNames)-1], "+") {
name = strings.TrimSuffix(valueNames[len(valueNames)-1], "+")
}
ret[name] = lit
if n == NValue(1) && len(ret) == 1 { if n == NValue(1) && len(ret) == 1 {
break break
} }
i++
} }
return ret, nil return ret, nil
@ -263,14 +311,14 @@ func (p *Parser) scanIdent() (string, error) {
tracef("scanIdent scanned tok=%s lit=%q pos=%v", tok, lit, pos) tracef("scanIdent scanned tok=%s lit=%q pos=%v", tok, lit, pos)
unscanBuf := []ScanEntry{} unscanBuf := []scanEntry{}
if tok == ASSIGN || tok == ARG_DELIMITER { if tok == ASSIGN || tok == ARG_DELIMITER {
entry := ScanEntry{tok: tok, lit: lit, pos: pos} entry := scanEntry{tok: tok, lit: lit, pos: pos}
tracef("scanIdent tok=%s; scanning next and pushing to unscan buffer entry=%+#v", tok, entry) tracef("scanIdent tok=%s; scanning next and pushing to unscan buffer entry=%+#v", tok, entry)
unscanBuf = append([]ScanEntry{entry}, unscanBuf...) unscanBuf = append([]scanEntry{entry}, unscanBuf...)
tok, lit, pos = p.scan() tok, lit, pos = p.scan()
} }
@ -279,11 +327,11 @@ func (p *Parser) scanIdent() (string, error) {
return lit, nil return lit, nil
} }
entry := ScanEntry{tok: tok, lit: lit, pos: pos} entry := scanEntry{tok: tok, lit: lit, pos: pos}
tracef("scanIdent tok=%s; unscanning entry=%+#v", tok, entry) tracef("scanIdent tok=%s; unscanning entry=%+#v", tok, entry)
unscanBuf = append([]ScanEntry{entry}, unscanBuf...) unscanBuf = append([]scanEntry{entry}, unscanBuf...)
for _, entry := range unscanBuf { for _, entry := range unscanBuf {
p.unscan(entry.tok, entry.lit, entry.pos) p.unscan(entry.tok, entry.lit, entry.pos)
@ -303,13 +351,13 @@ func (p *Parser) scan() (Token, string, int) {
tok, lit, pos := p.s.Scan() tok, lit, pos := p.s.Scan()
tracef("scan returning next=%s %+#v", tok, ScanEntry{tok: tok, lit: lit, pos: pos}) tracef("scan returning next=%s %+#v", tok, scanEntry{tok: tok, lit: lit, pos: pos})
return tok, lit, pos return tok, lit, pos
} }
func (p *Parser) unscan(tok Token, lit string, pos int) { func (p *Parser) unscan(tok Token, lit string, pos int) {
entry := ScanEntry{tok: tok, lit: lit, pos: pos} entry := scanEntry{tok: tok, lit: lit, pos: pos}
tracef("unscan entry=%s %+#v", tok, entry) tracef("unscan entry=%s %+#v", tok, entry)

@ -7,10 +7,6 @@ import (
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
) )
func ptr[T any](v T) *T {
return &v
}
func TestParser(t *testing.T) { func TestParser(t *testing.T) {
for _, tc := range []struct { for _, tc := range []struct {
name string name string
@ -19,7 +15,6 @@ func TestParser(t *testing.T) {
expPT []argh.Node expPT []argh.Node
expAST []argh.Node expAST []argh.Node
expErr error expErr error
skip bool
}{ }{
{ {
name: "bare", name: "bare",
@ -35,26 +30,26 @@ func TestParser(t *testing.T) {
name: "one positional arg", name: "one positional arg",
args: []string{"pizzas", "excel"}, args: []string{"pizzas", "excel"},
cfg: &argh.ParserConfig{ cfg: &argh.ParserConfig{
ProgValues: 1, Prog: argh.CommandConfig{NValue: 1},
}, },
expPT: []argh.Node{ expPT: []argh.Node{
argh.Program{Name: "pizzas", Values: []string{"excel"}}, argh.Program{Name: "pizzas", Values: map[string]string{"0": "excel"}},
}, },
expAST: []argh.Node{ expAST: []argh.Node{
argh.Program{Name: "pizzas", Values: []string{"excel"}}, argh.Program{Name: "pizzas", Values: map[string]string{"0": "excel"}},
}, },
}, },
{ {
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{
ProgValues: argh.OneOrMoreValue, Prog: argh.CommandConfig{NValue: argh.OneOrMoreValue},
}, },
expPT: []argh.Node{ expPT: []argh.Node{
argh.Program{Name: "pizzas", Values: []string{"excel", "wildly", "when", "feral"}}, argh.Program{Name: "pizzas", Values: map[string]string{"0": "excel", "1": "wildly", "2": "when", "3": "feral"}},
}, },
expAST: []argh.Node{ expAST: []argh.Node{
argh.Program{Name: "pizzas", Values: []string{"excel", "wildly", "when", "feral"}}, argh.Program{Name: "pizzas", Values: map[string]string{"0": "excel", "1": "wildly", "2": "when", "3": "feral"}},
}, },
}, },
{ {
@ -87,10 +82,10 @@ func TestParser(t *testing.T) {
"--please", "--please",
}, },
cfg: &argh.ParserConfig{ cfg: &argh.ParserConfig{
Commands: map[string]argh.NValue{}, Commands: map[string]argh.CommandConfig{},
Flags: map[string]argh.NValue{ Flags: map[string]argh.FlagConfig{
"fresh": 1, "fresh": argh.FlagConfig{NValue: 1},
"box": argh.OneOrMoreValue, "box": argh.FlagConfig{NValue: argh.OneOrMoreValue},
}, },
}, },
expPT: []argh.Node{ expPT: []argh.Node{
@ -98,20 +93,20 @@ func TestParser(t *testing.T) {
argh.ArgDelimiter{}, argh.ArgDelimiter{},
argh.Flag{Name: "tasty"}, argh.Flag{Name: "tasty"},
argh.ArgDelimiter{}, argh.ArgDelimiter{},
argh.Flag{Name: "fresh", Values: []string{"soon"}}, argh.Flag{Name: "fresh", Values: map[string]string{"0": "soon"}},
argh.ArgDelimiter{}, argh.ArgDelimiter{},
argh.Flag{Name: "super-hot-right-now"}, argh.Flag{Name: "super-hot-right-now"},
argh.ArgDelimiter{}, argh.ArgDelimiter{},
argh.Flag{Name: "box", Values: []string{"square", "shaped", "hot"}}, argh.Flag{Name: "box", Values: map[string]string{"0": "square", "1": "shaped", "2": "hot"}},
argh.ArgDelimiter{}, argh.ArgDelimiter{},
argh.Flag{Name: "please"}, argh.Flag{Name: "please"},
}, },
expAST: []argh.Node{ expAST: []argh.Node{
argh.Program{Name: "pizzas"}, argh.Program{Name: "pizzas"},
argh.Flag{Name: "tasty"}, argh.Flag{Name: "tasty"},
argh.Flag{Name: "fresh", Values: []string{"soon"}}, argh.Flag{Name: "fresh", Values: map[string]string{"0": "soon"}},
argh.Flag{Name: "super-hot-right-now"}, argh.Flag{Name: "super-hot-right-now"},
argh.Flag{Name: "box", Values: []string{"square", "shaped", "hot"}}, argh.Flag{Name: "box", Values: map[string]string{"0": "square", "1": "shaped", "2": "hot"}},
argh.Flag{Name: "please"}, argh.Flag{Name: "please"},
}, },
}, },
@ -172,8 +167,10 @@ func TestParser(t *testing.T) {
name: "mixed long short value flags", name: "mixed long short value flags",
args: []string{"pizzas", "-a", "--ca", "-b", "1312", "-lol"}, args: []string{"pizzas", "-a", "--ca", "-b", "1312", "-lol"},
cfg: &argh.ParserConfig{ cfg: &argh.ParserConfig{
Commands: map[string]argh.NValue{}, Commands: map[string]argh.CommandConfig{},
Flags: map[string]argh.NValue{"b": 1}, Flags: map[string]argh.FlagConfig{
"b": argh.FlagConfig{NValue: 1},
},
}, },
expPT: []argh.Node{ expPT: []argh.Node{
argh.Program{Name: "pizzas"}, argh.Program{Name: "pizzas"},
@ -182,7 +179,7 @@ func TestParser(t *testing.T) {
argh.ArgDelimiter{}, argh.ArgDelimiter{},
argh.Flag{Name: "ca"}, argh.Flag{Name: "ca"},
argh.ArgDelimiter{}, argh.ArgDelimiter{},
argh.Flag{Name: "b", Values: []string{"1312"}}, argh.Flag{Name: "b", Values: map[string]string{"0": "1312"}},
argh.ArgDelimiter{}, argh.ArgDelimiter{},
argh.CompoundShortFlag{ argh.CompoundShortFlag{
Nodes: []argh.Node{ Nodes: []argh.Node{
@ -196,7 +193,7 @@ func TestParser(t *testing.T) {
argh.Program{Name: "pizzas"}, argh.Program{Name: "pizzas"},
argh.Flag{Name: "a"}, argh.Flag{Name: "a"},
argh.Flag{Name: "ca"}, argh.Flag{Name: "ca"},
argh.Flag{Name: "b", Values: []string{"1312"}}, argh.Flag{Name: "b", Values: map[string]string{"0": "1312"}},
argh.Flag{Name: "l"}, argh.Flag{Name: "l"},
argh.Flag{Name: "o"}, argh.Flag{Name: "o"},
argh.Flag{Name: "l"}, argh.Flag{Name: "l"},
@ -206,28 +203,74 @@ func TestParser(t *testing.T) {
name: "commands", name: "commands",
args: []string{"pizzas", "fly", "fry"}, args: []string{"pizzas", "fly", "fry"},
cfg: &argh.ParserConfig{ cfg: &argh.ParserConfig{
Commands: map[string]argh.NValue{"fly": argh.ZeroValue, "fry": argh.ZeroValue}, Commands: map[string]argh.CommandConfig{
Flags: map[string]argh.NValue{}, "fly": argh.CommandConfig{},
"fry": argh.CommandConfig{},
},
Flags: map[string]argh.FlagConfig{},
},
expPT: []argh.Node{
argh.Program{Name: "pizzas"},
argh.ArgDelimiter{},
argh.Command{Name: "fly"},
argh.ArgDelimiter{},
argh.Command{Name: "fry"},
},
},
{
name: "command specific flags",
args: []string{"pizzas", "fly", "--freely", "fry", "--deeply", "-wAt"},
cfg: &argh.ParserConfig{
Commands: map[string]argh.CommandConfig{
"fly": argh.CommandConfig{
Flags: map[string]argh.FlagConfig{
"freely": {},
},
},
"fry": argh.CommandConfig{
Flags: map[string]argh.FlagConfig{
"deeply": {},
"w": {},
"A": {},
"t": {},
},
},
},
Flags: map[string]argh.FlagConfig{},
}, },
expPT: []argh.Node{ expPT: []argh.Node{
argh.Program{Name: "pizzas"}, argh.Program{Name: "pizzas"},
argh.ArgDelimiter{}, argh.ArgDelimiter{},
argh.Command{Name: "fly"}, argh.Command{Name: "fly"},
argh.ArgDelimiter{}, argh.ArgDelimiter{},
argh.Flag{Name: "freely"},
argh.ArgDelimiter{},
argh.Command{Name: "fry"}, argh.Command{Name: "fry"},
argh.ArgDelimiter{},
argh.Flag{Name: "deeply"},
argh.ArgDelimiter{},
argh.CompoundShortFlag{
Nodes: []argh.Node{
argh.Flag{Name: "w"},
argh.Flag{Name: "A"},
argh.Flag{Name: "t"},
},
},
}, },
}, },
{ {
name: "total weirdo", name: "total weirdo",
args: []string{"PIZZAs", "^wAT@golf", "^^hecKing", "goose", "bonk", "^^FIERCENESS@-2"}, args: []string{"PIZZAs", "^wAT@golf", "^^hecKing", "goose", "bonk", "^^FIERCENESS@-2"},
cfg: &argh.ParserConfig{ cfg: &argh.ParserConfig{
Commands: map[string]argh.NValue{"goose": 1}, Commands: map[string]argh.CommandConfig{
Flags: map[string]argh.NValue{ "goose": argh.CommandConfig{NValue: 1},
"w": 0, },
"A": 0, Flags: map[string]argh.FlagConfig{
"T": 1, "w": argh.FlagConfig{},
"hecking": 0, "A": argh.FlagConfig{},
"FIERCENESS": 1, "T": argh.FlagConfig{NValue: 1},
"hecking": argh.FlagConfig{},
"FIERCENESS": argh.FlagConfig{NValue: 1},
}, },
ScannerConfig: &argh.ScannerConfig{ ScannerConfig: &argh.ScannerConfig{
AssignmentOperator: '@', AssignmentOperator: '@',
@ -242,15 +285,15 @@ func TestParser(t *testing.T) {
Nodes: []argh.Node{ Nodes: []argh.Node{
argh.Flag{Name: "w"}, argh.Flag{Name: "w"},
argh.Flag{Name: "A"}, argh.Flag{Name: "A"},
argh.Flag{Name: "T", Values: []string{"golf"}}, argh.Flag{Name: "T", Values: map[string]string{"0": "golf"}},
}, },
}, },
argh.ArgDelimiter{}, argh.ArgDelimiter{},
argh.Flag{Name: "hecKing"}, argh.Flag{Name: "hecKing"},
argh.ArgDelimiter{}, argh.ArgDelimiter{},
argh.Command{Name: "goose", Values: []string{"bonk"}}, argh.Command{Name: "goose", Values: map[string]string{"0": "bonk"}},
argh.ArgDelimiter{}, argh.ArgDelimiter{},
argh.Flag{Name: "FIERCENESS", Values: []string{"-2"}}, argh.Flag{Name: "FIERCENESS", Values: map[string]string{"0": "-2"}},
}, },
}, },
{ {
@ -263,10 +306,6 @@ func TestParser(t *testing.T) {
}, },
{}, {},
} { } {
if tc.skip {
continue
}
if tc.expPT != nil { if tc.expPT != nil {
t.Run(tc.name+" parse tree", func(ct *testing.T) { t.Run(tc.name+" parse tree", func(ct *testing.T) {
actual, err := argh.ParseArgs(tc.args, tc.cfg) actual, err := argh.ParseArgs(tc.args, tc.cfg)
@ -275,7 +314,7 @@ func TestParser(t *testing.T) {
return return
} }
assert.Equal(ct, tc.expPT, actual.ParseTree.Nodes) assert.Equal(ct, tc.expPT, actual.Nodes)
}) })
} }
@ -287,7 +326,7 @@ func TestParser(t *testing.T) {
return return
} }
assert.Equal(ct, tc.expAST, actual.AST()) assert.Equal(ct, tc.expAST, argh.NewQuerier(actual).AST())
}) })
} }
} }

@ -2,14 +2,33 @@ package argh
import "fmt" import "fmt"
type ParseTree struct { type Querier interface {
Nodes []Node `json:"nodes"` Program() (Program, bool)
TypedAST() []TypedNode
AST() []Node
} }
func (pt *ParseTree) typedAST() []TypedNode { func NewQuerier(pt *ParseTree) Querier {
return &defaultQuerier{pt: pt}
}
type defaultQuerier struct {
pt *ParseTree
}
func (dq *defaultQuerier) Program() (Program, bool) {
if len(dq.pt.Nodes) == 0 {
return Program{}, false
}
v, ok := dq.pt.Nodes[0].(Program)
return v, ok
}
func (dq *defaultQuerier) TypedAST() []TypedNode {
ret := []TypedNode{} ret := []TypedNode{}
for _, node := range pt.Nodes { for _, node := range dq.pt.Nodes {
if _, ok := node.(ArgDelimiter); ok { if _, ok := node.(ArgDelimiter); ok {
continue continue
} }
@ -30,10 +49,10 @@ func (pt *ParseTree) typedAST() []TypedNode {
return ret return ret
} }
func (pt *ParseTree) ast() []Node { func (dq *defaultQuerier) AST() []Node {
ret := []Node{} ret := []Node{}
for _, node := range pt.Nodes { for _, node := range dq.pt.Nodes {
if _, ok := node.(ArgDelimiter); ok { if _, ok := node.(ArgDelimiter); ok {
continue continue
} }

@ -0,0 +1,46 @@
package argh_test
import (
"testing"
"git.meatballhat.com/x/box-o-sand/argh"
"github.com/stretchr/testify/require"
)
func TestQuerier_Program(t *testing.T) {
for _, tc := range []struct {
name string
args []string
cfg *argh.ParserConfig
exp argh.Program
expOK bool
}{
{
name: "typical",
args: []string{"pizzas", "ahoy", "--treatsa", "fun"},
exp: argh.Program{Name: "pizzas"},
expOK: true,
},
{
name: "minimal",
args: []string{"pizzas"},
exp: argh.Program{Name: "pizzas"},
expOK: true,
},
{
name: "invalid",
args: []string{},
exp: argh.Program{},
expOK: false,
},
} {
t.Run(tc.name, func(ct *testing.T) {
pt, err := argh.ParseArgs(tc.args, tc.cfg)
require.Nil(ct, err)
prog, ok := argh.NewQuerier(pt).Program()
require.Equal(ct, tc.exp, prog)
require.Equal(ct, tc.expOK, ok)
})
}
}
Loading…
Cancel
Save