From b207239aa3c961d60827bce78de14a2f5523cd72 Mon Sep 17 00:00:00 2001 From: Curtis La Graff Date: Sun, 6 Nov 2016 20:25:51 -0500 Subject: [PATCH 1/3] added minor fixes to various things Signed-off-by: Curtis La Graff --- actions.go | 6 ++- errors.go | 6 +-- option.go | 2 +- parser.go | 109 ++++++++++++++++++++++++++--------------------------- 4 files changed, 63 insertions(+), 60 deletions(-) diff --git a/actions.go b/actions.go index e57b77f..d6d31ad 100644 --- a/actions.go +++ b/actions.go @@ -47,7 +47,11 @@ func Store(p *Parser, f *Option, args ...string) ([]string, error) { } else if regexp.MustCompile(`^[1-9]+$`).MatchString(f.ArgNum) == true { num, _ := strconv.Atoi(f.ArgNum) if len(args) < num { - return args, TooFewArgsErr{*f} + if f.IsRequired == true { + return args, TooFewArgsErr{*f} + } else { + return args, nil + } } if num > 1 { diff --git a/errors.go b/errors.go index 4a4364c..6344b17 100644 --- a/errors.go +++ b/errors.go @@ -104,14 +104,14 @@ func (err MissingOneOrMoreArgsErr) Error() string { // MissingParserErr indicated that commands were available, but none were used. type MissingParserErr struct { - Parsers map[string]*Parser + Parsers []SubParser } // Error will return a string error message for the MissingParserErr func (err MissingParserErr) Error() string { var names []string - for name, _ := range err.Parsers { - names = append(names, name) + for _, subP := range err.Parsers { + names = append(names, subP.Name) } msg := "must use an available command: %s" return fmt.Sprintf(msg, join("", "{", join(",", names...), "}")) diff --git a/option.go b/option.go index 40235c6..93f327b 100644 --- a/option.go +++ b/option.go @@ -11,7 +11,7 @@ import ( // to StoreTrue, and its default value to false. func NewFlag(names, dest, help string) *Option { opt := NewOption(names, dest, help) - opt.Nargs("0").Action(StoreTrue).Default("false") + opt.Nargs("0").Action(StoreTrue).Default("false").NotRequired() return opt } diff --git a/parser.go b/parser.go index 4c91740..cdc70fa 100644 --- a/parser.go +++ b/parser.go @@ -6,13 +6,19 @@ import ( "strings" ) +type SubParser struct { + Parser *Parser + Name string +} + // Parser contains program-level settings and information, stores options, // and values collected upon parsing. type Parser struct { + Callback func(*Parser, *Namespace, []string, error) ProgramName string AllowAbbrev bool Options []*Option - Parsers map[string]*Parser + Parsers []SubParser UsageText string VersionDesc string Namespace *Namespace @@ -52,9 +58,9 @@ func (p *Parser) AddOptions(opts ...*Option) *Parser { // AddParser appends the provided parse to the current parser as an available command. func (p *Parser) AddParser(name string, parser *Parser) *Parser { if p.Parsers == nil { - p.Parsers = make(map[string]*Parser) + p.Parsers = make([]SubParser, 0) } - p.Parsers[name] = parser + p.Parsers = append(p.Parsers, SubParser{Name: name, Parser: parser}) return p } @@ -79,9 +85,13 @@ func (p Parser) GetParser(name string) (*Parser, error) { if len(name) <= 0 { return nil, InvalidParserNameErr{name} } - if parser, ok := p.Parsers[name]; ok == true { - return parser, nil + + for _, subP := range p.Parsers { + if subP.Name == name { + return subP.Parser, nil + } } + return nil, InvalidParserNameErr{name} } @@ -130,8 +140,8 @@ func (p *Parser) GetHelp() string { } if len(p.Parsers) > 0 { - for command, _ := range p.Parsers { - commands = append(commands, command) + for _, subP := range p.Parsers { + commands = append(commands, subP.Name) } commandStr = join("", "{", join(",", commands...), "}") if len(commandStr) > longest { @@ -246,7 +256,7 @@ func (p *Parser) GetVersion() string { // Parser accepts a slice of strings as options and arguments to be parsed. The // parser will call each encountered option's action. Unexpected options will // cause an error. All errors are returned. -func (p *Parser) Parse(allArgs ...string) (*Namespace, []string, error) { +func (p *Parser) Parse(allArgs ...string) { if p.Namespace == nil { p.Namespace = NewNamespace() } @@ -268,6 +278,33 @@ func (p *Parser) Parse(allArgs ...string) (*Namespace, []string, error) { } } + if len(p.Parsers) > 0 { + var usedParser bool + + for _, subParser := range p.Parsers { + name := subParser.Name + parser := subParser.Parser + if len(allArgs) <= 0 { + p.Callback(p, p.Namespace, nil, MissingParserErr{p.Parsers}) + return + } + if allArgs[0] == name { + if len(allArgs) > 0 { + allArgs = allArgs[1:len(allArgs)] + } else { + allArgs = []string{} + } + + parser.Parse(allArgs...) + return + } + } + + if usedParser != true { + p.Callback(p, p.Namespace, nil, MissingParserErr{p.Parsers}) + } + } + optionNames, args := extractOptions(allArgs...) for _, optionName := range optionNames { var option *Option @@ -288,51 +325,12 @@ func (p *Parser) Parse(allArgs ...string) (*Namespace, []string, error) { } if option == nil { - return nil, nil, InvalidOptionErr{optionName} + p.Callback(p, p.Namespace, args, InvalidOptionErr{optionName}) } args, err = option.DesiredAction(p, option, args...) if err != nil { - return nil, nil, err - } - } - - if len(p.Parsers) > 0 { - var usedParser bool - if len(allArgs) <= 0 { - return p.Namespace, nil, MissingParserErr{p.Parsers} - } - for name, parser := range p.Parsers { - if allArgs[0] == name { - if len(allArgs) > 0 { - allArgs = allArgs[1:len(allArgs)] - } else { - allArgs = []string{} - } - n, a, err := parser.Parse(allArgs...) - if err != nil { - switch err.(type) { - case ShowHelpErr, ShowVersionErr: - return nil, nil, err - default: - parser.ShowHelp() - return nil, nil, ShowHelpErr{} - } - } - if n != nil { - p.Namespace.merge(n) - } - allArgs = a - if err != nil { - return nil, nil, err - } - usedParser = true - break - } - } - - if usedParser != true { - return nil, nil, MissingParserErr{p.Parsers} + p.Callback(p, p.Namespace, args, err) } } @@ -343,7 +341,7 @@ func (p *Parser) Parse(allArgs ...string) (*Namespace, []string, error) { } _, err := opt.DesiredAction(p, opt, args...) if err != nil { - return nil, nil, err + p.Callback(p, p.Namespace, args, err) } } } @@ -357,16 +355,16 @@ func (p *Parser) Parse(allArgs ...string) (*Namespace, []string, error) { } args, err = f.DesiredAction(p, f, args...) if err != nil { - return nil, nil, err + p.Callback(p, p.Namespace, args, err) } } if len(requiredOptions) != 0 { for _, option := range requiredOptions { - return nil, nil, MissingOptionErr{option.DisplayName()} + p.Callback(p, p.Namespace, args, MissingOptionErr{option.DisplayName()}) } } - return p.Namespace, args, nil + p.Callback(p, p.Namespace, args, nil) } // Path will set the parser's program name to the program name specified by the @@ -410,12 +408,13 @@ func (p *Parser) Version(version string) *Parser { // NewParser returns an instantiated pointer to a new parser instance, with // a description matching the provided string. -func NewParser(desc string) *Parser { +func NewParser(desc string, callback func(*Parser, *Namespace, []string, error)) *Parser { p := Parser{UsageText: desc} p.Namespace = NewNamespace() - p.Parsers = make(map[string]*Parser) + p.Parsers = make([]SubParser, 0) if len(os.Args) >= 1 { p.Path(os.Args[0]) } + p.Callback = callback return &p } From 7dcac02d8d9e2828b78ec8b82997cafadbcffde3 Mon Sep 17 00:00:00 2001 From: Curtis La Graff Date: Sun, 6 Nov 2016 20:59:00 -0500 Subject: [PATCH 2/3] fixed failing unit tests Signed-off-by: Curtis La Graff --- actions_test.go | 26 +++++++++++++------------- parser_test.go | 16 ++++++++++------ 2 files changed, 23 insertions(+), 19 deletions(-) diff --git a/actions_test.go b/actions_test.go index bf7c129..856cbdd 100644 --- a/actions_test.go +++ b/actions_test.go @@ -6,7 +6,7 @@ import "testing" // return the appropriate args & error when operating upon a option with // one expected argument. func TestStore_OneNargs(t *testing.T) { - p := NewParser("parser") + p := NewParser("parser", emptyNamespace()) f := NewOption("option", "option", "option").Nargs("1") args := []string{"foobar"} @@ -25,7 +25,7 @@ func TestStore_OneNargs(t *testing.T) { } args = []string{} - _, err = Store(p, f, args...) + _, err = Store(p, f.Required(), args...) if err == nil { t.Error("An error was expected but did not occurr") } @@ -35,7 +35,7 @@ func TestStore_OneNargs(t *testing.T) { // return the appropriate args & error when operating upon a option with // three expected arguments. func TestStore_ThreeNargs(t *testing.T) { - p := NewParser("parser") + p := NewParser("parser", emptyNamespace()) f := NewOption("option", "option", "option").Nargs("3") args := []string{"foo", "bar", "fizzbuzz"} @@ -54,7 +54,7 @@ func TestStore_ThreeNargs(t *testing.T) { } args = []string{} - _, err = Store(p, f, args...) + _, err = Store(p, f.Required(), args...) if err == nil { t.Error("An error was expected but did not occurr") } @@ -64,7 +64,7 @@ func TestStore_ThreeNargs(t *testing.T) { // return the appropriate args & error when operating upon a option with // any number of expected arguments. func TestStore_AnyNargs(t *testing.T) { - p := NewParser("parser") + p := NewParser("parser", emptyNamespace()) f := NewOption("option", "option", "option").Nargs("*") args := []string{"foo", "bar", "fizz", "buzz", "hello", "world"} @@ -93,7 +93,7 @@ func TestStore_AnyNargs(t *testing.T) { // return the appropriate args & error when operating upon a option with // at least one number of expected arguments. func TestStore_LeastOneNargs(t *testing.T) { - p := NewParser("parser") + p := NewParser("parser", emptyNamespace()) f := NewOption("option", "option", "option").Nargs("+") args := []string{"foo", "fizz", "world"} @@ -120,7 +120,7 @@ func TestStore_LeastOneNargs(t *testing.T) { // TestStoreConst tests the StoreConst Action will store a option's ConstValue. func TestStoreConst(t *testing.T) { - p := NewParser("parser") + p := NewParser("parser", emptyNamespace()) f := NewOption("option", "option", "option").Nargs("0").Const("hello world") args := []string{} @@ -141,7 +141,7 @@ func TestStoreConst(t *testing.T) { // TestStoreFalse tests the StoreFalse Action will store a false boolean. func TestStoreFalse(t *testing.T) { - p := NewParser("parser") + p := NewParser("parser", emptyNamespace()) f := NewOption("option", "option", "option").Nargs("0") args := []string{} @@ -162,7 +162,7 @@ func TestStoreFalse(t *testing.T) { // TestStoreTrue tests the StoreFalse Action will store a true boolean. func TestStoreTrue(t *testing.T) { - p := NewParser("parser") + p := NewParser("parser", emptyNamespace()) f := NewOption("option", "option", "option").Nargs("0") args := []string{} @@ -185,7 +185,7 @@ func TestStoreTrue(t *testing.T) { // return the appropriate args & error when operating upon a option with // one expected argument. func TestAppend_OneNargs(t *testing.T) { - p := NewParser("parser") + p := NewParser("parser", emptyNamespace()) f := NewOption("option", "option", "option").Nargs("1") args := []string{"foobar"} @@ -214,7 +214,7 @@ func TestAppend_OneNargs(t *testing.T) { // return the appropriate args & error when operating upon a option with // three expected arguments. func TestAppend_ThreeNargs(t *testing.T) { - p := NewParser("parser") + p := NewParser("parser", emptyNamespace()) f := NewOption("option", "option", "option").Nargs("3") args := []string{"foo", "bar", "fizzbuzz"} @@ -243,7 +243,7 @@ func TestAppend_ThreeNargs(t *testing.T) { // return the appropriate args & error when operating upon a option with // any number of expected arguments. func TestAppend_AnyNargs(t *testing.T) { - p := NewParser("parser") + p := NewParser("parser", emptyNamespace()) f := NewOption("option", "option", "option").Nargs("*") args := []string{"foo", "bar", "fizz", "buzz", "hello", "world"} @@ -272,7 +272,7 @@ func TestAppend_AnyNargs(t *testing.T) { // return the appropriate args & error when operating upon a option with // at least one number of expected arguments. func TestAppend_LeastOneNargs(t *testing.T) { - p := NewParser("parser") + p := NewParser("parser", emptyNamespace()) f := NewOption("option", "option", "option").Nargs("+") args := []string{"foo", "fizz", "world"} diff --git a/parser_test.go b/parser_test.go index 3d61d29..660bd2d 100644 --- a/parser_test.go +++ b/parser_test.go @@ -4,7 +4,11 @@ import ( "bufio" "os" "testing" -) //import go package for testing related functionality +) + +func emptyNamespace() func(*Parser, *Namespace, []string, error) { + return func(*Parser, *Namespace, []string, error) {} +} // TestParserAddHelp tests the AddHelp method to ensure two help options // are appended to the parser, a short option & a long option. @@ -97,7 +101,7 @@ func TestParserGetOption_ValidOption(t *testing.T) { // return a help-string containing usage information and option-dependent // help text. func TestParserGetHelp(t *testing.T) { - p := NewParser("this is a description of the program") + p := NewParser("this is a description of the program", emptyNamespace()) if len(p.GetHelp()) <= 0 { t.Errorf("A non-empty string was expected but was not received") @@ -109,7 +113,7 @@ func TestParserGetHelp(t *testing.T) { // TestParserGetVersion tests the GetVersion method to ensure that the parser will // return a version-string containing version information of the parser. func TestParserGetVersion(t *testing.T) { - p := NewParser("some description").Version("1.0.b") + p := NewParser("some description", emptyNamespace()).Version("1.0.b") if p.GetVersion() != "argparse.test version 1.0.b" { t.Errorf("The retrieved version text did not match the expected string") @@ -194,7 +198,7 @@ func TestParserShowHelp(t *testing.T) { } os.Stdout = writeFile - p := NewParser("this is an awesome program which does awesome things") + p := NewParser("this is an awesome program which does awesome things", emptyNamespace()) p.ShowHelp() writeFile.Close() @@ -221,7 +225,7 @@ func TestParserShowVersion(t *testing.T) { } os.Stdout = writeFile - p := NewParser("this is a program").Version("1.foo.bar.0.42 alpha") + p := NewParser("this is a program", emptyNamespace()).Version("1.foo.bar.0.42 alpha") p.ShowVersion() writeFile.Close() @@ -242,7 +246,7 @@ func TestParserShowVersion(t *testing.T) { // is returned using the NewParser function. func TestNewParser(t *testing.T) { desc := "this program does things." - p := NewParser(desc) + p := NewParser(desc, emptyNamespace()) if p == nil { t.Error("The parser pointer cannot be null") From fe9266c86fed31c46c3333fd574fd6f8c17da646 Mon Sep 17 00:00:00 2001 From: Curtis La Graff Date: Sun, 6 Nov 2016 21:32:50 -0500 Subject: [PATCH 3/3] updated readme Signed-off-by: Curtis La Graff --- README.md | 48 +++++++++++++++++++++++++++--------------------- 1 file changed, 27 insertions(+), 21 deletions(-) diff --git a/README.md b/README.md index 80f9566..d63983d 100644 --- a/README.md +++ b/README.md @@ -30,17 +30,8 @@ import ( "github.com/clagraff/argparse" ) -func main() { - p := argparse.NewParser("Output a friendly greeting").Version("1.3.0a") - p.AddHelp().AddVersion() // Enable help and version flags - - upperFlag := argparse.NewFlag("u", "upper", "Use uppercase text").Default("false") - nameOption := argparse.NewArg("n name", "name", "Name of person to greet").Default("John").Required() - - p.AddOptions(upperFlag, nameOption) - - // Parse all available program arguments (except for the program path). - if ns, leftovers, err := p.Parse(os.Args[1:]...); err != nil { +func callback(p *argparse.Parser, ns *argparse.Namespace, leftovers []string, err error) { + if err != nil { switch err.(type) { case argparse.ShowHelpErr, argparse.ShowVersionErr: // For either ShowHelpErr or ShowVersionErr, the parser has already @@ -51,19 +42,34 @@ func main() { fmt.Println(err, "\n") p.ShowHelp() } - } else { - name := ns.String("name") - upper := ns.String("upper") == "true" - if upper == true { - name = strings.ToUpper(name) - } + return // Exit program + } - fmt.Printf("Hello, %s!\n", name) - if len(leftovers) > 0 { - fmt.Println("\nUnused args:", leftovers) - } + name := ns.Get("name").(string) + upper := ns.Get("upper").(string) == "true" + + if upper == true { + name = strings.ToUpper(name) } + + fmt.Printf("Hello, %s!\n", name) + if len(leftovers) > 0 { + fmt.Println("\nUnused args:", leftovers) + } +} + +func main() { + p := argparse.NewParser("Output a friendly greeting", callback).Version("1.3.0a") + p.AddHelp().AddVersion() // Enable help and version flags + + upperFlag := argparse.NewFlag("u", "upper", "Use uppercase text").Default("false") + nameOption := argparse.NewArg("n name", "name", "Name of person to greet").Default("John").Required() + + p.AddOptions(upperFlag, nameOption) + + // Parse all available program arguments (except for the program path). + p.Parse(os.Args[1:]...) } ```