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
This commit is contained in:
parent
392f3b2e7c
commit
8fa916e916
|
@ -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
|
||||
|
||||
|
||||
|
|
|
@ -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
|
||||
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))
|
||||
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
|
||||
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)
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue