| Index: subcommand.py
 | 
| diff --git a/subcommand.py b/subcommand.py
 | 
| index b6f8e5cd5f348cd8cf515951c543507435dc58fb..0201c90aa35d61a17252fd4eeb1e227bd76375d3 100644
 | 
| --- a/subcommand.py
 | 
| +++ b/subcommand.py
 | 
| @@ -37,6 +37,7 @@ Explanation:
 | 
|      will result in oldname not being documented but supported and redirecting to
 | 
|      newcmd. Make it a real function that calls the old function if you want it
 | 
|      to be documented.
 | 
| +  - CMDfoo_bar will be command 'foo-bar'.
 | 
|  """
 | 
|  
 | 
|  import difflib
 | 
| @@ -82,6 +83,11 @@ def _get_color_module():
 | 
|    return sys.modules.get('colorama') or sys.modules.get('third_party.colorama')
 | 
|  
 | 
|  
 | 
| +def _function_to_name(name):
 | 
| +  """Returns the name of a CMD function."""
 | 
| +  return name[3:].replace('_', '-')
 | 
| +
 | 
| +
 | 
|  class CommandDispatcher(object):
 | 
|    def __init__(self, module):
 | 
|      """module is the name of the main python module where to look for commands.
 | 
| @@ -103,32 +109,31 @@ class CommandDispatcher(object):
 | 
|  
 | 
|      Automatically adds 'help' if not already defined.
 | 
|  
 | 
| +    Normalizes '_' in the commands to '-'.
 | 
| +
 | 
|      A command can be effectively disabled by defining a global variable to None,
 | 
|      e.g.:
 | 
|        CMDhelp = None
 | 
|      """
 | 
|      cmds = dict(
 | 
| -        (fn[3:], getattr(self.module, fn))
 | 
| -        for fn in dir(self.module) if fn.startswith('CMD'))
 | 
| +        (_function_to_name(name), getattr(self.module, name))
 | 
| +        for name in dir(self.module) if name.startswith('CMD'))
 | 
|      cmds.setdefault('help', CMDhelp)
 | 
|      return cmds
 | 
|  
 | 
| -  def find_nearest_command(self, name):
 | 
| -    """Retrieves the function to handle a command.
 | 
| +  def find_nearest_command(self, name_asked):
 | 
| +    """Retrieves the function to handle a command as supplied by the user.
 | 
|  
 | 
| -    It automatically tries to guess the intended command by handling typos or
 | 
| -    incomplete names.
 | 
| +    It automatically tries to guess the _intended command_ by handling typos
 | 
| +    and/or incomplete names.
 | 
|      """
 | 
| -    # Implicitly replace foo-bar to foo_bar since foo-bar is not a valid python
 | 
| -    # symbol but it's faster to type.
 | 
| -    name = name.replace('-', '_')
 | 
|      commands = self.enumerate_commands()
 | 
| -    if name in commands:
 | 
| -      return commands[name]
 | 
| +    if name_asked in commands:
 | 
| +      return commands[name_asked]
 | 
|  
 | 
|      # An exact match was not found. Try to be smart and look if there's
 | 
|      # something similar.
 | 
| -    commands_with_prefix = [c for c in commands if c.startswith(name)]
 | 
| +    commands_with_prefix = [c for c in commands if c.startswith(name_asked)]
 | 
|      if len(commands_with_prefix) == 1:
 | 
|        return commands[commands_with_prefix[0]]
 | 
|  
 | 
| @@ -137,7 +142,7 @@ class CommandDispatcher(object):
 | 
|        return difflib.SequenceMatcher(a=a, b=b).ratio()
 | 
|  
 | 
|      hamming_commands = sorted(
 | 
| -        ((close_enough(c, name), c) for c in commands),
 | 
| +        ((close_enough(c, name_asked), c) for c in commands),
 | 
|          reverse=True)
 | 
|      if (hamming_commands[0][0] - hamming_commands[1][0]) < 0.3:
 | 
|        # Too ambiguous.
 | 
| @@ -153,8 +158,8 @@ class CommandDispatcher(object):
 | 
|      """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())
 | 
| +        (cmd_name, self._create_command_summary(cmd_name, handler))
 | 
| +        for cmd_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:
 | 
| @@ -169,14 +174,14 @@ class CommandDispatcher(object):
 | 
|      return (
 | 
|          'Commands are:\n' +
 | 
|          ''.join(
 | 
| -            '  %s%-*s%s %s\n' % (green, length, name, reset, doc)
 | 
| -            for name, doc in docs))
 | 
| +            '  %s%-*s%s %s\n' % (green, length, cmd_name, reset, doc)
 | 
| +            for cmd_name, doc in docs))
 | 
|  
 | 
|    def _add_command_usage(self, parser, command):
 | 
|      """Modifies an OptionParser object with the function's documentation."""
 | 
| -    name = command.__name__[3:]
 | 
| -    if name == 'help':
 | 
| -      name = '<command>'
 | 
| +    cmd_name = _function_to_name(command.__name__)
 | 
| +    if cmd_name == 'help':
 | 
| +      cmd_name = '<command>'
 | 
|        # Use the module's docstring as the description for the 'help' command if
 | 
|        # available.
 | 
|        parser.description = (self.module.__doc__ or '').rstrip()
 | 
| @@ -200,14 +205,15 @@ class CommandDispatcher(object):
 | 
|          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))
 | 
| +    extra = '' if not more else ' ' + more
 | 
| +    parser.set_usage('usage: %%prog %s [options]%s' % (cmd_name, extra))
 | 
|  
 | 
|    @staticmethod
 | 
| -  def _create_command_summary(name, command):
 | 
| -    """Creates a oneline summary from the command's docstring."""
 | 
| -    if name != command.__name__[3:]:
 | 
| -      # Skip aliases.
 | 
| +  def _create_command_summary(cmd_name, command):
 | 
| +    """Creates a oneliner summary from the command's docstring."""
 | 
| +    if cmd_name != _function_to_name(command.__name__):
 | 
| +      # Skip aliases. For example using at module level:
 | 
| +      # CMDfoo = CMDbar
 | 
|        return ''
 | 
|      doc = command.__doc__ or ''
 | 
|      line = doc.split('\n', 1)[0].rstrip('.')
 | 
| 
 |