Skip to content

Commit

Permalink
port AnyOptions shortcut and travis-ci setup from master
Browse files Browse the repository at this point in the history
  • Loading branch information
andreypopp committed Jun 1, 2012
1 parent 0c5122b commit 09e2663
Show file tree
Hide file tree
Showing 5 changed files with 94 additions and 7 deletions.
12 changes: 12 additions & 0 deletions .travis.yml
Original file line number Original file line Diff line number Diff line change
@@ -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
18 changes: 15 additions & 3 deletions docopt.py
Original file line number Original file line Diff line number Diff line change
Expand Up @@ -171,6 +171,12 @@ def name(self):
def __repr__(self): def __repr__(self):
return 'Option(%r, %r, %r)' % (self.short, self.long, self.value) 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): class Required(Pattern):


Expand Down Expand Up @@ -354,7 +360,6 @@ def do_shorts(raw, options, tokens, is_pattern):
parsed += [opt] parsed += [opt]
return parsed return parsed



def parse_pattern(source, options): def parse_pattern(source, options):
tokens = re.sub(r'([\[\]\(\)\|]|\.\.\.)', r' \1 ', source).split() tokens = re.sub(r'([\[\]\(\)\|]|\.\.\.)', r' \1 ', source).split()
tokens = ReversibleIterator(iter(tokens)) tokens = ReversibleIterator(iter(tokens))
Expand Down Expand Up @@ -412,7 +417,10 @@ def parse_seq(tokens, options):




def parse_atom(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) token = next(tokens)
result = [] result = []
if token == '(': if token == '(':
Expand All @@ -422,7 +430,11 @@ def parse_atom(tokens, options):
raise DocoptError("Unmatched '('") raise DocoptError("Unmatched '('")
return result return result
elif token == '[': 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') token = next(tokens, 'EOF')
if token != ']': if token != ']':
raise DocoptError("Unmatched '['") raise DocoptError("Unmatched '['")
Expand Down
10 changes: 10 additions & 0 deletions docs/index.rst
Original file line number Original file line Diff line number Diff line change
Expand Up @@ -265,6 +265,16 @@ Each pattern can consist of following elements:
If you want to accept zero or more things, use brackets, e.g.: If you want to accept zero or more things, use brackets, e.g.:
``my_program.py [FILE ...]``. Ellipsis works as unary operator on ``my_program.py [FILE ...]``. Ellipsis works as unary operator on
expression to the left. 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, 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 parser will put matched values into a list, e.g. in case pattern is
Expand Down
20 changes: 20 additions & 0 deletions examples/any_options_example.py
Original file line number Original file line Diff line number Diff line change
@@ -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)
41 changes: 37 additions & 4 deletions test_docopt.py
Original file line number Original file line Diff line number Diff line change
@@ -1,8 +1,8 @@
from __future__ import with_statement from __future__ import with_statement
from docopt import (Option, docopt, parse_args, Argument, Either, from docopt import (Option, docopt, parse_args, Argument, Either, Required,
Required, Optional, parse_pattern, OneOrMore, Optional, AnyOptions, parse_pattern, OneOrMore,
parse_doc_options, option, DocoptExit, parse_doc_options, option, DocoptExit, DocoptError,
DocoptError, printable_usage, formal_usage printable_usage, formal_usage
) )
from pytest import raises from pytest import raises


Expand Down Expand Up @@ -59,6 +59,16 @@ def test_docopt():
assert docopt(doc, '-v arg') == {'-v': True, 'A': 'arg'} 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(): def test_parse_doc_options():
doc = '''-h, --help Print help message. doc = '''-h, --help Print help message.
-o FILE Output file. -o FILE Output file.
Expand Down Expand Up @@ -131,6 +141,14 @@ def test_pattern():
assert parse_pattern('[ -h ] [N]', options=o) == \ assert parse_pattern('[ -h ] [N]', options=o) == \
Required(Optional(Option('h', None, True)), Required(Optional(Option('h', None, True)),
Optional(Argument('N'))) 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(): def test_option_match():
Expand Down Expand Up @@ -255,6 +273,16 @@ def test_basic_pattern_matching():
Argument(None, 9), Argument(None, 9),
Argument(None, 5)], []) 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(): def test_pattern_either():
assert Option('a').either == Either(Required(Option('a'))) assert Option('a').either == Either(Required(Option('a')))
Expand Down Expand Up @@ -339,3 +367,8 @@ def test_short_options_error_handling():
docopt('Usage: prog -o\n\n-o ARG') docopt('Usage: prog -o\n\n-o ARG')
with raises(DocoptExit): with raises(DocoptExit):
docopt('Usage: prog -o ARG\n\n-o ARG', '-o') 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.