123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239 |
- // 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
- }
|