Skip to content

Commit

Permalink
bump version, merge branch 'devel'
Browse files Browse the repository at this point in the history
  • Loading branch information
casperdcl committed Jan 22, 2018
2 parents 2b14d96 + 33c7b22 commit 3ed7954
Show file tree
Hide file tree
Showing 6 changed files with 70 additions and 65 deletions.
3 changes: 3 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,9 @@ clean:
@+python -c "import os; import glob; [os.remove(i) for i in glob.glob('argopt/*.py[co]')]"
@+python -c "import os; import glob; [os.remove(i) for i in glob.glob('examples/*.py[co]')]"
@+python -c "import os; import glob; [os.remove(i) for i in glob.glob('argopt/tests/*.py[co]')]"
toxclean:
@+python -c "import shutil; shutil.rmtree('.tox', True)"


installdev:
python setup.py develop --uninstall
Expand Down
97 changes: 50 additions & 47 deletions argopt/_argopt.py
Original file line number Diff line number Diff line change
@@ -1,16 +1,11 @@
from __future__ import print_function
try:
from argparse import ArgumentParser
except ImportError:
# py26
ArgumentParser = None
from argparse import ArgumentParser, RawDescriptionHelpFormatter

import re
import sys
from docopt import docopt # only for py26
from ._docopt import Argument, Option, AnyOptions, \
from ._docopt import Argument, Option, AnyOptions, DocoptLanguageError, \
parse_defaults, parse_pattern, printable_usage, formal_usage
from ._utils import _range, set_nargs, DictAttrWrap, typecast
from ._utils import _range, set_nargs
import logging
from ._version import __version__ # NOQA

__author__ = "Casper da Costa-Luis <casper@caspersci.uk.to>"
Expand Down Expand Up @@ -39,11 +34,14 @@ def findall_args(re, pattern):
for i in re.findall(pattern)]


def docopt_parser(doc='', **_kwargs):
def docopt_parser(doc='', logLevel=logging.NOTSET, **_kwargs):
"""
doc : docopt compatible, with optional type specifiers [default: '':str].
"""
log = logging.getLogger(__name__)
options, args = parse_defaults(doc)
log.log(logLevel, "options:%r" % options)
log.log(logLevel, "args:%r" % args)
usage = printable_usage(doc)
pattern = parse_pattern(formal_usage(usage), options)
# pattern_arguments = pattern.flat(Argument)
Expand All @@ -54,7 +52,16 @@ def docopt_parser(doc='', **_kwargs):
ao.children = list(set_options - pattern_options)

# args = pattern.flat(Argument)
opts = pattern.flat(Option)
opt_names = []
opts = []
for opt in pattern.flat(Option):
if not set([opt.short, opt.long]).intersection(opt_names):
opt_names.extend(filter(lambda x: x is not None,
[opt.short, opt.long]))
opts.append(opt)
else:
log.warn("dropped:%r" % opt)
log.log(logLevel, "opts:%r" % opts)

if 'version' in _kwargs:
if not any(o.name == '--version' for o in opts):
Expand Down Expand Up @@ -97,41 +104,24 @@ def docopt_parser(doc='', **_kwargs):
return once_args + qest_args + star_args + plus_args, opts


class DocoptArgumentParser(object):
"""Thin wrapper around docopt which behaves like argparse (for py26)
"""
def __init__(self, doc, version=None):
self.doc = doc
self.version = version

def parse_args(self, args=None):
args = docopt(
self.doc, version=self.version,
argv=args if args is not None else sys.argv[1:])
for (k, v) in args.items():
try:
args[k] = typecast(v.rsplit(':', 1))
except:
pass
return DictAttrWrap(args)

def print_help(self, file=sys.stderr):
file.write(self.doc)


def argopt(doc='', argparser=ArgumentParser, **_kwargs):
def argopt(doc='', argparser=ArgumentParser,
formatter_class=RawDescriptionHelpFormatter,
logLevel=logging.NOTSET, **_kwargs):
"""
Note that `docopt` supports neither type specifiers nor default
positional arguments. We support both here.
Parameters
----------
doc : docopt compatible, with optional type specifiers
[default: '':str]
[default: '':str]
argparser : Argument parser class [default: argparse.ArgumentParser]
version : Version string [default: None:str]
formatter_class : [default: argparse.RawDescriptionHelpFormatter]
logLevel : [default: logging.NOTSET]
_kwargs : any `argparser` initialiser arguments
N.B.: `prog`, `description`, and `epilog` are automatically
inferred if not `None`
Returns
-------
Expand All @@ -155,7 +145,7 @@ def argopt(doc='', argparser=ArgumentParser, **_kwargs):
(docopt extension) action choices
(docopt extension) action count
"""

log = logging.getLogger(__name__)
# TODO:
# TEST: prog name
# TEST: prog description
Expand All @@ -166,20 +156,33 @@ def argopt(doc='', argparser=ArgumentParser, **_kwargs):
# TEST: metavar
# TEST: version

if argparser is None:
return DocoptArgumentParser(doc, version=_kwargs.get("version"))

pu = printable_usage(doc)
args, opts = docopt_parser(doc, **_kwargs)
log.log(logLevel, doc[:doc.find(pu)])
args, opts = docopt_parser(doc,
log=max(logLevel - 10, logging.NOTSET),
**_kwargs)

_kwargs.setdefault("prog", pu.split()[1])
_kwargs.setdefault("description", doc[:doc.find(pu)])
# epilogue
try:
pLast = printable_usage(doc, "arguments")
except DocoptLanguageError:
pLast = pu
try:
pOpts = printable_usage(doc, "options")
except DocoptLanguageError:
pLast = doc.find(pLast)
else:
pLast = max(doc.find(pLast), doc.find(pOpts))
_kwargs.setdefault("epilog",
'\n\n'.join(doc[pLast:].split('\n\n')[1:]).strip())

