Skip to content
Browse files

port AnyOptions shortcut and travis-ci setup from master

  • Loading branch information...
1 parent 0c5122b commit 09e2663046b8439ab7bc4c9346a3337c3b4a1f34 @andreypopp andreypopp committed Jun 1, 2012
Showing with 94 additions and 7 deletions.
  1. +12 −0 .travis.yml
  2. +15 −3 docopt.py
  3. +10 −0 docs/index.rst
  4. +20 −0 examples/any_options_example.py
  5. +37 −4 test_docopt.py
View
12 .travis.yml
@@ -0,0 +1,12 @@
+language: python
+python:
+ - "2.5"
+ - "2.6"
+ - "2.7"
+ - "3.1"
+ - "3.2"
+ - "pypy"
+# command to install dependencies
+install: pip install pytest --use-mirrors
+# command to run tests
+script: py.test
View
18 docopt.py
@@ -171,6 +171,12 @@ def name(self):
def __repr__(self):
return 'Option(%r, %r, %r)' % (self.short, self.long, self.value)
+class AnyOptions(Pattern):
+
+ def match(self, left, collected=None):
+ collected = [] if collected is None else collected
+ left_ = [l for l in left if not type(l) == Option]
+ return (left != left_), left_, collected
class Required(Pattern):
@@ -354,7 +360,6 @@ def do_shorts(raw, options, tokens, is_pattern):
parsed += [opt]
return parsed
-
def parse_pattern(source, options):
tokens = re.sub(r'([\[\]\(\)\|]|\.\.\.)', r' \1 ', source).split()
tokens = ReversibleIterator(iter(tokens))
@@ -412,7 +417,10 @@ def parse_seq(tokens, options):
def parse_atom(tokens, options):
- """ATOM ::= LONG | SHORTS (plural!) | ARG | '[' EXPR ']' | '(' EXPR ')'"""
+ """ATOM ::=
+ LONG | SHORTS (plural!) | ANYOPTIONS
+ | ARG | '[' EXPR ']' | '(' EXPR ')'
+ """
token = next(tokens)
result = []
if token == '(':
@@ -422,7 +430,11 @@ def parse_atom(tokens, options):
raise DocoptError("Unmatched '('")
return result
elif token == '[':
- result = [Optional(*parse_expr(tokens, options))]
+ if tokens.ahead().lower() == 'options':
+ result = [Optional(AnyOptions())]
+ tokens.next()
+ else:
+ result = [Optional(*parse_expr(tokens, options))]
token = next(tokens, 'EOF')
if token != ']':
raise DocoptError("Unmatched '['")
View
10 docs/index.rst
@@ -265,6 +265,16 @@ Each pattern can consist of following elements:
If you want to accept zero or more things, use brackets, e.g.:
``my_program.py [FILE ...]``. Ellipsis works as unary operator on
expression to the left.
+- **Any options** shortcut ``[options]`` (case insensitive, so it could be
+ ``[OPTIONS]`` as well). You can use it if you want to specify that usage
+ pattern could be provided with any options defined below in
+ options-description section and do not want to enumerate them all in pattern.
+
+ .. note::
+ It is preferable that you use ``[options]`` shortcut only for command line
+ interfaces which allow huge number of options to appear in single usage
+ pattern, otherwise it's more advisable to enumerate allowed options pattern
+ to provide better user experience.
If your usage-patterns allow to match same-named argument several times,
parser will put matched values into a list, e.g. in case pattern is
View
20 examples/any_options_example.py
@@ -0,0 +1,20 @@
+"""Example of program which use [options] shortcut in pattern
+
+Usage:
+ any_options_example.py [options]
+
+Options:
+ -h --help show this help message and exit
+ --version show version and exit
+ -n, --number N use N as a number
+ -t, --timeout TIMEOUT set timeout TIMEOUT seconds
+ --apply apply changes to database
+ -q operate in quiet mode
+
+"""
+from docopt import docopt
+
+
+if __name__ == '__main__':
+ arguments = docopt(__doc__, version='1.0.0rc2')
+ print(arguments)
View
41 test_docopt.py
@@ -1,8 +1,8 @@
from __future__ import with_statement
-from docopt import (Option, docopt, parse_args, Argument, Either,
- Required, Optional, parse_pattern, OneOrMore,
- parse_doc_options, option, DocoptExit,
- DocoptError, printable_usage, formal_usage
+from docopt import (Option, docopt, parse_args, Argument, Either, Required,
+ Optional, AnyOptions, parse_pattern, OneOrMore,
+ parse_doc_options, option, DocoptExit, DocoptError,
+ printable_usage, formal_usage
)
from pytest import raises
@@ -59,6 +59,16 @@ def test_docopt():
assert docopt(doc, '-v arg') == {'-v': True, 'A': 'arg'}
+def test_any_options():
+ doc = '''Usage: prog [options] a
+
+ -q Be quiet
+ -v Be verbose.'''
+ assert docopt(doc, 'arg') == {'a': 'arg', '-v': False, '-q': False}
+ assert docopt(doc, '-v arg') == {'a': 'arg', '-v': True, '-q': False}
+ assert docopt(doc, '-q arg') == {'a': 'arg', '-v': False, '-q': True}
+
+
def test_parse_doc_options():
doc = '''-h, --help Print help message.
-o FILE Output file.
@@ -131,6 +141,14 @@ def test_pattern():
assert parse_pattern('[ -h ] [N]', options=o) == \
Required(Optional(Option('h', None, True)),
Optional(Argument('N')))
+ assert parse_pattern('[options]', options=o) == Required(
+ Optional(AnyOptions()))
+ assert parse_pattern('[options] A', options=o) == Required(
+ Optional(AnyOptions()),
+ Argument('A'))
+ assert parse_pattern('-v [options]', options=o) == Required(
+ Option('v', 'verbose', True),
+ Optional(AnyOptions()))
def test_option_match():
@@ -255,6 +273,16 @@ def test_basic_pattern_matching():
Argument(None, 9),
Argument(None, 5)], [])
+def test_pattern_any_option():
+ pattern = AnyOptions()
+ assert pattern.match([Option('a')]) == (True, [], [])
+ assert pattern.match([Option('b')]) == (True, [], [])
+ assert pattern.match([Option('l', 'long')]) == (True, [], [])
+ assert pattern.match([Option(None, 'long')]) == (True, [], [])
+ assert pattern.match([Option('a'), Option('b')]) == (True, [], [])
+ assert pattern.match([Option('a'), Option(None, 'long')]) == (True, [], [])
+ assert not pattern.match([Argument('N')])[0]
+
def test_pattern_either():
assert Option('a').either == Either(Required(Option('a')))
@@ -339,3 +367,8 @@ def test_short_options_error_handling():
docopt('Usage: prog -o\n\n-o ARG')
with raises(DocoptExit):
docopt('Usage: prog -o ARG\n\n-o ARG', '-o')
+
+def test_empty_pattern():
+ # See https://github.com/halst/docopt/issues/9
+ doc = '''usage: prog'''
+ docopt(doc, '')

0 comments on commit 09e2663

Please sign in to comment.
Something went wrong with that request. Please try again.