Skip to content

Commit

Permalink
Added parameters.pass_name
Browse files Browse the repository at this point in the history
* Removed the pass_name option of runner.Clize
* The iterator used by parser.CliBoundArguments for positional
  parameters is now exposed as an attribute.
  • Loading branch information
epsy committed Mar 3, 2015
1 parent 145069f commit 45efb12
Show file tree
Hide file tree
Showing 7 changed files with 126 additions and 35 deletions.
6 changes: 3 additions & 3 deletions clize/help.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
from sigtools.modifiers import annotate, kwoargs
from sigtools.wrappers import wrappers

from clize import runner, parser, util
from clize import runner, parser, util, parameters

def lines_to_paragraphs(L):
return list(itertools.chain.from_iterable((x, '') for x in L))
Expand All @@ -34,9 +34,9 @@ def prepare_once(self):
if not self.prepared:
self.prepare()

@runner.Clize(pass_name=True, hide_help=True)
@runner.Clize(hide_help=True)
@kwoargs('usage')
@annotate(args=parser.Parameter.UNDOCUMENTED)
@annotate(name=parameters.pass_name, args=parser.Parameter.UNDOCUMENTED)
def cli(self, name, usage=False, *args):
"""Show the help
Expand Down
63 changes: 62 additions & 1 deletion clize/parameters.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,12 @@
# See COPYING for details.

import inspect
from functools import update_wrapper

import six
from sigtools import modifiers, specifiers, signatures

from clize import parser, errors, util, help
from clize import parser, errors, util


class _ShowList(BaseException):
Expand Down Expand Up @@ -373,6 +374,7 @@ def unsatisfied(self, ba):
return False

def prepare_help(self, helper):
from clize import help # prevent circular import
for p in self.cli.parameters.values():
if not p.undocumented:
helper.sections[help.LABEL_OPT][p.argument_name] = (p, '')
Expand All @@ -395,3 +397,62 @@ def argument_decorator(f):
"""
return parser.use_mixin(
DecoratedArgumentParameter, kwargs={'decorator': f})


class ConstantParameter(parser.ParameterWithSourceEquivalent):
"""Parameter that provides an argument to the called function without
requiring an argument on the command line."""

def __init__(self, value_factory,
undocumented, default, conv, aliases=None,
display_name='constant_parameter',
**kwargs):
super(ConstantParameter, self).__init__(
undocumented=True, display_name=display_name, **kwargs)
self.required = True
self.value_factory = value_factory


class ConstantPositionalParameter(ConstantParameter):
def read_argument(self, ba, i):
ba.args.append(self.value_factory(ba))
# Get the next pos parameter to process this argument
try:
param = next(ba.posparam)
except StopIteration:
raise errors.TooManyArguments(ba.in_args[i])
with errors.SetArgumentErrorContext(param=param):
param.read_argument(ba, i)
param.apply_generic_flags(ba)

def unsatisfied(self, ba):
ba.args.append(self.value_factory(ba))


class ConstantNamedParameter(ConstantParameter):
def unsatisfied(self, ba):
ba.kwargs[self.argument_name] = self.value_factory(ba)


def constant_value(value_factory):
"""Create an annotation that hides a parameter from the command-line
and always gives it the result of a function.
:param function value_factory: Called to determine the value to provide
for the parameter. The current `.parser.CliBoundArguments` instance
is passed as argument, ie. ``value_factory(ba)``.
"""
uc = parser.use_class(
pos=ConstantPositionalParameter, named=ConstantNamedParameter,
kwargs={'value_factory': value_factory}
)
update_wrapper(uc, value_factory)
return uc


@constant_value
def pass_name(ba):
"""Parameters decorated with this will receive the script name as
argument."""
return ba.name

13 changes: 9 additions & 4 deletions clize/parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -578,7 +578,7 @@ def read_argument(self, ba, i):
"""Clears all processed arguments, sets up `.func` to be called later,
and lets all remaining arguments be collected as positional if this
was the first argument."""
ba.args[:] = []
ba.args[:] = [ba.name + ' ' + self.display_name]
ba.kwargs.clear()
ba.post_name.append(ba.in_args[i])
ba.func = self.func
Expand Down Expand Up @@ -966,6 +966,12 @@ class CliBoundArguments(object):
The following attributes only exist while arguments are being processed:
.. attribute:: posparam
:annotation: = iter(sig.positional)
The iterator over the positional parameters used to process positional
arguments.
.. attribute:: sticky
:annotation: = None
Expand Down Expand Up @@ -1001,13 +1007,12 @@ def __init__(self, sig, args, name):
self.kwargs = {}
self.meta = {}