version = _kwargs.pop('version', None)
parser = argparser(
prog=pu.split()[1],
description=doc[:doc.find(pu)],
**_kwargs)
parser = argparser(formatter_class=formatter_class, **_kwargs)

for a in args:
# debug('a', a.desc)
log.log(logLevel, "a:%r" % a)
k = {}
if a.type is not None:
k['type'] = a.type
Expand All @@ -190,7 +193,7 @@ def argopt(doc='', argparser=ArgumentParser, **_kwargs):
help=a.desc,
**k)
for o in opts:
# debug(o)
log.log(logLevel, "o:%r" % o)
if o.name in ('-h', '--help'):
continue
if '--version' == o.name:
Expand Down
11 changes: 7 additions & 4 deletions argopt/_docopt.py
Original file line number Diff line number Diff line change
Expand Up @@ -454,13 +454,16 @@ def parse_defaults(doc):
return options, arguments


def printable_usage(doc):
def printable_usage(doc, section="usage"):
# in python < 2.7 you can't pass flags=re.IGNORECASE
usage_split = re.split(r'([Uu][Ss][Aa][Gg][Ee]:)', doc)
regex = ''.join(["[%s%s]" % (i.lower(), i.upper()) for i in section])
usage_split = re.split('(' + regex + ':)', doc)
if len(usage_split) < 3:
raise DocoptLanguageError('"usage:" (case-insensitive) not found.')
raise DocoptLanguageError(
'"%s:" (case-insensitive) not found.' % section)
if len(usage_split) > 3:
raise DocoptLanguageError('More than one "usage:" (case-insensitive).')
raise DocoptLanguageError(
'More than one "%s:" (case-insensitive).' % section)
return re.split(r'\n\s*\n', ''.join(usage_split[1:]))[0].strip()


Expand Down
2 changes: 1 addition & 1 deletion argopt/_version.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
__all__ = ["__version__"]

# major, minor, patch, -extra
version_info = 0, 3, 3
version_info = 0, 3, 4

# Nice string for the version
__version__ = '.'.join(map(str, version_info))
Expand Down
16 changes: 5 additions & 11 deletions argopt/tests/tests_argopt.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,9 @@
from argopt import argopt
try:
from argparse import RawDescriptionHelpFormatter
except ImportError:
# py26
RawDescriptionHelpFormatter = None
from argparse import RawDescriptionHelpFormatter
try:
from StringIO import StringIO
except ImportError:
from io import StringIO
import sys


def test_basic():
Expand All @@ -28,7 +23,6 @@ def test_basic():
-p PAT, --patts PAT Or [default: '':str].
--bar=<b> Another [default: something] should assume str.
-f, --force Force.
-v, --version Print version and exit.
'''
parser = argopt(doc, version='0.1.2-3.4',
formatter_class=RawDescriptionHelpFormatter)
Expand All @@ -55,8 +49,8 @@ def test_basic():
-p PAT, --patts PAT Or [default: '':str].'''.split('\n'))

except AssertionError:
if not all([l.strip() in res for l in doc.split('\n')]):
raise AssertionError(res)
# if not all([l.strip() in res for l in doc.split('\n')]):
raise AssertionError(res)

args = parser.parse_args(args='such test much is'.split())
try:
Expand All @@ -67,7 +61,7 @@ def test_basic():
try:
parser.parse_args(args=['-v'])
except SystemExit as e:
assert(str(e) in ['0', ''])
assert (str(e) == '0')
else:
raise ValueError('System should have exited with code 0')

Expand Down Expand Up @@ -103,6 +97,6 @@ def test_verbose_and_version():
try:
parser.parse_args(args=['--version'])
except SystemExit as e:
assert (str(e) in ['0', ''])
assert (str(e) == '0')
else:
raise ValueError('System should have exited with code 0')
6 changes: 4 additions & 2 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -184,7 +184,7 @@ def execute_makefile_commands(commands, alias, verbose=False):
classifiers=[
# Trove classifiers
# (https://pypi.python.org/pypi?%3Aaction=list_classifiers)
'Development Status :: 3 - Alpha',
'Development Status :: 4 - Beta',
'Environment :: Console',
'Environment :: MacOS X',
'Environment :: Other Environment',
Expand All @@ -206,13 +206,15 @@ def execute_makefile_commands(commands, alias, verbose=False):
'Operating System :: POSIX :: SunOS/Solaris',
'Programming Language :: Python',
'Programming Language :: Python :: 2',
'Programming Language :: Python :: 2.6',
'Programming Language :: Python :: 2.7',
'Programming Language :: Python :: 3',
'Programming Language :: Python :: 3.2',
'Programming Language :: Python :: 3.3',
'Programming Language :: Python :: 3.4',
'Programming Language :: Python :: 3.5',
'Programming Language :: Python :: 3.6',
'Programming Language :: Python :: 3.7',
'Programming Language :: Python :: Implementation',
'Programming Language :: Python :: Implementation :: IronPython',
'Programming Language :: Python :: Implementation :: PyPy',
Expand All @@ -238,5 +240,5 @@ def execute_makefile_commands(commands, alias, verbose=False):
' console terminal command line CLI UI gui gooey',
test_suite='nose.collector',
tests_require=['nose', 'flake8', 'coverage'],
requires=["docopt"] if sys.version_info[:2] == (2, 6) else [],
install_requires=["argparse"],
)

0 comments on commit 3ed7954

Please sign in to comment.