arghing #1
@ -16,7 +16,8 @@ type CompoundShortFlag struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type Program struct {
|
type Program struct {
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
|
Values []string `json:"values"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type Ident struct {
|
type Ident struct {
|
||||||
@ -24,8 +25,8 @@ type Ident struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type Command struct {
|
type Command struct {
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
// Values []string `json:"values"
|
Values []string `json:"values"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type Flag struct {
|
type Flag struct {
|
||||||
|
@ -41,8 +41,7 @@ type Parser struct {
|
|||||||
|
|
||||||
buf []ScanEntry
|
buf []ScanEntry
|
||||||
|
|
||||||
commands map[string]NValue
|
cfg *ParserConfig
|
||||||
valueFlags map[string]NValue
|
|
||||||
|
|
||||||
nodes []Node
|
nodes []Node
|
||||||
stopSeen bool
|
stopSeen bool
|
||||||
@ -55,25 +54,21 @@ type ScanEntry struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type ParserConfig struct {
|
type ParserConfig struct {
|
||||||
|
ProgValues NValue
|
||||||
Commands map[string]NValue
|
Commands map[string]NValue
|
||||||
Flags map[string]NValue
|
Flags map[string]NValue
|
||||||
ScannerConfig *ScannerConfig
|
ScannerConfig *ScannerConfig
|
||||||
}
|
}
|
||||||
|
|
||||||
type parseDirective struct {
|
|
||||||
Break bool
|
|
||||||
}
|
|
||||||
|
|
||||||
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),
|
||||||
commands: pCfg.Commands,
|
cfg: pCfg,
|
||||||
valueFlags: pCfg.Flags,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
tracef("NewParser parser=%+#v", parser)
|
tracef("NewParser parser=%+#v", parser)
|
||||||
@ -86,12 +81,12 @@ func (p *Parser) Parse() (*Argh, error) {
|
|||||||
p.nodes = []Node{}
|
p.nodes = []Node{}
|
||||||
|
|
||||||
for {
|
for {
|
||||||
pd, err := p.parseArg()
|
br, err := p.parseArg()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if pd != nil && pd.Break {
|
if br {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -99,14 +94,14 @@ func (p *Parser) Parse() (*Argh, error) {
|
|||||||
return &Argh{ParseTree: &ParseTree{Nodes: p.nodes}}, nil
|
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()
|
tok, lit, pos := p.scan()
|
||||||
if tok == ILLEGAL {
|
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 {
|
if tok == EOL {
|
||||||
return &parseDirective{Break: true}, nil
|
return true, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
p.unscan(tok, lit, pos)
|
p.unscan(tok, lit, pos)
|
||||||
@ -116,14 +111,14 @@ func (p *Parser) parseArg() (*parseDirective, error) {
|
|||||||
tracef("parseArg node=%+#v err=%+#v", node, err)
|
tracef("parseArg node=%+#v err=%+#v", node, err)
|
||||||
|
|
||||||
if err != nil {
|
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 {
|
if node != nil {
|
||||||
p.nodes = append(p.nodes, node)
|
p.nodes = append(p.nodes, node)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil, nil
|
return false, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *Parser) nodify() (Node, error) {
|
func (p *Parser) nodify() (Node, error) {
|
||||||
@ -134,11 +129,21 @@ func (p *Parser) nodify() (Node, error) {
|
|||||||
switch tok {
|
switch tok {
|
||||||
case IDENT:
|
case IDENT:
|
||||||
if len(p.nodes) == 0 {
|
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 {
|
if n, ok := p.cfg.Commands[lit]; ok {
|
||||||
return p.scanValueCommand(lit, pos, n)
|
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
|
return Ident{Literal: lit}, nil
|
||||||
@ -147,7 +152,24 @@ func (p *Parser) nodify() (Node, error) {
|
|||||||
case COMPOUND_SHORT_FLAG:
|
case COMPOUND_SHORT_FLAG:
|
||||||
flagNodes := []Node{}
|
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 = append(
|
||||||
flagNodes,
|
flagNodes,
|
||||||
Flag{
|
Flag{
|
||||||
@ -159,15 +181,25 @@ func (p *Parser) nodify() (Node, error) {
|
|||||||
return CompoundShortFlag{Nodes: flagNodes}, nil
|
return CompoundShortFlag{Nodes: flagNodes}, nil
|
||||||
case SHORT_FLAG:
|
case SHORT_FLAG:
|
||||||
flagName := string(lit[1:])
|
flagName := string(lit[1:])
|
||||||
if n, ok := p.valueFlags[flagName]; ok {
|
if n, ok := p.cfg.Flags[flagName]; ok {
|
||||||
return p.scanValueFlag(flagName, pos, n)
|
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
|
||||||
case LONG_FLAG:
|
case LONG_FLAG:
|
||||||
flagName := string(lit[2:])
|
flagName := string(lit[2:])
|
||||||
if n, ok := p.valueFlags[flagName]; ok {
|
if n, ok := p.cfg.Flags[flagName]; ok {
|
||||||
return p.scanValueFlag(flagName, pos, n)
|
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
|
||||||
@ -177,8 +209,8 @@ func (p *Parser) nodify() (Node, error) {
|
|||||||
return Ident{Literal: lit}, nil
|
return Ident{Literal: lit}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *Parser) scanValueFlag(flagName string, pos int, n NValue) (Node, error) {
|
func (p *Parser) scanValues(lit string, pos int, n NValue) ([]string, error) {
|
||||||
tracef("scanValueFlag flagName=%q pos=%v n=%v", flagName, pos, n)
|
tracef("scanValues lit=%q pos=%v n=%v", lit, pos, n)
|
||||||
|
|
||||||
values, err := func() ([]string, error) {
|
values, err := func() ([]string, error) {
|
||||||
if n == ZeroValue {
|
if n == ZeroValue {
|
||||||
@ -213,11 +245,11 @@ func (p *Parser) scanValueFlag(flagName string, pos int, n NValue) (Node, error)
|
|||||||
return nil, err
|
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 values, nil
|
||||||
return Command{Name: lit}, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *Parser) scanIdent() (string, error) {
|
func (p *Parser) scanIdent() (string, error) {
|
||||||
|
@ -31,6 +31,32 @@ func TestParser(t *testing.T) {
|
|||||||
argh.Program{Name: "pizzas"},
|
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",
|
name: "long value-less flags",
|
||||||
args: []string{"pizzas", "--tasty", "--fresh", "--super-hot-right-now"},
|
args: []string{"pizzas", "--tasty", "--fresh", "--super-hot-right-now"},
|
||||||
@ -191,6 +217,42 @@ func TestParser(t *testing.T) {
|
|||||||
argh.Command{Name: "fry"},
|
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 {
|
if tc.skip {
|
||||||
continue
|
continue
|
||||||
|
Loading…
Reference in New Issue
Block a user