├── .gitignore ├── .travis.yml ├── LICENSE ├── README.md ├── docopt.go ├── docopt_test.go ├── example_test.go ├── examples ├── arguments_example.go ├── calculator_example.go ├── config_file_example.go ├── counted_example.go ├── git │ ├── git.go │ ├── git_add.go │ ├── git_branch.go │ ├── git_checkout.go │ ├── git_clone.go │ ├── git_push.go │ └── git_remote.go ├── naval_fate.go ├── odd_even_example.go ├── options_example.go ├── options_shortcut_example.go ├── quick_example.go └── type_assert_example.go ├── test_golang.docopt └── testcases.docopt /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled Object files, Static and Dynamic libs (Shared Objects) 2 | *.o 3 | *.a 4 | *.so 5 | 6 | # Folders 7 | _obj 8 | _test 9 | 10 | # Architecture specific extensions/prefixes 11 | *.[568vq] 12 | [568vq].out 13 | 14 | *.cgo1.go 15 | *.cgo2.c 16 | _cgo_defun.c 17 | _cgo_gotypes.go 18 | _cgo_export.* 19 | 20 | _testmain.go 21 | 22 | *.exe 23 | 24 | # coverage droppings 25 | profile.cov 26 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | # Travis CI (http://travis-ci.org/) is a continuous integration 2 | # service for open source projects. This file configures it 3 | # to run unit tests for docopt-go. 4 | 5 | language: go 6 | 7 | go: 8 | - 1.2.2 9 | - tip 10 | 11 | matrix: 12 | fast_finish: true 13 | 14 | before_install: 15 | - go get code.google.com/p/go.tools/cmd/vet 16 | - go get -v github.com/golang/lint/golint 17 | - go get -v code.google.com/p/go.tools/cmd/cover 18 | - go get -v github.com/mattn/goveralls 19 | 20 | install: 21 | - go get -d -v ./... && go build -v . 22 | 23 | script: 24 | - go vet -x ./... 25 | - $HOME/gopath/bin/golint . 26 | - go test -covermode=count -coverprofile=profile.cov -v . 27 | 28 | after_script: 29 | - $HOME/gopath/bin/goveralls -coverprofile=profile.cov -service=travis-ci 30 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2013 Keith Batten 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software is furnished to do so, 10 | subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | docopt-go 2 | ========= 3 | 4 | [![Build Status](https://travis-ci.org/docopt/docopt.go.svg?branch=master)](https://travis-ci.org/docopt/docopt.go) 5 | [![Coverage Status](https://coveralls.io/repos/docopt/docopt.go/badge.png)](https://coveralls.io/r/docopt/docopt.go) 6 | [![GoDoc](https://godoc.org/github.com/docopt/docopt.go?status.png)](https://godoc.org/github.com/docopt/docopt.go) 7 | 8 | An implementation of [docopt](http://docopt.org/) in the 9 | [Go](http://golang.org/) programming language. 10 | 11 | **docopt** helps you create *beautiful* command-line interfaces easily: 12 | 13 | ```go 14 | package main 15 | 16 | import ( 17 | "fmt" 18 | "github.com/docopt/docopt-go" 19 | ) 20 | 21 | func main() { 22 | usage := `Naval Fate. 23 | 24 | Usage: 25 | naval_fate ship new ... 26 | naval_fate ship move [--speed=] 27 | naval_fate ship shoot 28 | naval_fate mine (set|remove) [--moored|--drifting] 29 | naval_fate -h | --help 30 | naval_fate --version 31 | 32 | Options: 33 | -h --help Show this screen. 34 | --version Show version. 35 | --speed= Speed in knots [default: 10]. 36 | --moored Moored (anchored) mine. 37 | --drifting Drifting mine.` 38 | 39 | arguments, _ := docopt.Parse(usage, nil, true, "Naval Fate 2.0", false) 40 | fmt.Println(arguments) 41 | } 42 | ``` 43 | 44 | **docopt** parses command-line arguments based on a help message. Don't 45 | write parser code: a good help message already has all the necessary 46 | information in it. 47 | 48 | ## Installation 49 | 50 | ⚠ Use the alias “docopt-go”. To use docopt in your Go code: 51 | 52 | ```go 53 | import "github.com/docopt/docopt-go" 54 | ``` 55 | 56 | To install docopt according to your `$GOPATH`: 57 | 58 | ```console 59 | $ go get github.com/docopt/docopt-go 60 | ``` 61 | 62 | ## API 63 | 64 | ```go 65 | func Parse(doc string, argv []string, help bool, version string, 66 | optionsFirst bool, exit ...bool) (map[string]interface{}, error) 67 | ``` 68 | Parse `argv` based on the command-line interface described in `doc`. 69 | 70 | Given a conventional command-line help message, docopt creates a parser and 71 | processes the arguments. See 72 | https://github.com/docopt/docopt#help-message-format for a description of the 73 | help message format. If `argv` is `nil`, `os.Args[1:]` is used. 74 | 75 | docopt returns a map of option names to the values parsed from `argv`, and an 76 | error or `nil`. 77 | 78 | More documentation for docopt is available at 79 | [GoDoc.org](https://godoc.org/github.com/docopt/docopt.go). 80 | 81 | ## Testing 82 | 83 | All tests from the Python version are implemented and passing 84 | at [Travis CI](https://travis-ci.org/docopt/docopt.go). New 85 | language-agnostic tests have been added 86 | to [test_golang.docopt](test_golang.docopt). 87 | 88 | To run tests for docopt-go, use `go test`. 89 | -------------------------------------------------------------------------------- /docopt.go: -------------------------------------------------------------------------------- 1 | // Licensed under terms of MIT license (see LICENSE-MIT) 2 | // Copyright (c) 2013 Keith Batten, kbatten@gmail.com 3 | 4 | /* 5 | Package docopt parses command-line arguments based on a help message. 6 | 7 | ⚠ Use the alias “docopt-go”: 8 | import "github.com/docopt/docopt-go" 9 | or 10 | $ go get github.com/github/docopt-go 11 | */ 12 | package docopt 13 | 14 | import ( 15 | "fmt" 16 | "os" 17 | "reflect" 18 | "regexp" 19 | "strings" 20 | "unicode" 21 | ) 22 | 23 | /* 24 | Parse `argv` based on the command-line interface described in `doc`. 25 | 26 | Given a conventional command-line help message, docopt creates a parser and 27 | processes the arguments. See 28 | https://github.com/docopt/docopt#help-message-format for a description of the 29 | help message format. If `argv` is `nil`, `os.Args[1:]` is used. 30 | 31 | docopt returns a map of option names to the values parsed from `argv`, and an 32 | error or `nil`. 33 | 34 | Set `help` to `false` to disable automatic help messages on `-h` or `--help`. 35 | If `version` is a non-empty string, it will be printed when `--version` is 36 | specified. Set `optionsFirst` to `true` to require that options always come 37 | before positional arguments; otherwise they can overlap. 38 | 39 | By default, docopt calls `os.Exit(0)` if it handled a built-in option such as 40 | `-h` or `--version`. If the user errored with a wrong command or options, 41 | docopt exits with a return code of 1. To stop docopt from calling `os.Exit()` 42 | and to handle your own return codes, pass an optional last parameter of `false` 43 | for `exit`. 44 | */ 45 | func Parse(doc string, argv []string, help bool, version string, 46 | optionsFirst bool, exit ...bool) (map[string]interface{}, error) { 47 | // if "false" was the (optional) last arg, don't call os.Exit() 48 | exitOk := true 49 | if len(exit) > 0 { 50 | exitOk = exit[0] 51 | } 52 | args, output, err := parse(doc, argv, help, version, optionsFirst) 53 | if _, ok := err.(*UserError); ok { 54 | // the user gave us bad input 55 | fmt.Println(output) 56 | if exitOk { 57 | os.Exit(1) 58 | } 59 | } else if len(output) > 0 && err == nil { 60 | // the user asked for help or `--version` 61 | fmt.Println(output) 62 | if exitOk { 63 | os.Exit(0) 64 | } 65 | } 66 | return args, err 67 | } 68 | 69 | // parse and return a map of args, output and all errors 70 | func parse(doc string, argv []string, help bool, version string, optionsFirst bool) (args map[string]interface{}, output string, err error) { 71 | if argv == nil && len(os.Args) > 1 { 72 | argv = os.Args[1:] 73 | } 74 | 75 | usageSections := parseSection("usage:", doc) 76 | 77 | if len(usageSections) == 0 { 78 | err = newLanguageError("\"usage:\" (case-insensitive) not found.") 79 | return 80 | } 81 | if len(usageSections) > 1 { 82 | err = newLanguageError("More than one \"usage:\" (case-insensitive).") 83 | return 84 | } 85 | usage := usageSections[0] 86 | 87 | options := parseDefaults(doc) 88 | formal, err := formalUsage(usage) 89 | if err != nil { 90 | output = handleError(err, usage) 91 | return 92 | } 93 | 94 | pat, err := parsePattern(formal, &options) 95 | if err != nil { 96 | output = handleError(err, usage) 97 | return 98 | } 99 | 100 | patternArgv, err := parseArgv(newTokenList(argv, errorUser), &options, optionsFirst) 101 | if err != nil { 102 | output = handleError(err, usage) 103 | return 104 | } 105 | patFlat, err := pat.flat(patternOption) 106 | if err != nil { 107 | output = handleError(err, usage) 108 | return 109 | } 110 | patternOptions := patFlat.unique() 111 | 112 | patFlat, err = pat.flat(patternOptionSSHORTCUT) 113 | if err != nil { 114 | output = handleError(err, usage) 115 | return 116 | } 117 | for _, optionsShortcut := range patFlat { 118 | docOptions := parseDefaults(doc) 119 | optionsShortcut.children = docOptions.unique().diff(patternOptions) 120 | } 121 | 122 | if output = extras(help, version, patternArgv, doc); len(output) > 0 { 123 | return 124 | } 125 | 126 | err = pat.fix() 127 | if err != nil { 128 | output = handleError(err, usage) 129 | return 130 | } 131 | matched, left, collected := pat.match(&patternArgv, nil) 132 | if matched && len(*left) == 0 { 133 | patFlat, err = pat.flat(patternDefault) 134 | if err != nil { 135 | output = handleError(err, usage) 136 | return 137 | } 138 | args = append(patFlat, *collected...).dictionary() 139 | return 140 | } 141 | 142 | err = newUserError("") 143 | output = handleError(err, usage) 144 | return 145 | } 146 | 147 | func handleError(err error, usage string) string { 148 | if _, ok := err.(*UserError); ok { 149 | return strings.TrimSpace(fmt.Sprintf("%s\n%s", err, usage)) 150 | } 151 | return "" 152 | } 153 | 154 | func parseSection(name, source string) []string { 155 | p := regexp.MustCompile(`(?im)^([^\n]*` + name + `[^\n]*\n?(?:[ \t].*?(?:\n|$))*)`) 156 | s := p.FindAllString(source, -1) 157 | if s == nil { 158 | s = []string{} 159 | } 160 | for i, v := range s { 161 | s[i] = strings.TrimSpace(v) 162 | } 163 | return s 164 | } 165 | 166 | func parseDefaults(doc string) patternList { 167 | defaults := patternList{} 168 | p := regexp.MustCompile(`\n[ \t]*(-\S+?)`) 169 | for _, s := range parseSection("options:", doc) { 170 | // FIXME corner case "bla: options: --foo" 171 | _, _, s = stringPartition(s, ":") // get rid of "options:" 172 | split := p.Split("\n"+s, -1)[1:] 173 | match := p.FindAllStringSubmatch("\n"+s, -1) 174 | for i := range split { 175 | optionDescription := match[i][1] + split[i] 176 | if strings.HasPrefix(optionDescription, "-") { 177 | defaults = append(defaults, parseOption(optionDescription)) 178 | } 179 | } 180 | } 181 | return defaults 182 | } 183 | 184 | func parsePattern(source string, options *patternList) (*pattern, error) { 185 | tokens := tokenListFromPattern(source) 186 | result, err := parseExpr(tokens, options) 187 | if err != nil { 188 | return nil, err 189 | } 190 | if tokens.current() != nil { 191 | return nil, tokens.errorFunc("unexpected ending: %s" + strings.Join(tokens.tokens, " ")) 192 | } 193 | return newRequired(result...), nil 194 | } 195 | 196 | func parseArgv(tokens *tokenList, options *patternList, optionsFirst bool) (patternList, error) { 197 | /* 198 | Parse command-line argument vector. 199 | 200 | If options_first: 201 | argv ::= [ long | shorts ]* [ argument ]* [ '--' [ argument ]* ] ; 202 | else: 203 | argv ::= [ long | shorts | argument ]* [ '--' [ argument ]* ] ; 204 | */ 205 | parsed := patternList{} 206 | for tokens.current() != nil { 207 | if tokens.current().eq("--") { 208 | for _, v := range tokens.tokens { 209 | parsed = append(parsed, newArgument("", v)) 210 | } 211 | return parsed, nil 212 | } else if tokens.current().hasPrefix("--") { 213 | pl, err := parseLong(tokens, options) 214 | if err != nil { 215 | return nil, err 216 | } 217 | parsed = append(parsed, pl...) 218 | } else if tokens.current().hasPrefix("-") && !tokens.current().eq("-") { 219 | ps, err := parseShorts(tokens, options) 220 | if err != nil { 221 | return nil, err 222 | } 223 | parsed = append(parsed, ps...) 224 | } else if optionsFirst { 225 | for _, v := range tokens.tokens { 226 | parsed = append(parsed, newArgument("", v)) 227 | } 228 | return parsed, nil 229 | } else { 230 | parsed = append(parsed, newArgument("", tokens.move().String())) 231 | } 232 | } 233 | return parsed, nil 234 | } 235 | 236 | func parseOption(optionDescription string) *pattern { 237 | optionDescription = strings.TrimSpace(optionDescription) 238 | options, _, description := stringPartition(optionDescription, " ") 239 | options = strings.Replace(options, ",", " ", -1) 240 | options = strings.Replace(options, "=", " ", -1) 241 | 242 | short := "" 243 | long := "" 244 | argcount := 0 245 | var value interface{} 246 | value = false 247 | 248 | reDefault := regexp.MustCompile(`(?i)\[default: (.*)\]`) 249 | for _, s := range strings.Fields(options) { 250 | if strings.HasPrefix(s, "--") { 251 | long = s 252 | } else if strings.HasPrefix(s, "-") { 253 | short = s 254 | } else { 255 | argcount = 1 256 | } 257 | if argcount > 0 { 258 | matched := reDefault.FindAllStringSubmatch(description, -1) 259 | if len(matched) > 0 { 260 | value = matched[0][1] 261 | } else { 262 | value = nil 263 | } 264 | } 265 | } 266 | return newOption(short, long, argcount, value) 267 | } 268 | 269 | func parseExpr(tokens *tokenList, options *patternList) (patternList, error) { 270 | // expr ::= seq ( '|' seq )* ; 271 | seq, err := parseSeq(tokens, options) 272 | if err != nil { 273 | return nil, err 274 | } 275 | if !tokens.current().eq("|") { 276 | return seq, nil 277 | } 278 | var result patternList 279 | if len(seq) > 1 { 280 | result = patternList{newRequired(seq...)} 281 | } else { 282 | result = seq 283 | } 284 | for tokens.current().eq("|") { 285 | tokens.move() 286 | seq, err = parseSeq(tokens, options) 287 | if err != nil { 288 | return nil, err 289 | } 290 | if len(seq) > 1 { 291 | result = append(result, newRequired(seq...)) 292 | } else { 293 | result = append(result, seq...) 294 | } 295 | } 296 | if len(result) > 1 { 297 | return patternList{newEither(result...)}, nil 298 | } 299 | return result, nil 300 | } 301 | 302 | func parseSeq(tokens *tokenList, options *patternList) (patternList, error) { 303 | // seq ::= ( atom [ '...' ] )* ; 304 | result := patternList{} 305 | for !tokens.current().match(true, "]", ")", "|") { 306 | atom, err := parseAtom(tokens, options) 307 | if err != nil { 308 | return nil, err 309 | } 310 | if tokens.current().eq("...") { 311 | atom = patternList{newOneOrMore(atom...)} 312 | tokens.move() 313 | } 314 | result = append(result, atom...) 315 | } 316 | return result, nil 317 | } 318 | 319 | func parseAtom(tokens *tokenList, options *patternList) (patternList, error) { 320 | // atom ::= '(' expr ')' | '[' expr ']' | 'options' | long | shorts | argument | command ; 321 | tok := tokens.current() 322 | result := patternList{} 323 | if tokens.current().match(false, "(", "[") { 324 | tokens.move() 325 | var matching string 326 | pl, err := parseExpr(tokens, options) 327 | if err != nil { 328 | return nil, err 329 | } 330 | if tok.eq("(") { 331 | matching = ")" 332 | result = patternList{newRequired(pl...)} 333 | } else if tok.eq("[") { 334 | matching = "]" 335 | result = patternList{newOptional(pl...)} 336 | } 337 | moved := tokens.move() 338 | if !moved.eq(matching) { 339 | return nil, tokens.errorFunc("unmatched '%s', expected: '%s' got: '%s'", tok, matching, moved) 340 | } 341 | return result, nil 342 | } else if tok.eq("options") { 343 | tokens.move() 344 | return patternList{newOptionsShortcut()}, nil 345 | } else if tok.hasPrefix("--") && !tok.eq("--") { 346 | return parseLong(tokens, options) 347 | } else if tok.hasPrefix("-") && !tok.eq("-") && !tok.eq("--") { 348 | return parseShorts(tokens, options) 349 | } else if tok.hasPrefix("<") && tok.hasSuffix(">") || tok.isUpper() { 350 | return patternList{newArgument(tokens.move().String(), nil)}, nil 351 | } 352 | return patternList{newCommand(tokens.move().String(), false)}, nil 353 | } 354 | 355 | func parseLong(tokens *tokenList, options *patternList) (patternList, error) { 356 | // long ::= '--' chars [ ( ' ' | '=' ) chars ] ; 357 | long, eq, v := stringPartition(tokens.move().String(), "=") 358 | var value interface{} 359 | var opt *pattern 360 | if eq == "" && v == "" { 361 | value = nil 362 | } else { 363 | value = v 364 | } 365 | 366 | if !strings.HasPrefix(long, "--") { 367 | return nil, newError("long option '%s' doesn't start with --", long) 368 | } 369 | similar := patternList{} 370 | for _, o := range *options { 371 | if o.long == long { 372 | similar = append(similar, o) 373 | } 374 | } 375 | if tokens.err == errorUser && len(similar) == 0 { // if no exact match 376 | similar = patternList{} 377 | for _, o := range *options { 378 | if strings.HasPrefix(o.long, long) { 379 | similar = append(similar, o) 380 | } 381 | } 382 | } 383 | if len(similar) > 1 { // might be simply specified ambiguously 2+ times? 384 | similarLong := make([]string, len(similar)) 385 | for i, s := range similar { 386 | similarLong[i] = s.long 387 | } 388 | return nil, tokens.errorFunc("%s is not a unique prefix: %s?", long, strings.Join(similarLong, ", ")) 389 | } else if len(similar) < 1 { 390 | argcount := 0 391 | if eq == "=" { 392 | argcount = 1 393 | } 394 | opt = newOption("", long, argcount, false) 395 | *options = append(*options, opt) 396 | if tokens.err == errorUser { 397 | var val interface{} 398 | if argcount > 0 { 399 | val = value 400 | } else { 401 | val = true 402 | } 403 | opt = newOption("", long, argcount, val) 404 | } 405 | } else { 406 | opt = newOption(similar[0].short, similar[0].long, similar[0].argcount, similar[0].value) 407 | if opt.argcount == 0 { 408 | if value != nil { 409 | return nil, tokens.errorFunc("%s must not have an argument", opt.long) 410 | } 411 | } else { 412 | if value == nil { 413 | if tokens.current().match(true, "--") { 414 | return nil, tokens.errorFunc("%s requires argument", opt.long) 415 | } 416 | moved := tokens.move() 417 | if moved != nil { 418 | value = moved.String() // only set as string if not nil 419 | } 420 | } 421 | } 422 | if tokens.err == errorUser { 423 | if value != nil { 424 | opt.value = value 425 | } else { 426 | opt.value = true 427 | } 428 | } 429 | } 430 | 431 | return patternList{opt}, nil 432 | } 433 | 434 | func parseShorts(tokens *tokenList, options *patternList) (patternList, error) { 435 | // shorts ::= '-' ( chars )* [ [ ' ' ] chars ] ; 436 | tok := tokens.move() 437 | if !tok.hasPrefix("-") || tok.hasPrefix("--") { 438 | return nil, newError("short option '%s' doesn't start with -", tok) 439 | } 440 | left := strings.TrimLeft(tok.String(), "-") 441 | parsed := patternList{} 442 | for left != "" { 443 | var opt *pattern 444 | short := "-" + left[0:1] 445 | left = left[1:] 446 | similar := patternList{} 447 | for _, o := range *options { 448 | if o.short == short { 449 | similar = append(similar, o) 450 | } 451 | } 452 | if len(similar) > 1 { 453 | return nil, tokens.errorFunc("%s is specified ambiguously %d times", short, len(similar)) 454 | } else if len(similar) < 1 { 455 | opt = newOption(short, "", 0, false) 456 | *options = append(*options, opt) 457 | if tokens.err == errorUser { 458 | opt = newOption(short, "", 0, true) 459 | } 460 | } else { // why copying is necessary here? 461 | opt = newOption(short, similar[0].long, similar[0].argcount, similar[0].value) 462 | var value interface{} 463 | if opt.argcount > 0 { 464 | if left == "" { 465 | if tokens.current().match(true, "--") { 466 | return nil, tokens.errorFunc("%s requires argument", short) 467 | } 468 | value = tokens.move().String() 469 | } else { 470 | value = left 471 | left = "" 472 | } 473 | } 474 | if tokens.err == errorUser { 475 | if value != nil { 476 | opt.value = value 477 | } else { 478 | opt.value = true 479 | } 480 | } 481 | } 482 | parsed = append(parsed, opt) 483 | } 484 | return parsed, nil 485 | } 486 | 487 | func newTokenList(source []string, err errorType) *tokenList { 488 | errorFunc := newError 489 | if err == errorUser { 490 | errorFunc = newUserError 491 | } else if err == errorLanguage { 492 | errorFunc = newLanguageError 493 | } 494 | return &tokenList{source, errorFunc, err} 495 | } 496 | 497 | func tokenListFromString(source string) *tokenList { 498 | return newTokenList(strings.Fields(source), errorUser) 499 | } 500 | 501 | func tokenListFromPattern(source string) *tokenList { 502 | p := regexp.MustCompile(`([\[\]\(\)\|]|\.\.\.)`) 503 | source = p.ReplaceAllString(source, ` $1 `) 504 | p = regexp.MustCompile(`\s+|(\S*<.*?>)`) 505 | split := p.Split(source, -1) 506 | match := p.FindAllStringSubmatch(source, -1) 507 | var result []string 508 | l := len(split) 509 | for i := 0; i < l; i++ { 510 | if len(split[i]) > 0 { 511 | result = append(result, split[i]) 512 | } 513 | if i < l-1 && len(match[i][1]) > 0 { 514 | result = append(result, match[i][1]) 515 | } 516 | } 517 | return newTokenList(result, errorLanguage) 518 | } 519 | 520 | func formalUsage(section string) (string, error) { 521 | _, _, section = stringPartition(section, ":") // drop "usage:" 522 | pu := strings.Fields(section) 523 | 524 | if len(pu) == 0 { 525 | return "", newLanguageError("no fields found in usage (perhaps a spacing error).") 526 | } 527 | 528 | result := "( " 529 | for _, s := range pu[1:] { 530 | if s == pu[0] { 531 | result += ") | ( " 532 | } else { 533 | result += s + " " 534 | } 535 | } 536 | result += ")" 537 | 538 | return result, nil 539 | } 540 | 541 | func extras(help bool, version string, options patternList, doc string) string { 542 | if help { 543 | for _, o := range options { 544 | if (o.name == "-h" || o.name == "--help") && o.value == true { 545 | return "\n" + strings.Trim(doc, "\n") + "\n" 546 | } 547 | } 548 | } 549 | if version != "" { 550 | for _, o := range options { 551 | if (o.name == "--version") && o.value == true { 552 | return version 553 | } 554 | } 555 | } 556 | return "" 557 | } 558 | 559 | type errorType int 560 | 561 | const ( 562 | errorUser errorType = iota 563 | errorLanguage 564 | ) 565 | 566 | func (e errorType) String() string { 567 | switch e { 568 | case errorUser: 569 | return "errorUser" 570 | case errorLanguage: 571 | return "errorLanguage" 572 | } 573 | return "" 574 | } 575 | 576 | // UserError records an error with program arguments. 577 | type UserError struct { 578 | msg string 579 | Usage string 580 | } 581 | 582 | func (e UserError) Error() string { 583 | return e.msg 584 | } 585 | func newUserError(msg string, f ...interface{}) error { 586 | return &UserError{fmt.Sprintf(msg, f...), ""} 587 | } 588 | 589 | // LanguageError records an error with the doc string. 590 | type LanguageError struct { 591 | msg string 592 | } 593 | 594 | func (e LanguageError) Error() string { 595 | return e.msg 596 | } 597 | func newLanguageError(msg string, f ...interface{}) error { 598 | return &LanguageError{fmt.Sprintf(msg, f...)} 599 | } 600 | 601 | var newError = fmt.Errorf 602 | 603 | type tokenList struct { 604 | tokens []string 605 | errorFunc func(string, ...interface{}) error 606 | err errorType 607 | } 608 | type token string 609 | 610 | func (t *token) eq(s string) bool { 611 | if t == nil { 612 | return false 613 | } 614 | return string(*t) == s 615 | } 616 | func (t *token) match(matchNil bool, tokenStrings ...string) bool { 617 | if t == nil && matchNil { 618 | return true 619 | } else if t == nil && !matchNil { 620 | return false 621 | } 622 | 623 | for _, tok := range tokenStrings { 624 | if tok == string(*t) { 625 | return true 626 | } 627 | } 628 | return false 629 | } 630 | func (t *token) hasPrefix(prefix string) bool { 631 | if t == nil { 632 | return false 633 | } 634 | return strings.HasPrefix(string(*t), prefix) 635 | } 636 | func (t *token) hasSuffix(suffix string) bool { 637 | if t == nil { 638 | return false 639 | } 640 | return strings.HasSuffix(string(*t), suffix) 641 | } 642 | func (t *token) isUpper() bool { 643 | if t == nil { 644 | return false 645 | } 646 | return isStringUppercase(string(*t)) 647 | } 648 | func (t *token) String() string { 649 | if t == nil { 650 | return "" 651 | } 652 | return string(*t) 653 | } 654 | 655 | func (tl *tokenList) current() *token { 656 | if len(tl.tokens) > 0 { 657 | return (*token)(&(tl.tokens[0])) 658 | } 659 | return nil 660 | } 661 | 662 | func (tl *tokenList) length() int { 663 | return len(tl.tokens) 664 | } 665 | 666 | func (tl *tokenList) move() *token { 667 | if len(tl.tokens) > 0 { 668 | t := tl.tokens[0] 669 | tl.tokens = tl.tokens[1:] 670 | return (*token)(&t) 671 | } 672 | return nil 673 | } 674 | 675 | type patternType uint 676 | 677 | const ( 678 | // leaf 679 | patternArgument patternType = 1 << iota 680 | patternCommand 681 | patternOption 682 | 683 | // branch 684 | patternRequired 685 | patternOptionAL 686 | patternOptionSSHORTCUT // Marker/placeholder for [options] shortcut. 687 | patternOneOrMore 688 | patternEither 689 | 690 | patternLeaf = patternArgument + 691 | patternCommand + 692 | patternOption 693 | patternBranch = patternRequired + 694 | patternOptionAL + 695 | patternOptionSSHORTCUT + 696 | patternOneOrMore + 697 | patternEither 698 | patternAll = patternLeaf + patternBranch 699 | patternDefault = 0 700 | ) 701 | 702 | func (pt patternType) String() string { 703 | switch pt { 704 | case patternArgument: 705 | return "argument" 706 | case patternCommand: 707 | return "command" 708 | case patternOption: 709 | return "option" 710 | case patternRequired: 711 | return "required" 712 | case patternOptionAL: 713 | return "optional" 714 | case patternOptionSSHORTCUT: 715 | return "optionsshortcut" 716 | case patternOneOrMore: 717 | return "oneormore" 718 | case patternEither: 719 | return "either" 720 | case patternLeaf: 721 | return "leaf" 722 | case patternBranch: 723 | return "branch" 724 | case patternAll: 725 | return "all" 726 | case patternDefault: 727 | return "default" 728 | } 729 | return "" 730 | } 731 | 732 | type pattern struct { 733 | t patternType 734 | 735 | children patternList 736 | 737 | name string 738 | value interface{} 739 | 740 | short string 741 | long string 742 | argcount int 743 | } 744 | 745 | type patternList []*pattern 746 | 747 | func newBranchPattern(t patternType, pl ...*pattern) *pattern { 748 | var p pattern 749 | p.t = t 750 | p.children = make(patternList, len(pl)) 751 | copy(p.children, pl) 752 | return &p 753 | } 754 | 755 | func newRequired(pl ...*pattern) *pattern { 756 | return newBranchPattern(patternRequired, pl...) 757 | } 758 | 759 | func newEither(pl ...*pattern) *pattern { 760 | return newBranchPattern(patternEither, pl...) 761 | } 762 | 763 | func newOneOrMore(pl ...*pattern) *pattern { 764 | return newBranchPattern(patternOneOrMore, pl...) 765 | } 766 | 767 | func newOptional(pl ...*pattern) *pattern { 768 | return newBranchPattern(patternOptionAL, pl...) 769 | } 770 | 771 | func newOptionsShortcut() *pattern { 772 | var p pattern 773 | p.t = patternOptionSSHORTCUT 774 | return &p 775 | } 776 | 777 | func newLeafPattern(t patternType, name string, value interface{}) *pattern { 778 | // default: value=nil 779 | var p pattern 780 | p.t = t 781 | p.name = name 782 | p.value = value 783 | return &p 784 | } 785 | 786 | func newArgument(name string, value interface{}) *pattern { 787 | // default: value=nil 788 | return newLeafPattern(patternArgument, name, value) 789 | } 790 | 791 | func newCommand(name string, value interface{}) *pattern { 792 | // default: value=false 793 | var p pattern 794 | p.t = patternCommand 795 | p.name = name 796 | p.value = value 797 | return &p 798 | } 799 | 800 | func newOption(short, long string, argcount int, value interface{}) *pattern { 801 | // default: "", "", 0, false 802 | var p pattern 803 | p.t = patternOption 804 | p.short = short 805 | p.long = long 806 | if long != "" { 807 | p.name = long 808 | } else { 809 | p.name = short 810 | } 811 | p.argcount = argcount 812 | if value == false && argcount > 0 { 813 | p.value = nil 814 | } else { 815 | p.value = value 816 | } 817 | return &p 818 | } 819 | 820 | func (p *pattern) flat(types patternType) (patternList, error) { 821 | if p.t&patternLeaf != 0 { 822 | if types == patternDefault { 823 | types = patternAll 824 | } 825 | if p.t&types != 0 { 826 | return patternList{p}, nil 827 | } 828 | return patternList{}, nil 829 | } 830 | 831 | if p.t&patternBranch != 0 { 832 | if p.t&types != 0 { 833 | return patternList{p}, nil 834 | } 835 | result := patternList{} 836 | for _, child := range p.children { 837 | childFlat, err := child.flat(types) 838 | if err != nil { 839 | return nil, err 840 | } 841 | result = append(result, childFlat...) 842 | } 843 | return result, nil 844 | } 845 | return nil, newError("unknown pattern type: %d, %d", p.t, types) 846 | } 847 | 848 | func (p *pattern) fix() error { 849 | err := p.fixIdentities(nil) 850 | if err != nil { 851 | return err 852 | } 853 | p.fixRepeatingArguments() 854 | return nil 855 | } 856 | 857 | func (p *pattern) fixIdentities(uniq patternList) error { 858 | // Make pattern-tree tips point to same object if they are equal. 859 | if p.t&patternBranch == 0 { 860 | return nil 861 | } 862 | if uniq == nil { 863 | pFlat, err := p.flat(patternDefault) 864 | if err != nil { 865 | return err 866 | } 867 | uniq = pFlat.unique() 868 | } 869 | for i, child := range p.children { 870 | if child.t&patternBranch == 0 { 871 | ind, err := uniq.index(child) 872 | if err != nil { 873 | return err 874 | } 875 | p.children[i] = uniq[ind] 876 | } else { 877 | err := child.fixIdentities(uniq) 878 | if err != nil { 879 | return err 880 | } 881 | } 882 | } 883 | return nil 884 | } 885 | 886 | func (p *pattern) fixRepeatingArguments() { 887 | // Fix elements that should accumulate/increment values. 888 | var either []patternList 889 | 890 | for _, child := range p.transform().children { 891 | either = append(either, child.children) 892 | } 893 | for _, cas := range either { 894 | casMultiple := patternList{} 895 | for _, e := range cas { 896 | if cas.count(e) > 1 { 897 | casMultiple = append(casMultiple, e) 898 | } 899 | } 900 | for _, e := range casMultiple { 901 | if e.t == patternArgument || e.t == patternOption && e.argcount > 0 { 902 | switch e.value.(type) { 903 | case string: 904 | e.value = strings.Fields(e.value.(string)) 905 | case []string: 906 | default: 907 | e.value = []string{} 908 | } 909 | } 910 | if e.t == patternCommand || e.t == patternOption && e.argcount == 0 { 911 | e.value = 0 912 | } 913 | } 914 | } 915 | } 916 | 917 | func (p *pattern) match(left *patternList, collected *patternList) (bool, *patternList, *patternList) { 918 | if collected == nil { 919 | collected = &patternList{} 920 | } 921 | if p.t&patternRequired != 0 { 922 | l := left 923 | c := collected 924 | for _, p := range p.children { 925 | var matched bool 926 | matched, l, c = p.match(l, c) 927 | if !matched { 928 | return false, left, collected 929 | } 930 | } 931 | return true, l, c 932 | } else if p.t&patternOptionAL != 0 || p.t&patternOptionSSHORTCUT != 0 { 933 | for _, p := range p.children { 934 | _, left, collected = p.match(left, collected) 935 | } 936 | return true, left, collected 937 | } else if p.t&patternOneOrMore != 0 { 938 | if len(p.children) != 1 { 939 | panic("OneOrMore.match(): assert len(p.children) == 1") 940 | } 941 | l := left 942 | c := collected 943 | var lAlt *patternList 944 | matched := true 945 | times := 0 946 | for matched { 947 | // could it be that something didn't match but changed l or c? 948 | matched, l, c = p.children[0].match(l, c) 949 | if matched { 950 | times++ 951 | } 952 | if lAlt == l { 953 | break 954 | } 955 | lAlt = l 956 | } 957 | if times >= 1 { 958 | return true, l, c 959 | } 960 | return false, left, collected 961 | } else if p.t&patternEither != 0 { 962 | type outcomeStruct struct { 963 | matched bool 964 | left *patternList 965 | collected *patternList 966 | length int 967 | } 968 | outcomes := []outcomeStruct{} 969 | for _, p := range p.children { 970 | matched, l, c := p.match(left, collected) 971 | outcome := outcomeStruct{matched, l, c, len(*l)} 972 | if matched { 973 | outcomes = append(outcomes, outcome) 974 | } 975 | } 976 | if len(outcomes) > 0 { 977 | minLen := outcomes[0].length 978 | minIndex := 0 979 | for i, v := range outcomes { 980 | if v.length < minLen { 981 | minIndex = i 982 | } 983 | } 984 | return outcomes[minIndex].matched, outcomes[minIndex].left, outcomes[minIndex].collected 985 | } 986 | return false, left, collected 987 | } else if p.t&patternLeaf != 0 { 988 | pos, match := p.singleMatch(left) 989 | var increment interface{} 990 | if match == nil { 991 | return false, left, collected 992 | } 993 | leftAlt := make(patternList, len((*left)[:pos]), len((*left)[:pos])+len((*left)[pos+1:])) 994 | copy(leftAlt, (*left)[:pos]) 995 | leftAlt = append(leftAlt, (*left)[pos+1:]...) 996 | sameName := patternList{} 997 | for _, a := range *collected { 998 | if a.name == p.name { 999 | sameName = append(sameName, a) 1000 | } 1001 | } 1002 | 1003 | switch p.value.(type) { 1004 | case int, []string: 1005 | switch p.value.(type) { 1006 | case int: 1007 | increment = 1 1008 | case []string: 1009 | switch match.value.(type) { 1010 | case string: 1011 | increment = []string{match.value.(string)} 1012 | default: 1013 | increment = match.value 1014 | } 1015 | } 1016 | if len(sameName) == 0 { 1017 | match.value = increment 1018 | collectedMatch := make(patternList, len(*collected), len(*collected)+1) 1019 | copy(collectedMatch, *collected) 1020 | collectedMatch = append(collectedMatch, match) 1021 | return true, &leftAlt, &collectedMatch 1022 | } 1023 | switch sameName[0].value.(type) { 1024 | case int: 1025 | sameName[0].value = sameName[0].value.(int) + increment.(int) 1026 | case []string: 1027 | sameName[0].value = append(sameName[0].value.([]string), increment.([]string)...) 1028 | } 1029 | return true, &leftAlt, collected 1030 | } 1031 | collectedMatch := make(patternList, len(*collected), len(*collected)+1) 1032 | copy(collectedMatch, *collected) 1033 | collectedMatch = append(collectedMatch, match) 1034 | return true, &leftAlt, &collectedMatch 1035 | } 1036 | panic("unmatched type") 1037 | } 1038 | 1039 | func (p *pattern) singleMatch(left *patternList) (int, *pattern) { 1040 | if p.t&patternArgument != 0 { 1041 | for n, pat := range *left { 1042 | if pat.t&patternArgument != 0 { 1043 | return n, newArgument(p.name, pat.value) 1044 | } 1045 | } 1046 | return -1, nil 1047 | } else if p.t&patternCommand != 0 { 1048 | for n, pat := range *left { 1049 | if pat.t&patternArgument != 0 { 1050 | if pat.value == p.name { 1051 | return n, newCommand(p.name, true) 1052 | } 1053 | break 1054 | } 1055 | } 1056 | return -1, nil 1057 | } else if p.t&patternOption != 0 { 1058 | for n, pat := range *left { 1059 | if p.name == pat.name { 1060 | return n, pat 1061 | } 1062 | } 1063 | return -1, nil 1064 | } 1065 | panic("unmatched type") 1066 | } 1067 | 1068 | func (p *pattern) String() string { 1069 | if p.t&patternOption != 0 { 1070 | return fmt.Sprintf("%s(%s, %s, %d, %+v)", p.t, p.short, p.long, p.argcount, p.value) 1071 | } else if p.t&patternLeaf != 0 { 1072 | return fmt.Sprintf("%s(%s, %+v)", p.t, p.name, p.value) 1073 | } else if p.t&patternBranch != 0 { 1074 | result := "" 1075 | for i, child := range p.children { 1076 | if i > 0 { 1077 | result += ", " 1078 | } 1079 | result += child.String() 1080 | } 1081 | return fmt.Sprintf("%s(%s)", p.t, result) 1082 | } 1083 | panic("unmatched type") 1084 | } 1085 | 1086 | func (p *pattern) transform() *pattern { 1087 | /* 1088 | Expand pattern into an (almost) equivalent one, but with single Either. 1089 | 1090 | Example: ((-a | -b) (-c | -d)) => (-a -c | -a -d | -b -c | -b -d) 1091 | Quirks: [-a] => (-a), (-a...) => (-a -a) 1092 | */ 1093 | result := []patternList{} 1094 | groups := []patternList{patternList{p}} 1095 | parents := patternRequired + 1096 | patternOptionAL + 1097 | patternOptionSSHORTCUT + 1098 | patternEither + 1099 | patternOneOrMore 1100 | for len(groups) > 0 { 1101 | children := groups[0] 1102 | groups = groups[1:] 1103 | var child *pattern 1104 | for _, c := range children { 1105 | if c.t&parents != 0 { 1106 | child = c 1107 | break 1108 | } 1109 | } 1110 | if child != nil { 1111 | children.remove(child) 1112 | if child.t&patternEither != 0 { 1113 | for _, c := range child.children { 1114 | r := patternList{} 1115 | r = append(r, c) 1116 | r = append(r, children...) 1117 | groups = append(groups, r) 1118 | } 1119 | } else if child.t&patternOneOrMore != 0 { 1120 | r := patternList{} 1121 | r = append(r, child.children.double()...) 1122 | r = append(r, children...) 1123 | groups = append(groups, r) 1124 | } else { 1125 | r := patternList{} 1126 | r = append(r, child.children...) 1127 | r = append(r, children...) 1128 | groups = append(groups, r) 1129 | } 1130 | } else { 1131 | result = append(result, children) 1132 | } 1133 | } 1134 | either := patternList{} 1135 | for _, e := range result { 1136 | either = append(either, newRequired(e...)) 1137 | } 1138 | return newEither(either...) 1139 | } 1140 | 1141 | func (p *pattern) eq(other *pattern) bool { 1142 | return reflect.DeepEqual(p, other) 1143 | } 1144 | 1145 | func (pl patternList) unique() patternList { 1146 | table := make(map[string]bool) 1147 | result := patternList{} 1148 | for _, v := range pl { 1149 | if !table[v.String()] { 1150 | table[v.String()] = true 1151 | result = append(result, v) 1152 | } 1153 | } 1154 | return result 1155 | } 1156 | 1157 | func (pl patternList) index(p *pattern) (int, error) { 1158 | for i, c := range pl { 1159 | if c.eq(p) { 1160 | return i, nil 1161 | } 1162 | } 1163 | return -1, newError("%s not in list", p) 1164 | } 1165 | 1166 | func (pl patternList) count(p *pattern) int { 1167 | count := 0 1168 | for _, c := range pl { 1169 | if c.eq(p) { 1170 | count++ 1171 | } 1172 | } 1173 | return count 1174 | } 1175 | 1176 | func (pl patternList) diff(l patternList) patternList { 1177 | lAlt := make(patternList, len(l)) 1178 | copy(lAlt, l) 1179 | result := make(patternList, 0, len(pl)) 1180 | for _, v := range pl { 1181 | if v != nil { 1182 | match := false 1183 | for i, w := range lAlt { 1184 | if w.eq(v) { 1185 | match = true 1186 | lAlt[i] = nil 1187 | break 1188 | } 1189 | } 1190 | if match == false { 1191 | result = append(result, v) 1192 | } 1193 | } 1194 | } 1195 | return result 1196 | } 1197 | 1198 | func (pl patternList) double() patternList { 1199 | l := len(pl) 1200 | result := make(patternList, l*2) 1201 | copy(result, pl) 1202 | copy(result[l:2*l], pl) 1203 | return result 1204 | } 1205 | 1206 | func (pl *patternList) remove(p *pattern) { 1207 | (*pl) = pl.diff(patternList{p}) 1208 | } 1209 | 1210 | func (pl patternList) dictionary() map[string]interface{} { 1211 | dict := make(map[string]interface{}) 1212 | for _, a := range pl { 1213 | dict[a.name] = a.value 1214 | } 1215 | return dict 1216 | } 1217 | 1218 | func stringPartition(s, sep string) (string, string, string) { 1219 | sepPos := strings.Index(s, sep) 1220 | if sepPos == -1 { // no seperator found 1221 | return s, "", "" 1222 | } 1223 | split := strings.SplitN(s, sep, 2) 1224 | return split[0], sep, split[1] 1225 | } 1226 | 1227 | // returns true if all cased characters in the string are uppercase 1228 | // and there are there is at least one cased charcter 1229 | func isStringUppercase(s string) bool { 1230 | if strings.ToUpper(s) != s { 1231 | return false 1232 | } 1233 | for _, c := range []rune(s) { 1234 | if unicode.IsUpper(c) { 1235 | return true 1236 | } 1237 | } 1238 | return false 1239 | } 1240 | -------------------------------------------------------------------------------- /docopt_test.go: -------------------------------------------------------------------------------- 1 | /* 2 | Based of off docopt.py: https://github.com/docopt/docopt 3 | 4 | Licensed under terms of MIT license (see LICENSE-MIT) 5 | Copyright (c) 2013 Keith Batten, kbatten@gmail.com 6 | */ 7 | 8 | package docopt 9 | 10 | import ( 11 | "bytes" 12 | "encoding/json" 13 | "fmt" 14 | "io" 15 | "io/ioutil" 16 | "os" 17 | "reflect" 18 | "regexp" 19 | "strings" 20 | "testing" 21 | ) 22 | 23 | func TestPatternFlat(t *testing.T) { 24 | q := patternList{ 25 | newArgument("N", nil), 26 | newOption("-a", "", 0, false), 27 | newArgument("M", nil)} 28 | p, err := newRequired( 29 | newOneOrMore(newArgument("N", nil)), 30 | newOption("-a", "", 0, false), 31 | newArgument("M", nil)).flat(patternDefault) 32 | if reflect.DeepEqual(p, q) != true { 33 | t.Error(err) 34 | } 35 | 36 | q = patternList{newOptionsShortcut()} 37 | p, err = newRequired( 38 | newOptional(newOptionsShortcut()), 39 | newOptional(newOption("-a", "", 0, false))).flat(patternOptionSSHORTCUT) 40 | if reflect.DeepEqual(p, q) != true { 41 | t.Error(err) 42 | } 43 | return 44 | } 45 | 46 | func TestOption(t *testing.T) { 47 | if !parseOption("-h").eq(newOption("-h", "", 0, false)) { 48 | t.Fail() 49 | } 50 | if !parseOption("--help").eq(newOption("", "--help", 0, false)) { 51 | t.Fail() 52 | } 53 | if !parseOption("-h --help").eq(newOption("-h", "--help", 0, false)) { 54 | t.Fail() 55 | } 56 | if !parseOption("-h, --help").eq(newOption("-h", "--help", 0, false)) { 57 | t.Fail() 58 | } 59 | 60 | if !parseOption("-h TOPIC").eq(newOption("-h", "", 1, false)) { 61 | t.Fail() 62 | } 63 | if !parseOption("--help TOPIC").eq(newOption("", "--help", 1, false)) { 64 | t.Fail() 65 | } 66 | if !parseOption("-h TOPIC --help TOPIC").eq(newOption("-h", "--help", 1, false)) { 67 | t.Fail() 68 | } 69 | if !parseOption("-h TOPIC, --help TOPIC").eq(newOption("-h", "--help", 1, false)) { 70 | t.Fail() 71 | } 72 | if !parseOption("-h TOPIC, --help=TOPIC").eq(newOption("-h", "--help", 1, false)) { 73 | t.Fail() 74 | } 75 | 76 | if !parseOption("-h Description...").eq(newOption("-h", "", 0, false)) { 77 | t.Fail() 78 | } 79 | if !parseOption("-h --help Description...").eq(newOption("-h", "--help", 0, false)) { 80 | t.Fail() 81 | } 82 | if !parseOption("-h TOPIC Description...").eq(newOption("-h", "", 1, false)) { 83 | t.Fail() 84 | } 85 | 86 | if !parseOption(" -h").eq(newOption("-h", "", 0, false)) { 87 | t.Fail() 88 | } 89 | 90 | if !parseOption("-h TOPIC Description... [default: 2]").eq(newOption("-h", "", 1, "2")) { 91 | t.Fail() 92 | } 93 | if !parseOption("-h TOPIC Descripton... [default: topic-1]").eq(newOption("-h", "", 1, "topic-1")) { 94 | t.Fail() 95 | } 96 | if !parseOption("--help=TOPIC ... [default: 3.14]").eq(newOption("", "--help", 1, "3.14")) { 97 | t.Fail() 98 | } 99 | if !parseOption("-h, --help=DIR ... [default: ./]").eq(newOption("-h", "--help", 1, "./")) { 100 | t.Fail() 101 | } 102 | if !parseOption("-h TOPIC Descripton... [dEfAuLt: 2]").eq(newOption("-h", "", 1, "2")) { 103 | t.Fail() 104 | } 105 | return 106 | } 107 | 108 | func TestOptionName(t *testing.T) { 109 | if newOption("-h", "", 0, false).name != "-h" { 110 | t.Fail() 111 | } 112 | if newOption("-h", "--help", 0, false).name != "--help" { 113 | t.Fail() 114 | } 115 | if newOption("", "--help", 0, false).name != "--help" { 116 | t.Fail() 117 | } 118 | return 119 | } 120 | 121 | func TestCommands(t *testing.T) { 122 | if v, err := Parse("Usage: prog add", []string{"add"}, true, "", false, false); reflect.DeepEqual(v, map[string]interface{}{"add": true}) != true { 123 | t.Error(err) 124 | } 125 | if v, err := Parse("Usage: prog [add]", []string{}, true, "", false, false); reflect.DeepEqual(v, map[string]interface{}{"add": false}) != true { 126 | t.Error(err) 127 | } 128 | if v, err := Parse("Usage: prog [add]", []string{"add"}, true, "", false, false); reflect.DeepEqual(v, map[string]interface{}{"add": true}) != true { 129 | t.Error(err) 130 | } 131 | if v, err := Parse("Usage: prog (add|rm)", []string{"add"}, true, "", false, false); reflect.DeepEqual(v, map[string]interface{}{"add": true, "rm": false}) != true { 132 | t.Error(err) 133 | } 134 | if v, err := Parse("Usage: prog (add|rm)", []string{"rm"}, true, "", false, false); reflect.DeepEqual(v, map[string]interface{}{"add": false, "rm": true}) != true { 135 | t.Error(err) 136 | } 137 | if v, err := Parse("Usage: prog a b", []string{"a", "b"}, true, "", false, false); reflect.DeepEqual(v, map[string]interface{}{"a": true, "b": true}) != true { 138 | t.Error(err) 139 | } 140 | _, err := Parse("Usage: prog a b", []string{"b", "a"}, true, "", false, false) 141 | if _, ok := err.(*UserError); !ok { 142 | t.Error(err) 143 | } 144 | return 145 | } 146 | 147 | func TestFormalUsage(t *testing.T) { 148 | doc := ` 149 | Usage: prog [-hv] ARG 150 | prog N M 151 | 152 | prog is a program` 153 | usage := parseSection("usage:", doc)[0] 154 | if usage != "Usage: prog [-hv] ARG\n prog N M" { 155 | t.FailNow() 156 | } 157 | formal, err := formalUsage(usage) 158 | if err != nil { 159 | t.Fatal(err) 160 | } 161 | if formal != "( [-hv] ARG ) | ( N M )" { 162 | t.Fail() 163 | } 164 | return 165 | } 166 | 167 | func TestParseArgv(t *testing.T) { 168 | o := patternList{ 169 | newOption("-h", "", 0, false), 170 | newOption("-v", "--verbose", 0, false), 171 | newOption("-f", "--file", 1, false), 172 | } 173 | 174 | p, err := parseArgv(tokenListFromString(""), &o, false) 175 | q := patternList{} 176 | if reflect.DeepEqual(p, q) != true { 177 | t.Error(err) 178 | } 179 | 180 | p, err = parseArgv(tokenListFromString("-h"), &o, false) 181 | q = patternList{newOption("-h", "", 0, true)} 182 | if reflect.DeepEqual(p, q) != true { 183 | t.Error(err) 184 | } 185 | 186 | p, err = parseArgv(tokenListFromString("-h --verbose"), &o, false) 187 | q = patternList{ 188 | newOption("-h", "", 0, true), 189 | newOption("-v", "--verbose", 0, true), 190 | } 191 | if reflect.DeepEqual(p, q) != true { 192 | t.Error(err) 193 | } 194 | 195 | p, err = parseArgv(tokenListFromString("-h --file f.txt"), &o, false) 196 | q = patternList{ 197 | newOption("-h", "", 0, true), 198 | newOption("-f", "--file", 1, "f.txt"), 199 | } 200 | if reflect.DeepEqual(p, q) != true { 201 | t.Error(err) 202 | } 203 | 204 | p, err = parseArgv(tokenListFromString("-h --file f.txt arg"), &o, false) 205 | q = patternList{ 206 | newOption("-h", "", 0, true), 207 | newOption("-f", "--file", 1, "f.txt"), 208 | newArgument("", "arg"), 209 | } 210 | if reflect.DeepEqual(p, q) != true { 211 | t.Error(err) 212 | } 213 | 214 | p, err = parseArgv(tokenListFromString("-h --file f.txt arg arg2"), &o, false) 215 | q = patternList{ 216 | newOption("-h", "", 0, true), 217 | newOption("-f", "--file", 1, "f.txt"), 218 | newArgument("", "arg"), 219 | newArgument("", "arg2"), 220 | } 221 | if reflect.DeepEqual(p, q) != true { 222 | t.Error(err) 223 | } 224 | 225 | p, err = parseArgv(tokenListFromString("-h arg -- -v"), &o, false) 226 | q = patternList{ 227 | newOption("-h", "", 0, true), 228 | newArgument("", "arg"), 229 | newArgument("", "--"), 230 | newArgument("", "-v"), 231 | } 232 | if reflect.DeepEqual(p, q) != true { 233 | t.Error(err) 234 | } 235 | } 236 | 237 | func TestParsePattern(t *testing.T) { 238 | o := patternList{ 239 | newOption("-h", "", 0, false), 240 | newOption("-v", "--verbose", 0, false), 241 | newOption("-f", "--file", 1, false), 242 | } 243 | 244 | p, err := parsePattern("[ -h ]", &o) 245 | q := newRequired(newOptional(newOption("-h", "", 0, false))) 246 | if p.eq(q) != true { 247 | t.Error(err) 248 | } 249 | 250 | p, err = parsePattern("[ ARG ... ]", &o) 251 | q = newRequired(newOptional( 252 | newOneOrMore( 253 | newArgument("ARG", nil)))) 254 | if p.eq(q) != true { 255 | t.Error(err) 256 | } 257 | 258 | p, err = parsePattern("[ -h | -v ]", &o) 259 | q = newRequired( 260 | newOptional( 261 | newEither( 262 | newOption("-h", "", 0, false), 263 | newOption("-v", "--verbose", 0, false)))) 264 | if p.eq(q) != true { 265 | t.Error(err) 266 | } 267 | 268 | p, err = parsePattern("( -h | -v [ --file ] )", &o) 269 | q = newRequired( 270 | newRequired( 271 | newEither( 272 | newOption("-h", "", 0, false), 273 | newRequired( 274 | newOption("-v", "--verbose", 0, false), 275 | newOptional( 276 | newOption("-f", "--file", 1, nil)))))) 277 | if p.eq(q) != true { 278 | t.Error(err) 279 | } 280 | 281 | p, err = parsePattern("(-h|-v[--file=]N...)", &o) 282 | q = newRequired( 283 | newRequired( 284 | newEither( 285 | newOption("-h", "", 0, false), 286 | newRequired( 287 | newOption("-v", "--verbose", 0, false), 288 | newOptional( 289 | newOption("-f", "--file", 1, nil)), 290 | newOneOrMore( 291 | newArgument("N", nil)))))) 292 | if p.eq(q) != true { 293 | t.Error(err) 294 | } 295 | 296 | p, err = parsePattern("(N [M | (K | L)] | O P)", &o) 297 | q = newRequired( 298 | newRequired( 299 | newEither( 300 | newRequired( 301 | newArgument("N", nil), 302 | newOptional( 303 | newEither( 304 | newArgument("M", nil), 305 | newRequired( 306 | newEither( 307 | newArgument("K", nil), 308 | newArgument("L", nil)))))), 309 | newRequired( 310 | newArgument("O", nil), 311 | newArgument("P", nil))))) 312 | if p.eq(q) != true { 313 | t.Error(err) 314 | } 315 | 316 | p, err = parsePattern("[ -h ] [N]", &o) 317 | q = newRequired( 318 | newOptional( 319 | newOption("-h", "", 0, false)), 320 | newOptional( 321 | newArgument("N", nil))) 322 | if p.eq(q) != true { 323 | t.Error(err) 324 | } 325 | 326 | p, err = parsePattern("[options]", &o) 327 | q = newRequired( 328 | newOptional( 329 | newOptionsShortcut())) 330 | if p.eq(q) != true { 331 | t.Error(err) 332 | } 333 | 334 | p, err = parsePattern("[options] A", &o) 335 | q = newRequired( 336 | newOptional( 337 | newOptionsShortcut()), 338 | newArgument("A", nil)) 339 | if p.eq(q) != true { 340 | t.Error(err) 341 | } 342 | 343 | p, err = parsePattern("-v [options]", &o) 344 | q = newRequired( 345 | newOption("-v", "--verbose", 0, false), 346 | newOptional( 347 | newOptionsShortcut())) 348 | if p.eq(q) != true { 349 | t.Error(err) 350 | } 351 | 352 | p, err = parsePattern("ADD", &o) 353 | q = newRequired(newArgument("ADD", nil)) 354 | if p.eq(q) != true { 355 | t.Error(err) 356 | } 357 | 358 | p, err = parsePattern("", &o) 359 | q = newRequired(newArgument("", nil)) 360 | if p.eq(q) != true { 361 | t.Error(err) 362 | } 363 | 364 | p, err = parsePattern("add", &o) 365 | q = newRequired(newCommand("add", false)) 366 | if p.eq(q) != true { 367 | t.Error(err) 368 | } 369 | } 370 | 371 | func TestOptionMatch(t *testing.T) { 372 | v, w, x := newOption("-a", "", 0, false).match( 373 | &patternList{newOption("-a", "", 0, true)}, nil) 374 | y := patternList{newOption("-a", "", 0, true)} 375 | if v != true || 376 | reflect.DeepEqual(*w, patternList{}) != true || 377 | reflect.DeepEqual(*x, y) != true { 378 | t.Fail() 379 | } 380 | 381 | v, w, x = newOption("-a", "", 0, false).match( 382 | &patternList{newOption("-x", "", 0, false)}, nil) 383 | y = patternList{newOption("-x", "", 0, false)} 384 | if v != false || 385 | reflect.DeepEqual(*w, y) != true || 386 | reflect.DeepEqual(*x, patternList{}) != true { 387 | t.Fail() 388 | } 389 | 390 | v, w, x = newOption("-a", "", 0, false).match( 391 | &patternList{newOption("-x", "", 0, false)}, nil) 392 | y = patternList{newOption("-x", "", 0, false)} 393 | if v != false || 394 | reflect.DeepEqual(*w, y) != true || 395 | reflect.DeepEqual(*x, patternList{}) != true { 396 | t.Fail() 397 | } 398 | v, w, x = newOption("-a", "", 0, false).match( 399 | &patternList{newArgument("N", nil)}, nil) 400 | y = patternList{newArgument("N", nil)} 401 | if v != false || 402 | reflect.DeepEqual(*w, y) != true || 403 | reflect.DeepEqual(*x, patternList{}) != true { 404 | t.Fail() 405 | } 406 | 407 | v, w, x = newOption("-a", "", 0, false).match( 408 | &patternList{ 409 | newOption("-x", "", 0, false), 410 | newOption("-a", "", 0, false), 411 | newArgument("N", nil)}, nil) 412 | y = patternList{ 413 | newOption("-x", "", 0, false), 414 | newArgument("N", nil)} 415 | z := patternList{newOption("-a", "", 0, false)} 416 | if v != true || 417 | reflect.DeepEqual(*w, y) != true || 418 | reflect.DeepEqual(*x, z) != true { 419 | t.Fail() 420 | } 421 | 422 | v, w, x = newOption("-a", "", 0, false).match( 423 | &patternList{ 424 | newOption("-a", "", 0, true), 425 | newOption("-a", "", 0, false)}, nil) 426 | y = patternList{newOption("-a", "", 0, false)} 427 | z = patternList{newOption("-a", "", 0, true)} 428 | if v != true || 429 | reflect.DeepEqual(*w, y) != true || 430 | reflect.DeepEqual(*x, z) != true { 431 | t.Fail() 432 | } 433 | } 434 | 435 | func TestArgumentMatch(t *testing.T) { 436 | v, w, x := newArgument("N", nil).match( 437 | &patternList{newArgument("N", 9)}, nil) 438 | y := patternList{newArgument("N", 9)} 439 | if v != true || 440 | reflect.DeepEqual(*w, patternList{}) != true || 441 | reflect.DeepEqual(*x, y) != true { 442 | t.Fail() 443 | } 444 | 445 | v, w, x = newArgument("N", nil).match( 446 | &patternList{newOption("-x", "", 0, false)}, nil) 447 | y = patternList{newOption("-x", "", 0, false)} 448 | if v != false || 449 | reflect.DeepEqual(*w, y) != true || 450 | reflect.DeepEqual(*x, patternList{}) != true { 451 | t.Fail() 452 | } 453 | 454 | v, w, x = newArgument("N", nil).match( 455 | &patternList{newOption("-x", "", 0, false), 456 | newOption("-a", "", 0, false), 457 | newArgument("", 5)}, nil) 458 | y = patternList{newOption("-x", "", 0, false), 459 | newOption("-a", "", 0, false)} 460 | z := patternList{newArgument("N", 5)} 461 | if v != true || 462 | reflect.DeepEqual(*w, y) != true || 463 | reflect.DeepEqual(*x, z) != true { 464 | t.Fail() 465 | } 466 | 467 | v, w, x = newArgument("N", nil).match( 468 | &patternList{newArgument("", 9), 469 | newArgument("", 0)}, nil) 470 | y = patternList{newArgument("", 0)} 471 | z = patternList{newArgument("N", 9)} 472 | if v != true || 473 | reflect.DeepEqual(*w, y) != true || 474 | reflect.DeepEqual(*x, z) != true { 475 | t.Fail() 476 | } 477 | } 478 | 479 | func TestCommandMatch(t *testing.T) { 480 | v, w, x := newCommand("c", false).match( 481 | &patternList{newArgument("", "c")}, nil) 482 | y := patternList{newCommand("c", true)} 483 | if v != true || 484 | reflect.DeepEqual(*w, patternList{}) != true || 485 | reflect.DeepEqual(*x, y) != true { 486 | t.Fail() 487 | } 488 | 489 | v, w, x = newCommand("c", false).match( 490 | &patternList{newOption("-x", "", 0, false)}, nil) 491 | y = patternList{newOption("-x", "", 0, false)} 492 | if v != false || 493 | reflect.DeepEqual(*w, y) != true || 494 | reflect.DeepEqual(*x, patternList{}) != true { 495 | t.Fail() 496 | } 497 | 498 | v, w, x = newCommand("c", false).match( 499 | &patternList{ 500 | newOption("-x", "", 0, false), 501 | newOption("-a", "", 0, false), 502 | newArgument("", "c")}, nil) 503 | y = patternList{newOption("-x", "", 0, false), 504 | newOption("-a", "", 0, false)} 505 | z := patternList{newCommand("c", true)} 506 | if v != true || 507 | reflect.DeepEqual(*w, y) != true || 508 | reflect.DeepEqual(*x, z) != true { 509 | t.Fail() 510 | } 511 | 512 | v, w, x = newEither( 513 | newCommand("add", false), 514 | newCommand("rm", false)).match( 515 | &patternList{newArgument("", "rm")}, nil) 516 | y = patternList{newCommand("rm", true)} 517 | if v != true || 518 | reflect.DeepEqual(*w, patternList{}) != true || 519 | reflect.DeepEqual(*x, y) != true { 520 | t.Fail() 521 | } 522 | } 523 | 524 | func TestOptionalMatch(t *testing.T) { 525 | v, w, x := newOptional(newOption("-a", "", 0, false)).match( 526 | &patternList{newOption("-a", "", 0, false)}, nil) 527 | y := patternList{newOption("-a", "", 0, false)} 528 | if v != true || 529 | reflect.DeepEqual(*w, patternList{}) != true || 530 | reflect.DeepEqual(*x, y) != true { 531 | t.Fail() 532 | } 533 | 534 | v, w, x = newOptional(newOption("-a", "", 0, false)).match( 535 | &patternList{}, nil) 536 | if v != true || 537 | reflect.DeepEqual(*w, patternList{}) != true || 538 | reflect.DeepEqual(*x, patternList{}) != true { 539 | t.Fail() 540 | } 541 | 542 | v, w, x = newOptional(newOption("-a", "", 0, false)).match( 543 | &patternList{newOption("-x", "", 0, false)}, nil) 544 | y = patternList{newOption("-x", "", 0, false)} 545 | if v != true || 546 | reflect.DeepEqual(*w, y) != true || 547 | reflect.DeepEqual(*x, patternList{}) != true { 548 | t.Fail() 549 | } 550 | 551 | v, w, x = newOptional(newOption("-a", "", 0, false), 552 | newOption("-b", "", 0, false)).match( 553 | &patternList{newOption("-a", "", 0, false)}, nil) 554 | y = patternList{newOption("-a", "", 0, false)} 555 | if v != true || 556 | reflect.DeepEqual(*w, patternList{}) != true || 557 | reflect.DeepEqual(*x, y) != true { 558 | t.Fail() 559 | } 560 | 561 | v, w, x = newOptional(newOption("-a", "", 0, false), 562 | newOption("-b", "", 0, false)).match( 563 | &patternList{newOption("-b", "", 0, false)}, nil) 564 | y = patternList{newOption("-b", "", 0, false)} 565 | if v != true || 566 | reflect.DeepEqual(*w, patternList{}) != true || 567 | reflect.DeepEqual(*x, y) != true { 568 | t.Fail() 569 | } 570 | 571 | v, w, x = newOptional(newOption("-a", "", 0, false), 572 | newOption("-b", "", 0, false)).match( 573 | &patternList{newOption("-x", "", 0, false)}, nil) 574 | y = patternList{newOption("-x", "", 0, false)} 575 | if v != true || 576 | reflect.DeepEqual(*w, y) != true || 577 | reflect.DeepEqual(*x, patternList{}) != true { 578 | t.Fail() 579 | } 580 | 581 | v, w, x = newOptional(newArgument("N", nil)).match( 582 | &patternList{newArgument("", 9)}, nil) 583 | y = patternList{newArgument("N", 9)} 584 | if v != true || 585 | reflect.DeepEqual(*w, patternList{}) != true || 586 | reflect.DeepEqual(*x, y) != true { 587 | t.Fail() 588 | } 589 | 590 | v, w, x = newOptional(newOption("-a", "", 0, false), 591 | newOption("-b", "", 0, false)).match( 592 | &patternList{newOption("-b", "", 0, false), 593 | newOption("-x", "", 0, false), 594 | newOption("-a", "", 0, false)}, nil) 595 | y = patternList{newOption("-x", "", 0, false)} 596 | z := patternList{newOption("-a", "", 0, false), 597 | newOption("-b", "", 0, false)} 598 | if v != true || 599 | reflect.DeepEqual(*w, y) != true || 600 | reflect.DeepEqual(*x, z) != true { 601 | t.Fail() 602 | } 603 | } 604 | 605 | func TestRequiredMatch(t *testing.T) { 606 | v, w, x := newRequired(newOption("-a", "", 0, false)).match( 607 | &patternList{newOption("-a", "", 0, false)}, nil) 608 | y := patternList{newOption("-a", "", 0, false)} 609 | if v != true || 610 | reflect.DeepEqual(*w, patternList{}) != true || 611 | reflect.DeepEqual(*x, y) != true { 612 | t.Fail() 613 | } 614 | 615 | v, w, x = newRequired(newOption("-a", "", 0, false)).match(&patternList{}, nil) 616 | if v != false || 617 | reflect.DeepEqual(*w, patternList{}) != true || 618 | reflect.DeepEqual(*x, patternList{}) != true { 619 | t.Fail() 620 | } 621 | 622 | v, w, x = newRequired(newOption("-a", "", 0, false)).match( 623 | &patternList{newOption("-x", "", 0, false)}, nil) 624 | y = patternList{newOption("-x", "", 0, false)} 625 | if v != false || 626 | reflect.DeepEqual(*w, y) != true || 627 | reflect.DeepEqual(*x, patternList{}) != true { 628 | t.Fail() 629 | } 630 | v, w, x = newRequired(newOption("-a", "", 0, false), 631 | newOption("-b", "", 0, false)).match( 632 | &patternList{newOption("-a", "", 0, false)}, nil) 633 | y = patternList{newOption("-a", "", 0, false)} 634 | if v != false || 635 | reflect.DeepEqual(*w, y) != true || 636 | reflect.DeepEqual(*x, patternList{}) != true { 637 | t.Fail() 638 | } 639 | } 640 | 641 | func TestEitherMatch(t *testing.T) { 642 | v, w, x := newEither( 643 | newOption("-a", "", 0, false), 644 | newOption("-b", "", 0, false)).match( 645 | &patternList{newOption("-a", "", 0, false)}, nil) 646 | y := patternList{newOption("-a", "", 0, false)} 647 | if v != true || 648 | reflect.DeepEqual(*w, patternList{}) != true || 649 | reflect.DeepEqual(*x, y) != true { 650 | t.Fail() 651 | } 652 | 653 | v, w, x = newEither( 654 | newOption("-a", "", 0, false), 655 | newOption("-b", "", 0, false)).match(&patternList{ 656 | newOption("-a", "", 0, false), 657 | newOption("-b", "", 0, false)}, nil) 658 | y = patternList{newOption("-b", "", 0, false)} 659 | z := patternList{newOption("-a", "", 0, false)} 660 | if v != true || 661 | reflect.DeepEqual(*w, y) != true || 662 | reflect.DeepEqual(*x, z) != true { 663 | t.Fail() 664 | } 665 | 666 | v, w, x = newEither( 667 | newOption("-a", "", 0, false), 668 | newOption("-b", "", 0, false)).match(&patternList{ 669 | newOption("-x", "", 0, false)}, nil) 670 | y = patternList{newOption("-x", "", 0, false)} 671 | z = patternList{} 672 | if v != false || 673 | reflect.DeepEqual(*w, y) != true || 674 | reflect.DeepEqual(*x, z) != true { 675 | t.Fail() 676 | } 677 | 678 | v, w, x = newEither( 679 | newOption("-a", "", 0, false), 680 | newOption("-b", "", 0, false), 681 | newOption("-c", "", 0, false)).match(&patternList{ 682 | newOption("-x", "", 0, false), 683 | newOption("-b", "", 0, false)}, nil) 684 | y = patternList{newOption("-x", "", 0, false)} 685 | z = patternList{newOption("-b", "", 0, false)} 686 | if v != true || 687 | reflect.DeepEqual(*w, y) != true || 688 | reflect.DeepEqual(*x, z) != true { 689 | t.Fail() 690 | } 691 | v, w, x = newEither( 692 | newArgument("M", nil), 693 | newRequired(newArgument("N", nil), 694 | newArgument("M", nil))).match(&patternList{ 695 | newArgument("", 1), 696 | newArgument("", 2)}, nil) 697 | y = patternList{} 698 | z = patternList{newArgument("N", 1), newArgument("M", 2)} 699 | if v != true || 700 | reflect.DeepEqual(*w, y) != true || 701 | reflect.DeepEqual(*x, z) != true { 702 | t.Fail() 703 | } 704 | } 705 | 706 | func TestOneOrMoreMatch(t *testing.T) { 707 | v, w, x := newOneOrMore(newArgument("N", nil)).match( 708 | &patternList{newArgument("", 9)}, nil) 709 | y := patternList{newArgument("N", 9)} 710 | if v != true || 711 | reflect.DeepEqual(*w, patternList{}) != true || 712 | reflect.DeepEqual(*x, y) != true { 713 | t.Fail() 714 | } 715 | 716 | v, w, x = newOneOrMore(newArgument("N", nil)).match( 717 | &patternList{}, nil) 718 | y = patternList{} 719 | z := patternList{} 720 | if v != false || 721 | reflect.DeepEqual(*w, y) != true || 722 | reflect.DeepEqual(*x, z) != true { 723 | t.Fail() 724 | } 725 | 726 | v, w, x = newOneOrMore(newArgument("N", nil)).match( 727 | &patternList{newOption("-x", "", 0, false)}, nil) 728 | y = patternList{newOption("-x", "", 0, false)} 729 | z = patternList{} 730 | if v != false || 731 | reflect.DeepEqual(*w, y) != true || 732 | reflect.DeepEqual(*x, z) != true { 733 | t.Fail() 734 | } 735 | 736 | v, w, x = newOneOrMore(newArgument("N", nil)).match( 737 | &patternList{newArgument("", 9), newArgument("", 8)}, nil) 738 | y = patternList{} 739 | z = patternList{newArgument("N", 9), newArgument("N", 8)} 740 | if v != true || 741 | reflect.DeepEqual(*w, y) != true || 742 | reflect.DeepEqual(*x, z) != true { 743 | t.Fail() 744 | } 745 | 746 | v, w, x = newOneOrMore(newArgument("N", nil)).match(&patternList{ 747 | newArgument("", 9), 748 | newOption("-x", "", 0, false), 749 | newArgument("", 8)}, nil) 750 | y = patternList{newOption("-x", "", 0, false)} 751 | z = patternList{newArgument("N", 9), newArgument("N", 8)} 752 | if v != true || 753 | reflect.DeepEqual(*w, y) != true || 754 | reflect.DeepEqual(*x, z) != true { 755 | t.Fail() 756 | } 757 | 758 | v, w, x = newOneOrMore(newOption("-a", "", 0, false)).match(&patternList{ 759 | newOption("-a", "", 0, false), 760 | newArgument("", 8), 761 | newOption("-a", "", 0, false)}, nil) 762 | y = patternList{newArgument("", 8)} 763 | z = patternList{newOption("-a", "", 0, false), newOption("-a", "", 0, false)} 764 | if v != true || 765 | reflect.DeepEqual(*w, y) != true || 766 | reflect.DeepEqual(*x, z) != true { 767 | t.Fail() 768 | } 769 | 770 | v, w, x = newOneOrMore(newOption("-a", "", 0, false)).match(&patternList{ 771 | newArgument("", 8), 772 | newOption("-x", "", 0, false)}, nil) 773 | y = patternList{newArgument("", 8), newOption("-x", "", 0, false)} 774 | z = patternList{} 775 | if v != false || 776 | reflect.DeepEqual(*w, y) != true || 777 | reflect.DeepEqual(*x, z) != true { 778 | t.Fail() 779 | } 780 | 781 | v, w, x = newOneOrMore(newRequired(newOption("-a", "", 0, false), 782 | newArgument("N", nil))).match(&patternList{ 783 | newOption("-a", "", 0, false), 784 | newArgument("", 1), 785 | newOption("-x", "", 0, false), 786 | newOption("-a", "", 0, false), 787 | newArgument("", 2)}, nil) 788 | y = patternList{newOption("-x", "", 0, false)} 789 | z = patternList{newOption("-a", "", 0, false), 790 | newArgument("N", 1), 791 | newOption("-a", "", 0, false), 792 | newArgument("N", 2)} 793 | if v != true || 794 | reflect.DeepEqual(*w, y) != true || 795 | reflect.DeepEqual(*x, z) != true { 796 | t.Fail() 797 | } 798 | 799 | v, w, x = newOneOrMore(newOptional(newArgument("N", nil))).match( 800 | &patternList{newArgument("", 9)}, nil) 801 | y = patternList{} 802 | z = patternList{newArgument("N", 9)} 803 | if v != true || 804 | reflect.DeepEqual(*w, y) != true || 805 | reflect.DeepEqual(*x, z) != true { 806 | t.Fail() 807 | } 808 | } 809 | 810 | func TestListArgumentMatch(t *testing.T) { 811 | p := newRequired( 812 | newArgument("N", nil), 813 | newArgument("N", nil)) 814 | p.fix() 815 | v, w, x := p.match(&patternList{newArgument("", "1"), 816 | newArgument("", "2")}, nil) 817 | y := patternList{newArgument("N", []string{"1", "2"})} 818 | if v != true || 819 | reflect.DeepEqual(*w, patternList{}) != true || 820 | reflect.DeepEqual(*x, y) != true { 821 | t.Fail() 822 | } 823 | 824 | p = newOneOrMore(newArgument("N", nil)) 825 | p.fix() 826 | v, w, x = p.match(&patternList{newArgument("", "1"), 827 | newArgument("", "2"), newArgument("", "3")}, nil) 828 | y = patternList{newArgument("N", []string{"1", "2", "3"})} 829 | if v != true || 830 | reflect.DeepEqual(*w, patternList{}) != true || 831 | reflect.DeepEqual(*x, y) != true { 832 | t.Fail() 833 | } 834 | 835 | p = newRequired(newArgument("N", nil), 836 | newOneOrMore(newArgument("N", nil))) 837 | p.fix() 838 | v, w, x = p.match(&patternList{ 839 | newArgument("", "1"), 840 | newArgument("", "2"), 841 | newArgument("", "3")}, nil) 842 | y = patternList{newArgument("N", []string{"1", "2", "3"})} 843 | if v != true || 844 | reflect.DeepEqual(*w, patternList{}) != true || 845 | reflect.DeepEqual(*x, y) != true { 846 | t.Fail() 847 | } 848 | 849 | p = newRequired(newArgument("N", nil), 850 | newRequired(newArgument("N", nil))) 851 | p.fix() 852 | v, w, x = p.match(&patternList{ 853 | newArgument("", "1"), 854 | newArgument("", "2")}, nil) 855 | y = patternList{newArgument("N", []string{"1", "2"})} 856 | if v != true || 857 | reflect.DeepEqual(*w, patternList{}) != true || 858 | reflect.DeepEqual(*x, y) != true { 859 | t.Fail() 860 | } 861 | } 862 | 863 | func TestBasicPatternMatching(t *testing.T) { 864 | // ( -a N [ -x Z ] ) 865 | p := newRequired( 866 | newOption("-a", "", 0, false), 867 | newArgument("N", nil), 868 | newOptional( 869 | newOption("-x", "", 0, false), 870 | newArgument("Z", nil))) 871 | 872 | // -a N 873 | q := patternList{newOption("-a", "", 0, false), newArgument("", 9)} 874 | y := patternList{newOption("-a", "", 0, false), newArgument("N", 9)} 875 | v, w, x := p.match(&q, nil) 876 | if v != true || 877 | reflect.DeepEqual(*w, patternList{}) != true || 878 | reflect.DeepEqual(*x, y) != true { 879 | t.Fail() 880 | } 881 | 882 | // -a -x N Z 883 | q = patternList{newOption("-a", "", 0, false), 884 | newOption("-x", "", 0, false), 885 | newArgument("", 9), newArgument("", 5)} 886 | y = patternList{} 887 | z := patternList{newOption("-a", "", 0, false), newArgument("N", 9), 888 | newOption("-x", "", 0, false), newArgument("Z", 5)} 889 | v, w, x = p.match(&q, nil) 890 | if v != true || 891 | reflect.DeepEqual(*w, y) != true || 892 | reflect.DeepEqual(*x, z) != true { 893 | t.Fail() 894 | } 895 | 896 | // -x N Z # BZZ! 897 | q = patternList{newOption("-x", "", 0, false), 898 | newArgument("", 9), newArgument("", 5)} 899 | y = patternList{newOption("-x", "", 0, false), 900 | newArgument("", 9), newArgument("", 5)} 901 | z = patternList{} 902 | v, w, x = p.match(&q, nil) 903 | if v != false || 904 | reflect.DeepEqual(*w, y) != true || 905 | reflect.DeepEqual(*x, z) != true { 906 | t.Fail() 907 | } 908 | } 909 | 910 | func TestPatternEither(t *testing.T) { 911 | p := newOption("-a", "", 0, false).transform() 912 | q := newEither(newRequired( 913 | newOption("-a", "", 0, false))) 914 | if p.eq(q) != true { 915 | t.Fail() 916 | } 917 | 918 | p = newArgument("A", nil).transform() 919 | q = newEither(newRequired( 920 | newArgument("A", nil))) 921 | if p.eq(q) != true { 922 | t.Fail() 923 | } 924 | 925 | p = newRequired( 926 | newEither( 927 | newOption("-a", "", 0, false), 928 | newOption("-b", "", 0, false)), 929 | newOption("-c", "", 0, false)).transform() 930 | q = newEither( 931 | newRequired( 932 | newOption("-a", "", 0, false), 933 | newOption("-c", "", 0, false)), 934 | newRequired( 935 | newOption("-b", "", 0, false), 936 | newOption("-c", "", 0, false))) 937 | if p.eq(q) != true { 938 | t.Fail() 939 | } 940 | 941 | p = newOptional(newOption("-a", "", 0, false), 942 | newEither(newOption("-b", "", 0, false), 943 | newOption("-c", "", 0, false))).transform() 944 | q = newEither( 945 | newRequired( 946 | newOption("-b", "", 0, false), newOption("-a", "", 0, false)), 947 | newRequired( 948 | newOption("-c", "", 0, false), newOption("-a", "", 0, false))) 949 | if p.eq(q) != true { 950 | t.Fail() 951 | } 952 | 953 | p = newEither(newOption("-x", "", 0, false), 954 | newEither(newOption("-y", "", 0, false), 955 | newOption("-z", "", 0, false))).transform() 956 | q = newEither( 957 | newRequired(newOption("-x", "", 0, false)), 958 | newRequired(newOption("-y", "", 0, false)), 959 | newRequired(newOption("-z", "", 0, false))) 960 | if p.eq(q) != true { 961 | t.Fail() 962 | } 963 | 964 | p = newOneOrMore(newArgument("N", nil), 965 | newArgument("M", nil)).transform() 966 | q = newEither( 967 | newRequired(newArgument("N", nil), newArgument("M", nil), 968 | newArgument("N", nil), newArgument("M", nil))) 969 | if p.eq(q) != true { 970 | t.Fail() 971 | } 972 | } 973 | 974 | func TestPatternFixRepeatingArguments(t *testing.T) { 975 | p := newOption("-a", "", 0, false) 976 | p.fixRepeatingArguments() 977 | if p.eq(newOption("-a", "", 0, false)) != true { 978 | t.Fail() 979 | } 980 | 981 | p = newArgument("N", nil) 982 | p.fixRepeatingArguments() 983 | if p.eq(newArgument("N", nil)) != true { 984 | t.Fail() 985 | } 986 | 987 | p = newRequired( 988 | newArgument("N", nil), 989 | newArgument("N", nil)) 990 | q := newRequired( 991 | newArgument("N", []string{}), 992 | newArgument("N", []string{})) 993 | p.fixRepeatingArguments() 994 | if p.eq(q) != true { 995 | t.Fail() 996 | } 997 | 998 | p = newEither( 999 | newArgument("N", nil), 1000 | newOneOrMore(newArgument("N", nil))) 1001 | q = newEither( 1002 | newArgument("N", []string{}), 1003 | newOneOrMore(newArgument("N", []string{}))) 1004 | p.fix() 1005 | if p.eq(q) != true { 1006 | t.Fail() 1007 | } 1008 | } 1009 | 1010 | func TestSet(t *testing.T) { 1011 | p := newArgument("N", nil) 1012 | q := newArgument("N", nil) 1013 | if reflect.DeepEqual(p, q) != true { 1014 | t.Fail() 1015 | } 1016 | pl := patternList{newArgument("N", nil), newArgument("N", nil)} 1017 | ql := patternList{newArgument("N", nil)} 1018 | if reflect.DeepEqual(pl.unique(), ql.unique()) != true { 1019 | t.Fail() 1020 | } 1021 | } 1022 | 1023 | func TestPatternFixIdentities1(t *testing.T) { 1024 | p := newRequired( 1025 | newArgument("N", nil), 1026 | newArgument("N", nil)) 1027 | if len(p.children) < 2 { 1028 | t.FailNow() 1029 | } 1030 | if p.children[0].eq(p.children[1]) != true { 1031 | t.Fail() 1032 | } 1033 | if p.children[0] == p.children[1] { 1034 | t.Fail() 1035 | } 1036 | p.fixIdentities(nil) 1037 | if p.children[0] != p.children[1] { 1038 | t.Fail() 1039 | } 1040 | } 1041 | 1042 | func TestPatternFixIdentities2(t *testing.T) { 1043 | p := newRequired( 1044 | newOptional( 1045 | newArgument("X", nil), 1046 | newArgument("N", nil)), 1047 | newArgument("N", nil)) 1048 | if len(p.children) < 2 { 1049 | t.FailNow() 1050 | } 1051 | if len(p.children[0].children) < 2 { 1052 | t.FailNow() 1053 | } 1054 | if p.children[0].children[1].eq(p.children[1]) != true { 1055 | t.Fail() 1056 | } 1057 | if p.children[0].children[1] == p.children[1] { 1058 | t.Fail() 1059 | } 1060 | p.fixIdentities(nil) 1061 | if p.children[0].children[1] != p.children[1] { 1062 | t.Fail() 1063 | } 1064 | } 1065 | 1066 | func TestLongOptionsErrorHandling(t *testing.T) { 1067 | _, err := Parse("Usage: prog", []string{"--non-existent"}, true, "", false, false) 1068 | if _, ok := err.(*UserError); !ok { 1069 | t.Error(fmt.Sprintf("(%s) %s", reflect.TypeOf(err), err)) 1070 | } 1071 | _, err = Parse("Usage: prog [--version --verbose]\nOptions: --version\n --verbose", 1072 | []string{"--ver"}, true, "", false, false) 1073 | if _, ok := err.(*UserError); !ok { 1074 | t.Error(err) 1075 | } 1076 | _, err = Parse("Usage: prog --long\nOptions: --long ARG", []string{}, true, "", false, false) 1077 | if _, ok := err.(*LanguageError); !ok { 1078 | t.Error(err) 1079 | } 1080 | _, err = Parse("Usage: prog --long ARG\nOptions: --long ARG", 1081 | []string{"--long"}, true, "", false, false) 1082 | if _, ok := err.(*UserError); !ok { 1083 | t.Error(fmt.Sprintf("(%s) %s", reflect.TypeOf(err), err)) 1084 | } 1085 | _, err = Parse("Usage: prog --long=ARG\nOptions: --long", []string{}, true, "", false, false) 1086 | if _, ok := err.(*LanguageError); !ok { 1087 | t.Error(err) 1088 | } 1089 | _, err = Parse("Usage: prog --long\nOptions: --long", 1090 | []string{}, true, "--long=ARG", false, false) 1091 | if _, ok := err.(*UserError); !ok { 1092 | t.Error(err) 1093 | } 1094 | } 1095 | 1096 | func TestShortOptionsErrorHandling(t *testing.T) { 1097 | _, err := Parse("Usage: prog -x\nOptions: -x this\n -x that", []string{}, true, "", false, false) 1098 | if _, ok := err.(*LanguageError); !ok { 1099 | t.Error(fmt.Sprintf("(%s) %s", reflect.TypeOf(err), err)) 1100 | } 1101 | _, err = Parse("Usage: prog", []string{"-x"}, true, "", false, false) 1102 | if _, ok := err.(*UserError); !ok { 1103 | t.Error(err) 1104 | } 1105 | _, err = Parse("Usage: prog -o\nOptions: -o ARG", []string{}, true, "", false, false) 1106 | if _, ok := err.(*LanguageError); !ok { 1107 | t.Error(err) 1108 | } 1109 | _, err = Parse("Usage: prog -o ARG\nOptions: -o ARG", []string{"-o"}, true, "", false, false) 1110 | if _, ok := err.(*UserError); !ok { 1111 | t.Error(err) 1112 | } 1113 | } 1114 | 1115 | func TestMatchingParen(t *testing.T) { 1116 | _, err := Parse("Usage: prog [a [b]", []string{}, true, "", false, false) 1117 | if _, ok := err.(*LanguageError); !ok { 1118 | t.Error(err) 1119 | } 1120 | _, err = Parse("Usage: prog [a [b] ] c )", []string{}, true, "", false, false) 1121 | if _, ok := err.(*LanguageError); !ok { 1122 | t.Error(err) 1123 | } 1124 | } 1125 | 1126 | func TestAllowDoubleDash(t *testing.T) { 1127 | if v, err := Parse("usage: prog [-o] [--] \noptions: -o", []string{"--", "-o"}, true, "", false, false); reflect.DeepEqual(v, map[string]interface{}{"-o": false, "": "-o", "--": true}) != true { 1128 | t.Error(err) 1129 | } 1130 | if v, err := Parse("usage: prog [-o] [--] \noptions: -o", []string{"-o", "1"}, true, "", false, false); reflect.DeepEqual(v, map[string]interface{}{"-o": true, "": "1", "--": false}) != true { 1131 | t.Error(err) 1132 | } 1133 | _, err := Parse("usage: prog [-o] \noptions:-o", []string{"-o"}, true, "", false, false) 1134 | if _, ok := err.(*UserError); !ok { //"--" is not allowed; FIXME? 1135 | t.Error(err) 1136 | } 1137 | } 1138 | 1139 | func TestDocopt(t *testing.T) { 1140 | doc := `Usage: prog [-v] A 1141 | 1142 | Options: -v Be verbose.` 1143 | if v, err := Parse(doc, []string{"arg"}, true, "", false, false); reflect.DeepEqual(v, map[string]interface{}{"-v": false, "A": "arg"}) != true { 1144 | t.Error(err) 1145 | } 1146 | if v, err := Parse(doc, []string{"-v", "arg"}, true, "", false, false); reflect.DeepEqual(v, map[string]interface{}{"-v": true, "A": "arg"}) != true { 1147 | t.Error(err) 1148 | } 1149 | 1150 | doc = `Usage: prog [-vqr] [FILE] 1151 | prog INPUT OUTPUT 1152 | prog --help 1153 | 1154 | Options: 1155 | -v print status messages 1156 | -q report only file names 1157 | -r show all occurrences of the same error 1158 | --help 1159 | 1160 | ` 1161 | if v, err := Parse(doc, []string{"-v", "file.py"}, true, "", false, false); reflect.DeepEqual(v, map[string]interface{}{"-v": true, "-q": false, "-r": false, "--help": false, "FILE": "file.py", "INPUT": nil, "OUTPUT": nil}) != true { 1162 | t.Error(err) 1163 | } 1164 | if v, err := Parse(doc, []string{"-v"}, true, "", false, false); reflect.DeepEqual(v, map[string]interface{}{"-v": true, "-q": false, "-r": false, "--help": false, "FILE": nil, "INPUT": nil, "OUTPUT": nil}) != true { 1165 | t.Error(err) 1166 | } 1167 | 1168 | _, err := Parse(doc, []string{"-v", "input.py", "output.py"}, true, "", false, false) // does not match 1169 | if _, ok := err.(*UserError); !ok { 1170 | t.Error(err) 1171 | } 1172 | _, err = Parse(doc, []string{"--fake"}, true, "", false, false) 1173 | if _, ok := err.(*UserError); !ok { 1174 | t.Error(err) 1175 | } 1176 | _, output, err := parseOutput(doc, []string{"--hel"}, true, "", false) 1177 | if err != nil || len(output) == 0 { 1178 | t.Error(err) 1179 | } 1180 | } 1181 | 1182 | func TestLanguageErrors(t *testing.T) { 1183 | _, err := Parse("no usage with colon here", []string{}, true, "", false, false) 1184 | if _, ok := err.(*LanguageError); !ok { 1185 | t.Error(err) 1186 | } 1187 | _, err = Parse("usage: here \n\n and again usage: here", []string{}, true, "", false, false) 1188 | if _, ok := err.(*LanguageError); !ok { 1189 | t.Error(err) 1190 | } 1191 | } 1192 | 1193 | func TestIssue40(t *testing.T) { 1194 | _, output, err := parseOutput("usage: prog --help-commands | --help", []string{"--help"}, true, "", false) 1195 | if err != nil || len(output) == 0 { 1196 | t.Error(err) 1197 | } 1198 | if v, err := Parse("usage: prog --aabb | --aa", []string{"--aa"}, true, "", false, false); reflect.DeepEqual(v, map[string]interface{}{"--aabb": false, "--aa": true}) != true { 1199 | t.Error(err) 1200 | } 1201 | } 1202 | 1203 | func TestIssue34UnicodeStrings(t *testing.T) { 1204 | // TODO: see if applicable 1205 | } 1206 | 1207 | func TestCountMultipleFlags(t *testing.T) { 1208 | if v, err := Parse("usage: prog [-v]", []string{"-v"}, true, "", false, false); reflect.DeepEqual(v, map[string]interface{}{"-v": true}) != true { 1209 | t.Error(err) 1210 | } 1211 | if v, err := Parse("usage: prog [-vv]", []string{}, true, "", false, false); reflect.DeepEqual(v, map[string]interface{}{"-v": 0}) != true { 1212 | t.Error(err) 1213 | } 1214 | if v, err := Parse("usage: prog [-vv]", []string{"-v"}, true, "", false, false); reflect.DeepEqual(v, map[string]interface{}{"-v": 1}) != true { 1215 | t.Error(err) 1216 | } 1217 | if v, err := Parse("usage: prog [-vv]", []string{"-vv"}, true, "", false, false); reflect.DeepEqual(v, map[string]interface{}{"-v": 2}) != true { 1218 | t.Error(err) 1219 | } 1220 | _, err := Parse("usage: prog [-vv]", []string{"-vvv"}, true, "", false, false) 1221 | if _, ok := err.(*UserError); !ok { 1222 | t.Error(err) 1223 | } 1224 | if v, err := Parse("usage: prog [-v | -vv | -vvv]", []string{"-vvv"}, true, "", false, false); reflect.DeepEqual(v, map[string]interface{}{"-v": 3}) != true { 1225 | t.Error(err) 1226 | } 1227 | if v, err := Parse("usage: prog [-v...]", []string{"-vvvvvv"}, true, "", false, false); reflect.DeepEqual(v, map[string]interface{}{"-v": 6}) != true { 1228 | t.Error(err) 1229 | } 1230 | if v, err := Parse("usage: prog [--ver --ver]", []string{"--ver", "--ver"}, true, "", false, false); reflect.DeepEqual(v, map[string]interface{}{"--ver": 2}) != true { 1231 | t.Error(err) 1232 | } 1233 | } 1234 | 1235 | func TestAnyOptionsParameter(t *testing.T) { 1236 | _, err := Parse("usage: prog [options]", 1237 | []string{"-foo", "--bar", "--spam=eggs"}, true, "", false, false) 1238 | if _, ok := err.(*UserError); !ok { 1239 | t.Fail() 1240 | } 1241 | 1242 | _, err = Parse("usage: prog [options]", 1243 | []string{"--foo", "--bar", "--bar"}, true, "", false, false) 1244 | if _, ok := err.(*UserError); !ok { 1245 | t.Fail() 1246 | } 1247 | _, err = Parse("usage: prog [options]", 1248 | []string{"--bar", "--bar", "--bar", "-ffff"}, true, "", false, false) 1249 | if _, ok := err.(*UserError); !ok { 1250 | t.Fail() 1251 | } 1252 | _, err = Parse("usage: prog [options]", 1253 | []string{"--long=arg", "--long=another"}, true, "", false, false) 1254 | if _, ok := err.(*UserError); !ok { 1255 | t.Fail() 1256 | } 1257 | } 1258 | 1259 | func TestDefaultValueForPositionalArguments(t *testing.T) { 1260 | doc := "Usage: prog [--data=...]\nOptions:\n\t-d --data= Input data [default: x]" 1261 | if v, err := Parse(doc, []string{}, true, "", false, false); reflect.DeepEqual(v, map[string]interface{}{"--data": []string{"x"}}) != true { 1262 | t.Error(err) 1263 | } 1264 | 1265 | doc = "Usage: prog [--data=...]\nOptions:\n\t-d --data= Input data [default: x y]" 1266 | if v, err := Parse(doc, []string{}, true, "", false, false); reflect.DeepEqual(v, map[string]interface{}{"--data": []string{"x", "y"}}) != true { 1267 | t.Error(err) 1268 | } 1269 | 1270 | doc = "Usage: prog [--data=...]\nOptions:\n\t-d --data= Input data [default: x y]" 1271 | if v, err := Parse(doc, []string{"--data=this"}, true, "", false, false); reflect.DeepEqual(v, map[string]interface{}{"--data": []string{"this"}}) != true { 1272 | t.Error(err) 1273 | } 1274 | } 1275 | 1276 | func TestIssue59(t *testing.T) { 1277 | if v, err := Parse("usage: prog --long=", []string{"--long="}, true, "", false, false); reflect.DeepEqual(v, map[string]interface{}{"--long": ""}) != true { 1278 | t.Error(err) 1279 | } 1280 | 1281 | if v, err := Parse("usage: prog -l \noptions: -l ", []string{"-l", ""}, true, "", false, false); reflect.DeepEqual(v, map[string]interface{}{"-l": ""}) != true { 1282 | t.Error(err) 1283 | } 1284 | } 1285 | 1286 | func TestOptionsFirst(t *testing.T) { 1287 | if v, err := Parse("usage: prog [--opt] [...]", []string{"--opt", "this", "that"}, true, "", false, false); reflect.DeepEqual(v, map[string]interface{}{"--opt": true, "": []string{"this", "that"}}) != true { 1288 | t.Error(err) 1289 | } 1290 | 1291 | if v, err := Parse("usage: prog [--opt] [...]", []string{"this", "that", "--opt"}, true, "", false, false); reflect.DeepEqual(v, map[string]interface{}{"--opt": true, "": []string{"this", "that"}}) != true { 1292 | t.Error(err) 1293 | } 1294 | 1295 | if v, err := Parse("usage: prog [--opt] [...]", []string{"this", "that", "--opt"}, true, "", true, false); reflect.DeepEqual(v, map[string]interface{}{"--opt": false, "": []string{"this", "that", "--opt"}}) != true { 1296 | t.Error(err) 1297 | } 1298 | } 1299 | 1300 | func TestIssue68OptionsShortcutDoesNotIncludeOptionsInUsagePattern(t *testing.T) { 1301 | args, err := Parse("usage: prog [-ab] [options]\noptions: -x\n -y", []string{"-ax"}, true, "", false, false) 1302 | 1303 | if args["-a"] != true { 1304 | t.Error(err) 1305 | } 1306 | if args["-b"] != false { 1307 | t.Error(err) 1308 | } 1309 | if args["-x"] != true { 1310 | t.Error(err) 1311 | } 1312 | if args["-y"] != false { 1313 | t.Error(err) 1314 | } 1315 | } 1316 | 1317 | func TestIssue65EvaluateArgvWhenCalledNotWhenImported(t *testing.T) { 1318 | os.Args = strings.Fields("prog -a") 1319 | v, err := Parse("usage: prog [-ab]", nil, true, "", false, false) 1320 | w := map[string]interface{}{"-a": true, "-b": false} 1321 | if reflect.DeepEqual(v, w) != true { 1322 | t.Error(err) 1323 | } 1324 | 1325 | os.Args = strings.Fields("prog -b") 1326 | v, err = Parse("usage: prog [-ab]", nil, true, "", false, false) 1327 | w = map[string]interface{}{"-a": false, "-b": true} 1328 | if reflect.DeepEqual(v, w) != true { 1329 | t.Error(err) 1330 | } 1331 | } 1332 | 1333 | func TestIssue71DoubleDashIsNotAValidOptionArgument(t *testing.T) { 1334 | _, err := Parse("usage: prog [--log=LEVEL] [--] ...", 1335 | []string{"--log", "--", "1", "2"}, true, "", false, false) 1336 | if _, ok := err.(*UserError); !ok { 1337 | t.Fail() 1338 | } 1339 | 1340 | _, err = Parse(`usage: prog [-l LEVEL] [--] ... 1341 | options: -l LEVEL`, []string{"-l", "--", "1", "2"}, true, "", false, false) 1342 | if _, ok := err.(*UserError); !ok { 1343 | t.Fail() 1344 | } 1345 | } 1346 | 1347 | func TestParseSection(t *testing.T) { 1348 | v := parseSection("usage:", "foo bar fizz buzz") 1349 | w := []string{} 1350 | if reflect.DeepEqual(v, w) != true { 1351 | t.Fail() 1352 | } 1353 | 1354 | v = parseSection("usage:", "usage: prog") 1355 | w = []string{"usage: prog"} 1356 | if reflect.DeepEqual(v, w) != true { 1357 | t.Fail() 1358 | } 1359 | 1360 | v = parseSection("usage:", "usage: -x\n -y") 1361 | w = []string{"usage: -x\n -y"} 1362 | if reflect.DeepEqual(v, w) != true { 1363 | t.Fail() 1364 | } 1365 | 1366 | usage := `usage: this 1367 | 1368 | usage:hai 1369 | usage: this that 1370 | 1371 | usage: foo 1372 | bar 1373 | 1374 | PROGRAM USAGE: 1375 | foo 1376 | bar 1377 | usage: 1378 | ` + "\t" + `too 1379 | ` + "\t" + `tar 1380 | Usage: eggs spam 1381 | BAZZ 1382 | usage: pit stop` 1383 | 1384 | v = parseSection("usage:", usage) 1385 | w = []string{"usage: this", 1386 | "usage:hai", 1387 | "usage: this that", 1388 | "usage: foo\n bar", 1389 | "PROGRAM USAGE:\n foo\n bar", 1390 | "usage:\n\ttoo\n\ttar", 1391 | "Usage: eggs spam", 1392 | "usage: pit stop", 1393 | } 1394 | if reflect.DeepEqual(v, w) != true { 1395 | t.Fail() 1396 | } 1397 | } 1398 | 1399 | func TestIssue126DefaultsNotParsedCorrectlyWhenTabs(t *testing.T) { 1400 | section := "Options:\n\t--foo= [default: bar]" 1401 | v := patternList{newOption("", "--foo", 1, "bar")} 1402 | if reflect.DeepEqual(parseDefaults(section), v) != true { 1403 | t.Fail() 1404 | } 1405 | } 1406 | 1407 | // conf file based test cases 1408 | func TestFileTestcases(t *testing.T) { 1409 | filenames := []string{"testcases.docopt", "test_golang.docopt"} 1410 | for _, filename := range filenames { 1411 | raw, err := ioutil.ReadFile(filename) 1412 | if err != nil { 1413 | t.Fatal(err) 1414 | } 1415 | 1416 | tests, err := parseTest(raw) 1417 | if err != nil { 1418 | t.Fatal(err) 1419 | } 1420 | for _, c := range tests { 1421 | result, err := Parse(c.doc, c.argv, true, "", false, false) 1422 | if _, ok := err.(*UserError); c.userError && !ok { 1423 | // expected a user-error 1424 | t.Error("testcase:", c.id, "result:", result) 1425 | } else if _, ok := err.(*UserError); !c.userError && ok { 1426 | // unexpected user-error 1427 | t.Error("testcase:", c.id, "error:", err, "result:", result) 1428 | } else if reflect.DeepEqual(c.expect, result) != true { 1429 | t.Error("testcase:", c.id, "result:", result, "expect:", c.expect) 1430 | } 1431 | } 1432 | } 1433 | } 1434 | 1435 | type testcase struct { 1436 | id int 1437 | doc string 1438 | prog string 1439 | argv []string 1440 | expect map[string]interface{} 1441 | userError bool 1442 | } 1443 | 1444 | func parseTest(raw []byte) ([]testcase, error) { 1445 | var res []testcase 1446 | commentPattern := regexp.MustCompile("#.*") 1447 | raw = commentPattern.ReplaceAll(raw, []byte("")) 1448 | raw = bytes.TrimSpace(raw) 1449 | if bytes.HasPrefix(raw, []byte(`"""`)) { 1450 | raw = raw[3:] 1451 | } 1452 | 1453 | id := 0 1454 | for _, fixture := range bytes.Split(raw, []byte(`r"""`)) { 1455 | doc, _, body := stringPartition(string(fixture), `"""`) 1456 | for _, cas := range strings.Split(body, "$")[1:] { 1457 | argvString, _, expectString := stringPartition(strings.TrimSpace(cas), "\n") 1458 | prog, _, argvString := stringPartition(strings.TrimSpace(argvString), " ") 1459 | argv := []string{} 1460 | if len(argvString) > 0 { 1461 | argv = strings.Fields(argvString) 1462 | } 1463 | var expectUntyped interface{} 1464 | err := json.Unmarshal([]byte(expectString), &expectUntyped) 1465 | if err != nil { 1466 | return nil, err 1467 | } 1468 | switch expect := expectUntyped.(type) { 1469 | case string: // user-error 1470 | res = append(res, testcase{id, doc, prog, argv, nil, true}) 1471 | case map[string]interface{}: 1472 | // convert []interface{} values to []string 1473 | // convert float64 values to int 1474 | for k, vUntyped := range expect { 1475 | switch v := vUntyped.(type) { 1476 | case []interface{}: 1477 | itemList := make([]string, len(v)) 1478 | for i, itemUntyped := range v { 1479 | if item, ok := itemUntyped.(string); ok { 1480 | itemList[i] = item 1481 | } 1482 | } 1483 | expect[k] = itemList 1484 | case float64: 1485 | expect[k] = int(v) 1486 | } 1487 | } 1488 | res = append(res, testcase{id, doc, prog, argv, expect, false}) 1489 | default: 1490 | return nil, fmt.Errorf("unhandled json data type") 1491 | } 1492 | id++ 1493 | } 1494 | } 1495 | return res, nil 1496 | } 1497 | 1498 | // parseOutput wraps the Parse() function to also return stdout 1499 | func parseOutput(doc string, argv []string, help bool, version string, 1500 | optionsFirst bool) (map[string]interface{}, string, error) { 1501 | stdout := os.Stdout 1502 | r, w, _ := os.Pipe() 1503 | os.Stdout = w 1504 | 1505 | args, err := Parse(doc, argv, help, version, optionsFirst, false) 1506 | 1507 | outChan := make(chan string) 1508 | go func() { 1509 | var buf bytes.Buffer 1510 | io.Copy(&buf, r) 1511 | outChan <- buf.String() 1512 | }() 1513 | 1514 | w.Close() 1515 | os.Stdout = stdout 1516 | output := <-outChan 1517 | 1518 | return args, output, err 1519 | } 1520 | 1521 | var debugEnabled = false 1522 | 1523 | func debugOn(l ...interface{}) { 1524 | debugEnabled = true 1525 | debug(l...) 1526 | } 1527 | func debugOff(l ...interface{}) { 1528 | debug(l...) 1529 | debugEnabled = false 1530 | } 1531 | 1532 | func debug(l ...interface{}) { 1533 | if debugEnabled { 1534 | fmt.Println(l...) 1535 | } 1536 | } 1537 | -------------------------------------------------------------------------------- /example_test.go: -------------------------------------------------------------------------------- 1 | package docopt 2 | 3 | import ( 4 | "fmt" 5 | "sort" 6 | ) 7 | 8 | func ExampleParse() { 9 | usage := `Usage: 10 | config_example tcp [] [--force] [--timeout=] 11 | config_example serial [--baud=] [--timeout=] 12 | config_example -h | --help | --version` 13 | // parse the command line `comfig_example tcp 127.0.0.1 --force` 14 | argv := []string{"tcp", "127.0.0.1", "--force"} 15 | arguments, _ := Parse(usage, argv, true, "0.1.1rc", false) 16 | // sort the keys of the arguments map 17 | var keys []string 18 | for k := range arguments { 19 | keys = append(keys, k) 20 | } 21 | sort.Strings(keys) 22 | // print the argument keys and values 23 | for _, k := range keys { 24 | fmt.Printf("%9s %v\n", k, arguments[k]) 25 | } 26 | // output: 27 | // --baud 28 | // --force true 29 | // --help false 30 | // --timeout 31 | // --version false 32 | // -h false 33 | // 127.0.0.1 34 | // 35 | // serial false 36 | // tcp true 37 | } 38 | -------------------------------------------------------------------------------- /examples/arguments_example.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "github.com/docopt/docopt-go" 6 | ) 7 | 8 | func main() { 9 | usage := `Usage: arguments_example [-vqrh] [FILE] ... 10 | arguments_example (--left | --right) CORRECTION FILE 11 | 12 | Process FILE and optionally apply correction to either left-hand side or 13 | right-hand side. 14 | 15 | Arguments: 16 | FILE optional input file 17 | CORRECTION correction angle, needs FILE, --left or --right to be present 18 | 19 | Options: 20 | -h --help 21 | -v verbose mode 22 | -q quiet mode 23 | -r make report 24 | --left use left-hand side 25 | --right use right-hand side` 26 | 27 | arguments, _ := docopt.Parse(usage, nil, true, "", false) 28 | fmt.Println(arguments) 29 | } 30 | -------------------------------------------------------------------------------- /examples/calculator_example.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "github.com/docopt/docopt-go" 6 | ) 7 | 8 | func main() { 9 | usage := `Not a serious example. 10 | 11 | Usage: 12 | calculator_example ( ( + | - | * | / ) )... 13 | calculator_example [( , )]... 14 | calculator_example (-h | --help) 15 | 16 | Examples: 17 | calculator_example 1 + 2 + 3 + 4 + 5 18 | calculator_example 1 + 2 '*' 3 / 4 - 5 # note quotes around '*' 19 | calculator_example sum 10 , 20 , 30 , 40 20 | 21 | Options: 22 | -h, --help 23 | ` 24 | arguments, _ := docopt.Parse(usage, nil, true, "", false) 25 | fmt.Println(arguments) 26 | } 27 | -------------------------------------------------------------------------------- /examples/config_file_example.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "github.com/docopt/docopt-go" 7 | "strings" 8 | ) 9 | 10 | func loadJSONConfig() map[string]interface{} { 11 | var result map[string]interface{} 12 | jsonData := []byte(`{"--force": true, "--timeout": "10", "--baud": "9600"}`) 13 | json.Unmarshal(jsonData, &result) 14 | return result 15 | } 16 | 17 | func loadIniConfig() map[string]interface{} { 18 | iniData := ` 19 | [default-arguments] 20 | --force 21 | --baud=19200 22 | =localhost` 23 | // trivial ini parser 24 | // default value for an item is bool: true (for --force) 25 | // otherwise the value is a string 26 | iniParsed := make(map[string]map[string]interface{}) 27 | var section string 28 | for _, line := range strings.Split(iniData, "\n") { 29 | if strings.HasPrefix(line, "[") { 30 | section = line 31 | iniParsed[section] = make(map[string]interface{}) 32 | } else if section != "" { 33 | kv := strings.SplitN(line, "=", 2) 34 | if len(kv) == 1 { 35 | iniParsed[section][kv[0]] = true 36 | } else if len(kv) == 2 { 37 | iniParsed[section][kv[0]] = kv[1] 38 | } 39 | } 40 | } 41 | return iniParsed["[default-arguments]"] 42 | } 43 | 44 | // merge combines two maps. 45 | // truthiness takes priority over falsiness 46 | // mapA takes priority over mapB 47 | func merge(mapA, mapB map[string]interface{}) map[string]interface{} { 48 | result := make(map[string]interface{}) 49 | for k, v := range mapA { 50 | result[k] = v 51 | } 52 | for k, v := range mapB { 53 | if _, ok := result[k]; !ok || result[k] == nil || result[k] == false { 54 | result[k] = v 55 | } 56 | } 57 | return result 58 | } 59 | 60 | func main() { 61 | usage := `Usage: 62 | config_file_example tcp [] [--force] [--timeout=] 63 | config_file_example serial [--baud=] [--timeout=] 64 | config_file_example -h | --help | --version` 65 | 66 | jsonConfig := loadJSONConfig() 67 | iniConfig := loadIniConfig() 68 | arguments, _ := docopt.Parse(usage, nil, true, "0.1.1rc", false) 69 | 70 | // Arguments take priority over INI, INI takes priority over JSON 71 | result := merge(arguments, merge(iniConfig, jsonConfig)) 72 | 73 | fmt.Println("JSON config: ", jsonConfig) 74 | fmt.Println("INI config: ", iniConfig) 75 | fmt.Println("Result: ", result) 76 | } 77 | -------------------------------------------------------------------------------- /examples/counted_example.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "github.com/docopt/docopt-go" 6 | ) 7 | 8 | func main() { 9 | usage := `Usage: counted_example --help 10 | counted_example -v... 11 | counted_example go [go] 12 | counted_example (--path=)... 13 | counted_example 14 | 15 | Try: counted_example -vvvvvvvvvv 16 | counted_example go go 17 | counted_example --path ./here --path ./there 18 | counted_example this.txt that.txt` 19 | 20 | arguments, _ := docopt.Parse(usage, nil, true, "", false) 21 | fmt.Println(arguments) 22 | } 23 | -------------------------------------------------------------------------------- /examples/git/git.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "github.com/docopt/docopt-go" 6 | "os" 7 | "os/exec" 8 | ) 9 | 10 | func main() { 11 | usage := `usage: git [--version] [--exec-path=] [--html-path] 12 | [-p|--paginate|--no-pager] [--no-replace-objects] 13 | [--bare] [--git-dir=] [--work-tree=] 14 | [-c =] [--help] 15 | [...] 16 | 17 | options: 18 | -c 19 | -h, --help 20 | -p, --paginate 21 | 22 | The most commonly used git commands are: 23 | add Add file contents to the index 24 | branch List, create, or delete branches 25 | checkout Checkout a branch or paths to the working tree 26 | clone Clone a repository into a new directory 27 | commit Record changes to the repository 28 | push Update remote refs along with associated objects 29 | remote Manage set of tracked repositories 30 | 31 | See 'git help ' for more information on a specific command. 32 | ` 33 | args, _ := docopt.Parse(usage, nil, true, "git version 1.7.4.4", true) 34 | 35 | fmt.Println("global arguments:") 36 | fmt.Println(args) 37 | 38 | fmt.Println("command arguments:") 39 | cmd := args[""].(string) 40 | cmdArgs := args[""].([]string) 41 | 42 | err := runCommand(cmd, cmdArgs) 43 | if err != nil { 44 | fmt.Println(err) 45 | os.Exit(1) 46 | } 47 | } 48 | 49 | func goRun(scriptName string, args []string) (err error) { 50 | cmdArgs := make([]string, 2) 51 | cmdArgs[0] = "run" 52 | cmdArgs[1] = scriptName 53 | cmdArgs = append(cmdArgs, args...) 54 | osCmd := exec.Command("go", cmdArgs...) 55 | var out []byte 56 | out, err = osCmd.Output() 57 | fmt.Println(string(out)) 58 | if err != nil { 59 | return 60 | } 61 | return 62 | } 63 | 64 | func runCommand(cmd string, args []string) (err error) { 65 | argv := make([]string, 1) 66 | argv[0] = cmd 67 | argv = append(argv, args...) 68 | switch cmd { 69 | case "add": 70 | // subcommand is a function call 71 | return cmdAdd(argv) 72 | case "branch": 73 | // subcommand is a script 74 | return goRun("git_branch.go", argv) 75 | case "checkout", "clone", "commit", "push", "remote": 76 | // subcommand is a script 77 | scriptName := fmt.Sprintf("git_%s.go", cmd) 78 | return goRun(scriptName, argv) 79 | case "help", "": 80 | return goRun("git.go", []string{"git_add.go", "--help"}) 81 | } 82 | 83 | return fmt.Errorf("%s is not a git command. See 'git help'", cmd) 84 | } 85 | -------------------------------------------------------------------------------- /examples/git/git_add.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "github.com/docopt/docopt-go" 6 | ) 7 | 8 | func cmdAdd(argv []string) (err error) { 9 | usage := `usage: git add [options] [--] [...] 10 | 11 | options: 12 | -h, --help 13 | -n, --dry-run dry run 14 | -v, --verbose be verbose 15 | -i, --interactive interactive picking 16 | -p, --patch select hunks interactively 17 | -e, --edit edit current diff and apply 18 | -f, --force allow adding otherwise ignored files 19 | -u, --update update tracked files 20 | -N, --intent-to-add record only the fact that the path will be added later 21 | -A, --all add all, noticing removal of tracked files 22 | --refresh don't add, only refresh the index 23 | --ignore-errors just skip files which cannot be added because of errors 24 | --ignore-missing check if - even missing - files are ignored in dry run 25 | ` 26 | 27 | args, _ := docopt.Parse(usage, nil, true, "", false) 28 | fmt.Println(args) 29 | return 30 | } 31 | -------------------------------------------------------------------------------- /examples/git/git_branch.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "github.com/docopt/docopt-go" 6 | ) 7 | 8 | func main() { 9 | usage := `usage: git branch [options] [-r | -a] [--merged= | --no-merged=] 10 | git branch [options] [-l] [-f] [] 11 | git branch [options] [-r] (-d | -D) 12 | git branch [options] (-m | -M) [] 13 | 14 | Generic options: 15 | -h, --help 16 | -v, --verbose show hash and subject, give twice for upstream branch 17 | -t, --track set up tracking mode (see git-pull(1)) 18 | --set-upstream change upstream info 19 | --color= use colored output 20 | -r act on remote-tracking branches 21 | --contains= print only branches that contain the commit 22 | --abbrev= use digits to display SHA-1s 23 | 24 | Specific git-branch actions: 25 | -a list both remote-tracking and local branches 26 | -d delete fully merged branch 27 | -D delete branch (even if not merged) 28 | -m move/rename a branch and its reflog 29 | -M move/rename a branch, even if target exists 30 | -l create the branch's reflog 31 | -f, --force force creation (when already exists) 32 | --no-merged= print only not merged branches 33 | --merged= print only merged branches 34 | ` 35 | 36 | args, _ := docopt.Parse(usage, nil, true, "", false) 37 | fmt.Println(args) 38 | } 39 | -------------------------------------------------------------------------------- /examples/git/git_checkout.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "github.com/docopt/docopt-go" 6 | ) 7 | 8 | func main() { 9 | usage := `usage: git checkout [options] 10 | git checkout [options] -- ... 11 | 12 | options: 13 | -q, --quiet suppress progress reporting 14 | -b create and checkout a new branch 15 | -B create/reset and checkout a branch 16 | -l create reflog for new branch 17 | -t, --track set upstream info for new branch 18 | --orphan 19 | new unparented branch 20 | -2, --ours checkout our version for unmerged files 21 | -3, --theirs checkout their version for unmerged files 22 | -f, --force force checkout (throw away local modifications) 23 | -m, --merge perform a 3-way merge with the new branch 24 | --conflict