Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP

Comparing changes

Choose two branches to see what's changed or to start a new pull request. If you need to, you can also compare across forks.

Open a pull request

Create a new pull request by comparing changes across two branches. If you need to, you can also compare across forks.
base fork: keleshev/docopt
base: 7b132478aa
...
head fork: keleshev/docopt
compare: 04124a234d
  • 6 commits
  • 2 files changed
  • 0 commit comments
  • 1 contributor
Showing with 178 additions and 148 deletions.
  1. +131 −130 docopt.py
  2. +47 −18 test_docopt.py
View
261 docopt.py
@@ -1,26 +1,34 @@
-from getopt import gnu_getopt, GetoptError
from ast import literal_eval
from copy import deepcopy
import sys
import re
-class Argument(object):
+class DocoptError(Exception):
+ pass
- def __init__(self, name, value=None):
- self.name = name
- self.value = value
- def __repr__(self):
- return 'Argument(%r, %r)' % (self.name, self.value)
+class Pattern(object):
+
+ def __init__(self, *args):
+ self.args = args
def __eq__(self, other):
return repr(self) == repr(other)
+ def __repr__(self):
+ return '%s(%s)' % (self.__class__.__name__,
+ ', '.join([repr(a) for a in self.args]))
+
+
+class Argument(Pattern):
+
+ def __init__(self, name, value=None):
+ self.name = name
+ self.value = value
+ self.args = [name, value]
+
def match(self, left):
- #left_ = [l for l in left if not (type(l) == Argument)]
- #return (left != left_), left_
- #left = deepcopy(left)
args = [l for l in left if type(l) == Argument]
if not len(args):
return False, left
@@ -28,26 +36,9 @@ def match(self, left):
return True, left
-class Option(object):
+class Option(Pattern):
def __init__(self, short=None, long=None, value=False, parse=None):
- is_flag = True
- if parse:
- split = parse.strip().split(' ')
- options = split[0].replace(',', ' ').replace('=', ' ')
- description = ''.join(split[1:])
- for s in options.split():
- if s.startswith('--'):
- long = s.lstrip('-')
- elif s.startswith('-'):
- short = s.lstrip('-')
- else:
- is_flag = False
- if not is_flag:
- matched = re.findall('\[default: (.*)\]', description)
- value = argument_eval(matched[0]) if matched else False
- short = short + ':' if short else None
- long = long + '=' if long else None
self.short = short
self.long = long
self.value = value
@@ -86,8 +77,40 @@ def forms(self):
def __repr__(self):
return 'Option(%r, %r, %r)' % (self.short, self.long, self.value)
- def __eq__(self, other):
- return repr(self) == repr(other)
+
+class VerticalBar(object):
+ pass
+
+
+class Parens(Pattern):
+
+ def match(self, left):
+ left = deepcopy(left)
+ matched = True
+ for p in self.args:
+ m, left = p.match(left)
+ if not m:
+ matched = False
+ return matched, left
+
+
+class Brackets(Pattern):
+
+ def match(self, left):
+ left = deepcopy(left)
+ for p in self.args:
+ m, left = p.match(left)
+ return True, left
+
+
+class OneOrMore(Pattern):
+
+ def match(self, left):
+ left_ = deepcopy(left)
+ matched = True
+ while matched:
+ matched, left_ = self.args[0].match(left_)
+ return (left != left_), left_
class Namespace(object):
@@ -99,8 +122,37 @@ def __eq__(self, other):
return repr(self) == repr(other)
def __repr__(self):
- return 'Namespace(%s)' % ',\n '.join(["%s=%s" % (kw, repr(a))
- for kw, a in self.__dict__.items()])
+ return '%s(%s)' % (self.__class__.__name__,
+ ',\n '.join(["%s=%s" % (kw, repr(a))
+ for kw, a in self.__dict__.items()]))
+
+class Options(Namespace):
+ pass
+
+
+class Arguments(Namespace):
+ pass
+
+
+def option(parse):
+ is_flag = True
+ short, long, value = None, None, False
+ split = parse.strip().split(' ')
+ options = split[0].replace(',', ' ').replace('=', ' ')
+ description = ''.join(split[1:])
+ for s in options.split():
+ if s.startswith('--'):
+ long = s.lstrip('-')
+ elif s.startswith('-'):
+ short = s.lstrip('-')
+ else:
+ is_flag = False
+ if not is_flag:
+ matched = re.findall('\[default: (.*)\]', description)
+ value = argument_eval(matched[0]) if matched else False
+ short = short + ':' if short else None
+ long = long + '=' if long else None
+ return Option(short, long, value)
def argument_eval(s):
@@ -110,129 +162,50 @@ def argument_eval(s):
return s
-def docopt(doc, args=sys.argv[1:], help=True, version=None):
- docopts = [Option(parse='-' + s) for s in re.split('^ *-|\n *-', doc)[1:]]
- try:
- getopts, args = gnu_getopt(args,
- ''.join([d.short for d in docopts if d.short]),
- [d.long for d in docopts if d.long])
- except GetoptError as e:
- exit(e.msg)
- for k, v in getopts:
- for o in docopts:
- if k in o.forms:
- o.value = True if o.is_flag else argument_eval(v)
- if help and k in ('-h', '--help'):
- exit(doc.strip())
- if version is not None and k == '--version':
- exit(version)
- return Namespace(**dict([(o.name, o.value) for o in docopts])), args
-
-
def do_longs(parsed, raw, options, parse):
try:
i = raw.index('=')
raw, value = raw[:i], raw[i+1:]
except ValueError:
value = None
- option = [o for o in options if o.long and o.long.startswith(raw)]
- assert len(option) == 1
- option = option[0]
- if not option.is_flag:
+ opt = [o for o in options if o.long and o.long.startswith(raw)]
+ if len(opt) < 1:
+ raise DocoptError('--%s is not recognized' % raw)
+ if len(opt) > 1:
+ raise DocoptError('--%s is not a unique prefix: %s?' % (raw,
+ ', '.join('--%s' % o.long for o in opt)))
+ opt = opt[0]
+ if not opt.is_flag:
if value is None:
if not parse:
- raise GetoptError('option --%s requires argument' % option)
+ raise DocoptError('--%s requires argument' % opt)
value, parse = parse[0], parse[1:]
elif value is not None:
- raise GetoptError('option --%s must not have an argument' % option)
- option.value = value or True
- parsed += [option]
+ raise DocoptError('--%s must not have an argument' % opt)
+ opt.value = value or True
+ parsed += [opt]
return parsed, parse
def do_shorts(parsed, raw, options, parse):
while raw != '':
- option = [o for o in options if o.short and o.short.startswith(raw[0])]
- assert len(option) == 1
- option = option[0]
+ opt = [o for o in options if o.short and o.short.startswith(raw[0])]
+ assert len(opt) == 1
+ opt = opt[0]
raw = raw[1:]
- if option.is_flag:
+ if opt.is_flag:
value = True
else:
if raw == '':
if not parse:
- raise GetoptError('option -%s requires argument' % option)
+ raise DocoptError('opt -%s requires argument' % opt)
raw, parse = parse[0], parse[1:]
value, raw = raw, ''
- option.value = value
- parsed += [option]
+ opt.value = value
+ parsed += [opt]
return parsed, parse
-class VerticalBar(object):
- pass
-
-
-class Parens(object):
-
- def __init__(self, *state):
- self.state = state
-
- def __repr__(self):
- return 'Parens(%s)' % ', '.join([repr(a) for a in self.state])
-
- def __eq__(self, other):
- return repr(self) == repr(other)
-
- def match(self, left):
- left = deepcopy(left)
- matched = True
- for p in self.state:
- m, left = p.match(left)
- if not m:
- matched = False
- return matched, left
- #return all([pattern.match(other) for pattern in self.state])
-
-
-class Brackets(object):
-
- def __init__(self, *state):
- self.state = state
-
- def __repr__(self):
- return 'Brackets(%s)' % ', '.join([repr(a) for a in self.state])
-
- def __eq__(self, other):
- return repr(self) == repr(other)
-
- def match(self, left):
- left = deepcopy(left)
- for p in self.state:
- m, left = p.match(left)
- return True, left
-
-
-class OneOrMore(object):
-
- def __init__(self, what):
- self.what = what
-
- def __repr__(self):
- return 'OneOrMore(%r)' % self.what
-
- def __eq__(self, other):
- return repr(self) == repr(other)
-
- def match(self, left):
- left_ = deepcopy(left)
- matched = True
- while matched:
- matched, left_ = self.what.match(left_)
- #left_ = [l for l in left if not self.what.match([l])[0]]
- return (left != left_), left_
-
-
def pattern(source, options=None):
return parse(source=source, options=options, is_pattern=True)
@@ -280,3 +253,31 @@ def parse(source, options=None, is_pattern=False):
parsed += [argument]
source = source[1:]
return parsed
+
+
+def parse_doc_options(doc):
+ return [option('-' + s) for s in re.split('^ *-|\n *-', doc)[1:]]
+
+
+def parse_doc_usage(doc, options=[]):
+ raw_usage = re.split(r'\n\s*\n', re.split(r'[Uu]sage:', doc)[1])[0].strip()
+ prog = raw_usage.split()[0]
+ raw_patterns = raw_usage.strip(prog).split(prog)
+ return [Parens(*pattern(s, options=options)) for s in raw_patterns]
+
+
+def docopt(doc, args=sys.argv[1:], help=True, version=None):
+ options = parse_doc_options(doc)
+ try:
+ args = parse(args, options=options)
+ except DocoptError as e:
+ exit(e.message)
+ options += [o for o in args if type(o) is Option]
+ if help and any(o for o in options
+ if (o.short == 'h' or o.long == 'help') and o.value):
+ exit(doc.strip())
+ if version and any(o for o in options if o.long == 'version' and o.value):
+ exit(str(version))
+ arguments = [a for a in args if type(a) is Argument]
+ return (Options(**dict([(o.name, o.value) for o in options])),
+ [a.value for a in arguments])
View
65 test_docopt.py
@@ -1,32 +1,33 @@
from docopt import (Option, Namespace, docopt, parse, Argument, VerticalBar,
- Parens, Brackets, pattern, OneOrMore)
+ Parens, Brackets, pattern, OneOrMore, parse_doc_options,
+ parse_doc_usage, option)
def test_option():
- assert Option(parse='-h') == Option('h', None)
- assert Option(parse='--help') == Option(None, 'help')
- assert Option(parse='-h --help') == Option('h', 'help')
- assert Option(parse='-h, --help') == Option('h', 'help')
+ assert option('-h') == Option('h', None)
+ assert option('--help') == Option(None, 'help')
+ assert option('-h --help') == Option('h', 'help')
+ assert option('-h, --help') == Option('h', 'help')
- assert Option(parse='-h TOPIC') == Option('h:', None)
- assert Option(parse='--help TOPIC') == Option(None, 'help=')
- assert Option(parse='-h TOPIC --help TOPIC') == Option('h:', 'help=')
- assert Option(parse='-h TOPIC, --help TOPIC') == Option('h:', 'help=')
- assert Option(parse='-h TOPIC, --help=TOPIC') == Option('h:', 'help=')
+ assert option('-h TOPIC') == Option('h:', None)
+ assert option('--help TOPIC') == Option(None, 'help=')
+ assert option('-h TOPIC --help TOPIC') == Option('h:', 'help=')
+ assert option('-h TOPIC, --help TOPIC') == Option('h:', 'help=')
+ assert option('-h TOPIC, --help=TOPIC') == Option('h:', 'help=')
- assert Option(parse='-h Description...') == Option('h', None)
- assert Option(parse='-h --help Description...') == Option('h', 'help')
- assert Option(parse='-h TOPIC Description...') == Option('h:', None)
+ assert option('-h Description...') == Option('h', None)
+ assert option('-h --help Description...') == Option('h', 'help')
+ assert option('-h TOPIC Description...') == Option('h:', None)
- assert Option(parse=' -h') == Option('h', None)
+ assert option(' -h') == Option('h', None)
- assert Option(parse='-h TOPIC Descripton... [default: 2]') == \
+ assert option('-h TOPIC Descripton... [default: 2]') == \
Option('h:', None, 2)
- assert Option(parse='-h TOPIC Descripton... [default: topic-1]') == \
+ assert option('-h TOPIC Descripton... [default: topic-1]') == \
Option('h:', None, 'topic-1')
- assert Option(parse='--help=TOPIC ... [default: 3.14]') == \
+ assert option('--help=TOPIC ... [default: 3.14]') == \
Option(None, 'help=', 3.14)
- assert Option(parse='-h, --help=DIR ... [default: "./"]') == \
+ assert option('-h, --help=DIR ... [default: "./"]') == \
Option('h:', 'help=', "./")
@@ -43,6 +44,34 @@ def test_docopt():
assert docopt('-v Be verbose.', ['-v']) == (Namespace(v=True), [])
+def test_parse_doc_options():
+ doc = """-h, --help Print help message.
+ -o FILE Output file.
+ --verbose Verbose mode."""
+ assert parse_doc_options(doc) == [Option('h', 'help'),
+ Option('o:'),
+ Option(None, 'verbose')]
+
+
+def test_parse_doc_usage():
+ assert parse_doc_usage('usage: prog ARG') == [Parens(Argument('ARG'))]
+ doc = """
+ Usage: prog [-hv]
+ ARG
+ prog N M
+
+ prog is a program.
+
+ -h
+ -v
+
+ """
+ assert parse_doc_usage(doc, options=parse_doc_options(doc)) == [
+ Parens(Brackets(Option('h', None, True),
+ Option('v', None, True)), Argument('ARG')),
+ Parens(Argument('N'), Argument('M'))]
+
+
def test_parse():
o = [Option('h'),

No commit comments for this range

Something went wrong with that request. Please try again.