summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorZane Bitter <zbitter@redhat.com>2021-06-04 22:44:13 -0400
committerZane Bitter <zbitter@redhat.com>2021-07-12 21:03:32 -0400
commit8fa916e9169f507ce7b861f3d9df9a14734672d2 (patch)
tree8fb6ae34c40f074652989c2b49ece568932ce9f4
parent392f3b2e7cb2dad8036ebbaefbb75dd758914421 (diff)
downloadcliff-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.py8
-rw-r--r--cliff/help.py86
-rw-r--r--requirements.txt1
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