[PATCH] [RFC] Allow multiple levels of command dispatching
Bryan O'Sullivan
bos at serpentine.com
Mon Apr 14 16:35:10 CDT 2008
# HG changeset patch
# User Bryan O'Sullivan <bos at serpentine.com>
# Date 1208208827 25200
# Node ID 4de599e16c27964ad12c227b5dee1f41cb2ff671
# Parent 2af1b9de62b34728b2ebe482ac2d36c1beab3b62
Allow multiple levels of command dispatching
This makes it possible to create a "master" command with several
subcommands, so one can run e.g. "hg module add".
This patch contains a single example command, "module add".
diff -r 2af1b9de62b3 -r 4de599e16c27 hgext/alias.py
--- a/hgext/alias.py Mon Apr 14 23:04:34 2008 +0200
+++ b/hgext/alias.py Mon Apr 14 14:33:47 2008 -0700
@@ -42,7 +42,7 @@
return
try:
- self._cmd = findcmd(self._ui, self._target, commands.table)[1]
+ self._cmd = findcmd(self._ui, [self._target], commands.table)[2]
if self._cmd == self:
raise RecursiveCommand()
if self._target in commands.norepo.split(' '):
diff -r 2af1b9de62b3 -r 4de599e16c27 hgext/keyword.py
--- a/hgext/keyword.py Mon Apr 14 23:04:34 2008 +0200
+++ b/hgext/keyword.py Mon Apr 14 14:33:47 2008 -0700
@@ -417,9 +417,10 @@
kwtools['exc'].append(pat)
if kwtools['inc']:
- def kwdispatch_parse(ui, args):
+ def kwdispatch_parse(ui, args, table):
'''Monkeypatch dispatch._parse to obtain running hg command.'''
- cmd, func, args, options, cmdoptions = dispatch_parse(ui, args)
+ cmd, func, args, options, cmdoptions = dispatch_parse(ui, args,
+ table)
kwtools['hgcmd'] = cmd
return cmd, func, args, options, cmdoptions
diff -r 2af1b9de62b3 -r 4de599e16c27 mercurial/cmdutil.py
--- a/mercurial/cmdutil.py Mon Apr 14 23:04:34 2008 +0200
+++ b/mercurial/cmdutil.py Mon Apr 14 14:33:47 2008 -0700
@@ -46,22 +46,32 @@
return choice
-def findcmd(ui, cmd, table):
- """Return (aliases, command table entry) for command string."""
- choice = findpossible(ui, cmd, table)
+def findcmd(ui, args, table):
+ "Return (aliases, command, command table entry, args) for command string."
+ trail = []
+ for i in xrange(len(args)):
+ cmd = args[i]
+ choice = findpossible(ui, cmd, table)
- if cmd in choice:
- return choice[cmd]
+ entry = None
- if len(choice) > 1:
- clist = choice.keys()
- clist.sort()
- raise AmbiguousCommand(cmd, clist)
+ if cmd in choice:
+ aliases, entry = choice[cmd]
+ elif len(choice) > 1:
+ clist = choice.keys()
+ clist.sort()
+ raise AmbiguousCommand(' '.join(args[:i+1]), clist)
+ elif choice:
+ aliases, entry = choice.values()[0]
+ else:
+ raise UnknownCommand(' '.join(args[:i+1]))
- if choice:
- return choice.values()[0]
-
- raise UnknownCommand(cmd)
+ trail.append(aliases[0])
+ if isinstance(entry[0], dict):
+ table = entry[0]
+ else:
+ return aliases, trail, entry, args[i+1:]
+ return [trail[-1]], trail, entry, args[i+1:]
def bail_if_changed(repo):
if repo.dirstate.parents()[1] != nullid:
diff -r 2af1b9de62b3 -r 4de599e16c27 mercurial/commands.py
--- a/mercurial/commands.py Mon Apr 14 23:04:34 2008 +0200
+++ b/mercurial/commands.py Mon Apr 14 14:33:47 2008 -0700
@@ -11,7 +11,7 @@
import os, re, sys, urllib
import hg, util, revlog, bundlerepo, extensions, copies
import difflib, patch, time, help, mdiff, tempfile
-import version, socket
+import dispatch, version, socket
import archival, changegroup, cmdutil, hgweb.server, sshserver, hbisect
import merge as merge_
@@ -609,14 +609,14 @@
a = r.ancestor(lookup(rev1), lookup(rev2))
ui.write("%d:%s\n" % (r.rev(a), hex(a)))
-def debugcomplete(ui, cmd='', **opts):
+def debugcomplete(ui, *cmds, **opts):
"""returns the completion list associated with the given command"""
if opts['options']:
options = []
otables = [globalopts]
- if cmd:
- aliases, entry = cmdutil.findcmd(ui, cmd, table)
+ if cmds:
+ entry = cmdutil.findcmd(ui, cmds, table)[2]
otables.append(entry[1])
for t in otables:
for o in t:
@@ -626,7 +626,21 @@
ui.write("%s\n" % "\n".join(options))
return
- clist = cmdutil.findpossible(ui, cmd, table).keys()
+ if not cmds:
+ clist = cmdutil.findpossible(ui, '', table).keys()
+ else:
+ cmdtable = table
+ for cmd in cmds:
+ c = cmdutil.findpossible(ui, cmd, cmdtable)
+ clist = c.keys()
+ if not c or len(c) > 1:
+ break
+ ent = c.values()[0][1][0]
+ if isinstance(ent, dict):
+ cmdtable = ent
+ else:
+ break
+
clist.sort()
ui.write("%s\n" % "\n".join(clist))
@@ -1213,7 +1227,7 @@
for n in heads:
displayer.show(changenode=n)
-def help_(ui, name=None, with_version=False):
+def help_(ui, *names, **opts):
"""show help for a command, extension, or list of commands
With no arguments, print a list of commands and short help.
@@ -1224,42 +1238,51 @@
commands it provides."""
option_lists = []
+ pname = ' '.join(names)
+
def addglobalopts(aliases):
if ui.verbose:
option_lists.append((_("global options:"), globalopts))
- if name == 'shortlist':
+ if names == ('shortlist',):
option_lists.append((_('use "hg help" for the full list '
'of commands'), ()))
else:
- if name == 'shortlist':
+ if names == ('shortlist',):
msg = _('use "hg help" for the full list of commands '
'or "hg -v" for details')
elif aliases:
msg = _('use "hg -v help%s" to show aliases and '
- 'global options') % (name and " " + name or "")
+ 'global options') % (pname and " " + pname or "")
else:
- msg = _('use "hg -v help %s" to show global options') % name
+ msg = _('use "hg -v help %s" to show global options') % pname
option_lists.append((msg, ()))
- def helpcmd(name):
- if with_version:
+ def helpcmd(*names):
+ if opts.get('with_version'):
version_(ui)
ui.write('\n')
- aliases, i = cmdutil.findcmd(ui, name, table)
+ aliases, cmd, i = cmdutil.findcmd(ui, names, table)[:3]
# synopsis
ui.write("%s\n" % i[2])
# aliases
if not ui.quiet and len(aliases) > 1:
- ui.write(_("\naliases: %s\n") % ', '.join(aliases[1:]))
+ if len(cmd) > 1:
+ prefix = ' '.join(cmd) + ' '
+ else:
+ prefix = ''
+ ui.write(_("\naliases: %s%s\n") % (prefix, ', '.join(aliases[1:])))
- # description
- doc = i[0].__doc__
- if not doc:
- doc = _("(No help text available)")
- if ui.quiet:
- doc = doc.splitlines(0)[0]
- ui.write("\n%s\n" % doc.rstrip())
+ if isinstance(i[0], dict):
+ helplist(_('list of subcommands:\n\n'), cmdtable=i[0])
+ else:
+ # description
+ doc = i[0].__doc__
+ if not doc:
+ doc = _("(No help text available)")
+ if ui.quiet:
+ doc = doc.splitlines(0)[0]
+ ui.write("\n%s\n" % doc.rstrip())
if not ui.quiet:
# options
@@ -1268,23 +1291,33 @@
addglobalopts(False)
- def helplist(header, select=None):
+ def helplist(header, select=None, cmdtable=table):
h = {}
cmds = {}
- for c, e in table.items():
- f = c.split("|", 1)[0]
- if select and not select(f):
- continue
- if name == "shortlist" and not f.startswith("^"):
- continue
- f = f.lstrip("^")
- if not ui.debugflag and f.startswith("debug"):
- continue
- doc = e[0].__doc__
- if not doc:
- doc = _("(No help text available)")
- h[f] = doc.splitlines(0)[0].rstrip()
- cmds[f] = c.lstrip("^")
+ def findcmds(prefix, table):
+ for c, e in table.iteritems():
+ f = c.split("|", 1)[0]
+ if select and not select(f):
+ continue
+ if names == ('shortlist',) and not f.startswith("^"):
+ continue
+ f = f.lstrip("^")
+ if not ui.debugflag and f.startswith("debug"):
+ continue
+ f = prefix + f
+ if isinstance(e[0], dict):
+ if ui.verbose:
+ findcmds(f + ' ', e[0])
+ continue
+ else:
+ doc = e[2]
+ else:
+ doc = e[0].__doc__
+ if not doc:
+ doc = _("(No help text available)")
+ h[f] = doc.splitlines(0)[0].rstrip()
+ cmds[f] = prefix + c.lstrip("^")
+ findcmds('', cmdtable)
if not h:
ui.status(_('no commands defined\n'))
@@ -1343,13 +1376,13 @@
ct = {}
modcmds = dict.fromkeys([c.split('|', 1)[0] for c in ct])
- helplist(_('list of commands:\n\n'), modcmds.has_key)
+ helplist(_('list of commands:\n\n'), select=modcmds.has_key)
- if name and name != 'shortlist':
+ if names and names != ('shortlist',):
i = None
for f in (helpcmd, helptopic, helpext):
try:
- f(name)
+ f(*names)
i = None
break
except cmdutil.UnknownCommand, inst:
@@ -1359,14 +1392,14 @@
else:
# program name
- if ui.verbose or with_version:
+ if ui.verbose or opts.get('with_version'):
version_(ui)
else:
ui.status(_("Mercurial Distributed SCM\n"))
ui.status('\n')
# list of commands
- if name == "shortlist":
+ if names == ('shortlist',):
header = _('basic commands:\n\n')
else:
header = _('list of commands:\n\n')
@@ -1895,6 +1928,14 @@
'use "hg update" or merge with an explicit rev'))
node = parent == heads[0] and heads[-1] or heads[0]
return hg.merge(repo, node, force=force)
+
+def module_add(ui, repo, mod):
+ "floom!"
+ print mod
+
+module_table = {
+ "add": (module_add, [], _('hg module add [OPTION] MOD')),
+ }
def outgoing(ui, repo, dest=None, **opts):
"""show changesets not found in destination
@@ -3179,6 +3220,8 @@
('r', 'rev', '', _('revision to merge')),
],
_('hg merge [-f] [[-r] REV]')),
+ "module":
+ (module_table, [], _('module management commands\n')),
"outgoing|out":
(outgoing,
[('f', 'force', None,
diff -r 2af1b9de62b3 -r 4de599e16c27 mercurial/dispatch.py
--- a/mercurial/dispatch.py Mon Apr 14 23:04:34 2008 +0200
+++ b/mercurial/dispatch.py Mon Apr 14 14:33:47 2008 -0700
@@ -158,7 +158,7 @@
return p
-def _parse(ui, args):
+def _parse(ui, args, table):
options = {}
cmdoptions = {}
@@ -168,10 +168,8 @@
raise ParseError(None, inst)
if args:
- cmd, args = args[0], args[1:]
- aliases, i = cmdutil.findcmd(ui, cmd, commands.table)
- cmd = aliases[0]
- defaults = ui.config("defaults", cmd)
+ cmd, i, args = cmdutil.findcmd(ui, args, table)[1:]
+ defaults = ui.config("defaults", ' '.join(cmd))
if defaults:
args = shlex.split(defaults) + args
c = list(i[1])
@@ -186,7 +184,7 @@
try:
args = fancyopts.fancyopts(args, c, cmdoptions)
except fancyopts.getopt.GetoptError, inst:
- raise ParseError(cmd, inst)
+ raise ParseError(' '.join(cmd), inst)
# separate global options back out
for o in commands.globalopts:
@@ -296,7 +294,8 @@
util._fallbackencoding = fallback
fullargs = args
- cmd, func, args, options, cmdoptions = _parse(lui, args)
+ cmd, func, args, options, cmdoptions = _parse(lui, args, commands.table)
+ tcmd = ' '.join(cmd or [])
if options["config"]:
raise util.Abort(_("Option --config may not be abbreviated!"))
@@ -328,14 +327,15 @@
not options["noninteractive"], options["traceback"])
if options['help']:
- return commands.help_(ui, cmd, options['version'])
+ return commands.help_(ui, with_version=options['version'],
+ *(cmd or []))
elif options['version']:
return commands.version_(ui)
elif not cmd:
return commands.help_(ui, 'shortlist')
repo = None
- if cmd not in commands.norepo.split():
+ if tcmd not in commands.norepo.split():
try:
repo = hg.repository(ui, path=path)
ui = repo.ui
@@ -343,7 +343,7 @@
raise util.Abort(_("repository '%s' is not local") % path)
ui.setconfig("bundle", "mainreporoot", repo.root)
except RepoError:
- if cmd not in commands.optionalrepo.split():
+ if tcmd not in commands.optionalrepo.split():
if args and not path: # try to infer -R from command args
repos = map(_findrepo, args)
guess = repos[0]
@@ -358,13 +358,14 @@
d = lambda: func(ui, *args, **cmdoptions)
# run pre-hook, and abort if it fails
- ret = hook.hook(lui, repo, "pre-%s" % cmd, False, args=" ".join(fullargs))
+ ret = hook.hook(lui, repo, "pre-%s" % '-'.join(cmd), False,
+ args=" ".join(fullargs))
if ret:
return ret
ret = _runcommand(ui, options, cmd, d)
# run post-hook, passing command result
- hook.hook(lui, repo, "post-%s" % cmd, False, args=" ".join(fullargs),
- result = ret)
+ hook.hook(lui, repo, "post-%s" % '-'.join(cmd), False,
+ args=" ".join(fullargs), result=ret)
return ret
def _runcommand(ui, options, cmd, cmdfunc):
@@ -376,9 +377,9 @@
tb = traceback.extract_tb(sys.exc_info()[2])
if len(tb) != 2: # no
raise
- raise ParseError(cmd, _("invalid arguments"))
+ raise ParseError(' '.join(cmd), _("invalid arguments"))
- if options['profile']:
+ if options.get('profile'):
import hotshot, hotshot.stats
prof = hotshot.Profile("hg.prof")
try:
@@ -397,7 +398,7 @@
stats.strip_dirs()
stats.sort_stats('time', 'calls')
stats.print_stats(40)
- elif options['lsprof']:
+ elif options.get('lsprof'):
try:
from mercurial import lsprof
except ImportError:
diff -r 2af1b9de62b3 -r 4de599e16c27 tests/test-debugcomplete.out
--- a/tests/test-debugcomplete.out Mon Apr 14 23:04:34 2008 +0200
+++ b/tests/test-debugcomplete.out Mon Apr 14 14:33:47 2008 -0700
@@ -25,6 +25,7 @@
log
manifest
merge
+module
outgoing
parents
paths
diff -r 2af1b9de62b3 -r 4de599e16c27 tests/test-globalopts.out
--- a/tests/test-globalopts.out Mon Apr 14 23:04:34 2008 +0200
+++ b/tests/test-globalopts.out Mon Apr 14 14:33:47 2008 -0700
@@ -175,6 +175,7 @@
log show revision history of entire repository or files
manifest output the current or given revision of the project manifest
merge merge working directory with another revision
+ module add (No help text available)
outgoing show changesets not found in destination
parents show the parents of the working dir or revision
paths show definition of symbolic path names
@@ -229,6 +230,7 @@
log show revision history of entire repository or files
manifest output the current or given revision of the project manifest
merge merge working directory with another revision
+ module add (No help text available)
outgoing show changesets not found in destination
parents show the parents of the working dir or revision
paths show definition of symbolic path names
diff -r 2af1b9de62b3 -r 4de599e16c27 tests/test-help.out
--- a/tests/test-help.out Mon Apr 14 23:04:34 2008 +0200
+++ b/tests/test-help.out Mon Apr 14 14:33:47 2008 -0700
@@ -66,6 +66,7 @@
log show revision history of entire repository or files
manifest output the current or given revision of the project manifest
merge merge working directory with another revision
+ module add (No help text available)
outgoing show changesets not found in destination
parents show the parents of the working dir or revision
paths show definition of symbolic path names
@@ -116,6 +117,7 @@
log show revision history of entire repository or files
manifest output the current or given revision of the project manifest
merge merge working directory with another revision
+ module add (No help text available)
outgoing show changesets not found in destination
parents show the parents of the working dir or revision
paths show definition of symbolic path names
More information about the Mercurial-devel
mailing list