150
148
raise BzrCommandError(msg)
153
def _builtin_commands():
151
def get_merge_type(typestring):
152
"""Attempt to find the merge class/factory associated with a string."""
153
from merge import merge_types
155
return merge_types[typestring][0]
157
templ = '%s%%7s: %%s' % (' '*12)
158
lines = [templ % (f[0], f[1][1]) for f in merge_types.iteritems()]
159
type_list = '\n'.join(lines)
160
msg = "No known merge type %s. Supported types are:\n%s" %\
161
(typestring, type_list)
162
raise BzrCommandError(msg)
166
def _get_cmd_dict(plugins_override=True):
154
167
import bzrlib.builtins
156
170
builtins = bzrlib.builtins.__dict__
157
171
for name in builtins:
158
172
if name.startswith("cmd_"):
159
real_name = _unsquish_command_name(name)
160
r[real_name] = builtins[name]
165
def builtin_command_names():
166
"""Return list of builtin command names."""
167
return _builtin_commands().keys()
170
def plugin_command_names():
171
return plugin_cmds.keys()
174
def _get_cmd_dict(plugins_override=True):
175
"""Return name->class mapping for all commands."""
176
d = _builtin_commands()
173
d[_unsquish_command_name(name)] = builtins[name]
174
# If we didn't load plugins, the plugin_cmds dict will be empty
177
175
if plugins_override:
178
176
d.update(plugin_cmds)
178
d2 = plugin_cmds.copy()
188
def get_cmd_object(cmd_name, plugins_override=True):
190
def get_cmd_class(cmd, plugins_override=True):
189
191
"""Return the canonical name and command class for a command.
192
If true, plugin commands can override builtins.
194
from bzrlib.externalcommand import ExternalCommand
196
cmd_name = str(cmd_name) # not unicode
193
cmd = str(cmd) # not unicode
198
195
# first look up this command under the specified name
199
196
cmds = _get_cmd_dict(plugins_override=plugins_override)
197
mutter("all commands: %r", cmds.keys())
201
return cmds[cmd_name]()
199
return cmd, cmds[cmd]
205
203
# look for any command which claims this as an alias
206
for real_cmd_name, cmd_class in cmds.iteritems():
207
if cmd_name in cmd_class.aliases:
210
cmd_obj = ExternalCommand.find_command(cmd_name)
214
raise BzrCommandError("unknown command %r" % cmd_name)
204
for cmdname, cmdclass in cmds.iteritems():
205
if cmd in cmdclass.aliases:
206
return cmdname, cmdclass
208
cmdclass = ExternalCommand.find_command(cmd)
212
raise BzrCommandError("unknown command %r" % cmd)
217
215
class Command(object):
218
216
"""Base class for commands.
220
Commands are the heart of the command-line bzr interface.
222
The command object mostly handles the mapping of command-line
223
parameters into one or more bzrlib operations, and of the results
226
Commands normally don't have any state. All their arguments are
227
passed in to the run method. (Subclasses may take a different
228
policy if the behaviour of the instance needs to depend on e.g. a
229
shell plugin and not just its Python class.)
231
218
The docstring for an actual command should give a single-line
232
219
summary, then a complete description of the command. A grammar
233
220
description will be inserted.
236
Other accepted names for this command.
239
223
List of argument forms, marked with whether they are optional,
257
"""Construct an instance of this command."""
239
def __init__(self, options, arguments):
240
"""Construct and run the command.
242
Sets self.status to the return value of run()."""
243
assert isinstance(options, dict)
244
assert isinstance(arguments, dict)
245
cmdargs = options.copy()
246
cmdargs.update(arguments)
258
247
if self.__doc__ == Command.__doc__:
248
from warnings import warn
259
249
warn("No help message set for %r" % self)
262
def run_argv(self, argv):
263
"""Parse command line and run."""
264
args, opts = parse_args(argv)
266
if 'help' in opts: # e.g. bzr add --help
267
from bzrlib.help import help_on_command
268
help_on_command(self.name())
271
# check options are reasonable
272
allowed = self.takes_options
274
if oname not in allowed:
275
raise BzrCommandError("option '--%s' is not allowed for command %r"
276
% (oname, self.name()))
278
# mix arguments and options into one dictionary
279
cmdargs = _match_argform(self.name(), self.takes_args, args)
281
for k, v in opts.items():
282
cmdopts[k.replace('-', '_')] = v
284
all_cmd_args = cmdargs.copy()
285
all_cmd_args.update(cmdopts)
287
return self.run(**all_cmd_args)
250
self.status = self.run(**cmdargs)
251
if self.status is None:
291
"""Actually run the command.
255
def run(self, *args, **kwargs):
256
"""Override this in sub-classes.
293
258
This is invoked with the options and arguments bound to
294
259
keyword parameters.
296
Return 0 or None if the command was successful, or a non-zero
297
shell error code if not. It's OK for this method to allow
298
an exception to raise up.
261
Return 0 or None if the command was successful, or a shell
300
264
raise NotImplementedError()
304
"""Return help message for this class."""
305
if self.__doc__ is Command.__doc__:
310
return _unsquish_command_name(self.__class__.__name__)
267
class ExternalCommand(Command):
268
"""Class to wrap external commands.
270
We cheat a little here, when get_cmd_class() calls us we actually
271
give it back an object we construct that has the appropriate path,
272
help, options etc for the specified command.
274
When run_bzr() tries to instantiate that 'class' it gets caught by
275
the __call__ method, which we override to call the Command.__init__
276
method. That then calls our run method which is pretty straight
279
The only wrinkle is that we have to map bzr's dictionary of options
280
and arguments back into command line options and arguments for the
284
def find_command(cls, cmd):
286
bzrpath = os.environ.get('BZRPATH', '')
288
for dir in bzrpath.split(os.pathsep):
289
path = os.path.join(dir, cmd)
290
if os.path.isfile(path):
291
return ExternalCommand(path)
295
find_command = classmethod(find_command)
297
def __init__(self, path):
300
pipe = os.popen('%s --bzr-usage' % path, 'r')
301
self.takes_options = pipe.readline().split()
303
for opt in self.takes_options:
304
if not opt in OPTIONS:
305
raise BzrError("Unknown option '%s' returned by external command %s"
308
# TODO: Is there any way to check takes_args is valid here?
309
self.takes_args = pipe.readline().split()
311
if pipe.close() is not None:
312
raise BzrError("Failed funning '%s --bzr-usage'" % path)
314
pipe = os.popen('%s --bzr-help' % path, 'r')
315
self.__doc__ = pipe.read()
316
if pipe.close() is not None:
317
raise BzrError("Failed funning '%s --bzr-help'" % path)
319
def __call__(self, options, arguments):
320
Command.__init__(self, options, arguments)
323
def run(self, **kargs):
330
optname = name.replace('_','-')
332
if OPTIONS.has_key(optname):
334
opts.append('--%s' % optname)
335
if value is not None and value is not True:
336
opts.append(str(value))
338
# it's an arg, or arg list
339
if type(value) is not list:
345
self.status = os.spawnv(os.P_WAIT, self.path, [self.path] + opts + args)
313
350
def parse_spec(spec):
605
if (not argv) or (argv[0] == '--help'):
617
if not opt_no_plugins:
618
from bzrlib.plugin import load_plugins
621
args, opts = parse_args(argv)
606
624
from bzrlib.help import help
613
if argv[0] == '--version':
631
if 'version' in opts:
614
632
from bzrlib.builtins import show_version
618
if not opt_no_plugins:
619
from bzrlib.plugin import load_plugins
622
cmd = str(argv.pop(0))
624
cmd_obj = get_cmd_object(cmd, plugins_override=not opt_builtin)
637
from bzrlib.help import help
641
cmd = str(args.pop(0))
643
canonical_cmd, cmd_class = \
644
get_cmd_class(cmd, plugins_override=not opt_builtin)
646
# check options are reasonable
647
allowed = cmd_class.takes_options
649
if oname not in allowed:
650
raise BzrCommandError("option '--%s' is not allowed for command %r"
653
# mix arguments and options into one dictionary
654
cmdargs = _match_argform(cmd, cmd_class.takes_args, args)
656
for k, v in opts.items():
657
cmdopts[k.replace('-', '_')] = v
627
ret = apply_profiled(cmd_obj.run_argv, argv)
660
import hotshot, tempfile
661
pffileno, pfname = tempfile.mkstemp()
663
prof = hotshot.Profile(pfname)
664
ret = prof.runcall(cmd_class, cmdopts, cmdargs) or 0
668
stats = hotshot.stats.load(pfname)
670
stats.sort_stats('time')
671
## XXX: Might like to write to stderr or the trace file instead but
672
## print_stats seems hardcoded to stdout
673
stats.print_stats(20)
629
ret = cmd_obj.run_argv(argv)
681
return cmd_class(cmdopts, cmdargs).status