Index: subcommand.py |
diff --git a/subcommand.py b/subcommand.py |
index 2f58a209ee9e80484b0284b7c3722a44e341bdfa..6afe3dcda1d31b458174f97c491dcf4ab0fb68d7 100644 |
--- a/subcommand.py |
+++ b/subcommand.py |
@@ -41,6 +41,7 @@ Explanation: |
import difflib |
import sys |
+import textwrap |
def usage(more): |
@@ -51,6 +52,17 @@ def usage(more): |
return hook |
+def epilog(text): |
+ """Adds an 'epilog' property to a CMD function. |
+ |
+ It will be shown in the epilog. Usually useful for examples. |
+ """ |
+ def hook(fn): |
+ fn.epilog = text |
+ return fn |
+ return hook |
+ |
+ |
def CMDhelp(parser, args): |
"""Prints list of commands or help for a specific command.""" |
# This is the default help implementation. It can be disabled or overriden if |
@@ -62,6 +74,14 @@ def CMDhelp(parser, args): |
assert False |
+def _get_color_module(): |
+ """Returns the colorama module if available. |
+ |
+ If so, assumes colors are supported and return the module handle. |
+ """ |
+ return sys.modules.get('colorama') or sys.modules.get('third_party.colorama') |
+ |
+ |
class CommandDispatcher(object): |
def __init__(self, module): |
"""module is the name of the main python module where to look for commands. |
@@ -126,21 +146,57 @@ class CommandDispatcher(object): |
return commands[hamming_commands[0][1]] |
+ def _gen_commands_list(self): |
+ """Generates the short list of supported commands.""" |
+ commands = self.enumerate_commands() |
+ docs = sorted( |
+ (name, self._create_command_summary(name, handler)) |
+ for name, handler in commands.iteritems()) |
+ # Skip commands without a docstring. |
+ docs = [i for i in docs if i[1]] |
+ # Then calculate maximum length for alignment: |
+ length = max(len(c) for c in commands) |
+ |
+ # Look if color is supported. |
+ colors = _get_color_module() |
+ green = reset = '' |
+ if colors: |
+ green = colors.Fore.GREEN |
+ reset = colors.Fore.RESET |
+ return ( |
+ 'Commands are:\n' + |
+ ''.join( |
+ ' %s%-*s%s %s\n' % (green, length, name, reset, doc) |
+ for name, doc in docs)) |
+ |
def _add_command_usage(self, parser, command): |
"""Modifies an OptionParser object with the function's documentation.""" |
name = command.__name__[3:] |
- more = getattr(command, 'usage_more', '') |
if name == 'help': |
name = '<command>' |
# Use the module's docstring as the description for the 'help' command if |
# available. |
- parser.description = self.module.__doc__ |
+ parser.description = (self.module.__doc__ or '').rstrip() |
+ if parser.description: |
+ parser.description += '\n\n' |
+ parser.description += self._gen_commands_list() |
+ # Do not touch epilog. |
else: |
- # Use the command's docstring if available. |
- parser.description = command.__doc__ |
- parser.description = (parser.description or '').strip() |
- if parser.description: |
- parser.description += '\n' |
+ # Use the command's docstring if available. For commands, unlike module |
+ # docstring, realign. |
+ lines = (command.__doc__ or '').rstrip().splitlines() |
+ if lines[:1]: |
+ rest = textwrap.dedent('\n'.join(lines[1:])) |
+ parser.description = '\n'.join((lines[0], rest)) |
+ else: |
+ parser.description = lines[0] |
+ if parser.description: |
+ parser.description += '\n' |
+ parser.epilog = getattr(command, 'epilog', None) |
+ if parser.epilog: |
+ parser.epilog = '\n' + parser.epilog.strip() + '\n' |
+ |
+ more = getattr(command, 'usage_more', '') |
parser.set_usage( |
'usage: %%prog %s [options]%s' % (name, '' if not more else ' ' + more)) |
@@ -161,18 +217,11 @@ class CommandDispatcher(object): |
Fallbacks to 'help' if not disabled. |
""" |
- commands = self.enumerate_commands() |
- length = max(len(c) for c in commands) |
- |
- # Lists all the commands in 'help'. |
- if commands['help']: |
- docs = sorted( |
- (name, self._create_command_summary(name, handler)) |
- for name, handler in commands.iteritems()) |
- # Skip commands without a docstring. |
- commands['help'].usage_more = ( |
- '\n\nCommands are:\n' + '\n'.join( |
- ' %-*s %s' % (length, name, doc) for name, doc in docs if doc)) |
+ # Unconditionally disable format_description() and format_epilog(). |
+ # Technically, a formatter should be used but it's not worth (yet) the |
+ # trouble. |
+ parser.format_description = lambda _: parser.description or '' |
+ parser.format_epilog = lambda _: parser.epilog or '' |
if args: |
if args[0] in ('-h', '--help') and len(args) > 1: |
@@ -192,10 +241,11 @@ class CommandDispatcher(object): |
self._add_command_usage(parser, command) |
return command(parser, args[1:]) |
- if commands['help']: |
+ cmdhelp = self.enumerate_commands().get('help') |
+ if cmdhelp: |
# Not a known command. Default to help. |
- self._add_command_usage(parser, commands['help']) |
- return commands['help'](parser, args) |
+ self._add_command_usage(parser, cmdhelp) |
+ return cmdhelp(parser, args) |
# Nothing can be done. |
return 2 |