Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for position arguments #47

Open
wants to merge 10 commits into
base: master
Choose a base branch
from
49 changes: 25 additions & 24 deletions docopt.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,21 +6,21 @@
* Copyright (c) 2013 Vladimir Keleshev, [email protected]

"""

from __future__ import print_function

import re
import sys
import re


__all__ = ['docopt']
__version__ = '0.6.2'
__version__ = '0.6.2tj1'


class DocoptLanguageError(Exception):

"""Error in construction of usage-message by developer."""


class DocoptExit(SystemExit):

"""Exit in case user invoked program with incorrect arguments."""

usage = ''
Expand All @@ -30,6 +30,7 @@ def __init__(self, message=''):


class Pattern(object):

def __eq__(self, other):
return repr(self) == repr(other)

Expand Down Expand Up @@ -96,14 +97,11 @@ def transform(pattern):


class LeafPattern(Pattern):

"""Leaf/terminal node of a pattern tree."""
name = value = None

def __init__(self, name, value=None):
if name is not None:
self.name = name
if value is not None:
self.value = value
self.name, self.value = name, value

def __repr__(self):
return '%s(%r, %r)' % (self.__class__.__name__, self.name, self.value)
Expand Down Expand Up @@ -133,6 +131,7 @@ def match(self, left, collected=None):


class BranchPattern(Pattern):

"""Branch/inner node of a pattern tree."""

def __init__(self, *children):
Expand All @@ -149,22 +148,23 @@ def flat(self, *types):


class Argument(LeafPattern):

def single_match(self, left):
for n, pattern in enumerate(left):
if type(pattern) is Argument:
return n, Argument(self.name, pattern.value)
return None, None

@classmethod
def parse(cls, source):
name = re.findall('(<\S*?>)', source)[0]
value = re.findall('\[default: (.*)\]', source, flags=re.I)
return cls(name, value[0] if value else None)
def parse(class_, source):
name = re.findall('(<\\S*?>)', source)[0]
value = re.findall('\\[default: (.*)\\]', source, flags=re.I)
return class_(name, value[0] if value else None)


class Command(Argument):

def __init__(self, name, value=False):
super(Command, self).__init__(name, value)
self.name, self.value = name, value

def single_match(self, left):
Expand All @@ -178,8 +178,8 @@ def single_match(self, left):


class Option(LeafPattern):

def __init__(self, short=None, long=None, argcount=0, value=False):
super(Option, self).__init__(None, value)
assert argcount in (0, 1)
self.short, self.long, self.argcount = short, long, argcount
self.value = None if value is False and argcount else value
Expand All @@ -197,7 +197,7 @@ def parse(class_, option_description):
else:
argcount = 1
if argcount:
matched = re.findall('\[default: (.*)\]', description, flags=re.I)
matched = re.findall('\\[default: (.*)\\]', description, flags=re.I)
value = matched[0] if matched else None
return class_(short, long, argcount, value)

Expand Down Expand Up @@ -275,20 +275,20 @@ def match(self, left, collected=None):
if matched:
outcomes.append(outcome)
if outcomes:
return min(outcomes, key=lambda _outcome: len(_outcome[1]))
return min(outcomes, key=lambda outcome: len(outcome[1]))
return False, left, collected


class Tokens(list):

def __init__(self, source, error=DocoptExit):
super(Tokens, self).__init__()
self += source.split() if hasattr(source, 'split') else source
self.error = error

@staticmethod
def from_pattern(source):
source = re.sub(r'([\[\]\(\)\|]|\.\.\.)', r' \1 ', source)
source = [s for s in re.split('\s+|(\S*<.*?>)', source) if s]
source = [s for s in re.split('\\s+|(\\S*<.*?>)', source) if s]
return Tokens(source, error=DocoptLanguageError)

def move(self):
Expand Down Expand Up @@ -404,6 +404,7 @@ def parse_atom(tokens, options):
| long | shorts | argument | command ;
"""
token = tokens.current()
result = []
if token in '([':
tokens.move()
matching, pattern = {'(': [')', Required], '[': [']', Optional]}[token]
Expand Down Expand Up @@ -453,7 +454,7 @@ def parse_defaults(doc):
for s in parse_section('options:', doc):
# FIXME corner case "bla: options: --foo"
_, _, s = s.partition(':') # get rid of "options:"
split = re.split('\n[ \t]*(-\S+?)', '\n' + s)[1:]
split = re.split('\n[ \t]*(-\\S+?)', '\n' + s)[1:]
split = [s1 + s2 for s1, s2 in zip(split[::2], split[1::2])]
options = [Option.parse(s) for s in split if s.startswith('-')]
defaults += options
Expand Down Expand Up @@ -520,7 +521,7 @@ def docopt(doc, argv=None, help=True, version=None, options_first=False):

Example
-------
>>> from docopt_c.docopt import docopt
>>> from docopt import docopt
>>> doc = '''
... Usage:
... my_program tcp <host> <port> [--timeout=<seconds>]
Expand Down Expand Up @@ -561,7 +562,7 @@ def docopt(doc, argv=None, help=True, version=None, options_first=False):
options = parse_defaults(doc)
pattern = parse_pattern(formal_usage(DocoptExit.usage), options)
# [default] syntax for argument is disabled
# for a in pattern.flat(Argument):
#for a in pattern.flat(Argument):
# same_name = [d for d in arguments if d.name == a.name]
# if same_name:
# a.value = same_name[0].value
Expand All @@ -570,7 +571,7 @@ def docopt(doc, argv=None, help=True, version=None, options_first=False):
for options_shortcut in pattern.flat(OptionsShortcut):
doc_options = parse_defaults(doc)
options_shortcut.children = list(set(doc_options) - pattern_options)
# if any_options:
#if any_options:
# options_shortcut.children += [Option(o.short, o.long, o.argcount)
# for o in argv if type(o) is Option]
extras(help, version, argv, doc)
Expand Down
Loading