self.posparam = iter(self.sig.positional)
self.sticky = None
self.posarg_only = False
self.skip = 0
self.unsatisfied = set(self.sig.required)

posparam = iter(self.sig.positional)

with _SeekFallbackCommand():
for i, arg in enumerate(self.in_args):
if self.skip > 0:
Expand All @@ -1019,7 +1024,7 @@ def __init__(self, sig, args, name):
param = self.sticky
else:
try:
param = next(posparam)
param = next(self.posparam)
except StopIteration:
exc = errors.TooManyArguments(
self.in_args[i:])
Expand Down
18 changes: 6 additions & 12 deletions clize/runner.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,8 @@

from sigtools.modifiers import annotate, autokwoargs
from sigtools.specifiers import forwards_to_method, signature
from sigtools.signatures import mask

from clize import util, errors, parser
from clize import util, errors, parser, parameters

class _CliWrapper(object):
def __init__(self, obj):
Expand Down Expand Up @@ -50,12 +49,10 @@ def __new__(cls, fn=None, **kwargs):
else:
return super(Clize, cls).__new__(cls)

def __init__(self, fn, owner=None, alt=(), extra=(), pass_name=False,
def __init__(self, fn, owner=None, alt=(), extra=(),
help_names=('help', 'h'), helper_class=None, hide_help=False):
"""
:param sequence alt: Alternate actions the CLI will handle.
:param bool pass_name: Pass the command name as first argument to the
wrapped function.
:param help_names: Names to use to trigger the help.
:type help_names: sequence of strings
:param helper_class: A callable to produce a helper object to be
Expand All @@ -69,7 +66,6 @@ def __init__(self, fn, owner=None, alt=(), extra=(), pass_name=False,
self.owner = owner
self.alt = util.maybe_iter(alt)
self.extra = extra
self.pass_name = pass_name
self.help_names = help_names
self.help_aliases = [util.name_py2cli(s, kw=True) for s in help_names]
self.helper_class = helper_class
Expand All @@ -81,7 +77,6 @@ def parameters(self):
return {
'owner': self.owner,
'alt': self.alt,
'pass_name': self.pass_name,
'help_names': self.help_names,
'helper_class': self.helper_class,
'hide_help': self.hide_help,
Expand Down Expand Up @@ -174,7 +169,7 @@ def helper(self):
def signature(self):
"""The `.parser.CliSignature` object used to parse arguments."""
return parser.CliSignature.from_signature(
mask(signature(self.func), self.pass_name),
signature(self.func),
extra=itertools.chain(self._process_alt(self.alt), self.extra))

def _process_alt(self, alt):
Expand Down Expand Up @@ -206,8 +201,6 @@ def read_commandline(self, args):
ba = self.signature.read_arguments(args[1:], args[0])
func, post, posargs, kwargs = ba
name = ' '.join([args[0]] + post)
if func or self.pass_name:
posargs.insert(0, name)
return func or self.func, name, posargs, kwargs

def _dispatcher_helper(*args, **kwargs):
Expand All @@ -234,8 +227,9 @@ def __init__(self, commands=(), description=None, footnotes=None):
self.description = description
self.footnotes = footnotes

@Clize(pass_name=True, helper_class=make_dispatcher_helper)
@annotate(command=(lowercase, parser.Parameter.LAST_OPTION))
@Clize(helper_class=make_dispatcher_helper)
@annotate(name=parameters.pass_name,
command=(lowercase, parser.Parameter.LAST_OPTION))
def cli(self, name, command, *args):
try:
func = self.cmds_by_name[command]
Expand Down
30 changes: 30 additions & 0 deletions clize/tests/test_parameters.py
Original file line number Diff line number Diff line change
Expand Up @@ -182,6 +182,11 @@ def _nest(arg, ab, cd='cd'):
deco_nest_kwd = '*, par:a="d"', _nest, '[' + _nest_rep + '--par=STR]'
deco_nest_args = '*par:a', _nest, '[' + _nest_rep + 'par...]'

pn_pos = 'par:a', parameters.pass_name, ''
pn_pos_first = 'par:a, other', parameters.pass_name, 'other'
pn_pos_nextpicky = 'par:a, other:int', parameters.pass_name, 'other'
pn_kw = '*, par:a', parameters.pass_name, ''


@test_bad_param
class BadParamTests(object):
Expand All @@ -190,6 +195,9 @@ def _with_pokarg(arg, invalid):
raise NotImplementedError
deco_with_pokarg = 'par: a', _with_pokarg

pn_varargs = '*par: a', parameters.pass_name
pn_varkwargs = '**par: a', parameters.pass_name


@annotated_sigtests
class MappedTests(object):
Expand Down Expand Up @@ -646,3 +654,25 @@ def _subst(arg, kw, flag=False, kwd='D', i=0):
Other actions:
-h, --help Show the help
"""

@annotated_sigtests
class PnTests(object):
pn_pos_solo = RepTests.pn_pos, (), ['test'], {}
pn_pos_first = RepTests.pn_pos_first, ('arg',), ['test', 'arg'], {}
pn_kw_solo = RepTests.pn_kw, (), [], {'par': 'test'}

@annotated_sigerror_tests
class PnErrorTests(object):
pn_pos_toomany = RepTests.pn_pos, ('arg',), errors.TooManyArguments
pn_pos_errinnext = (
RepTests.pn_pos_nextpicky, ('bad',), errors.BadArgumentFormat
)

def test_pn_pos_errinnext_context(self):
sig_str, annotation, str_rep = RepTests.pn_pos_nextpicky
sig = support.s(sig_str, locals={'a': annotation})
csig = parser.CliSignature.from_signature(sig)
try:
util.read_arguments(csig, ('bad',))
except errors.BadArgumentFormat as exc:
self.assertEqual(exc.param.display_name, 'other')
21 changes: 14 additions & 7 deletions clize/tests/test_parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -310,36 +310,43 @@ class ExtraParamsTests(object):
_func2 = support.f('')
alt_cmd = (
'', [parser.AlternateCommandParameter(func=_func, aliases=['--alt'])],
('--alt', 'a', '-b', '--third'), ['a', '-b', '--third'], {}, _func
('--alt', 'a', '-b', '--third'),
['test --alt', 'a', '-b', '--third'], {}, _func
)
alt_cmd2 = (
'', [parser.AlternateCommandParameter(func=_func, aliases=['--alt'])],
('--alt', '--alpha', '-b'), ['--alpha', '-b'], {}, _func
('--alt', '--alpha', '-b'),
['test --alt', '--alpha', '-b'], {}, _func
)
flb_cmd_start = (
'', [parser.FallbackCommandParameter(func=_func, aliases=['--alt'])],
('--alt', '-a', 'b', '--third'), ['-a', 'b', '--third'], {}, _func
('--alt', '-a', 'b', '--third'),
['test --alt', '-a', 'b', '--third'], {}, _func
)
flb_cmd_valid = (
'*a', [parser.FallbackCommandParameter(func=_func, aliases=['--alt'])],
('a', '--alt', 'b', '-c', '--fourth'), [], {}, _func
('a', '--alt', 'b', '-c', '--fourth'),
['test --alt'], {}, _func
)
flb_cmd_invalid = (
'', [parser.FallbackCommandParameter(func=_func, aliases=['--alt'])],
('a', '--alt', 'a', '-b'), [], {}, _func
('a', '--alt', 'a', '-b'),
['test --alt'], {}, _func
)
flb_cmd_invalid_valid = (
'a: int, b',
[parser.FallbackCommandParameter(func=_func, aliases=['--alt'])],
('xyz', 'abc', '--alt', 'def', '-g', '--hij'), [], {}, _func
('xyz', 'abc', '--alt', 'def', '-g', '--hij'),
['test --alt'], {}, _func
)
flb_after_alt = (
'a: int, b',
[
parser.AlternateCommandParameter(func=_func, aliases=['--alt']),
parser.FallbackCommandParameter(func=_func2, aliases=['--flb']),
],
('--invalid', '--alt', '--flb'), [], {}, _func2
('--invalid', '--alt', '--flb'),
['test --flb'], {}, _func2
)

def test_alt_middle(self):
Expand Down
10 changes: 2 additions & 8 deletions clize/tests/test_runner.py
Original file line number Diff line number Diff line change
Expand Up @@ -210,13 +210,13 @@ def func(): pass

def test_keep_args(self):
def func(): pass
c = runner.Clize.keep(pass_name=True)(func)
c = runner.Clize.keep(hide_help=True)(func)
ru = runner.Clize.get_cli(c)
repr(ru)
self.assertTrue(c is func)
self.assertTrue(isinstance(c.cli, runner.Clize))
self.assertTrue(c.cli.func is func)
self.assertTrue(c.cli.pass_name)
self.assertTrue(c.cli.hide_help)
self.assertTrue(ru.func is func)

def test_decorated(self):
Expand Down Expand Up @@ -274,12 +274,6 @@ def test_unknown(self):
self.assertRaises(TypeError, runner.Clize.get_cli, obj)

class RunnerTests(unittest.TestCase):
def test_pass_name(self):
def func(name):
return name
ru = runner.Clize.get_cli(func, pass_name=True)
self.assertEqual(ru('test'), 'test')

def test_subcommand(self):
def func1(x):
return x+' world'
Expand Down

0 comments on commit 45efb12

Please sign in to comment.