// Licensed under terms of MIT license (see LICENSE-MIT) // Copyright (c) 2013 Keith Batten, kbatten@gmail.com /* Package docopt parses command-line arguments based on a help message. ⚠ Use the alias “docopt-go”: import "github.com/docopt/docopt-go" or $ go get github.com/docopt/docopt-go */ package docopt import ( "fmt" "os" "reflect" "regexp" "strings" "unicode" ) /* Parse `argv` based on the command-line interface described in `doc`. Given a conventional command-line help message, docopt creates a parser and processes the arguments. See https://github.com/docopt/docopt#help-message-format for a description of the help message format. If `argv` is `nil`, `os.Args[1:]` is used. docopt returns a map of option names to the values parsed from `argv`, and an error or `nil`. Set `help` to `false` to disable automatic help messages on `-h` or `--help`. If `version` is a non-empty string, it will be printed when `--version` is specified. Set `optionsFirst` to `true` to require that options always come before positional arguments; otherwise they can overlap. By default, docopt calls `os.Exit(0)` if it handled a built-in option such as `-h` or `--version`. If the user errored with a wrong command or options, docopt exits with a return code of 1. To stop docopt from calling `os.Exit()` and to handle your own return codes, pass an optional last parameter of `false` for `exit`. */ func Parse(doc string, argv []string, help bool, version string, optionsFirst bool, exit ...bool) (map[string]interface{}, error) { // if "false" was the (optional) last arg, don't call os.Exit() exitOk := true if len(exit) > 0 { exitOk = exit[0] } args, output, err := parse(doc, argv, help, version, optionsFirst) if _, ok := err.(*UserError); ok { // the user gave us bad input fmt.Fprintln(os.Stderr, output) if exitOk { os.Exit(1) } } else if len(output) > 0 && err == nil { // the user asked for help or `--version` fmt.Println(output) if exitOk { os.Exit(0) } } return args, err } // 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) formal, err := formalUsage(usage) if err != nil { output = handleError(err, usage) return } pat, err := parsePattern(formal, &options) if err != nil { output = handleError(err, usage) return } patternArgv, err := parseArgv(newTokenList(argv, errorUser), &options, optionsFirst) if err != nil { output = handleError(err, usage) return } patFlat, err := pat.flat(patternOption) if err != nil { output = handleError(err, usage) return } patternOptions := patFlat.unique() patFlat, err = pat.flat(patternOptionSSHORTCUT) 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(patternDefault) if err != nil { output = handleError(err, usage) return } args = append(patFlat, *collected...).dictionary() return } err = newUserError("") output = handleError(err, usage) return } 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 == errorUser && 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 == errorUser { 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 == errorUser { 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 == errorUser { 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 == errorUser { 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 == errorUser { errorFunc = newUserError } else if err == errorLanguage { errorFunc = newLanguageError } return &tokenList{source, errorFunc, err} } func tokenListFromString(source string) *tokenList { return newTokenList(strings.Fields(source), errorUser) } 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, errorLanguage) } func formalUsage(section string) (string, error) { _, _, section = stringPartition(section, ":") // drop "usage:" pu := strings.Fields(section) if len(pu) == 0 { return "", newLanguageError("no fields found in usage (perhaps a spacing error).") } result := "( " for _, s := range pu[1:] { if s == pu[0] { result += ") | ( " } else { result += s + " " } } result += ")" return result, nil } 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 ( errorUser errorType = iota errorLanguage ) func (e errorType) String() string { switch e { case errorUser: return "errorUser" case errorLanguage: return "errorLanguage" } return "" } // UserError records an error with program arguments. 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...), ""} } // LanguageError records an error with the doc string. 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 (t *token) eq(s string) bool { if t == nil { return false } return string(*t) == s } func (t *token) match(matchNil bool, tokenStrings ...string) bool { if t == nil && matchNil { return true } else if t == nil && !matchNil { return false } for _, tok := range tokenStrings { if tok == string(*t) { return true } } return false } func (t *token) hasPrefix(prefix string) bool { if t == nil { return false } return strings.HasPrefix(string(*t), prefix) } func (t *token) hasSuffix(suffix string) bool { if t == nil { return false } return strings.HasSuffix(string(*t), suffix) } func (t *token) isUpper() bool { if t == nil { return false } return isStringUppercase(string(*t)) } func (t *token) String() string { if t == nil { return "" } return string(*t) } func (tl *tokenList) current() *token { if len(tl.tokens) > 0 { return (*token)(&(tl.tokens[0])) } return nil } func (tl *tokenList) length() int { return len(tl.tokens) } func (tl *tokenList) move() *token { if len(tl.tokens) > 0 { t := tl.tokens[0] tl.tokens = tl.tokens[1:] return (*token)(&t) } return nil } type patternType uint const ( // leaf patternArgument patternType = 1 << iota patternCommand patternOption // branch patternRequired patternOptionAL patternOptionSSHORTCUT // Marker/placeholder for [options] shortcut. patternOneOrMore patternEither patternLeaf = patternArgument + patternCommand + patternOption patternBranch = patternRequired + patternOptionAL + patternOptionSSHORTCUT + patternOneOrMore + patternEither patternAll = patternLeaf + patternBranch patternDefault = 0 ) func (pt patternType) String() string { switch pt { case patternArgument: return "argument" case patternCommand: return "command" case patternOption: return "option" case patternRequired: return "required" case patternOptionAL: return "optional" case patternOptionSSHORTCUT: return "optionsshortcut" case patternOneOrMore: return "oneormore" case patternEither: return "either" case patternLeaf: return "leaf" case patternBranch: return "branch" case patternAll: return "all" case patternDefault: 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(patternRequired, pl...) } func newEither(pl ...*pattern) *pattern { return newBranchPattern(patternEither, pl...) } func newOneOrMore(pl ...*pattern) *pattern { return newBranchPattern(patternOneOrMore, pl...) } func newOptional(pl ...*pattern) *pattern { return newBranchPattern(patternOptionAL, pl...) } func newOptionsShortcut() *pattern { var p pattern p.t = patternOptionSSHORTCUT 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(patternArgument, name, value) } func newCommand(name string, value interface{}) *pattern { // default: value=false var p pattern p.t = patternCommand 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 = patternOption 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 (p *pattern) flat(types patternType) (patternList, error) { if p.t&patternLeaf != 0 { if types == patternDefault { types = patternAll } if p.t&types != 0 { return patternList{p}, nil } return patternList{}, nil } if p.t&patternBranch != 0 { if p.t&types != 0 { return patternList{p}, nil } result := patternList{} for _, child := range p.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", p.t, types) } func (p *pattern) fix() error { err := p.fixIdentities(nil) if err != nil { return err } p.fixRepeatingArguments() return nil } func (p *pattern) fixIdentities(uniq patternList) error { // Make pattern-tree tips point to same object if they are equal. if p.t&patternBranch == 0 { return nil } if uniq == nil { pFlat, err := p.flat(patternDefault) if err != nil { return err } uniq = pFlat.unique() } for i, child := range p.children { if child.t&patternBranch == 0 { ind, err := uniq.index(child) if err != nil { return err } p.children[i] = uniq[ind] } else { err := child.fixIdentities(uniq) if err != nil { return err } } } return nil } func (p *pattern) fixRepeatingArguments() { // Fix elements that should accumulate/increment values. var either []patternList for _, child := range p.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 == patternArgument || e.t == patternOption && 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 == patternCommand || e.t == patternOption && e.argcount == 0 { e.value = 0 } } } } func (p *pattern) match(left *patternList, collected *patternList) (bool, *patternList, *patternList) { if collected == nil { collected = &patternList{} } if p.t&patternRequired != 0 { l := left c := collected for _, p := range p.children { var matched bool matched, l, c = p.match(l, c) if !matched { return false, left, collected } } return true, l, c } else if p.t&patternOptionAL != 0 || p.t&patternOptionSSHORTCUT != 0 { for _, p := range p.children { _, left, collected = p.match(left, collected) } return true, left, collected } else if p.t&patternOneOrMore != 0 { if len(p.children) != 1 { panic("OneOrMore.match(): assert len(p.children) == 1") } l := left c := collected var lAlt *patternList matched := true times := 0 for matched { // could it be that something didn't match but changed l or c? matched, l, c = p.children[0].match(l, c) if matched { times++ } if lAlt == l { break } lAlt = l } if times >= 1 { return true, l, c } return false, left, collected } else if p.t&patternEither != 0 { type outcomeStruct struct { matched bool left *patternList collected *patternList length int } outcomes := []outcomeStruct{} for _, p := range p.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 p.t&patternLeaf != 0 { pos, match := p.singleMatch(left) var increment interface{} if match == nil { return false, left, collected } leftAlt := make(patternList, len((*left)[:pos]), len((*left)[:pos])+len((*left)[pos+1:])) copy(leftAlt, (*left)[:pos]) leftAlt = append(leftAlt, (*left)[pos+1:]...) sameName := patternList{} for _, a := range *collected { if a.name == p.name { sameName = append(sameName, a) } } switch p.value.(type) { case int, []string: switch p.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, &leftAlt, &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, &leftAlt, collected } collectedMatch := make(patternList, len(*collected), len(*collected)+1) copy(collectedMatch, *collected) collectedMatch = append(collectedMatch, match) return true, &leftAlt, &collectedMatch } panic("unmatched type") } func (p *pattern) singleMatch(left *patternList) (int, *pattern) { if p.t&patternArgument != 0 { for n, pat := range *left { if pat.t&patternArgument != 0 { return n, newArgument(p.name, pat.value) } } return -1, nil } else if p.t&patternCommand != 0 { for n, pat := range *left { if pat.t&patternArgument != 0 { if pat.value == p.name { return n, newCommand(p.name, true) } break } } return -1, nil } else if p.t&patternOption != 0 { for n, pat := range *left { if p.name == pat.name { return n, pat } } return -1, nil } panic("unmatched type") } func (p *pattern) String() string { if p.t&patternOption != 0 { return fmt.Sprintf("%s(%s, %s, %d, %+v)", p.t, p.short, p.long, p.argcount, p.value) } else if p.t&patternLeaf != 0 { return fmt.Sprintf("%s(%s, %+v)", p.t, p.name, p.value) } else if p.t&patternBranch != 0 { result := "" for i, child := range p.children { if i > 0 { result += ", " } result += child.String() } return fmt.Sprintf("%s(%s)", p.t, result) } panic("unmatched type") } func (p *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{p}} parents := patternRequired + patternOptionAL + patternOptionSSHORTCUT + patternEither + patternOneOrMore 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&patternEither != 0 { for _, c := range child.children { r := patternList{} r = append(r, c) r = append(r, children...) groups = append(groups, r) } } else if child.t&patternOneOrMore != 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 (p *pattern) eq(other *pattern) bool { return reflect.DeepEqual(p, 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 { lAlt := make(patternList, len(l)) copy(lAlt, l) result := make(patternList, 0, len(pl)) for _, v := range pl { if v != nil { match := false for i, w := range lAlt { if w.eq(v) { match = true lAlt[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 (pl *patternList) remove(p *pattern) { (*pl) = pl.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] } // returns true if all cased characters in the string are uppercase // and there are there is at least one cased charcter func isStringUppercase(s string) bool { if strings.ToUpper(s) != s { return false } for _, c := range []rune(s) { if unicode.IsUpper(c) { return true } } return false }