diff --git a/parser.go b/parser.go index f98303e..8c5d0dc 100644 --- a/parser.go +++ b/parser.go @@ -11,7 +11,7 @@ type parser struct { cfg *ParserConfig - errors ScannerErrorList + errors ParserErrorList tok Token lit string @@ -36,8 +36,12 @@ func ParseArgs(args []string, pCfg *ParserConfig) (*ParseTree, error) { return p.parseArgs() } +func (p *parser) addError(msg string) { + p.errors.Add(Position{Column: int(p.pos)}, msg) +} + func (p *parser) init(r io.Reader, pCfg *ParserConfig) { - p.errors = ScannerErrorList{} + p.errors = ParserErrorList{} if pCfg == nil { pCfg = POSIXyParserConfig @@ -156,7 +160,7 @@ func (p *parser) parseCommand(cCfg *CommandConfig) Node { case ASSIGN: tracef("parseCommand(...) error on bare %s", p.tok) - p.errors.Add(Position{Column: int(p.pos)}, "invalid bare assignment") + p.addError("invalid bare assignment") break default: @@ -203,6 +207,8 @@ func (p *parser) parseShortFlag(flCfgMap map[string]FlagConfig) Node { flCfg, ok := flCfgMap[node.Name] if !ok { + p.addError(fmt.Sprintf("unknown flag %q", node.Name)) + return node } @@ -214,6 +220,8 @@ func (p *parser) parseLongFlag(flCfgMap map[string]FlagConfig) Node { flCfg, ok := flCfgMap[node.Name] if !ok { + p.addError(fmt.Sprintf("unknown flag %q", node.Name)) + return node } @@ -230,11 +238,15 @@ func (p *parser) parseCompoundShortFlag(flCfgMap map[string]FlagConfig) Node { if i == len(withoutFlagPrefix)-1 { flCfg, ok := flCfgMap[node.Name] - if ok { - flagNodes = append(flagNodes, p.parseConfiguredFlag(node, flCfg)) + if !ok { + p.addError(fmt.Sprintf("unknown flag %q", node.Name)) continue } + + flagNodes = append(flagNodes, p.parseConfiguredFlag(node, flCfg)) + + continue } flagNodes = append(flagNodes, node) diff --git a/parser_error.go b/parser_error.go new file mode 100644 index 0000000..62ae3b7 --- /dev/null +++ b/parser_error.go @@ -0,0 +1,88 @@ +package argh + +import ( + "fmt" + "io" + "sort" +) + +// ParserError is largely borrowed from go/scanner.Error +type ParserError struct { + Pos Position + Msg string +} + +func (e ParserError) Error() string { + if e.Pos.IsValid() { + return e.Pos.String() + ":" + e.Msg + } + + return e.Msg +} + +// ParserErrorList is largely borrowed from go/scanner.ErrorList +type ParserErrorList []*ParserError + +func (el *ParserErrorList) Add(pos Position, msg string) { + *el = append(*el, &ParserError{Pos: pos, Msg: msg}) +} + +func (el *ParserErrorList) Reset() { *el = (*el)[0:0] } + +func (el ParserErrorList) Len() int { return len(el) } + +func (el ParserErrorList) Swap(i, j int) { el[i], el[j] = el[j], el[i] } + +func (el ParserErrorList) Less(i, j int) bool { + e := &el[i].Pos + f := &el[j].Pos + + if e.Column != f.Column { + return e.Column < f.Column + } + + return el[i].Msg < el[j].Msg +} + +func (el ParserErrorList) Sort() { + sort.Sort(el) +} + +func (el ParserErrorList) Error() string { + switch len(el) { + case 0: + return "no errors" + case 1: + return el[0].Error() + } + return fmt.Sprintf("%s (and %d more errors)", el[0], len(el)-1) +} + +func (el ParserErrorList) Err() error { + if len(el) == 0 { + return nil + } + return el +} + +func (el ParserErrorList) Is(other error) bool { + if _, ok := other.(ParserErrorList); ok { + return el.Error() == other.Error() + } + + if v, ok := other.(*ParserErrorList); ok { + return el.Error() == (*v).Error() + } + + return false +} + +func PrintParserError(w io.Writer, err error) { + if list, ok := err.(ParserErrorList); ok { + for _, e := range list { + fmt.Fprintf(w, "%s\n", e) + } + } else if err != nil { + fmt.Fprintf(w, "%s\n", err) + } +} diff --git a/parser_test.go b/parser_test.go index 0a4ad7c..fe57c77 100644 --- a/parser_test.go +++ b/parser_test.go @@ -25,6 +25,12 @@ func TestParser(t *testing.T) { }, cfg: &argh.ParserConfig{ Prog: argh.CommandConfig{ + Flags: map[string]argh.FlagConfig{ + "e": {}, + "a": {}, + "t": {}, + "wat": {}, + }, Commands: map[string]argh.CommandConfig{ "hello": argh.CommandConfig{ NValue: 1, @@ -173,6 +179,15 @@ func TestParser(t *testing.T) { { name: "long value-less flags", args: []string{"pizzas", "--tasty", "--fresh", "--super-hot-right-now"}, + cfg: &argh.ParserConfig{ + Prog: argh.CommandConfig{ + Flags: map[string]argh.FlagConfig{ + "tasty": {}, + "fresh": {}, + "super-hot-right-now": {}, + }, + }, + }, expPT: []argh.Node{ &argh.Command{ Name: "pizzas", @@ -211,8 +226,11 @@ func TestParser(t *testing.T) { Prog: argh.CommandConfig{ Commands: map[string]argh.CommandConfig{}, Flags: map[string]argh.FlagConfig{ - "fresh": argh.FlagConfig{NValue: 1}, - "box": argh.FlagConfig{NValue: argh.OneOrMoreValue}, + "tasty": {}, + "fresh": argh.FlagConfig{NValue: 1}, + "super-hot-right-now": {}, + "box": argh.FlagConfig{NValue: argh.OneOrMoreValue}, + "please": {}, }, }, }, @@ -281,6 +299,15 @@ func TestParser(t *testing.T) { { name: "short value-less flags", args: []string{"pizzas", "-t", "-f", "-s"}, + cfg: &argh.ParserConfig{ + Prog: argh.CommandConfig{ + Flags: map[string]argh.FlagConfig{ + "t": {}, + "f": {}, + "s": {}, + }, + }, + }, expPT: []argh.Node{ &argh.Command{ Name: "pizzas", @@ -308,6 +335,17 @@ func TestParser(t *testing.T) { { name: "compound short flags", args: []string{"pizzas", "-aca", "-blol"}, + cfg: &argh.ParserConfig{ + Prog: argh.CommandConfig{ + Flags: map[string]argh.FlagConfig{ + "a": {}, + "b": {}, + "c": {}, + "l": {}, + "o": {}, + }, + }, + }, expPT: []argh.Node{ &argh.Command{ Name: "pizzas", @@ -354,7 +392,11 @@ func TestParser(t *testing.T) { Prog: argh.CommandConfig{ Commands: map[string]argh.CommandConfig{}, Flags: map[string]argh.FlagConfig{ - "b": argh.FlagConfig{NValue: 1}, + "a": {}, + "b": argh.FlagConfig{NValue: 1}, + "ca": {}, + "l": {}, + "o": {}, }, }, }, @@ -414,7 +456,11 @@ func TestParser(t *testing.T) { Commands: map[string]argh.CommandConfig{ "fly": argh.CommandConfig{ Commands: map[string]argh.CommandConfig{ - "fry": argh.CommandConfig{}, + "fry": argh.CommandConfig{ + Flags: map[string]argh.FlagConfig{ + "forever": {}, + }, + }, }, }, }, @@ -689,7 +735,7 @@ func TestParser(t *testing.T) { "w": argh.FlagConfig{}, "A": argh.FlagConfig{}, "T": argh.FlagConfig{NValue: 1}, - "hecking": argh.FlagConfig{}, + "hecKing": argh.FlagConfig{}, }, }, ScannerConfig: &argh.ScannerConfig{ @@ -741,11 +787,61 @@ func TestParser(t *testing.T) { }, }, }, + { + name: "windows like", + args: []string{"hotdog", "/f", "/L", "/o:ppy", "hats"}, + cfg: &argh.ParserConfig{ + Prog: argh.CommandConfig{ + Flags: map[string]argh.FlagConfig{ + "f": {}, + "L": {}, + "o": argh.FlagConfig{NValue: 1}, + }, + Commands: map[string]argh.CommandConfig{ + "hats": {}, + }, + }, + ScannerConfig: &argh.ScannerConfig{ + AssignmentOperator: ':', + FlagPrefix: '/', + MultiValueDelim: ',', + }, + }, + expPT: []argh.Node{ + &argh.Command{ + Name: "hotdog", + Nodes: []argh.Node{ + &argh.ArgDelimiter{}, + &argh.Flag{Name: "f"}, + &argh.ArgDelimiter{}, + &argh.Flag{Name: "L"}, + &argh.ArgDelimiter{}, + &argh.Flag{ + Name: "o", + Values: map[string]string{"0": "ppy"}, + Nodes: []argh.Node{ + &argh.Assign{}, + &argh.Ident{Literal: "ppy"}, + }, + }, + &argh.ArgDelimiter{}, + &argh.Command{Name: "hats"}, + }, + }, + }, + }, { name: "invalid bare assignment", args: []string{"pizzas", "=", "--wat"}, - expErr: argh.ScannerErrorList{ - &argh.ScannerError{Pos: argh.Position{Column: 8}, Msg: "invalid bare assignment"}, + cfg: &argh.ParserConfig{ + Prog: argh.CommandConfig{ + Flags: map[string]argh.FlagConfig{ + "wat": {}, + }, + }, + }, + expErr: argh.ParserErrorList{ + &argh.ParserError{Pos: argh.Position{Column: 8}, Msg: "invalid bare assignment"}, }, expPT: []argh.Node{ &argh.Command{ diff --git a/querier_test.go b/querier_test.go index 06e2dae..bb3b767 100644 --- a/querier_test.go +++ b/querier_test.go @@ -16,8 +16,19 @@ func TestQuerier_Program(t *testing.T) { expOK bool }{ { - name: "typical", - args: []string{"pizzas", "ahoy", "--treatsa", "fun"}, + name: "typical", + args: []string{"pizzas", "ahoy", "--treatsa", "fun"}, + cfg: &argh.ParserConfig{ + Prog: argh.CommandConfig{ + Commands: map[string]argh.CommandConfig{ + "ahoy": argh.CommandConfig{ + Flags: map[string]argh.FlagConfig{ + "treatsa": argh.FlagConfig{NValue: 1}, + }, + }, + }, + }, + }, exp: "pizzas", expOK: true, }, diff --git a/scanner_error.go b/scanner_error.go deleted file mode 100644 index e5b634b..0000000 --- a/scanner_error.go +++ /dev/null @@ -1,88 +0,0 @@ -package argh - -import ( - "fmt" - "io" - "sort" -) - -// ScannerError is largely borrowed from go/scanner.Error -type ScannerError struct { - Pos Position - Msg string -} - -func (e ScannerError) Error() string { - if e.Pos.IsValid() { - return e.Pos.String() + ":" + e.Msg - } - - return e.Msg -} - -// ScannerErrorList is largely borrowed from go/scanner.ErrorList -type ScannerErrorList []*ScannerError - -func (el *ScannerErrorList) Add(pos Position, msg string) { - *el = append(*el, &ScannerError{Pos: pos, Msg: msg}) -} - -func (el *ScannerErrorList) Reset() { *el = (*el)[0:0] } - -func (el ScannerErrorList) Len() int { return len(el) } - -func (el ScannerErrorList) Swap(i, j int) { el[i], el[j] = el[j], el[i] } - -func (el ScannerErrorList) Less(i, j int) bool { - e := &el[i].Pos - f := &el[j].Pos - - if e.Column != f.Column { - return e.Column < f.Column - } - - return el[i].Msg < el[j].Msg -} - -func (el ScannerErrorList) Sort() { - sort.Sort(el) -} - -func (el ScannerErrorList) Error() string { - switch len(el) { - case 0: - return "no errors" - case 1: - return el[0].Error() - } - return fmt.Sprintf("%s (and %d more errors)", el[0], len(el)-1) -} - -func (el ScannerErrorList) Err() error { - if len(el) == 0 { - return nil - } - return el -} - -func (el ScannerErrorList) Is(other error) bool { - if _, ok := other.(ScannerErrorList); ok { - return el.Error() == other.Error() - } - - if v, ok := other.(*ScannerErrorList); ok { - return el.Error() == (*v).Error() - } - - return false -} - -func PrintScannerError(w io.Writer, err error) { - if list, ok := err.(ScannerErrorList); ok { - for _, e := range list { - fmt.Fprintf(w, "%s\n", e) - } - } else if err != nil { - fmt.Fprintf(w, "%s\n", err) - } -}