diff options
author | Zane Bitter <zbitter@redhat.com> | 2021-06-04 22:44:13 -0400 |
---|---|---|
committer | Zane Bitter <zbitter@redhat.com> | 2021-07-12 21:03:32 -0400 |
commit | 8fa916e9169f507ce7b861f3d9df9a14734672d2 (patch) | |
tree | 8fb6ae34c40f074652989c2b49ece568932ce9f4 | |
parent | 392f3b2e7cb2dad8036ebbaefbb75dd758914421 (diff) | |
download | cliff-8fa916e9169f507ce7b861f3d9df9a14734672d2.tar.gz |
Colourise and automatically page help output
Using the autopage library we can automatically send the help output to
a pager (less, by default), git-style. The pager is configured to not
reset the terminal on exit, avoiding the problem when piping to less
manually that the help text you want to refer to disappears off the
screen when you go to use it. The pager is only invoked when the output
is to the terminal.
Since we invoke the pager, we can ensure that it is correctly set up to
interpret ANSI escape codes, so it is safe to use colour to make the
output easier to read. The autopage library provides light styling of
the default argparse help output, and some additional colour
highlighting is added here for the command list (which is generated by
cliff, not using argparse's formatting code).
Change-Id: If9e1aa5166da32c58cc0fa617f4f81eaa9b2c470
Depends-On: https://review.opendev.org/c/openstack/requirements/+/799343
-rw-r--r-- | cliff/_argparse.py | 8 | ||||
-rw-r--r-- | cliff/help.py | 86 | ||||
-rw-r--r-- | requirements.txt | 1 |
3 files changed, 57 insertions, 38 deletions
diff --git a/cliff/_argparse.py b/cliff/_argparse.py index c03d709..cb5e4c5 100644 --- a/cliff/_argparse.py +++ b/cliff/_argparse.py @@ -12,9 +12,11 @@ """Overrides of standard argparse behavior.""" -import argparse +import argparse as orig_argparse import warnings +from autopage import argparse + class _ArgumentContainerMixIn(object): @@ -75,12 +77,12 @@ def _handle_conflict_ignore(container, option_string_actions, ) -class _ArgumentGroup(_ArgumentContainerMixIn, argparse._ArgumentGroup): +class _ArgumentGroup(_ArgumentContainerMixIn, orig_argparse._ArgumentGroup): pass class _MutuallyExclusiveGroup(_ArgumentContainerMixIn, - argparse._MutuallyExclusiveGroup): + orig_argparse._MutuallyExclusiveGroup): pass diff --git a/cliff/help.py b/cliff/help.py index 9ccfeb7..2a235de 100644 --- a/cliff/help.py +++ b/cliff/help.py @@ -14,6 +14,8 @@ import argparse import inspect import traceback +import autopage.argparse + from . import command @@ -37,43 +39,53 @@ class HelpAction(argparse.Action): """ 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() + pager = autopage.argparse.help_pager(app.stdout) + color = pager.to_terminal() + autopage.argparse.use_color_for_parser(parser, color) + with pager as out: + parser.print_help(out) + title_hl = ('\033[4m', '\033[0m') if color else ('', '') + out.write('\n%sCommands%s:\n' % title_hl) + 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) + 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: - factory = ep.load() - except Exception: - app.stdout.write('Could not load %r\n' % ep) - if namespace.debug: - traceback.print_exc(file=app.stdout) - continue - try: - kwargs = {} - if 'cmd_name' in inspect.getfullargspec(factory.__init__).args: - kwargs['cmd_name'] = name - cmd = factory(app, None, **kwargs) - if cmd.deprecated: + app_dist = dist_for_obj(app) + command_manager = app.command_manager + for name, ep in sorted(command_manager): + try: + factory = ep.load() + except Exception: + out.write('Could not load %r\n' % ep) + if namespace.debug: + traceback.print_exc(file=out) + continue + try: + kwargs = {} + fact_args = inspect.getfullargspec(factory.__init__).args + if 'cmd_name' in fact_args: + kwargs['cmd_name'] = name + cmd = factory(app, None, **kwargs) + if cmd.deprecated: + continue + except Exception as err: + out.write('Could not instantiate %r: %s\n' % (ep, err)) + if namespace.debug: + traceback.print_exc(file=out) continue - except Exception as err: - app.stdout.write('Could not instantiate %r: %s\n' % (ep, err)) - if namespace.debug: - traceback.print_exc(file=app.stdout) - continue - one_liner = cmd.get_description().split('\n')[0] - 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)) + one_liner = cmd.get_description().split('\n')[0] + dist_name = dist_for_obj(factory) + if dist_name and dist_name != app_dist: + dist_info = ' (' + dist_name + ')' + if color: + dist_info = '\033[90m%s\033[39m' % dist_info + else: + dist_info = '' + if color: + name = '\033[36m%s\033[39m' % name + out.write(' %-13s %s%s\n' % (name, one_liner, dist_info)) raise HelpExit() @@ -118,7 +130,11 @@ class HelpCommand(command.Command): else ' '.join([self.app.NAME, cmd_name]) ) cmd_parser = cmd.get_parser(full_name) - cmd_parser.print_help(self.app.stdout) + pager = autopage.argparse.help_pager(self.app.stdout) + with pager as out: + autopage.argparse.use_color_for_parser(cmd_parser, + pager.to_terminal()) + cmd_parser.print_help(out) else: action = HelpAction(None, None, default=self.app) action(self.app.parser, self.app.options, None, None) diff --git a/requirements.txt b/requirements.txt index 774d9d2..4450bd7 100644 --- a/requirements.txt +++ b/requirements.txt @@ -2,6 +2,7 @@ # of appearance. Changing the order has an impact on the overall integration # process, which may cause wedges in the gate later. pbr!=2.1.0,>=2.0.0 # Apache-2.0 +autopage>=0.4.0 # Apache 2.0 cmd2>=1.0.0 # MIT PrettyTable>=0.7.2 # BSD pyparsing>=2.1.0 # MIT |