add all current code
based off of docopt 0.6.1 (master branch at 8/29/2013)
all tests are passing
diff --git a/docopt.go b/docopt.go
new file mode 100644
index 0000000..77c52ab
--- /dev/null
+++ b/docopt.go
@@ -0,0 +1,1213 @@
+/*
+Based of off docopt.py: https://github.com/docopt/docopt
+
+Licensed under terms of MIT license (see LICENSE-MIT)
+Copyright (c) 2013 Keith Batten, kbatten@gmail.com
+*/
+
+package docopt
+
+import (
+ "fmt"
+ "os"
+ "reflect"
+ "regexp"
+ "strings"
+ "unicode"
+)
+
+// parse and return a map of args, output and all errors
+func Parse(doc string, argv []string, help bool, version string, optionsFirst bool) (args map[string]interface{}, output string, err error) {
+ if argv == nil && len(os.Args) > 1 {
+ argv = os.Args[1:]
+ }
+
+ usageSections := parseSection("usage:", doc)
+
+ if len(usageSections) == 0 {
+ err = newLanguageError("\"usage:\" (case-insensitive) not found.")
+ return
+ }
+ if len(usageSections) > 1 {
+ err = newLanguageError("More than one \"usage:\" (case-insensitive).")
+ return
+ }
+ usage := usageSections[0]
+
+ options := parseDefaults(doc)
+ pat, err := parsePattern(formalUsage(usage), &options)
+ if err != nil {
+ output = handleError(err, usage)
+ return
+ }
+
+ patternArgv, err := parseArgv(newTokenList(argv, ERROR_USER), &options, optionsFirst)
+ if err != nil {
+ output = handleError(err, usage)
+ return
+ }
+ patFlat, err := pat.flat(PATTERN_OPTION)
+ if err != nil {
+ output = handleError(err, usage)
+ return
+ }
+ patternOptions := patFlat.unique()
+
+ patFlat, err = pat.flat(PATTERN_OPTIONSSHORTCUT)
+ if err != nil {
+ output = handleError(err, usage)
+ return
+ }
+ for _, optionsShortcut := range patFlat {
+ docOptions := parseDefaults(doc)
+ optionsShortcut.children = docOptions.unique().diff(patternOptions)
+ }
+
+ if output = extras(help, version, patternArgv, doc); len(output) > 0 {
+ return
+ }
+
+ err = pat.fix()
+ if err != nil {
+ output = handleError(err, usage)
+ return
+ }
+ matched, left, collected := pat.match(&patternArgv, nil)
+ if matched && len(*left) == 0 {
+ patFlat, err = pat.flat(PATTERN_DEFAULT)
+ if err != nil {
+ output = handleError(err, usage)
+ return
+ }
+ args = append(patFlat, *collected...).dictionary()
+ return
+ }
+
+ err = newUserError("")
+ output = handleError(err, usage)
+ return
+}
+
+// parse just doc and return a map of args
+// handle all printing and non-fatal errors
+// panic on fatal errors
+// exit on user error or help
+func ParseEasy(doc string) map[string]interface{} {
+ return ParseLoud(doc, nil, true, "", false)
+}
+
+// parse and return a map of args and fatal errors
+// handle printing of help
+// exit on user error or help
+func ParseQuiet(doc string, argv []string, help bool, version string, optionsFirst bool) (map[string]interface{}, error) {
+ args, output, err := Parse(doc, argv, help, version, optionsFirst)
+ if _, ok := err.(*UserError); ok {
+ fmt.Println(output)
+ os.Exit(1)
+ } else if len(output) > 0 && err == nil {
+ fmt.Println(output)
+ os.Exit(0)
+ }
+ return args, err
+}
+
+// parse and return a map of args
+// handle all printing and non-fatal errors
+// panic on fatal errors
+// exit on user error or help
+func ParseLoud(doc string, argv []string, help bool, version string, optionsFirst bool) map[string]interface{} {
+ args, err := ParseQuiet(doc, argv, help, version, optionsFirst)
+ if _, ok := err.(*LanguageError); ok {
+ panic(fmt.Sprintf("(language) %s", err))
+ } else if err != nil {
+ panic(fmt.Sprintf("(internal) %s", err))
+ }
+ return args
+}
+
+func handleError(err error, usage string) string {
+ if _, ok := err.(*UserError); ok {
+ return strings.TrimSpace(fmt.Sprintf("%s\n%s", err, usage))
+ }
+ return ""
+}
+
+func parseSection(name, source string) []string {
+ p := regexp.MustCompile(`(?im)^([^\n]*` + name + `[^\n]*\n?(?:[ \t].*?(?:\n|$))*)`)
+ s := p.FindAllString(source, -1)
+ if s == nil {
+ s = []string{}
+ }
+ for i, v := range s {
+ s[i] = strings.TrimSpace(v)
+ }
+ return s
+}
+
+func parseDefaults(doc string) patternList {
+ defaults := patternList{}
+ p := regexp.MustCompile(`\n[ \t]*(-\S+?)`)
+ for _, s := range parseSection("options:", doc) {
+ // FIXME corner case "bla: options: --foo"
+ _, _, s = stringPartition(s, ":") // get rid of "options:"
+ split := p.Split("\n"+s, -1)[1:]
+ match := p.FindAllStringSubmatch("\n"+s, -1)
+ for i := range split {
+ optionDescription := match[i][1] + split[i]
+ if strings.HasPrefix(optionDescription, "-") {
+ defaults = append(defaults, parseOption(optionDescription))
+ }
+ }
+ }
+ return defaults
+}
+
+func parsePattern(source string, options *patternList) (*pattern, error) {
+ tokens := tokenListFromPattern(source)
+ result, err := parseExpr(tokens, options)
+ if err != nil {
+ return nil, err
+ }
+ if tokens.current() != nil {
+ return nil, tokens.errorFunc("unexpected ending: %s" + strings.Join(tokens.tokens, " "))
+ }
+ return newRequired(result...), nil
+}
+
+func parseArgv(tokens *tokenList, options *patternList, optionsFirst bool) (patternList, error) {
+ /*
+ Parse command-line argument vector.
+
+ If options_first:
+ argv ::= [ long | shorts ]* [ argument ]* [ '--' [ argument ]* ] ;
+ else:
+ argv ::= [ long | shorts | argument ]* [ '--' [ argument ]* ] ;
+ */
+ parsed := patternList{}
+ for tokens.current() != nil {
+ if tokens.current().eq("--") {
+ for _, v := range tokens.tokens {
+ parsed = append(parsed, newArgument("", v))
+ }
+ return parsed, nil
+ } else if tokens.current().hasPrefix("--") {
+ pl, err := parseLong(tokens, options)
+ if err != nil {
+ return nil, err
+ }
+ parsed = append(parsed, pl...)
+ } else if tokens.current().hasPrefix("-") && !tokens.current().eq("-") {
+ ps, err := parseShorts(tokens, options)
+ if err != nil {
+ return nil, err
+ }
+ parsed = append(parsed, ps...)
+ } else if optionsFirst {
+ for _, v := range tokens.tokens {
+ parsed = append(parsed, newArgument("", v))
+ }
+ return parsed, nil
+ } else {
+ parsed = append(parsed, newArgument("", tokens.move().String()))
+ }
+ }
+ return parsed, nil
+}
+
+func parseOption(optionDescription string) *pattern {
+ optionDescription = strings.TrimSpace(optionDescription)
+ options, _, description := stringPartition(optionDescription, " ")
+ options = strings.Replace(options, ",", " ", -1)
+ options = strings.Replace(options, "=", " ", -1)
+
+ short := ""
+ long := ""
+ argcount := 0
+ var value interface{}
+ value = false
+
+ reDefault := regexp.MustCompile(`(?i)\[default: (.*)\]`)
+ for _, s := range strings.Fields(options) {
+ if strings.HasPrefix(s, "--") {
+ long = s
+ } else if strings.HasPrefix(s, "-") {
+ short = s
+ } else {
+ argcount = 1
+ }
+ if argcount > 0 {
+ matched := reDefault.FindAllStringSubmatch(description, -1)
+ if len(matched) > 0 {
+ value = matched[0][1]
+ } else {
+ value = nil
+ }
+ }
+ }
+ return newOption(short, long, argcount, value)
+}
+
+func parseExpr(tokens *tokenList, options *patternList) (patternList, error) {
+ // expr ::= seq ( '|' seq )* ;
+ seq, err := parseSeq(tokens, options)
+ if err != nil {
+ return nil, err
+ }
+ if !tokens.current().eq("|") {
+ return seq, nil
+ }
+ var result patternList
+ if len(seq) > 1 {
+ result = patternList{newRequired(seq...)}
+ } else {
+ result = seq
+ }
+ for tokens.current().eq("|") {
+ tokens.move()
+ seq, err = parseSeq(tokens, options)
+ if err != nil {
+ return nil, err
+ }
+ if len(seq) > 1 {
+ result = append(result, newRequired(seq...))
+ } else {
+ result = append(result, seq...)
+ }
+ }
+ if len(result) > 1 {
+ return patternList{newEither(result...)}, nil
+ }
+ return result, nil
+}
+
+func parseSeq(tokens *tokenList, options *patternList) (patternList, error) {
+ // seq ::= ( atom [ '...' ] )* ;
+ result := patternList{}
+ for !tokens.current().match(true, "]", ")", "|") {
+ atom, err := parseAtom(tokens, options)
+ if err != nil {
+ return nil, err
+ }
+ if tokens.current().eq("...") {
+ atom = patternList{newOneOrMore(atom...)}
+ tokens.move()
+ }
+ result = append(result, atom...)
+ }
+ return result, nil
+}
+
+func parseAtom(tokens *tokenList, options *patternList) (patternList, error) {
+ // atom ::= '(' expr ')' | '[' expr ']' | 'options' | long | shorts | argument | command ;
+ tok := tokens.current()
+ result := patternList{}
+ if tokens.current().match(false, "(", "[") {
+ tokens.move()
+ var matching string
+ pl, err := parseExpr(tokens, options)
+ if err != nil {
+ return nil, err
+ }
+ if tok.eq("(") {
+ matching = ")"
+ result = patternList{newRequired(pl...)}
+ } else if tok.eq("[") {
+ matching = "]"
+ result = patternList{newOptional(pl...)}
+ }
+ moved := tokens.move()
+ if !moved.eq(matching) {
+ return nil, tokens.errorFunc("unmatched '%s', expected: '%s' got: '%s'", tok, matching, moved)
+ }
+ return result, nil
+ } else if tok.eq("options") {
+ tokens.move()
+ return patternList{newOptionsShortcut()}, nil
+ } else if tok.hasPrefix("--") && !tok.eq("--") {
+ return parseLong(tokens, options)
+ } else if tok.hasPrefix("-") && !tok.eq("-") && !tok.eq("--") {
+ return parseShorts(tokens, options)
+ } else if tok.hasPrefix("<") && tok.hasSuffix(">") || tok.isUpper() {
+ return patternList{newArgument(tokens.move().String(), nil)}, nil
+ }
+ return patternList{newCommand(tokens.move().String(), false)}, nil
+}
+
+func parseLong(tokens *tokenList, options *patternList) (patternList, error) {
+ // long ::= '--' chars [ ( ' ' | '=' ) chars ] ;
+ long, eq, v := stringPartition(tokens.move().String(), "=")
+ var value interface{}
+ var opt *pattern
+ if eq == "" && v == "" {
+ value = nil
+ } else {
+ value = v
+ }
+
+ if !strings.HasPrefix(long, "--") {
+ return nil, newError("long option '%s' doesn't start with --", long)
+ }
+ similar := patternList{}
+ for _, o := range *options {
+ if o.long == long {
+ similar = append(similar, o)
+ }
+ }
+ if tokens.err == ERROR_USER && len(similar) == 0 { // if no exact match
+ similar = patternList{}
+ for _, o := range *options {
+ if strings.HasPrefix(o.long, long) {
+ similar = append(similar, o)
+ }
+ }
+ }
+ if len(similar) > 1 { // might be simply specified ambiguously 2+ times?
+ similarLong := make([]string, len(similar))
+ for i, s := range similar {
+ similarLong[i] = s.long
+ }
+ return nil, tokens.errorFunc("%s is not a unique prefix: %s?", long, strings.Join(similarLong, ", "))
+ } else if len(similar) < 1 {
+ argcount := 0
+ if eq == "=" {
+ argcount = 1
+ }
+ opt = newOption("", long, argcount, false)
+ *options = append(*options, opt)
+ if tokens.err == ERROR_USER {
+ var val interface{}
+ if argcount > 0 {
+ val = value
+ } else {
+ val = true
+ }
+ opt = newOption("", long, argcount, val)
+ }
+ } else {
+ opt = newOption(similar[0].short, similar[0].long, similar[0].argcount, similar[0].value)
+ if opt.argcount == 0 {
+ if value != nil {
+ return nil, tokens.errorFunc("%s must not have an argument", opt.long)
+ }
+ } else {
+ if value == nil {
+ if tokens.current().match(true, "--") {
+ return nil, tokens.errorFunc("%s requires argument", opt.long)
+ }
+ moved := tokens.move()
+ if moved != nil {
+ value = moved.String() // only set as string if not nil
+ }
+ }
+ }
+ if tokens.err == ERROR_USER {
+ if value != nil {
+ opt.value = value
+ } else {
+ opt.value = true
+ }
+ }
+ }
+
+ return patternList{opt}, nil
+}
+
+func parseShorts(tokens *tokenList, options *patternList) (patternList, error) {
+ // shorts ::= '-' ( chars )* [ [ ' ' ] chars ] ;
+ tok := tokens.move()
+ if !tok.hasPrefix("-") || tok.hasPrefix("--") {
+ return nil, newError("short option '%s' doesn't start with -", tok)
+ }
+ left := strings.TrimLeft(tok.String(), "-")
+ parsed := patternList{}
+ for left != "" {
+ var opt *pattern
+ short := "-" + left[0:1]
+ left = left[1:]
+ similar := patternList{}
+ for _, o := range *options {
+ if o.short == short {
+ similar = append(similar, o)
+ }
+ }
+ if len(similar) > 1 {
+ return nil, tokens.errorFunc("%s is specified ambiguously %d times", short, len(similar))
+ } else if len(similar) < 1 {
+ opt = newOption(short, "", 0, false)
+ *options = append(*options, opt)
+ if tokens.err == ERROR_USER {
+ opt = newOption(short, "", 0, true)
+ }
+ } else { // why copying is necessary here?
+ opt = newOption(short, similar[0].long, similar[0].argcount, similar[0].value)
+ var value interface{}
+ if opt.argcount > 0 {
+ if left == "" {
+ if tokens.current().match(true, "--") {
+ return nil, tokens.errorFunc("%s requires argument", short)
+ }
+ value = tokens.move().String()
+ } else {
+ value = left
+ left = ""
+ }
+ }
+ if tokens.err == ERROR_USER {
+ if value != nil {
+ opt.value = value
+ } else {
+ opt.value = true
+ }
+ }
+ }
+ parsed = append(parsed, opt)
+ }
+ return parsed, nil
+}
+
+func newTokenList(source []string, err errorType) *tokenList {
+ errorFunc := newError
+ if err == ERROR_USER {
+ errorFunc = newUserError
+ } else if err == ERROR_LANGUAGE {
+ errorFunc = newLanguageError
+ }
+ return &tokenList{source, errorFunc, err}
+}
+
+func tokenListFromString(source string) *tokenList {
+ return newTokenList(strings.Fields(source), ERROR_USER)
+}
+
+func tokenListFromPattern(source string) *tokenList {
+ p := regexp.MustCompile(`([\[\]\(\)\|]|\.\.\.)`)
+ source = p.ReplaceAllString(source, ` $1 `)
+ p = regexp.MustCompile(`\s+|(\S*<.*?>)`)
+ split := p.Split(source, -1)
+ match := p.FindAllStringSubmatch(source, -1)
+ var result []string
+ l := len(split)
+ for i := 0; i < l; i++ {
+ if len(split[i]) > 0 {
+ result = append(result, split[i])
+ }
+ if i < l-1 && len(match[i][1]) > 0 {
+ result = append(result, match[i][1])
+ }
+ }
+ return newTokenList(result, ERROR_LANGUAGE)
+}
+
+func formalUsage(section string) string {
+ _, _, section = stringPartition(section, ":") // drop "usage:"
+ pu := strings.Fields(section)
+
+ result := "( "
+ for _, s := range pu[1:] {
+ if s == pu[0] {
+ result += ") | ( "
+ } else {
+ result += s + " "
+ }
+ }
+ result += ")"
+
+ return result
+}
+
+func extras(help bool, version string, options patternList, doc string) string {
+ if help {
+ for _, o := range options {
+ if (o.name == "-h" || o.name == "--help") && o.value == true {
+ return strings.Trim(doc, "\n")
+ }
+ }
+ }
+ if version != "" {
+ for _, o := range options {
+ if (o.name == "--version") && o.value == true {
+ return version
+ }
+ }
+ }
+ return ""
+}
+
+type errorType int
+
+const (
+ ERROR_USER errorType = iota
+ ERROR_LANGUAGE
+)
+
+func (self errorType) String() string {
+ switch self {
+ case ERROR_USER:
+ return "userError"
+ case ERROR_LANGUAGE:
+ return "languageError"
+ }
+ return ""
+}
+
+type UserError struct {
+ msg string
+ Usage string
+}
+
+func (e UserError) Error() string {
+ return e.msg
+}
+func newUserError(msg string, f ...interface{}) error {
+ return &UserError{fmt.Sprintf(msg, f...), ""}
+}
+
+type LanguageError struct {
+ msg string
+}
+
+func (e LanguageError) Error() string {
+ return e.msg
+}
+func newLanguageError(msg string, f ...interface{}) error {
+ return &LanguageError{fmt.Sprintf(msg, f...)}
+}
+
+var newError = fmt.Errorf
+
+type tokenList struct {
+ tokens []string
+ errorFunc func(string, ...interface{}) error
+ err errorType
+}
+type token string
+
+func (self *token) eq(s string) bool {
+ if self == nil {
+ return false
+ }
+ return string(*self) == s
+}
+func (self *token) match(matchNil bool, tokenStrings ...string) bool {
+ if self == nil && matchNil {
+ return true
+ } else if self == nil && !matchNil {
+ return false
+ }
+
+ for _, t := range tokenStrings {
+ if t == string(*self) {
+ return true
+ }
+ }
+ return false
+}
+func (self *token) hasPrefix(prefix string) bool {
+ if self == nil {
+ return false
+ }
+ return strings.HasPrefix(string(*self), prefix)
+}
+func (self *token) hasSuffix(suffix string) bool {
+ if self == nil {
+ return false
+ }
+ return strings.HasSuffix(string(*self), suffix)
+}
+func (self *token) isUpper() bool {
+ if self == nil {
+ return false
+ }
+ return isStringUppercase(string(*self))
+}
+func (self *token) String() string {
+ if self == nil {
+ return ""
+ }
+ return string(*self)
+}
+
+func (self *tokenList) current() *token {
+ if len(self.tokens) > 0 {
+ return (*token)(&(self.tokens[0]))
+ }
+ return nil
+}
+
+func (self *tokenList) length() int {
+ return len(self.tokens)
+}
+
+func (self *tokenList) move() *token {
+ if len(self.tokens) > 0 {
+ t := self.tokens[0]
+ self.tokens = self.tokens[1:]
+ return (*token)(&t)
+ }
+ return nil
+}
+
+type patternType uint
+
+const (
+ // leaf
+ PATTERN_ARGUMENT patternType = 1 << iota
+ PATTERN_COMMAND
+ PATTERN_OPTION
+
+ // branch
+ PATTERN_REQUIRED
+ PATTERN_OPTIONAL
+ PATTERN_OPTIONSSHORTCUT // Marker/placeholder for [options] shortcut.
+ PATTERN_ONEORMORE
+ PATTERN_EITHER
+
+ PATTERN_LEAF = PATTERN_ARGUMENT +
+ PATTERN_COMMAND +
+ PATTERN_OPTION
+ PATTERN_BRANCH = PATTERN_REQUIRED +
+ PATTERN_OPTIONAL +
+ PATTERN_OPTIONSSHORTCUT +
+ PATTERN_ONEORMORE +
+ PATTERN_EITHER
+ PATTERN_ALL = PATTERN_LEAF + PATTERN_BRANCH
+ PATTERN_DEFAULT = 0
+)
+
+func (self patternType) String() string {
+ switch self {
+ case PATTERN_ARGUMENT:
+ return "argument"
+ case PATTERN_COMMAND:
+ return "command"
+ case PATTERN_OPTION:
+ return "option"
+ case PATTERN_REQUIRED:
+ return "required"
+ case PATTERN_OPTIONAL:
+ return "optional"
+ case PATTERN_OPTIONSSHORTCUT:
+ return "optionsshortcut"
+ case PATTERN_ONEORMORE:
+ return "oneormore"
+ case PATTERN_EITHER:
+ return "either"
+ case PATTERN_LEAF:
+ return "leaf"
+ case PATTERN_BRANCH:
+ return "branch"
+ case PATTERN_ALL:
+ return "all"
+ case PATTERN_DEFAULT:
+ return "default"
+ }
+ return ""
+}
+
+type pattern struct {
+ t patternType
+
+ children patternList
+
+ name string
+ value interface{}
+
+ short string
+ long string
+ argcount int
+}
+
+type patternList []*pattern
+
+func newBranchPattern(t patternType, pl ...*pattern) *pattern {
+ var p pattern
+ p.t = t
+ p.children = make(patternList, len(pl))
+ copy(p.children, pl)
+ return &p
+}
+
+func newRequired(pl ...*pattern) *pattern {
+ return newBranchPattern(PATTERN_REQUIRED, pl...)
+}
+
+func newEither(pl ...*pattern) *pattern {
+ return newBranchPattern(PATTERN_EITHER, pl...)
+}
+
+func newOneOrMore(pl ...*pattern) *pattern {
+ return newBranchPattern(PATTERN_ONEORMORE, pl...)
+}
+
+func newOptional(pl ...*pattern) *pattern {
+ return newBranchPattern(PATTERN_OPTIONAL, pl...)
+}
+
+func newOptionsShortcut() *pattern {
+ var p pattern
+ p.t = PATTERN_OPTIONSSHORTCUT
+ return &p
+}
+
+func newLeafPattern(t patternType, name string, value interface{}) *pattern {
+ // default: value=nil
+ var p pattern
+ p.t = t
+ p.name = name
+ p.value = value
+ return &p
+}
+
+func newArgument(name string, value interface{}) *pattern {
+ // default: value=nil
+ return newLeafPattern(PATTERN_ARGUMENT, name, value)
+}
+
+func newCommand(name string, value interface{}) *pattern {
+ // default: value=false
+ var p pattern
+ p.t = PATTERN_COMMAND
+ p.name = name
+ p.value = value
+ return &p
+}
+
+func newOption(short, long string, argcount int, value interface{}) *pattern {
+ // default: "", "", 0, false
+ var p pattern
+ p.t = PATTERN_OPTION
+ p.short = short
+ p.long = long
+ if long != "" {
+ p.name = long
+ } else {
+ p.name = short
+ }
+ p.argcount = argcount
+ if value == false && argcount > 0 {
+ p.value = nil
+ } else {
+ p.value = value
+ }
+ return &p
+}
+
+func (self *pattern) flat(types patternType) (patternList, error) {
+ if self.t&PATTERN_LEAF != 0 {
+ if types == PATTERN_DEFAULT {
+ types = PATTERN_ALL
+ }
+ if self.t&types != 0 {
+ return patternList{self}, nil
+ }
+ return patternList{}, nil
+ }
+
+ if self.t&PATTERN_BRANCH != 0 {
+ if self.t&types != 0 {
+ return patternList{self}, nil
+ }
+ result := patternList{}
+ for _, child := range self.children {
+ childFlat, err := child.flat(types)
+ if err != nil {
+ return nil, err
+ }
+ result = append(result, childFlat...)
+ }
+ return result, nil
+ }
+ return nil, newError("unknown pattern type: %d, %d", self.t, types)
+}
+
+func (self *pattern) fix() error {
+ err := self.fixIdentities(nil)
+ if err != nil {
+ return err
+ }
+ self.fixRepeatingArguments()
+ return nil
+}
+
+func (self *pattern) fixIdentities(uniq patternList) error {
+ // Make pattern-tree tips point to same object if they are equal.
+ if self.t&PATTERN_BRANCH == 0 {
+ return nil
+ }
+ if uniq == nil {
+ selfFlat, err := self.flat(PATTERN_DEFAULT)
+ if err != nil {
+ return err
+ }
+ uniq = selfFlat.unique()
+ }
+ for i, child := range self.children {
+ if child.t&PATTERN_BRANCH == 0 {
+ ind, err := uniq.index(child)
+ if err != nil {
+ return err
+ }
+ self.children[i] = uniq[ind]
+ } else {
+ err := child.fixIdentities(uniq)
+ if err != nil {
+ return err
+ }
+ }
+ }
+ return nil
+}
+
+func (self *pattern) fixRepeatingArguments() {
+ // Fix elements that should accumulate/increment values.
+ var either []patternList
+
+ for _, child := range self.transform().children {
+ either = append(either, child.children)
+ }
+ for _, cas := range either {
+ casMultiple := patternList{}
+ for _, e := range cas {
+ if cas.count(e) > 1 {
+ casMultiple = append(casMultiple, e)
+ }
+ }
+ for _, e := range casMultiple {
+ if e.t == PATTERN_ARGUMENT || e.t == PATTERN_OPTION && e.argcount > 0 {
+ switch e.value.(type) {
+ case string:
+ e.value = strings.Fields(e.value.(string))
+ case []string:
+ default:
+ e.value = []string{}
+ }
+ }
+ if e.t == PATTERN_COMMAND || e.t == PATTERN_OPTION && e.argcount == 0 {
+ e.value = 0
+ }
+ }
+ }
+}
+
+func (self *pattern) match(left *patternList, collected *patternList) (bool, *patternList, *patternList) {
+ if collected == nil {
+ collected = &patternList{}
+ }
+ if self.t&PATTERN_REQUIRED != 0 {
+ l := left
+ c := collected
+ for _, p := range self.children {
+ var matched bool
+ matched, l, c = p.match(l, c)
+ if !matched {
+ return false, left, collected
+ }
+ }
+ return true, l, c
+ } else if self.t&PATTERN_OPTIONAL != 0 || self.t&PATTERN_OPTIONSSHORTCUT != 0 {
+ for _, p := range self.children {
+ _, left, collected = p.match(left, collected)
+ }
+ return true, left, collected
+ } else if self.t&PATTERN_ONEORMORE != 0 {
+ if len(self.children) != 1 {
+ panic("OneOrMore.match(): assert len(self.children) == 1")
+ }
+ l := left
+ c := collected
+ var l_ *patternList
+ matched := true
+ times := 0
+ for matched {
+ // could it be that something didn't match but changed l or c?
+ matched, l, c = self.children[0].match(l, c)
+ if matched {
+ times += 1
+ }
+ if l_ == l {
+ break
+ }
+ l_ = l
+ }
+ if times >= 1 {
+ return true, l, c
+ }
+ return false, left, collected
+ } else if self.t&PATTERN_EITHER != 0 {
+ type outcomeStruct struct {
+ matched bool
+ left *patternList
+ collected *patternList
+ length int
+ }
+ outcomes := []outcomeStruct{}
+ for _, p := range self.children {
+ matched, l, c := p.match(left, collected)
+ outcome := outcomeStruct{matched, l, c, len(*l)}
+ if matched {
+ outcomes = append(outcomes, outcome)
+ }
+ }
+ if len(outcomes) > 0 {
+ minLen := outcomes[0].length
+ minIndex := 0
+ for i, v := range outcomes {
+ if v.length < minLen {
+ minIndex = i
+ }
+ }
+ return outcomes[minIndex].matched, outcomes[minIndex].left, outcomes[minIndex].collected
+ }
+ return false, left, collected
+ } else if self.t&PATTERN_LEAF != 0 {
+ pos, match := self.singleMatch(left)
+ var increment interface{}
+ if match == nil {
+ return false, left, collected
+ }
+ left_ := make(patternList, len((*left)[:pos]), len((*left)[:pos])+len((*left)[pos+1:]))
+ copy(left_, (*left)[:pos])
+ left_ = append(left_, (*left)[pos+1:]...)
+ sameName := patternList{}
+ for _, a := range *collected {
+ if a.name == self.name {
+ sameName = append(sameName, a)
+ }
+ }
+
+ switch self.value.(type) {
+ case int, []string:
+ switch self.value.(type) {
+ case int:
+ increment = 1
+ case []string:
+ switch match.value.(type) {
+ case string:
+ increment = []string{match.value.(string)}
+ default:
+ increment = match.value
+ }
+ }
+ if len(sameName) == 0 {
+ match.value = increment
+ collectedMatch := make(patternList, len(*collected), len(*collected)+1)
+ copy(collectedMatch, *collected)
+ collectedMatch = append(collectedMatch, match)
+ return true, &left_, &collectedMatch
+ }
+ switch sameName[0].value.(type) {
+ case int:
+ sameName[0].value = sameName[0].value.(int) + increment.(int)
+ case []string:
+ sameName[0].value = append(sameName[0].value.([]string), increment.([]string)...)
+ }
+ return true, &left_, collected
+ }
+ collectedMatch := make(patternList, len(*collected), len(*collected)+1)
+ copy(collectedMatch, *collected)
+ collectedMatch = append(collectedMatch, match)
+ return true, &left_, &collectedMatch
+ }
+ panic("unmatched type")
+ return false, &patternList{}, &patternList{}
+}
+
+func (self *pattern) singleMatch(left *patternList) (int, *pattern) {
+ if self.t&PATTERN_ARGUMENT != 0 {
+ for n, p := range *left {
+ if p.t&PATTERN_ARGUMENT != 0 {
+ return n, newArgument(self.name, p.value)
+ }
+ }
+ return -1, nil
+ } else if self.t&PATTERN_COMMAND != 0 {
+ for n, p := range *left {
+ if p.t&PATTERN_ARGUMENT != 0 {
+ if p.value == self.name {
+ return n, newCommand(self.name, true)
+ } else {
+ break
+ }
+ }
+ }
+ return -1, nil
+ } else if self.t&PATTERN_OPTION != 0 {
+ for n, p := range *left {
+ if self.name == p.name {
+ return n, p
+ }
+ }
+ return -1, nil
+ }
+ panic("unmatched type")
+ return -1, nil
+}
+
+func (self *pattern) String() string {
+ if self.t&PATTERN_OPTION != 0 {
+ return fmt.Sprintf("%s(%s, %s, %d, %+v)", self.t, self.short, self.long, self.argcount, self.value)
+ } else if self.t&PATTERN_LEAF != 0 {
+ return fmt.Sprintf("%s(%s, %+v)", self.t, self.name, self.value)
+ } else if self.t&PATTERN_BRANCH != 0 {
+ result := ""
+ for i, child := range self.children {
+ if i > 0 {
+ result += ", "
+ }
+ result += child.String()
+ }
+ return fmt.Sprintf("%s(%s)", self.t, result)
+ }
+ panic("unmatched type")
+ return ""
+}
+
+func (self *pattern) transform() *pattern {
+ /*
+ Expand pattern into an (almost) equivalent one, but with single Either.
+
+ Example: ((-a | -b) (-c | -d)) => (-a -c | -a -d | -b -c | -b -d)
+ Quirks: [-a] => (-a), (-a...) => (-a -a)
+ */
+ result := []patternList{}
+ groups := []patternList{patternList{self}}
+ parents := PATTERN_REQUIRED +
+ PATTERN_OPTIONAL +
+ PATTERN_OPTIONSSHORTCUT +
+ PATTERN_EITHER +
+ PATTERN_ONEORMORE
+ for len(groups) > 0 {
+ children := groups[0]
+ groups = groups[1:]
+ var child *pattern
+ for _, c := range children {
+ if c.t&parents != 0 {
+ child = c
+ break
+ }
+ }
+ if child != nil {
+ children.remove(child)
+ if child.t&PATTERN_EITHER != 0 {
+ for _, c := range child.children {
+ r := patternList{}
+ r = append(r, c)
+ r = append(r, children...)
+ groups = append(groups, r)
+ }
+ } else if child.t&PATTERN_ONEORMORE != 0 {
+ r := patternList{}
+ r = append(r, child.children.double()...)
+ r = append(r, children...)
+ groups = append(groups, r)
+ } else {
+ r := patternList{}
+ r = append(r, child.children...)
+ r = append(r, children...)
+ groups = append(groups, r)
+ }
+ } else {
+ result = append(result, children)
+ }
+ }
+ either := patternList{}
+ for _, e := range result {
+ either = append(either, newRequired(e...))
+ }
+ return newEither(either...)
+}
+
+func (self *pattern) eq(other *pattern) bool {
+ return reflect.DeepEqual(self, other)
+}
+
+func (pl patternList) unique() patternList {
+ table := make(map[string]bool)
+ result := patternList{}
+ for _, v := range pl {
+ if !table[v.String()] {
+ table[v.String()] = true
+ result = append(result, v)
+ }
+ }
+ return result
+}
+
+func (pl patternList) index(p *pattern) (int, error) {
+ for i, c := range pl {
+ if c.eq(p) {
+ return i, nil
+ }
+ }
+ return -1, newError("%s not in list", p)
+}
+
+func (pl patternList) count(p *pattern) int {
+ count := 0
+ for _, c := range pl {
+ if c.eq(p) {
+ count++
+ }
+ }
+ return count
+}
+
+func (pl patternList) diff(l patternList) patternList {
+ l_ := make(patternList, len(l))
+ copy(l_, l)
+ result := make(patternList, 0, len(pl))
+ for _, v := range pl {
+ if v != nil {
+ match := false
+ for i, w := range l_ {
+ if w.eq(v) {
+ match = true
+ l_[i] = nil
+ break
+ }
+ }
+ if match == false {
+ result = append(result, v)
+ }
+ }
+ }
+ return result
+}
+
+func (pl patternList) double() patternList {
+ l := len(pl)
+ result := make(patternList, l*2)
+ copy(result, pl)
+ copy(result[l:2*l], pl)
+ return result
+}
+
+func (self *patternList) remove(p *pattern) {
+ (*self) = self.diff(patternList{p})
+}
+
+func (pl patternList) dictionary() map[string]interface{} {
+ dict := make(map[string]interface{})
+ for _, a := range pl {
+ dict[a.name] = a.value
+ }
+ return dict
+}
+
+func stringPartition(s, sep string) (string, string, string) {
+ sepPos := strings.Index(s, sep)
+ if sepPos == -1 { // no seperator found
+ return s, "", ""
+ }
+ split := strings.SplitN(s, sep, 2)
+ return split[0], sep, split[1]
+}
+
+func isStringUppercase(s string) bool {
+ for _, c := range s {
+ if !unicode.IsUpper(c) {
+ return false
+ }
+ }
+ return true
+}
diff --git a/docopt_test.go b/docopt_test.go
new file mode 100644
index 0000000..5f21d07
--- /dev/null
+++ b/docopt_test.go
@@ -0,0 +1,1541 @@
+/*
+Based of off docopt.py: https://github.com/docopt/docopt
+
+Licensed under terms of MIT license (see LICENSE-MIT)
+Copyright (c) 2013 Keith Batten, kbatten@gmail.com
+*/
+
+package docopt
+
+import (
+ "bufio"
+ "bytes"
+ "encoding/json"
+ "fmt"
+ "io"
+ "os"
+ "reflect"
+ "regexp"
+ "strings"
+ "testing"
+)
+
+func TestPatternFlat(t *testing.T) {
+ q := patternList{
+ newArgument("N", nil),
+ newOption("-a", "", 0, false),
+ newArgument("M", nil)}
+ p, err := newRequired(
+ newOneOrMore(newArgument("N", nil)),
+ newOption("-a", "", 0, false),
+ newArgument("M", nil)).flat(PATTERN_DEFAULT)
+ if reflect.DeepEqual(p, q) != true {
+ t.Error(err)
+ }
+
+ q = patternList{newOptionsShortcut()}
+ p, err = newRequired(
+ newOptional(newOptionsShortcut()),
+ newOptional(newOption("-a", "", 0, false))).flat(PATTERN_OPTIONSSHORTCUT)
+ if reflect.DeepEqual(p, q) != true {
+ t.Error(err)
+ }
+ return
+}
+
+func TestOption(t *testing.T) {
+ if !parseOption("-h").eq(newOption("-h", "", 0, false)) {
+ t.Error()
+ }
+ if !parseOption("--help").eq(newOption("", "--help", 0, false)) {
+ t.Error()
+ }
+ if !parseOption("-h --help").eq(newOption("-h", "--help", 0, false)) {
+ t.Error()
+ }
+ if !parseOption("-h, --help").eq(newOption("-h", "--help", 0, false)) {
+ t.Error()
+ }
+
+ if !parseOption("-h TOPIC").eq(newOption("-h", "", 1, false)) {
+ t.Error()
+ }
+ if !parseOption("--help TOPIC").eq(newOption("", "--help", 1, false)) {
+ t.Error()
+ }
+ if !parseOption("-h TOPIC --help TOPIC").eq(newOption("-h", "--help", 1, false)) {
+ t.Error()
+ }
+ if !parseOption("-h TOPIC, --help TOPIC").eq(newOption("-h", "--help", 1, false)) {
+ t.Error()
+ }
+ if !parseOption("-h TOPIC, --help=TOPIC").eq(newOption("-h", "--help", 1, false)) {
+ t.Error()
+ }
+
+ if !parseOption("-h Description...").eq(newOption("-h", "", 0, false)) {
+ t.Error()
+ }
+ if !parseOption("-h --help Description...").eq(newOption("-h", "--help", 0, false)) {
+ t.Error()
+ }
+ if !parseOption("-h TOPIC Description...").eq(newOption("-h", "", 1, false)) {
+ t.Error()
+ }
+
+ if !parseOption(" -h").eq(newOption("-h", "", 0, false)) {
+ t.Error()
+ }
+
+ if !parseOption("-h TOPIC Description... [default: 2]").eq(newOption("-h", "", 1, "2")) {
+ t.Error()
+ }
+ if !parseOption("-h TOPIC Descripton... [default: topic-1]").eq(newOption("-h", "", 1, "topic-1")) {
+ t.Error()
+ }
+ if !parseOption("--help=TOPIC ... [default: 3.14]").eq(newOption("", "--help", 1, "3.14")) {
+ t.Error()
+ }
+ if !parseOption("-h, --help=DIR ... [default: ./]").eq(newOption("-h", "--help", 1, "./")) {
+ t.Error()
+ }
+ if !parseOption("-h TOPIC Descripton... [dEfAuLt: 2]").eq(newOption("-h", "", 1, "2")) {
+ t.Error()
+ }
+ return
+}
+
+func TestOptionName(t *testing.T) {
+ if newOption("-h", "", 0, false).name != "-h" {
+ t.Error()
+ }
+ if newOption("-h", "--help", 0, false).name != "--help" {
+ t.Error()
+ }
+ if newOption("", "--help", 0, false).name != "--help" {
+ t.Error()
+ }
+ return
+}
+
+func TestCommands(t *testing.T) {
+ if v, _, err := Parse("Usage: prog add", []string{"add"}, true, "", false); reflect.DeepEqual(v, map[string]interface{}{"add": true}) != true {
+ t.Error(err)
+ }
+ if v, _, err := Parse("Usage: prog [add]", []string{}, true, "", false); reflect.DeepEqual(v, map[string]interface{}{"add": false}) != true {
+ t.Error(err)
+ }
+ if v, _, err := Parse("Usage: prog [add]", []string{"add"}, true, "", false); reflect.DeepEqual(v, map[string]interface{}{"add": true}) != true {
+ t.Error(err)
+ }
+ if v, _, err := Parse("Usage: prog (add|rm)", []string{"add"}, true, "", false); reflect.DeepEqual(v, map[string]interface{}{"add": true, "rm": false}) != true {
+ t.Error(err)
+ }
+ if v, _, err := Parse("Usage: prog (add|rm)", []string{"rm"}, true, "", false); reflect.DeepEqual(v, map[string]interface{}{"add": false, "rm": true}) != true {
+ t.Error(err)
+ }
+ if v, _, err := Parse("Usage: prog a b", []string{"a", "b"}, true, "", false); reflect.DeepEqual(v, map[string]interface{}{"a": true, "b": true}) != true {
+ t.Error(err)
+ }
+ _, _, err := Parse("Usage: prog a b", []string{"b", "a"}, true, "", false)
+ if _, ok := err.(*UserError); !ok {
+ t.Error(err)
+ }
+ return
+}
+
+func TestFormalUsage(t *testing.T) {
+ doc := `
+ Usage: prog [-hv] ARG
+ prog N M
+
+ prog is a program`
+ usage := parseSection("usage:", doc)[0]
+ if usage != "Usage: prog [-hv] ARG\n prog N M" {
+ t.Error()
+ }
+ if formalUsage(usage) != "( [-hv] ARG ) | ( N M )" {
+ t.Error()
+ }
+ return
+}
+
+func TestParseArgv(t *testing.T) {
+ o := patternList{
+ newOption("-h", "", 0, false),
+ newOption("-v", "--verbose", 0, false),
+ newOption("-f", "--file", 1, false),
+ }
+
+ p, err := parseArgv(tokenListFromString(""), &o, false)
+ q := patternList{}
+ if reflect.DeepEqual(p, q) != true {
+ t.Error(err)
+ }
+
+ p, err = parseArgv(tokenListFromString("-h"), &o, false)
+ q = patternList{newOption("-h", "", 0, true)}
+ if reflect.DeepEqual(p, q) != true {
+ t.Error(err)
+ }
+
+ p, err = parseArgv(tokenListFromString("-h --verbose"), &o, false)
+ q = patternList{
+ newOption("-h", "", 0, true),
+ newOption("-v", "--verbose", 0, true),
+ }
+ if reflect.DeepEqual(p, q) != true {
+ t.Error(err)
+ }
+
+ p, err = parseArgv(tokenListFromString("-h --file f.txt"), &o, false)
+ q = patternList{
+ newOption("-h", "", 0, true),
+ newOption("-f", "--file", 1, "f.txt"),
+ }
+ if reflect.DeepEqual(p, q) != true {
+ t.Error(err)
+ }
+
+ p, err = parseArgv(tokenListFromString("-h --file f.txt arg"), &o, false)
+ q = patternList{
+ newOption("-h", "", 0, true),
+ newOption("-f", "--file", 1, "f.txt"),
+ newArgument("", "arg"),
+ }
+ if reflect.DeepEqual(p, q) != true {
+ t.Error(err)
+ }
+
+ p, err = parseArgv(tokenListFromString("-h --file f.txt arg arg2"), &o, false)
+ q = patternList{
+ newOption("-h", "", 0, true),
+ newOption("-f", "--file", 1, "f.txt"),
+ newArgument("", "arg"),
+ newArgument("", "arg2"),
+ }
+ if reflect.DeepEqual(p, q) != true {
+ t.Error(err)
+ }
+
+ p, err = parseArgv(tokenListFromString("-h arg -- -v"), &o, false)
+ q = patternList{
+ newOption("-h", "", 0, true),
+ newArgument("", "arg"),
+ newArgument("", "--"),
+ newArgument("", "-v"),
+ }
+ if reflect.DeepEqual(p, q) != true {
+ t.Error(err)
+ }
+}
+
+func TestParsePattern(t *testing.T) {
+ o := patternList{
+ newOption("-h", "", 0, false),
+ newOption("-v", "--verbose", 0, false),
+ newOption("-f", "--file", 1, false),
+ }
+
+ p, err := parsePattern("[ -h ]", &o)
+ q := newRequired(newOptional(newOption("-h", "", 0, false)))
+ if p.eq(q) != true {
+ t.Error(err)
+ }
+
+ p, err = parsePattern("[ ARG ... ]", &o)
+ q = newRequired(newOptional(
+ newOneOrMore(
+ newArgument("ARG", nil))))
+ if p.eq(q) != true {
+ t.Error(err)
+ }
+
+ p, err = parsePattern("[ -h | -v ]", &o)
+ q = newRequired(
+ newOptional(
+ newEither(
+ newOption("-h", "", 0, false),
+ newOption("-v", "--verbose", 0, false))))
+ if p.eq(q) != true {
+ t.Error(err)
+ }
+
+ p, err = parsePattern("( -h | -v [ --file <f> ] )", &o)
+ q = newRequired(
+ newRequired(
+ newEither(
+ newOption("-h", "", 0, false),
+ newRequired(
+ newOption("-v", "--verbose", 0, false),
+ newOptional(
+ newOption("-f", "--file", 1, nil))))))
+ if p.eq(q) != true {
+ t.Error(err)
+ }
+
+ p, err = parsePattern("(-h|-v[--file=<f>]N...)", &o)
+ q = newRequired(
+ newRequired(
+ newEither(
+ newOption("-h", "", 0, false),
+ newRequired(
+ newOption("-v", "--verbose", 0, false),
+ newOptional(
+ newOption("-f", "--file", 1, nil)),
+ newOneOrMore(
+ newArgument("N", nil))))))
+ if p.eq(q) != true {
+ t.Error(err)
+ }
+
+ p, err = parsePattern("(N [M | (K | L)] | O P)", &o)
+ q = newRequired(
+ newRequired(
+ newEither(
+ newRequired(
+ newArgument("N", nil),
+ newOptional(
+ newEither(
+ newArgument("M", nil),
+ newRequired(
+ newEither(
+ newArgument("K", nil),
+ newArgument("L", nil)))))),
+ newRequired(
+ newArgument("O", nil),
+ newArgument("P", nil)))))
+ if p.eq(q) != true {
+ t.Error(err)
+ }
+
+ p, err = parsePattern("[ -h ] [N]", &o)
+ q = newRequired(
+ newOptional(
+ newOption("-h", "", 0, false)),
+ newOptional(
+ newArgument("N", nil)))
+ if p.eq(q) != true {
+ t.Error(err)
+ }
+
+ p, err = parsePattern("[options]", &o)
+ q = newRequired(
+ newOptional(
+ newOptionsShortcut()))
+ if p.eq(q) != true {
+ t.Error(err)
+ }
+
+ p, err = parsePattern("[options] A", &o)
+ q = newRequired(
+ newOptional(
+ newOptionsShortcut()),
+ newArgument("A", nil))
+ if p.eq(q) != true {
+ t.Error(err)
+ }
+
+ p, err = parsePattern("-v [options]", &o)
+ q = newRequired(
+ newOption("-v", "--verbose", 0, false),
+ newOptional(
+ newOptionsShortcut()))
+ if p.eq(q) != true {
+ t.Error(err)
+ }
+
+ p, err = parsePattern("ADD", &o)
+ q = newRequired(newArgument("ADD", nil))
+ if p.eq(q) != true {
+ t.Error(err)
+ }
+
+ p, err = parsePattern("<add>", &o)
+ q = newRequired(newArgument("<add>", nil))
+ if p.eq(q) != true {
+ t.Error(err)
+ }
+
+ p, err = parsePattern("add", &o)
+ q = newRequired(newCommand("add", false))
+ if p.eq(q) != true {
+ t.Error(err)
+ }
+}
+
+func TestOptionMatch(t *testing.T) {
+ v, w, x := newOption("-a", "", 0, false).match(
+ &patternList{newOption("-a", "", 0, true)}, nil)
+ y := patternList{newOption("-a", "", 0, true)}
+ if v != true ||
+ reflect.DeepEqual(*w, patternList{}) != true ||
+ reflect.DeepEqual(*x, y) != true {
+ t.Error()
+ }
+
+ v, w, x = newOption("-a", "", 0, false).match(
+ &patternList{newOption("-x", "", 0, false)}, nil)
+ y = patternList{newOption("-x", "", 0, false)}
+ if v != false ||
+ reflect.DeepEqual(*w, y) != true ||
+ reflect.DeepEqual(*x, patternList{}) != true {
+ t.Error()
+ }
+
+ v, w, x = newOption("-a", "", 0, false).match(
+ &patternList{newOption("-x", "", 0, false)}, nil)
+ y = patternList{newOption("-x", "", 0, false)}
+ if v != false ||
+ reflect.DeepEqual(*w, y) != true ||
+ reflect.DeepEqual(*x, patternList{}) != true {
+ t.Error()
+ }
+ v, w, x = newOption("-a", "", 0, false).match(
+ &patternList{newArgument("N", nil)}, nil)
+ y = patternList{newArgument("N", nil)}
+ if v != false ||
+ reflect.DeepEqual(*w, y) != true ||
+ reflect.DeepEqual(*x, patternList{}) != true {
+ t.Error()
+ }
+
+ v, w, x = newOption("-a", "", 0, false).match(
+ &patternList{
+ newOption("-x", "", 0, false),
+ newOption("-a", "", 0, false),
+ newArgument("N", nil)}, nil)
+ y = patternList{
+ newOption("-x", "", 0, false),
+ newArgument("N", nil)}
+ z := patternList{newOption("-a", "", 0, false)}
+ if v != true ||
+ reflect.DeepEqual(*w, y) != true ||
+ reflect.DeepEqual(*x, z) != true {
+ t.Error()
+ }
+
+ v, w, x = newOption("-a", "", 0, false).match(
+ &patternList{
+ newOption("-a", "", 0, true),
+ newOption("-a", "", 0, false)}, nil)
+ y = patternList{newOption("-a", "", 0, false)}
+ z = patternList{newOption("-a", "", 0, true)}
+ if v != true ||
+ reflect.DeepEqual(*w, y) != true ||
+ reflect.DeepEqual(*x, z) != true {
+ t.Error()
+ }
+}
+
+func TestArgumentMatch(t *testing.T) {
+ v, w, x := newArgument("N", nil).match(
+ &patternList{newArgument("N", 9)}, nil)
+ y := patternList{newArgument("N", 9)}
+ if v != true ||
+ reflect.DeepEqual(*w, patternList{}) != true ||
+ reflect.DeepEqual(*x, y) != true {
+ t.Error()
+ }
+
+ v, w, x = newArgument("N", nil).match(
+ &patternList{newOption("-x", "", 0, false)}, nil)
+ y = patternList{newOption("-x", "", 0, false)}
+ if v != false ||
+ reflect.DeepEqual(*w, y) != true ||
+ reflect.DeepEqual(*x, patternList{}) != true {
+ t.Error()
+ }
+
+ v, w, x = newArgument("N", nil).match(
+ &patternList{newOption("-x", "", 0, false),
+ newOption("-a", "", 0, false),
+ newArgument("", 5)}, nil)
+ y = patternList{newOption("-x", "", 0, false),
+ newOption("-a", "", 0, false)}
+ z := patternList{newArgument("N", 5)}
+ if v != true ||
+ reflect.DeepEqual(*w, y) != true ||
+ reflect.DeepEqual(*x, z) != true {
+ t.Error()
+ }
+
+ v, w, x = newArgument("N", nil).match(
+ &patternList{newArgument("", 9),
+ newArgument("", 0)}, nil)
+ y = patternList{newArgument("", 0)}
+ z = patternList{newArgument("N", 9)}
+ if v != true ||
+ reflect.DeepEqual(*w, y) != true ||
+ reflect.DeepEqual(*x, z) != true {
+ t.Error()
+ }
+}
+
+func TestCommandMatch(t *testing.T) {
+ v, w, x := newCommand("c", false).match(
+ &patternList{newArgument("", "c")}, nil)
+ y := patternList{newCommand("c", true)}
+ if v != true ||
+ reflect.DeepEqual(*w, patternList{}) != true ||
+ reflect.DeepEqual(*x, y) != true {
+ t.Error()
+ }
+
+ v, w, x = newCommand("c", false).match(
+ &patternList{newOption("-x", "", 0, false)}, nil)
+ y = patternList{newOption("-x", "", 0, false)}
+ if v != false ||
+ reflect.DeepEqual(*w, y) != true ||
+ reflect.DeepEqual(*x, patternList{}) != true {
+ t.Error()
+ }
+
+ v, w, x = newCommand("c", false).match(
+ &patternList{
+ newOption("-x", "", 0, false),
+ newOption("-a", "", 0, false),
+ newArgument("", "c")}, nil)
+ y = patternList{newOption("-x", "", 0, false),
+ newOption("-a", "", 0, false)}
+ z := patternList{newCommand("c", true)}
+ if v != true ||
+ reflect.DeepEqual(*w, y) != true ||
+ reflect.DeepEqual(*x, z) != true {
+ t.Error()
+ }
+
+ v, w, x = newEither(
+ newCommand("add", false),
+ newCommand("rm", false)).match(
+ &patternList{newArgument("", "rm")}, nil)
+ y = patternList{newCommand("rm", true)}
+ if v != true ||
+ reflect.DeepEqual(*w, patternList{}) != true ||
+ reflect.DeepEqual(*x, y) != true {
+ t.Error()
+ }
+}
+
+func TestOptionalMatch(t *testing.T) {
+ v, w, x := newOptional(newOption("-a", "", 0, false)).match(
+ &patternList{newOption("-a", "", 0, false)}, nil)
+ y := patternList{newOption("-a", "", 0, false)}
+ if v != true ||
+ reflect.DeepEqual(*w, patternList{}) != true ||
+ reflect.DeepEqual(*x, y) != true {
+ t.Error()
+ }
+
+ v, w, x = newOptional(newOption("-a", "", 0, false)).match(
+ &patternList{}, nil)
+ if v != true ||
+ reflect.DeepEqual(*w, patternList{}) != true ||
+ reflect.DeepEqual(*x, patternList{}) != true {
+ t.Error()
+ }
+
+ v, w, x = newOptional(newOption("-a", "", 0, false)).match(
+ &patternList{newOption("-x", "", 0, false)}, nil)
+ y = patternList{newOption("-x", "", 0, false)}
+ if v != true ||
+ reflect.DeepEqual(*w, y) != true ||
+ reflect.DeepEqual(*x, patternList{}) != true {
+ t.Error()
+ }
+
+ v, w, x = newOptional(newOption("-a", "", 0, false),
+ newOption("-b", "", 0, false)).match(
+ &patternList{newOption("-a", "", 0, false)}, nil)
+ y = patternList{newOption("-a", "", 0, false)}
+ if v != true ||
+ reflect.DeepEqual(*w, patternList{}) != true ||
+ reflect.DeepEqual(*x, y) != true {
+ t.Error()
+ }
+
+ v, w, x = newOptional(newOption("-a", "", 0, false),
+ newOption("-b", "", 0, false)).match(
+ &patternList{newOption("-b", "", 0, false)}, nil)
+ y = patternList{newOption("-b", "", 0, false)}
+ if v != true ||
+ reflect.DeepEqual(*w, patternList{}) != true ||
+ reflect.DeepEqual(*x, y) != true {
+ t.Error()
+ }
+
+ v, w, x = newOptional(newOption("-a", "", 0, false),
+ newOption("-b", "", 0, false)).match(
+ &patternList{newOption("-x", "", 0, false)}, nil)
+ y = patternList{newOption("-x", "", 0, false)}
+ if v != true ||
+ reflect.DeepEqual(*w, y) != true ||
+ reflect.DeepEqual(*x, patternList{}) != true {
+ t.Error()
+ }
+
+ v, w, x = newOptional(newArgument("N", nil)).match(
+ &patternList{newArgument("", 9)}, nil)
+ y = patternList{newArgument("N", 9)}
+ if v != true ||
+ reflect.DeepEqual(*w, patternList{}) != true ||
+ reflect.DeepEqual(*x, y) != true {
+ t.Error()
+ }
+
+ v, w, x = newOptional(newOption("-a", "", 0, false),
+ newOption("-b", "", 0, false)).match(
+ &patternList{newOption("-b", "", 0, false),
+ newOption("-x", "", 0, false),
+ newOption("-a", "", 0, false)}, nil)
+ y = patternList{newOption("-x", "", 0, false)}
+ z := patternList{newOption("-a", "", 0, false),
+ newOption("-b", "", 0, false)}
+ if v != true ||
+ reflect.DeepEqual(*w, y) != true ||
+ reflect.DeepEqual(*x, z) != true {
+ t.Error()
+ }
+}
+
+func TestRequiredMatch(t *testing.T) {
+ v, w, x := newRequired(newOption("-a", "", 0, false)).match(
+ &patternList{newOption("-a", "", 0, false)}, nil)
+ y := patternList{newOption("-a", "", 0, false)}
+ if v != true ||
+ reflect.DeepEqual(*w, patternList{}) != true ||
+ reflect.DeepEqual(*x, y) != true {
+ t.Error()
+ }
+
+ v, w, x = newRequired(newOption("-a", "", 0, false)).match(&patternList{}, nil)
+ if v != false ||
+ reflect.DeepEqual(*w, patternList{}) != true ||
+ reflect.DeepEqual(*x, patternList{}) != true {
+ t.Error()
+ }
+
+ v, w, x = newRequired(newOption("-a", "", 0, false)).match(
+ &patternList{newOption("-x", "", 0, false)}, nil)
+ y = patternList{newOption("-x", "", 0, false)}
+ if v != false ||
+ reflect.DeepEqual(*w, y) != true ||
+ reflect.DeepEqual(*x, patternList{}) != true {
+ t.Error()
+ }
+ v, w, x = newRequired(newOption("-a", "", 0, false),
+ newOption("-b", "", 0, false)).match(
+ &patternList{newOption("-a", "", 0, false)}, nil)
+ y = patternList{newOption("-a", "", 0, false)}
+ if v != false ||
+ reflect.DeepEqual(*w, y) != true ||
+ reflect.DeepEqual(*x, patternList{}) != true {
+ t.Error()
+ }
+}
+
+func TestEitherMatch(t *testing.T) {
+ v, w, x := newEither(
+ newOption("-a", "", 0, false),
+ newOption("-b", "", 0, false)).match(
+ &patternList{newOption("-a", "", 0, false)}, nil)
+ y := patternList{newOption("-a", "", 0, false)}
+ if v != true ||
+ reflect.DeepEqual(*w, patternList{}) != true ||
+ reflect.DeepEqual(*x, y) != true {
+ t.Error()
+ }
+
+ v, w, x = newEither(
+ newOption("-a", "", 0, false),
+ newOption("-b", "", 0, false)).match(&patternList{
+ newOption("-a", "", 0, false),
+ newOption("-b", "", 0, false)}, nil)
+ y = patternList{newOption("-b", "", 0, false)}
+ z := patternList{newOption("-a", "", 0, false)}
+ if v != true ||
+ reflect.DeepEqual(*w, y) != true ||
+ reflect.DeepEqual(*x, z) != true {
+ t.Error()
+ }
+
+ v, w, x = newEither(
+ newOption("-a", "", 0, false),
+ newOption("-b", "", 0, false)).match(&patternList{
+ newOption("-x", "", 0, false)}, nil)
+ y = patternList{newOption("-x", "", 0, false)}
+ z = patternList{}
+ if v != false ||
+ reflect.DeepEqual(*w, y) != true ||
+ reflect.DeepEqual(*x, z) != true {
+ t.Error()
+ }
+
+ v, w, x = newEither(
+ newOption("-a", "", 0, false),
+ newOption("-b", "", 0, false),
+ newOption("-c", "", 0, false)).match(&patternList{
+ newOption("-x", "", 0, false),
+ newOption("-b", "", 0, false)}, nil)
+ y = patternList{newOption("-x", "", 0, false)}
+ z = patternList{newOption("-b", "", 0, false)}
+ if v != true ||
+ reflect.DeepEqual(*w, y) != true ||
+ reflect.DeepEqual(*x, z) != true {
+ t.Error()
+ }
+ v, w, x = newEither(
+ newArgument("M", nil),
+ newRequired(newArgument("N", nil),
+ newArgument("M", nil))).match(&patternList{
+ newArgument("", 1),
+ newArgument("", 2)}, nil)
+ y = patternList{}
+ z = patternList{newArgument("N", 1), newArgument("M", 2)}
+ if v != true ||
+ reflect.DeepEqual(*w, y) != true ||
+ reflect.DeepEqual(*x, z) != true {
+ t.Error()
+ }
+}
+
+func TestOneOrMoreMatch(t *testing.T) {
+ v, w, x := newOneOrMore(newArgument("N", nil)).match(
+ &patternList{newArgument("", 9)}, nil)
+ y := patternList{newArgument("N", 9)}
+ if v != true ||
+ reflect.DeepEqual(*w, patternList{}) != true ||
+ reflect.DeepEqual(*x, y) != true {
+ t.Error()
+ }
+
+ v, w, x = newOneOrMore(newArgument("N", nil)).match(
+ &patternList{}, nil)
+ y = patternList{}
+ z := patternList{}
+ if v != false ||
+ reflect.DeepEqual(*w, y) != true ||
+ reflect.DeepEqual(*x, z) != true {
+ t.Error()
+ }
+
+ v, w, x = newOneOrMore(newArgument("N", nil)).match(
+ &patternList{newOption("-x", "", 0, false)}, nil)
+ y = patternList{newOption("-x", "", 0, false)}
+ z = patternList{}
+ if v != false ||
+ reflect.DeepEqual(*w, y) != true ||
+ reflect.DeepEqual(*x, z) != true {
+ t.Error()
+ }
+
+ v, w, x = newOneOrMore(newArgument("N", nil)).match(
+ &patternList{newArgument("", 9), newArgument("", 8)}, nil)
+ y = patternList{}
+ z = patternList{newArgument("N", 9), newArgument("N", 8)}
+ if v != true ||
+ reflect.DeepEqual(*w, y) != true ||
+ reflect.DeepEqual(*x, z) != true {
+ t.Error()
+ }
+
+ v, w, x = newOneOrMore(newArgument("N", nil)).match(&patternList{
+ newArgument("", 9),
+ newOption("-x", "", 0, false),
+ newArgument("", 8)}, nil)
+ y = patternList{newOption("-x", "", 0, false)}
+ z = patternList{newArgument("N", 9), newArgument("N", 8)}
+ if v != true ||
+ reflect.DeepEqual(*w, y) != true ||
+ reflect.DeepEqual(*x, z) != true {
+ t.Error()
+ }
+
+ v, w, x = newOneOrMore(newOption("-a", "", 0, false)).match(&patternList{
+ newOption("-a", "", 0, false),
+ newArgument("", 8),
+ newOption("-a", "", 0, false)}, nil)
+ y = patternList{newArgument("", 8)}
+ z = patternList{newOption("-a", "", 0, false), newOption("-a", "", 0, false)}
+ if v != true ||
+ reflect.DeepEqual(*w, y) != true ||
+ reflect.DeepEqual(*x, z) != true {
+ t.Error()
+ }
+
+ v, w, x = newOneOrMore(newOption("-a", "", 0, false)).match(&patternList{
+ newArgument("", 8),
+ newOption("-x", "", 0, false)}, nil)
+ y = patternList{newArgument("", 8), newOption("-x", "", 0, false)}
+ z = patternList{}
+ if v != false ||
+ reflect.DeepEqual(*w, y) != true ||
+ reflect.DeepEqual(*x, z) != true {
+ t.Error()
+ }
+
+ v, w, x = newOneOrMore(newRequired(newOption("-a", "", 0, false),
+ newArgument("N", nil))).match(&patternList{
+ newOption("-a", "", 0, false),
+ newArgument("", 1),
+ newOption("-x", "", 0, false),
+ newOption("-a", "", 0, false),
+ newArgument("", 2)}, nil)
+ y = patternList{newOption("-x", "", 0, false)}
+ z = patternList{newOption("-a", "", 0, false),
+ newArgument("N", 1),
+ newOption("-a", "", 0, false),
+ newArgument("N", 2)}
+ if v != true ||
+ reflect.DeepEqual(*w, y) != true ||
+ reflect.DeepEqual(*x, z) != true {
+ t.Error()
+ }
+
+ v, w, x = newOneOrMore(newOptional(newArgument("N", nil))).match(
+ &patternList{newArgument("", 9)}, nil)
+ y = patternList{}
+ z = patternList{newArgument("N", 9)}
+ if v != true ||
+ reflect.DeepEqual(*w, y) != true ||
+ reflect.DeepEqual(*x, z) != true {
+ t.Error()
+ }
+}
+
+func TestListArgumentMatch(t *testing.T) {
+ p := newRequired(
+ newArgument("N", nil),
+ newArgument("N", nil))
+ p.fix()
+ v, w, x := p.match(&patternList{newArgument("", "1"),
+ newArgument("", "2")}, nil)
+ y := patternList{newArgument("N", []string{"1", "2"})}
+ if v != true ||
+ reflect.DeepEqual(*w, patternList{}) != true ||
+ reflect.DeepEqual(*x, y) != true {
+ t.Error()
+ }
+
+ p = newOneOrMore(newArgument("N", nil))
+ p.fix()
+ v, w, x = p.match(&patternList{newArgument("", "1"),
+ newArgument("", "2"), newArgument("", "3")}, nil)
+ y = patternList{newArgument("N", []string{"1", "2", "3"})}
+ if v != true ||
+ reflect.DeepEqual(*w, patternList{}) != true ||
+ reflect.DeepEqual(*x, y) != true {
+ t.Error()
+ }
+
+ p = newRequired(newArgument("N", nil),
+ newOneOrMore(newArgument("N", nil)))
+ p.fix()
+ v, w, x = p.match(&patternList{
+ newArgument("", "1"),
+ newArgument("", "2"),
+ newArgument("", "3")}, nil)
+ y = patternList{newArgument("N", []string{"1", "2", "3"})}
+ if v != true ||
+ reflect.DeepEqual(*w, patternList{}) != true ||
+ reflect.DeepEqual(*x, y) != true {
+ t.Error()
+ }
+
+ p = newRequired(newArgument("N", nil),
+ newRequired(newArgument("N", nil)))
+ p.fix()
+ v, w, x = p.match(&patternList{
+ newArgument("", "1"),
+ newArgument("", "2")}, nil)
+ y = patternList{newArgument("N", []string{"1", "2"})}
+ if v != true ||
+ reflect.DeepEqual(*w, patternList{}) != true ||
+ reflect.DeepEqual(*x, y) != true {
+ t.Error()
+ }
+}
+
+func TestBasicPatternMatching(t *testing.T) {
+ // ( -a N [ -x Z ] )
+ p := newRequired(
+ newOption("-a", "", 0, false),
+ newArgument("N", nil),
+ newOptional(
+ newOption("-x", "", 0, false),
+ newArgument("Z", nil)))
+
+ // -a N
+ q := patternList{newOption("-a", "", 0, false), newArgument("", 9)}
+ y := patternList{newOption("-a", "", 0, false), newArgument("N", 9)}
+ v, w, x := p.match(&q, nil)
+ if v != true ||
+ reflect.DeepEqual(*w, patternList{}) != true ||
+ reflect.DeepEqual(*x, y) != true {
+ t.Error()
+ }
+
+ // -a -x N Z
+ q = patternList{newOption("-a", "", 0, false),
+ newOption("-x", "", 0, false),
+ newArgument("", 9), newArgument("", 5)}
+ y = patternList{}
+ z := patternList{newOption("-a", "", 0, false), newArgument("N", 9),
+ newOption("-x", "", 0, false), newArgument("Z", 5)}
+ v, w, x = p.match(&q, nil)
+ if v != true ||
+ reflect.DeepEqual(*w, y) != true ||
+ reflect.DeepEqual(*x, z) != true {
+ t.Error()
+ }
+
+ // -x N Z # BZZ!
+ q = patternList{newOption("-x", "", 0, false),
+ newArgument("", 9), newArgument("", 5)}
+ y = patternList{newOption("-x", "", 0, false),
+ newArgument("", 9), newArgument("", 5)}
+ z = patternList{}
+ v, w, x = p.match(&q, nil)
+ if v != false ||
+ reflect.DeepEqual(*w, y) != true ||
+ reflect.DeepEqual(*x, z) != true {
+ t.Error()
+ }
+}
+
+func TestPatternEither(t *testing.T) {
+ p := newOption("-a", "", 0, false).transform()
+ q := newEither(newRequired(
+ newOption("-a", "", 0, false)))
+ if p.eq(q) != true {
+ t.Error()
+ }
+
+ p = newArgument("A", nil).transform()
+ q = newEither(newRequired(
+ newArgument("A", nil)))
+ if p.eq(q) != true {
+ t.Error()
+ }
+
+ p = newRequired(
+ newEither(
+ newOption("-a", "", 0, false),
+ newOption("-b", "", 0, false)),
+ newOption("-c", "", 0, false)).transform()
+ q = newEither(
+ newRequired(
+ newOption("-a", "", 0, false),
+ newOption("-c", "", 0, false)),
+ newRequired(
+ newOption("-b", "", 0, false),
+ newOption("-c", "", 0, false)))
+ if p.eq(q) != true {
+ t.Error()
+ }
+
+ p = newOptional(newOption("-a", "", 0, false),
+ newEither(newOption("-b", "", 0, false),
+ newOption("-c", "", 0, false))).transform()
+ q = newEither(
+ newRequired(
+ newOption("-b", "", 0, false), newOption("-a", "", 0, false)),
+ newRequired(
+ newOption("-c", "", 0, false), newOption("-a", "", 0, false)))
+ if p.eq(q) != true {
+ t.Error()
+ }
+
+ p = newEither(newOption("-x", "", 0, false),
+ newEither(newOption("-y", "", 0, false),
+ newOption("-z", "", 0, false))).transform()
+ q = newEither(
+ newRequired(newOption("-x", "", 0, false)),
+ newRequired(newOption("-y", "", 0, false)),
+ newRequired(newOption("-z", "", 0, false)))
+ if p.eq(q) != true {
+ t.Error()
+ }
+
+ p = newOneOrMore(newArgument("N", nil),
+ newArgument("M", nil)).transform()
+ q = newEither(
+ newRequired(newArgument("N", nil), newArgument("M", nil),
+ newArgument("N", nil), newArgument("M", nil)))
+ if p.eq(q) != true {
+ t.Error()
+ }
+}
+
+func TestPatternFixRepeatingArguments(t *testing.T) {
+ p := newOption("-a", "", 0, false)
+ p.fixRepeatingArguments()
+ if p.eq(newOption("-a", "", 0, false)) != true {
+ t.Error()
+ }
+
+ p = newArgument("N", nil)
+ p.fixRepeatingArguments()
+ if p.eq(newArgument("N", nil)) != true {
+ t.Error()
+ }
+
+ p = newRequired(
+ newArgument("N", nil),
+ newArgument("N", nil))
+ q := newRequired(
+ newArgument("N", []string{}),
+ newArgument("N", []string{}))
+ p.fixRepeatingArguments()
+ if p.eq(q) != true {
+ t.Error()
+ }
+
+ p = newEither(
+ newArgument("N", nil),
+ newOneOrMore(newArgument("N", nil)))
+ q = newEither(
+ newArgument("N", []string{}),
+ newOneOrMore(newArgument("N", []string{})))
+ p.fix()
+ if p.eq(q) != true {
+ t.Error()
+ }
+}
+
+func TestSet(t *testing.T) {
+ p := newArgument("N", nil)
+ q := newArgument("N", nil)
+ if reflect.DeepEqual(p, q) != true {
+ t.Error()
+ }
+ pl := patternList{newArgument("N", nil), newArgument("N", nil)}
+ ql := patternList{newArgument("N", nil)}
+ if reflect.DeepEqual(pl.unique(), ql.unique()) != true {
+ t.Error()
+ }
+}
+
+func TestPatternFixIdentities1(t *testing.T) {
+ p := newRequired(
+ newArgument("N", nil),
+ newArgument("N", nil))
+ if len(p.children) < 2 {
+ t.Fatal()
+ }
+ if p.children[0].eq(p.children[1]) != true {
+ t.Error()
+ }
+ if p.children[0] == p.children[1] {
+ t.Error()
+ }
+ p.fixIdentities(nil)
+ if p.children[0] != p.children[1] {
+ t.Error()
+ }
+}
+
+func TestPatternFixIdentities2(t *testing.T) {
+ p := newRequired(
+ newOptional(
+ newArgument("X", nil),
+ newArgument("N", nil)),
+ newArgument("N", nil))
+ if len(p.children) < 2 {
+ t.Fatal()
+ }
+ if len(p.children[0].children) < 2 {
+ t.Fatal()
+ }
+ if p.children[0].children[1].eq(p.children[1]) != true {
+ t.Error()
+ }
+ if p.children[0].children[1] == p.children[1] {
+ t.Error()
+ }
+ p.fixIdentities(nil)
+ if p.children[0].children[1] != p.children[1] {
+ t.Error()
+ }
+}
+
+func TestLongOptionsErrorHandling(t *testing.T) {
+ _, _, err := Parse("Usage: prog", []string{"--non-existent"}, true, "", false)
+ if _, ok := err.(*UserError); !ok {
+ t.Error(fmt.Sprintf("(%s) %s", reflect.TypeOf(err), err))
+ }
+ _, _, err = Parse("Usage: prog [--version --verbose]\nOptions: --version\n --verbose",
+ []string{"--ver"}, true, "", false)
+ if _, ok := err.(*UserError); !ok {
+ t.Error(err)
+ }
+ _, _, err = Parse("Usage: prog --long\nOptions: --long ARG", []string{}, true, "", false)
+ if _, ok := err.(*LanguageError); !ok {
+ t.Error(err)
+ }
+ _, _, err = Parse("Usage: prog --long ARG\nOptions: --long ARG",
+ []string{"--long"}, true, "", false)
+ if _, ok := err.(*UserError); !ok {
+ t.Error(fmt.Sprintf("(%s) %s", reflect.TypeOf(err), err))
+ }
+ _, _, err = Parse("Usage: prog --long=ARG\nOptions: --long", []string{}, true, "", false)
+ if _, ok := err.(*LanguageError); !ok {
+ t.Error(err)
+ }
+ _, _, err = Parse("Usage: prog --long\nOptions: --long",
+ []string{}, true, "--long=ARG", false)
+ if _, ok := err.(*UserError); !ok {
+ t.Error(err)
+ }
+}
+
+func TestShortOptionsErrorHandling(t *testing.T) {
+ _, _, err := Parse("Usage: prog -x\nOptions: -x this\n -x that", []string{}, true, "", false)
+ if _, ok := err.(*LanguageError); !ok {
+ t.Error(fmt.Sprintf("(%s) %s", reflect.TypeOf(err), err))
+ }
+ _, _, err = Parse("Usage: prog", []string{"-x"}, true, "", false)
+ if _, ok := err.(*UserError); !ok {
+ t.Error(err)
+ }
+ _, _, err = Parse("Usage: prog -o\nOptions: -o ARG", []string{}, true, "", false)
+ if _, ok := err.(*LanguageError); !ok {
+ t.Error(err)
+ }
+ _, _, err = Parse("Usage: prog -o ARG\nOptions: -o ARG", []string{"-o"}, true, "", false)
+ if _, ok := err.(*UserError); !ok {
+ t.Error(err)
+ }
+}
+
+func TestMatchingParen(t *testing.T) {
+ _, _, err := Parse("Usage: prog [a [b]", []string{}, true, "", false)
+ if _, ok := err.(*LanguageError); !ok {
+ t.Error(err)
+ }
+ _, _, err = Parse("Usage: prog [a [b] ] c )", []string{}, true, "", false)
+ if _, ok := err.(*LanguageError); !ok {
+ t.Error(err)
+ }
+}
+
+func TestAllowDoubleDash(t *testing.T) {
+ if v, _, err := Parse("usage: prog [-o] [--] <arg>\noptions: -o", []string{"--", "-o"}, true, "", false); reflect.DeepEqual(v, map[string]interface{}{"-o": false, "<arg>": "-o", "--": true}) != true {
+ t.Error(err)
+ }
+ if v, _, err := Parse("usage: prog [-o] [--] <arg>\noptions: -o", []string{"-o", "1"}, true, "", false); reflect.DeepEqual(v, map[string]interface{}{"-o": true, "<arg>": "1", "--": false}) != true {
+ t.Error(err)
+ }
+ _, _, err := Parse("usage: prog [-o] <arg>\noptions:-o", []string{"-o"}, true, "", false)
+ if _, ok := err.(*UserError); !ok { //"--" is not allowed; FIXME?
+ t.Error(err)
+ }
+}
+
+func TestDocopt(t *testing.T) {
+ doc := `Usage: prog [-v] A
+
+ Options: -v Be verbose.`
+ if v, _, err := Parse(doc, []string{"arg"}, true, "", false); reflect.DeepEqual(v, map[string]interface{}{"-v": false, "A": "arg"}) != true {
+ t.Error(err)
+ }
+ if v, _, err := Parse(doc, []string{"-v", "arg"}, true, "", false); reflect.DeepEqual(v, map[string]interface{}{"-v": true, "A": "arg"}) != true {
+ t.Error(err)
+ }
+
+ doc = `Usage: prog [-vqr] [FILE]
+ prog INPUT OUTPUT
+ prog --help
+
+ Options:
+ -v print status messages
+ -q report only file names
+ -r show all occurrences of the same error
+ --help
+
+ `
+ if v, _, err := Parse(doc, []string{"-v", "file.py"}, true, "", false); reflect.DeepEqual(v, map[string]interface{}{"-v": true, "-q": false, "-r": false, "--help": false, "FILE": "file.py", "INPUT": nil, "OUTPUT": nil}) != true {
+ t.Error(err)
+ }
+ if v, _, err := Parse(doc, []string{"-v"}, true, "", false); reflect.DeepEqual(v, map[string]interface{}{"-v": true, "-q": false, "-r": false, "--help": false, "FILE": nil, "INPUT": nil, "OUTPUT": nil}) != true {
+ t.Error(err)
+ }
+
+ _, _, err := Parse(doc, []string{"-v", "input.py", "output.py"}, true, "", false) // does not match
+ if _, ok := err.(*UserError); !ok {
+ t.Error(err)
+ }
+ _, _, err = Parse(doc, []string{"--fake"}, true, "", false)
+ if _, ok := err.(*UserError); !ok {
+ t.Error(err)
+ }
+ _, output, err := Parse(doc, []string{"--hel"}, true, "", false)
+ if err != nil || len(output) == 0 {
+ t.Error(err)
+ }
+}
+
+func TestLanguageErrors(t *testing.T) {
+ _, _, err := Parse("no usage with colon here", []string{}, true, "", false)
+ if _, ok := err.(*LanguageError); !ok {
+ t.Error(err)
+ }
+ _, _, err = Parse("usage: here \n\n and again usage: here", []string{}, true, "", false)
+ if _, ok := err.(*LanguageError); !ok {
+ t.Error(err)
+ }
+}
+
+func TestIssue40(t *testing.T) {
+ _, output, err := Parse("usage: prog --help-commands | --help", []string{"--help"}, true, "", false)
+ if err != nil || len(output) == 0 {
+ t.Error(err)
+ }
+ if v, _, err := Parse("usage: prog --aabb | --aa", []string{"--aa"}, true, "", false); reflect.DeepEqual(v, map[string]interface{}{"--aabb": false, "--aa": true}) != true {
+ t.Error(err)
+ }
+}
+
+func TestIssue34UnicodeStrings(t *testing.T) {
+ // TODO: see if applicable
+}
+
+func TestCountMultipleFlags(t *testing.T) {
+ if v, _, err := Parse("usage: prog [-v]", []string{"-v"}, true, "", false); reflect.DeepEqual(v, map[string]interface{}{"-v": true}) != true {
+ t.Error(err)
+ }
+ if v, _, err := Parse("usage: prog [-vv]", nil, true, "", false); reflect.DeepEqual(v, map[string]interface{}{"-v": 0}) != true {
+ t.Error(err)
+ }
+ if v, _, err := Parse("usage: prog [-vv]", []string{"-v"}, true, "", false); reflect.DeepEqual(v, map[string]interface{}{"-v": 1}) != true {
+ t.Error(err)
+ }
+ if v, _, err := Parse("usage: prog [-vv]", []string{"-vv"}, true, "", false); reflect.DeepEqual(v, map[string]interface{}{"-v": 2}) != true {
+ t.Error(err)
+ }
+ _, _, err := Parse("usage: prog [-vv]", []string{"-vvv"}, true, "", false)
+ if _, ok := err.(*UserError); !ok {
+ t.Error(err)
+ }
+ if v, _, err := Parse("usage: prog [-v | -vv | -vvv]", []string{"-vvv"}, true, "", false); reflect.DeepEqual(v, map[string]interface{}{"-v": 3}) != true {
+ t.Error(err)
+ }
+ if v, _, err := Parse("usage: prog [-v...]", []string{"-vvvvvv"}, true, "", false); reflect.DeepEqual(v, map[string]interface{}{"-v": 6}) != true {
+ t.Error(err)
+ }
+ if v, _, err := Parse("usage: prog [--ver --ver]", []string{"--ver", "--ver"}, true, "", false); reflect.DeepEqual(v, map[string]interface{}{"--ver": 2}) != true {
+ t.Error(err)
+ }
+}
+
+func TestAnyOptionsParameter(t *testing.T) {
+ _, _, err := Parse("usage: prog [options]",
+ []string{"-foo", "--bar", "--spam=eggs"}, true, "", false)
+ if _, ok := err.(*UserError); !ok {
+ t.Error()
+ }
+
+ _, _, err = Parse("usage: prog [options]",
+ []string{"--foo", "--bar", "--bar"}, true, "", false)
+ if _, ok := err.(*UserError); !ok {
+ t.Error()
+ }
+ _, _, err = Parse("usage: prog [options]",
+ []string{"--bar", "--bar", "--bar", "-ffff"}, true, "", false)
+ if _, ok := err.(*UserError); !ok {
+ t.Error()
+ }
+ _, _, err = Parse("usage: prog [options]",
+ []string{"--long=arg", "--long=another"}, true, "", false)
+ if _, ok := err.(*UserError); !ok {
+ t.Error()
+ }
+}
+
+func TestDefaultValueForPositionalArguments(t *testing.T) {
+ if v, _, err := Parse("usage: prog [<p>]\n\n<p> [default: x]", []string{}, true, "", false); reflect.DeepEqual(v, map[string]interface{}{"<p>": nil}) != true {
+ t.Error(err)
+ }
+
+ if v, _, err := Parse("usage: prog [<p>]...\n\n<p> [default: x y]", []string{}, true, "", false); reflect.DeepEqual(v, map[string]interface{}{"<p>": []string{}}) != true {
+ t.Error(err)
+ }
+
+ if v, _, err := Parse("usage: prog [<p>]...\n\n<p> [default: x y]", []string{"this"}, true, "", false); reflect.DeepEqual(v, map[string]interface{}{"<p>": []string{"this"}}) != true {
+ t.Error(err)
+ }
+}
+
+func TestIssue59(t *testing.T) {
+ if v, _, err := Parse("usage: prog --long=<a>", []string{"--long="}, true, "", false); reflect.DeepEqual(v, map[string]interface{}{"--long": ""}) != true {
+ t.Error(err)
+ }
+
+ if v, _, err := Parse("usage: prog -l <a>\noptions: -l <a>", []string{"-l", ""}, true, "", false); reflect.DeepEqual(v, map[string]interface{}{"-l": ""}) != true {
+ t.Error(err)
+ }
+}
+
+func TestOptionsFirst(t *testing.T) {
+ if v, _, err := Parse("usage: prog [--opt] [<args>...]", []string{"--opt", "this", "that"}, true, "", false); reflect.DeepEqual(v, map[string]interface{}{"--opt": true, "<args>": []string{"this", "that"}}) != true {
+ t.Error(err)
+ }
+
+ if v, _, err := Parse("usage: prog [--opt] [<args>...]", []string{"this", "that", "--opt"}, true, "", false); reflect.DeepEqual(v, map[string]interface{}{"--opt": true, "<args>": []string{"this", "that"}}) != true {
+ t.Error(err)
+ }
+
+ if v, _, err := Parse("usage: prog [--opt] [<args>...]", []string{"this", "that", "--opt"}, true, "", true); reflect.DeepEqual(v, map[string]interface{}{"--opt": false, "<args>": []string{"this", "that", "--opt"}}) != true {
+ t.Error(err)
+ }
+}
+
+func TestIssue68OptionsShortcutDoesNotIncludeOptionsInUsagePattern(t *testing.T) {
+ args, _, err := Parse("usage: prog [-ab] [options]\noptions: -x\n -y", []string{"-ax"}, true, "", false)
+
+ if args["-a"] != true {
+ t.Error(err)
+ }
+ if args["-b"] != false {
+ t.Error(err)
+ }
+ if args["-x"] != true {
+ t.Error(err)
+ }
+ if args["-y"] != false {
+ t.Error(err)
+ }
+}
+
+func TestIssue65EvaluateArgvWhenCalledNotWhenImported(t *testing.T) {
+ os.Args = strings.Fields("prog -a")
+ v, _, err := Parse("usage: prog [-ab]", nil, true, "", false)
+ w := map[string]interface{}{"-a": true, "-b": false}
+ if reflect.DeepEqual(v, w) != true {
+ t.Error(err)
+ }
+
+ os.Args = strings.Fields("prog -b")
+ v, _, err = Parse("usage: prog [-ab]", nil, true, "", false)
+ w = map[string]interface{}{"-a": false, "-b": true}
+ if reflect.DeepEqual(v, w) != true {
+ t.Error(err)
+ }
+}
+
+func TestIssue71DoubleDashIsNotAValidOptionArgument(t *testing.T) {
+ _, _, err := Parse("usage: prog [--log=LEVEL] [--] <args>...",
+ []string{"--log", "--", "1", "2"}, true, "", false)
+ if _, ok := err.(*UserError); !ok {
+ t.Error()
+ }
+
+ _, _, err = Parse(`usage: prog [-l LEVEL] [--] <args>...
+ options: -l LEVEL`, []string{"-l", "--", "1", "2"}, true, "", false)
+ if _, ok := err.(*UserError); !ok {
+ t.Error()
+ }
+}
+
+func TestParseSection(t *testing.T) {
+ v := parseSection("usage:", "foo bar fizz buzz")
+ w := []string{}
+ if reflect.DeepEqual(v, w) != true {
+ t.Error()
+ }
+
+ v = parseSection("usage:", "usage: prog")
+ w = []string{"usage: prog"}
+ if reflect.DeepEqual(v, w) != true {
+ t.Error()
+ }
+
+ v = parseSection("usage:", "usage: -x\n -y")
+ w = []string{"usage: -x\n -y"}
+ if reflect.DeepEqual(v, w) != true {
+ t.Error()
+ }
+
+ usage := `usage: this
+
+usage:hai
+usage: this that
+
+usage: foo
+ bar
+
+PROGRAM USAGE:
+ foo
+ bar
+usage:
+` + "\t" + `too
+` + "\t" + `tar
+Usage: eggs spam
+BAZZ
+usage: pit stop`
+
+ v = parseSection("usage:", usage)
+ w = []string{"usage: this",
+ "usage:hai",
+ "usage: this that",
+ "usage: foo\n bar",
+ "PROGRAM USAGE:\n foo\n bar",
+ "usage:\n\ttoo\n\ttar",
+ "Usage: eggs spam",
+ "usage: pit stop",
+ }
+ if reflect.DeepEqual(v, w) != true {
+ t.Error()
+ }
+}
+
+func TestIssue126DefaultsNotParsedCorrectlyWhenTabs(t *testing.T) {
+ section := "Options:\n\t--foo=<arg> [default: bar]"
+ v := patternList{newOption("", "--foo", 1, "bar")}
+ if reflect.DeepEqual(parseDefaults(section), v) != true {
+ t.Error()
+ }
+}
+
+// conf file based test cases
+func TestFileTestcases(t *testing.T) {
+ filename := "testcases.docopt"
+ file, err := os.Open(filename)
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer file.Close()
+
+ for c := range parseTest(file) {
+ if c.err != nil {
+ t.Fatal(err)
+ break
+ }
+ result, _, err := Parse(c.doc, c.argv, true, "", false)
+ if _, ok := err.(*UserError); c.userError && !ok {
+ // expected a user-error
+ t.Error("testcase:", c.id, "result:", result)
+ } else if _, ok := err.(*UserError); !c.userError && ok {
+ // unexpected user-error
+ t.Error("testcase:", c.id, "error:", err, "result:", result)
+ } else if reflect.DeepEqual(c.expect, result) != true {
+ t.Error("testcase:", c.id, "result:", result)
+ }
+ }
+}
+
+type testcase struct {
+ id int
+ doc string
+ prog string
+ argv []string
+ expect map[string]interface{}
+ userError bool
+ err error
+}
+
+func parseTest(raw io.Reader) <-chan testcase {
+ c := make(chan testcase)
+
+ go func() {
+ scanner := bufio.NewScanner(raw)
+ commentPattern := regexp.MustCompile("#.*($|\n)")
+ scanner.Split(func(data []byte, atEOF bool) (advance int, token []byte, err error) {
+ // look for matching `r"""` or `r"""`+EOF
+ sep := []byte(`r"""`)
+ count := bytes.Count(data, sep)
+ start := bytes.Index(data, sep)
+ end := len(data)
+
+ if atEOF && count == 0 {
+ // no more matches, so consume everything
+ return end, nil, nil
+ }
+ if atEOF && count == 1 {
+ // already matched up to end
+ } else if count >= 2 {
+ end = start + len(sep) + bytes.Index(data[start+len(sep):], sep)
+ } else {
+ // haven't found a match yet so ask for more data
+ return 0, nil, nil
+ }
+ token = data[start:end]
+ token = commentPattern.ReplaceAllLiteral(token, []byte(""))
+ return end, token, nil
+ })
+
+ for id := 0; scanner.Scan(); {
+ data := scanner.Bytes()
+
+ offset := 4
+ end := offset + bytes.Index(data[offset:], []byte(`"""`))
+ doc := string(data[offset:end])
+ offset = end + 3
+
+ for offset < len(data) {
+ offset = offset + bytes.Index(data[offset:], []byte(`$ `)) + 2
+ end = offset + bytes.Index(data[offset:], []byte("\n"))
+ prog, _, args := stringPartition(string(data[offset:end]), " ")
+ argv := strings.Fields(args)
+ offset = end + 1
+
+ if bytes.Contains(data[offset:], []byte("$ ")) {
+ end = offset + bytes.Index(data[offset:], []byte("$ "))
+ } else {
+ end = len(data)
+ }
+ var expectUntyped interface{}
+ err := json.Unmarshal(data[offset:end], &expectUntyped)
+ offset = end
+
+ if err != nil {
+ c <- testcase{id, doc, prog, argv, nil, false, err}
+ break
+ }
+
+ switch expect := expectUntyped.(type) {
+ case string: // user-error
+ c <- testcase{id, doc, prog, argv, nil, true, nil}
+ case map[string]interface{}:
+ // convert []interface{} values to []string
+ // convert float64 values to int
+ for k, vUntyped := range expect {
+ switch v := vUntyped.(type) {
+ case []interface{}:
+ itemList := make([]string, len(v))
+ for i, itemUntyped := range v {
+ if item, ok := itemUntyped.(string); ok {
+ itemList[i] = item
+ }
+ }
+ expect[k] = itemList
+ case float64:
+ expect[k] = int(v)
+ }
+ }
+ c <- testcase{id, doc, prog, argv, expect, false, nil}
+ }
+ id++
+ }
+ }
+ close(c)
+ }()
+ return c
+}
+
+var debugEnabled = false
+
+func debugOn(l ...interface{}) {
+ debugEnabled = true
+ debug(l...)
+}
+func debugOff(l ...interface{}) {
+ debug(l...)
+ debugEnabled = false
+}
+
+func debug(l ...interface{}) {
+ if debugEnabled {
+ fmt.Println(l...)
+ }
+}
diff --git a/testcases.docopt b/testcases.docopt
new file mode 100644
index 0000000..efe9a07
--- /dev/null
+++ b/testcases.docopt
@@ -0,0 +1,957 @@
+r"""Usage: prog
+
+"""
+$ prog
+{}
+
+$ prog --xxx
+"user-error"
+
+
+r"""Usage: prog [options]
+
+Options: -a All.
+
+"""
+$ prog
+{"-a": false}
+
+$ prog -a
+{"-a": true}
+
+$ prog -x
+"user-error"
+
+
+r"""Usage: prog [options]
+
+Options: --all All.
+
+"""
+$ prog
+{"--all": false}
+
+$ prog --all
+{"--all": true}
+
+$ prog --xxx
+"user-error"
+
+
+r"""Usage: prog [options]
+
+Options: -v, --verbose Verbose.
+
+"""
+$ prog --verbose
+{"--verbose": true}
+
+$ prog --ver
+{"--verbose": true}
+
+$ prog -v
+{"--verbose": true}
+
+
+r"""Usage: prog [options]
+
+Options: -p PATH
+
+"""
+$ prog -p home/
+{"-p": "home/"}
+
+$ prog -phome/
+{"-p": "home/"}
+
+$ prog -p
+"user-error"
+
+
+r"""Usage: prog [options]
+
+Options: --path <path>
+
+"""
+$ prog --path home/
+{"--path": "home/"}
+
+$ prog --path=home/
+{"--path": "home/"}
+
+$ prog --pa home/
+{"--path": "home/"}
+
+$ prog --pa=home/
+{"--path": "home/"}
+
+$ prog --path
+"user-error"
+
+
+r"""Usage: prog [options]
+
+Options: -p PATH, --path=<path> Path to files.
+
+"""
+$ prog -proot
+{"--path": "root"}
+
+
+r"""Usage: prog [options]
+
+Options: -p --path PATH Path to files.
+
+"""
+$ prog -p root
+{"--path": "root"}
+
+$ prog --path root
+{"--path": "root"}
+
+
+r"""Usage: prog [options]
+
+Options:
+ -p PATH Path to files [default: ./]
+
+"""
+$ prog
+{"-p": "./"}
+
+$ prog -phome
+{"-p": "home"}
+
+
+r"""UsAgE: prog [options]
+
+OpTiOnS: --path=<files> Path to files
+ [dEfAuLt: /root]
+
+"""
+$ prog
+{"--path": "/root"}
+
+$ prog --path=home
+{"--path": "home"}
+
+
+r"""usage: prog [options]
+
+options:
+ -a Add
+ -r Remote
+ -m <msg> Message
+
+"""
+$ prog -a -r -m Hello
+{"-a": true,
+ "-r": true,
+ "-m": "Hello"}
+
+$ prog -armyourass
+{"-a": true,
+ "-r": true,
+ "-m": "yourass"}
+
+$ prog -a -r
+{"-a": true,
+ "-r": true,
+ "-m": null}
+
+
+r"""Usage: prog [options]
+
+Options: --version
+ --verbose
+
+"""
+$ prog --version
+{"--version": true,
+ "--verbose": false}
+
+$ prog --verbose
+{"--version": false,
+ "--verbose": true}
+
+$ prog --ver
+"user-error"
+
+$ prog --verb
+{"--version": false,
+ "--verbose": true}
+
+
+r"""usage: prog [-a -r -m <msg>]
+
+options:
+ -a Add
+ -r Remote
+ -m <msg> Message
+
+"""
+$ prog -armyourass
+{"-a": true,
+ "-r": true,
+ "-m": "yourass"}
+
+
+r"""usage: prog [-armmsg]
+
+options: -a Add
+ -r Remote
+ -m <msg> Message
+
+"""
+$ prog -a -r -m Hello
+{"-a": true,
+ "-r": true,
+ "-m": "Hello"}
+
+
+r"""usage: prog -a -b
+
+options:
+ -a
+ -b
+
+"""
+$ prog -a -b
+{"-a": true, "-b": true}
+
+$ prog -b -a
+{"-a": true, "-b": true}
+
+$ prog -a
+"user-error"
+
+$ prog
+"user-error"
+
+
+r"""usage: prog (-a -b)
+
+options: -a
+ -b
+
+"""
+$ prog -a -b
+{"-a": true, "-b": true}
+
+$ prog -b -a
+{"-a": true, "-b": true}
+
+$ prog -a
+"user-error"
+
+$ prog
+"user-error"
+
+
+r"""usage: prog [-a] -b
+
+options: -a
+ -b
+
+"""
+$ prog -a -b
+{"-a": true, "-b": true}
+
+$ prog -b -a
+{"-a": true, "-b": true}
+
+$ prog -a
+"user-error"
+
+$ prog -b
+{"-a": false, "-b": true}
+
+$ prog
+"user-error"
+
+
+r"""usage: prog [(-a -b)]
+
+options: -a
+ -b
+
+"""
+$ prog -a -b
+{"-a": true, "-b": true}
+
+$ prog -b -a
+{"-a": true, "-b": true}
+
+$ prog -a
+"user-error"
+
+$ prog -b
+"user-error"
+
+$ prog
+{"-a": false, "-b": false}
+
+
+r"""usage: prog (-a|-b)
+
+options: -a
+ -b
+
+"""
+$ prog -a -b
+"user-error"
+
+$ prog
+"user-error"
+
+$ prog -a
+{"-a": true, "-b": false}
+
+$ prog -b
+{"-a": false, "-b": true}
+
+
+r"""usage: prog [ -a | -b ]
+
+options: -a
+ -b
+
+"""
+$ prog -a -b
+"user-error"
+
+$ prog
+{"-a": false, "-b": false}
+
+$ prog -a
+{"-a": true, "-b": false}
+
+$ prog -b
+{"-a": false, "-b": true}
+
+
+r"""usage: prog <arg>"""
+$ prog 10
+{"<arg>": "10"}
+
+$ prog 10 20
+"user-error"
+
+$ prog
+"user-error"
+
+
+r"""usage: prog [<arg>]"""
+$ prog 10
+{"<arg>": "10"}
+
+$ prog 10 20
+"user-error"
+
+$ prog
+{"<arg>": null}
+
+
+r"""usage: prog <kind> <name> <type>"""
+$ prog 10 20 40
+{"<kind>": "10", "<name>": "20", "<type>": "40"}
+
+$ prog 10 20
+"user-error"
+
+$ prog
+"user-error"
+
+
+r"""usage: prog <kind> [<name> <type>]"""
+$ prog 10 20 40
+{"<kind>": "10", "<name>": "20", "<type>": "40"}
+
+$ prog 10 20
+{"<kind>": "10", "<name>": "20", "<type>": null}
+
+$ prog
+"user-error"
+
+
+r"""usage: prog [<kind> | <name> <type>]"""
+$ prog 10 20 40
+"user-error"
+
+$ prog 20 40
+{"<kind>": null, "<name>": "20", "<type>": "40"}
+
+$ prog
+{"<kind>": null, "<name>": null, "<type>": null}
+
+
+r"""usage: prog (<kind> --all | <name>)
+
+options:
+ --all
+
+"""
+$ prog 10 --all
+{"<kind>": "10", "--all": true, "<name>": null}
+
+$ prog 10
+{"<kind>": null, "--all": false, "<name>": "10"}
+
+$ prog
+"user-error"
+
+
+r"""usage: prog [<name> <name>]"""
+$ prog 10 20
+{"<name>": ["10", "20"]}
+
+$ prog 10
+{"<name>": ["10"]}
+
+$ prog
+{"<name>": []}
+
+
+r"""usage: prog [(<name> <name>)]"""
+$ prog 10 20
+{"<name>": ["10", "20"]}
+
+$ prog 10
+"user-error"
+
+$ prog
+{"<name>": []}
+
+
+r"""usage: prog NAME..."""
+$ prog 10 20
+{"NAME": ["10", "20"]}
+
+$ prog 10
+{"NAME": ["10"]}
+
+$ prog
+"user-error"
+
+
+r"""usage: prog [NAME]..."""
+$ prog 10 20
+{"NAME": ["10", "20"]}
+
+$ prog 10
+{"NAME": ["10"]}
+
+$ prog
+{"NAME": []}
+
+
+r"""usage: prog [NAME...]"""
+$ prog 10 20
+{"NAME": ["10", "20"]}
+
+$ prog 10
+{"NAME": ["10"]}
+
+$ prog
+{"NAME": []}
+
+
+r"""usage: prog [NAME [NAME ...]]"""
+$ prog 10 20
+{"NAME": ["10", "20"]}
+
+$ prog 10
+{"NAME": ["10"]}
+
+$ prog
+{"NAME": []}
+
+
+r"""usage: prog (NAME | --foo NAME)
+
+options: --foo
+
+"""
+$ prog 10
+{"NAME": "10", "--foo": false}
+
+$ prog --foo 10
+{"NAME": "10", "--foo": true}
+
+$ prog --foo=10
+"user-error"
+
+
+r"""usage: prog (NAME | --foo) [--bar | NAME]
+
+options: --foo
+options: --bar
+
+"""
+$ prog 10
+{"NAME": ["10"], "--foo": false, "--bar": false}
+
+$ prog 10 20
+{"NAME": ["10", "20"], "--foo": false, "--bar": false}
+
+$ prog --foo --bar
+{"NAME": [], "--foo": true, "--bar": true}
+
+
+r"""Naval Fate.
+
+Usage:
+ prog ship new <name>...
+ prog ship [<name>] move <x> <y> [--speed=<kn>]
+ prog ship shoot <x> <y>
+ prog mine (set|remove) <x> <y> [--moored|--drifting]
+ prog -h | --help
+ prog --version
+
+Options:
+ -h --help Show this screen.
+ --version Show version.
+ --speed=<kn> Speed in knots [default: 10].
+ --moored Mored (anchored) mine.
+ --drifting Drifting mine.
+
+"""
+$ prog ship Guardian move 150 300 --speed=20
+{"--drifting": false,
+ "--help": false,
+ "--moored": false,
+ "--speed": "20",
+ "--version": false,
+ "<name>": ["Guardian"],
+ "<x>": "150",
+ "<y>": "300",
+ "mine": false,
+ "move": true,
+ "new": false,
+ "remove": false,
+ "set": false,
+ "ship": true,
+ "shoot": false}
+
+
+r"""usage: prog --hello"""
+$ prog --hello
+{"--hello": true}
+
+
+r"""usage: prog [--hello=<world>]"""
+$ prog
+{"--hello": null}
+
+$ prog --hello wrld
+{"--hello": "wrld"}
+
+
+r"""usage: prog [-o]"""
+$ prog
+{"-o": false}
+
+$ prog -o
+{"-o": true}
+
+
+r"""usage: prog [-opr]"""
+$ prog -op
+{"-o": true, "-p": true, "-r": false}
+
+
+r"""usage: prog --aabb | --aa"""
+$ prog --aa
+{"--aabb": false, "--aa": true}
+
+$ prog --a
+"user-error" # not a unique prefix
+
+#
+# Counting number of flags
+#
+
+r"""Usage: prog -v"""
+$ prog -v
+{"-v": true}
+
+
+r"""Usage: prog [-v -v]"""
+$ prog
+{"-v": 0}
+
+$ prog -v
+{"-v": 1}
+
+$ prog -vv
+{"-v": 2}
+
+
+r"""Usage: prog -v ..."""
+$ prog
+"user-error"
+
+$ prog -v
+{"-v": 1}
+
+$ prog -vv
+{"-v": 2}
+
+$ prog -vvvvvv
+{"-v": 6}
+
+
+r"""Usage: prog [-v | -vv | -vvv]
+
+This one is probably most readable user-friednly variant.
+
+"""
+$ prog
+{"-v": 0}
+
+$ prog -v
+{"-v": 1}
+
+$ prog -vv
+{"-v": 2}
+
+$ prog -vvvv
+"user-error"
+
+
+r"""usage: prog [--ver --ver]"""
+$ prog --ver --ver
+{"--ver": 2}
+
+
+#
+# Counting commands
+#
+
+r"""usage: prog [go]"""
+$ prog go
+{"go": true}
+
+
+r"""usage: prog [go go]"""
+$ prog
+{"go": 0}
+
+$ prog go
+{"go": 1}
+
+$ prog go go
+{"go": 2}
+
+$ prog go go go
+"user-error"
+
+r"""usage: prog go..."""
+$ prog go go go go go
+{"go": 5}
+
+#
+# [options] does not include options from usage-pattern
+#
+r"""usage: prog [options] [-a]
+
+options: -a
+ -b
+"""
+$ prog -a
+{"-a": true, "-b": false}
+
+$ prog -aa
+"user-error"
+
+#
+# Test [options] shourtcut
+#
+
+r"""Usage: prog [options] A
+Options:
+ -q Be quiet
+ -v Be verbose.
+
+"""
+$ prog arg
+{"A": "arg", "-v": false, "-q": false}
+
+$ prog -v arg
+{"A": "arg", "-v": true, "-q": false}
+
+$ prog -q arg
+{"A": "arg", "-v": false, "-q": true}
+
+#
+# Test single dash
+#
+
+r"""usage: prog [-]"""
+
+$ prog -
+{"-": true}
+
+$ prog
+{"-": false}
+
+#
+# If argument is repeated, its value should always be a list
+#
+
+r"""usage: prog [NAME [NAME ...]]"""
+
+$ prog a b
+{"NAME": ["a", "b"]}
+
+$ prog
+{"NAME": []}
+
+#
+# Option's argument defaults to null/None
+#
+
+r"""usage: prog [options]
+options:
+ -a Add
+ -m <msg> Message
+
+"""
+$ prog -a
+{"-m": null, "-a": true}
+
+#
+# Test options without description
+#
+
+r"""usage: prog --hello"""
+$ prog --hello
+{"--hello": true}
+
+r"""usage: prog [--hello=<world>]"""
+$ prog
+{"--hello": null}
+
+$ prog --hello wrld
+{"--hello": "wrld"}
+
+r"""usage: prog [-o]"""
+$ prog
+{"-o": false}
+
+$ prog -o
+{"-o": true}
+
+r"""usage: prog [-opr]"""
+$ prog -op
+{"-o": true, "-p": true, "-r": false}
+
+r"""usage: git [-v | --verbose]"""
+$ prog -v
+{"-v": true, "--verbose": false}
+
+r"""usage: git remote [-v | --verbose]"""
+$ prog remote -v
+{"remote": true, "-v": true, "--verbose": false}
+
+#
+# Test empty usage pattern
+#
+
+r"""usage: prog"""
+$ prog
+{}
+
+r"""usage: prog
+ prog <a> <b>
+"""
+$ prog 1 2
+{"<a>": "1", "<b>": "2"}
+
+$ prog
+{"<a>": null, "<b>": null}
+
+r"""usage: prog <a> <b>
+ prog
+"""
+$ prog
+{"<a>": null, "<b>": null}
+
+#
+# Option's argument should not capture default value from usage pattern
+#
+
+r"""usage: prog [--file=<f>]"""
+$ prog
+{"--file": null}
+
+r"""usage: prog [--file=<f>]
+
+options: --file <a>
+
+"""
+$ prog
+{"--file": null}
+
+r"""Usage: prog [-a <host:port>]
+
+Options: -a, --address <host:port> TCP address [default: localhost:6283].
+
+"""
+$ prog
+{"--address": "localhost:6283"}
+
+#
+# If option with argument could be repeated,
+# its arguments should be accumulated into a list
+#
+
+r"""usage: prog --long=<arg> ..."""
+
+$ prog --long one
+{"--long": ["one"]}
+
+$ prog --long one --long two
+{"--long": ["one", "two"]}
+
+#
+# Test multiple elements repeated at once
+#
+
+r"""usage: prog (go <direction> --speed=<km/h>)..."""
+$ prog go left --speed=5 go right --speed=9
+{"go": 2, "<direction>": ["left", "right"], "--speed": ["5", "9"]}
+
+#
+# Required options should work with option shortcut
+#
+
+r"""usage: prog [options] -a
+
+options: -a
+
+"""
+$ prog -a
+{"-a": true}
+
+#
+# If option could be repeated its defaults should be split into a list
+#
+
+r"""usage: prog [-o <o>]...
+
+options: -o <o> [default: x]
+
+"""
+$ prog -o this -o that
+{"-o": ["this", "that"]}
+
+$ prog
+{"-o": ["x"]}
+
+r"""usage: prog [-o <o>]...
+
+options: -o <o> [default: x y]
+
+"""
+$ prog -o this
+{"-o": ["this"]}
+
+$ prog
+{"-o": ["x", "y"]}
+
+#
+# Test stacked option's argument
+#
+
+r"""usage: prog -pPATH
+
+options: -p PATH
+
+"""
+$ prog -pHOME
+{"-p": "HOME"}
+
+#
+# Issue 56: Repeated mutually exclusive args give nested lists sometimes
+#
+
+r"""Usage: foo (--xx=x|--yy=y)..."""
+$ prog --xx=1 --yy=2
+{"--xx": ["1"], "--yy": ["2"]}
+
+#
+# POSIXly correct tokenization
+#
+
+r"""usage: prog [<input file>]"""
+$ prog f.txt
+{"<input file>": "f.txt"}
+
+r"""usage: prog [--input=<file name>]..."""
+$ prog --input a.txt --input=b.txt
+{"--input": ["a.txt", "b.txt"]}
+
+#
+# Issue 85: `[options]` shourtcut with multiple subcommands
+#
+
+r"""usage: prog good [options]
+ prog fail [options]
+
+options: --loglevel=N
+
+"""
+$ prog fail --loglevel 5
+{"--loglevel": "5", "fail": true, "good": false}
+
+#
+# Usage-section syntax
+#
+
+r"""usage:prog --foo"""
+$ prog --foo
+{"--foo": true}
+
+r"""PROGRAM USAGE: prog --foo"""
+$ prog --foo
+{"--foo": true}
+
+r"""Usage: prog --foo
+ prog --bar
+NOT PART OF SECTION"""
+$ prog --foo
+{"--foo": true, "--bar": false}
+
+r"""Usage:
+ prog --foo
+ prog --bar
+
+NOT PART OF SECTION"""
+$ prog --foo
+{"--foo": true, "--bar": false}
+
+r"""Usage:
+ prog --foo
+ prog --bar
+NOT PART OF SECTION"""
+$ prog --foo
+{"--foo": true, "--bar": false}
+
+#
+# Options-section syntax
+#
+
+r"""Usage: prog [options]
+
+global options: --foo
+local options: --baz
+ --bar
+other options:
+ --egg
+ --spam
+-not-an-option-
+
+"""
+$ prog --baz --egg
+{"--foo": false, "--baz": true, "--bar": false, "--egg": true, "--spam": false}