Skip to content

Commit

Permalink
Automatically group identical subcommands as aliases
Browse files Browse the repository at this point in the history
  • Loading branch information
epsy committed Nov 8, 2021
1 parent 95bc4cd commit 5bc882d
Show file tree
Hide file tree
Showing 3 changed files with 53 additions and 3 deletions.
25 changes: 22 additions & 3 deletions clize/runner.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,20 +34,21 @@ def __init__(self, obj, description, usages):

def cli_commands(obj, namef, clizer):
cmds = util.OrderedDict()
cmd_by_name = {}
try:
names = util.dict_from_names(obj).items()
except AttributeError:
raise ValueError("Cannot guess name for anonymous objects "
"(lists, dicts, etc)")
func_to_names = util.OrderedDict()
for key, val in names:
if not key:
continue
names = tuple(namef(name) for name in util.maybe_iter(key))
cli = clizer.get_cli(val)
cmds[names] = cli
for name in names:
cmd_by_name[name] = cli
func_to_names.setdefault(cli, []).append(name)
cmds = util.OrderedDict((tuple(names), cli) for cli, names in func_to_names.items())
cmd_by_name = {name: cli for names, cli in cmds.items() for name in names}
return cmds, cmd_by_name

class Clize(object):
Expand Down Expand Up @@ -94,6 +95,24 @@ def parameters(self):
'hide_help': self.hide_help,
}

def _key(self):
return (
self.func,
self.owner,
tuple(self.alt),
tuple(self.extra),
tuple(self.help_names),
tuple(self.help_aliases),
self.helper_class,
self.hide_help,
)

def __eq__(self, other):
return type(self) is type(other) and self._key() == other._key()

def __hash__(self):
return hash(self._key())

@classmethod
def keep(cls, fn=None, **kwargs):
"""Instead of wrapping the decorated callable, sets its ``cli``
Expand Down
27 changes: 27 additions & 0 deletions clize/tests/test_runner.py
Original file line number Diff line number Diff line change
Expand Up @@ -195,6 +195,33 @@ def func2(): raise NotImplementedError
self.assertEqual(sd.cmds_by_name['abc'].func, func1)
self.assertEqual(sd.cmds_by_name['def'].func, func2)

def test_sub_dict_extra_names(self):
def func1(): raise NotImplementedError
def func2(): raise NotImplementedError
ru = runner.Clize.get_cli({('abc', 'ghi'): func1, 'def': func2})
repr(ru)
sd = ru.func.__self__
self.assertTrue(isinstance(sd,
runner.SubcommandDispatcher))
self.assertEqual(len(sd.cmds_by_name), 3)
self.assertEqual(len(sd.cmds), 2)
self.assertEqual(sd.cmds_by_name['abc'].func, func1)
self.assertEqual(sd.cmds_by_name['def'].func, func2)
self.assertEqual(sd.cmds_by_name['ghi'].func, func1)

def test_sub_dict_repeat_values(self):
def func1(): raise NotImplementedError
def func2(): raise NotImplementedError
ru = runner.Clize.get_cli({'abc': func1, 'def': func2, 'ghi': func1})
repr(ru)
sd = ru.func.__self__
self.assertTrue(isinstance(sd,
runner.SubcommandDispatcher))
self.assertEqual(len(sd.cmds_by_name), 3)
self.assertEqual(len(sd.cmds), 2)
self.assertEqual(sd.cmds_by_name['abc'].func, func1)
self.assertEqual(sd.cmds_by_name['def'].func, func2)

def test_sub_alt(self):
def func1(): raise NotImplementedError
def func2(): raise NotImplementedError
Expand Down
4 changes: 4 additions & 0 deletions docs/dispatching.rst
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,10 @@ pass in an :term:`python:iterable` of functions, a `dict` or an
`~collections.OrderedDict`. If you pass an iterable of functions, :ref:`name
conversion <name conversion>` will apply.

.. code-block:: python
run({ 'add': add, 'list': list_, 'show': list_ })
Because it isn't passed a regular function with a docstring, Clize can't
determine an appropriate description from a docstring. You can explicitly give
it a description with the ``description=`` parameter. Likewise, you an add
Expand Down

0 comments on commit 5bc882d

Please sign in to comment.