Skip to content

Commit

Permalink
show the distribution providing the command in help output
Browse files Browse the repository at this point in the history
When listing all available commands, include the distribution name if
it does not match the distribution of the main application.

When showing the verbose help for a single command, include the
distribution name if it does not match the distribution of the main
application.

Change-Id: I4ddb3327c62cfd0f82167c15e9513ece6a3689c4
Signed-off-by: Doug Hellmann <doug@doughellmann.com>
  • Loading branch information
dhellmann authored and Dean Troyer committed Sep 22, 2017
1 parent dacbd60 commit 31ec27d
Show file tree
Hide file tree
Showing 2 changed files with 62 additions and 8 deletions.
56 changes: 49 additions & 7 deletions cliff/command.py
Expand Up @@ -13,11 +13,47 @@
import abc
import inspect

import pkg_resources
import six
from stevedore import extension

from cliff import _argparse

_dists_by_mods = None


def _get_distributions_by_modules():
"""Return dict mapping module name to distribution names.
The python package name (the name used for importing) and the
distribution name (the name used with pip and PyPI) do not
always match. We want to report which distribution caused the
command to be installed, so we need to look up the values.
"""
global _dists_by_mods
if _dists_by_mods is None:
results = {}
for dist in pkg_resources.working_set:
try:
mod_name = dist.get_metadata('top_level.txt').strip()
except KeyError:
# Could not retrieve metadata. Ignore.
pass
else:
results[mod_name] = dist.project_name
_dists_by_mods = results
return _dists_by_mods


def _get_distribution_for_module(module):
"Return the distribution containing the module."
dist_name = None
if module:
pkg_name = module.__name__.partition('.')[0]
dist_name = _get_distributions_by_modules().get(pkg_name)
return dist_name


@six.add_metaclass(abc.ABCMeta)
class Command(object):
Expand Down Expand Up @@ -89,17 +125,23 @@ def get_description(self):

def get_epilog(self):
"""Return the command epilog."""
# replace a None in self._epilog with an empty string
parts = [self._epilog or '']
hook_epilogs = filter(
None,
(h.obj.get_epilog() for h in self._hooks),
)
if hook_epilogs:
# combine them, replacing a None in self._epilog with an
# empty string
parts = [self._epilog or '']
parts.extend(hook_epilogs)
return '\n\n'.join(parts)
return self._epilog
parts.extend(hook_epilogs)
app_dist_name = _get_distribution_for_module(
inspect.getmodule(self.app)
)
dist_name = _get_distribution_for_module(inspect.getmodule(self))
if dist_name and dist_name != app_dist_name:
parts.append(
'This command is provided by the %s plugin.' %
(dist_name,)
)
return '\n\n'.join(parts)

def get_parser(self, prog_name):
"""Return an :class:`argparse.ArgumentParser`.
Expand Down
14 changes: 13 additions & 1 deletion cliff/help.py
Expand Up @@ -29,6 +29,13 @@ def __call__(self, parser, namespace, values, option_string=None):
app = self.default
parser.print_help(app.stdout)
app.stdout.write('\nCommands:\n')
dists_by_module = command._get_distributions_by_modules()

def dist_for_obj(obj):
name = inspect.getmodule(obj).__name__.partition('.')[0]
return dists_by_module.get(name)

app_dist = dist_for_obj(app)
command_manager = app.command_manager
for name, ep in sorted(command_manager):
try:
Expand All @@ -51,7 +58,12 @@ def __call__(self, parser, namespace, values, option_string=None):
traceback.print_exc(file=app.stdout)
continue
one_liner = cmd.get_description().split('\n')[0]
app.stdout.write(' %-13s %s\n' % (name, one_liner))
dist_name = dist_for_obj(factory)
if dist_name and dist_name != app_dist:
dist_info = ' (' + dist_name + ')'
else:
dist_info = ''
app.stdout.write(' %-13s %s%s\n' % (name, one_liner, dist_info))
sys.exit(0)


Expand Down

0 comments on commit 31ec27d

Please sign in to comment.