1
# Copyright (C) 2004, 2005 by Canonical Ltd
3
# This program is free software; you can redistribute it and/or modify
4
# it under the terms of the GNU General Public License as published by
5
# the Free Software Foundation; either version 2 of the License, or
6
# (at your option) any later version.
8
# This program is distributed in the hope that it will be useful,
9
# but WITHOUT ANY WARRANTY; without even the implied warranty of
10
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11
# GNU General Public License for more details.
13
# You should have received a copy of the GNU General Public License
14
# along with this program; if not, write to the Free Software
15
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
18
# TODO: probably should say which arguments are candidates for glob
19
# expansion on windows and do that at the command level.
21
# TODO: Help messages for options.
23
# TODO: Define arguments by objects, rather than just using names.
24
# Those objects can specify the expected type of the argument, which
25
# would help with validation and shell completion.
27
# TODO: "--profile=cum", to change sort order. Is there any value in leaving
28
# the profile output behind so it can be interactively examined?
32
from warnings import warn
33
from inspect import getdoc
37
from bzrlib.trace import mutter, note, log_error, warning
38
from bzrlib.errors import BzrError, BzrCheckError, BzrCommandError, NotBranchError
39
from bzrlib.revisionspec import RevisionSpec
40
from bzrlib import BZRDIR
41
from bzrlib.option import Option
46
def register_command(cmd):
47
"Utility function to help register a command"
50
if k.startswith("cmd_"):
51
k_unsquished = _unsquish_command_name(k)
54
if not plugin_cmds.has_key(k_unsquished):
55
plugin_cmds[k_unsquished] = cmd
56
mutter('registered plugin command %s', k_unsquished)
58
log_error('Two plugins defined the same command: %r' % k)
59
log_error('Not loading the one in %r' % sys.modules[cmd.__module__])
62
def _squish_command_name(cmd):
63
return 'cmd_' + cmd.replace('-', '_')
66
def _unsquish_command_name(cmd):
67
assert cmd.startswith("cmd_")
68
return cmd[4:].replace('_','-')
71
def _builtin_commands():
72
import bzrlib.builtins
74
builtins = bzrlib.builtins.__dict__
76
if name.startswith("cmd_"):
77
real_name = _unsquish_command_name(name)
78
r[real_name] = builtins[name]
83
def builtin_command_names():
84
"""Return list of builtin command names."""
85
return _builtin_commands().keys()
88
def plugin_command_names():
89
return plugin_cmds.keys()
92
def _get_cmd_dict(plugins_override=True):
93
"""Return name->class mapping for all commands."""
94
d = _builtin_commands()
100
def get_all_cmds(plugins_override=True):
101
"""Return canonical name and class for all registered commands."""
102
for k, v in _get_cmd_dict(plugins_override=plugins_override).iteritems():
106
def get_cmd_object(cmd_name, plugins_override=True):
107
"""Return the canonical name and command class for a command.
110
If true, plugin commands can override builtins.
112
from bzrlib.externalcommand import ExternalCommand
114
cmd_name = str(cmd_name) # not unicode
116
# first look up this command under the specified name
117
cmds = _get_cmd_dict(plugins_override=plugins_override)
119
return cmds[cmd_name]()
123
# look for any command which claims this as an alias
124
for real_cmd_name, cmd_class in cmds.iteritems():
125
if cmd_name in cmd_class.aliases:
128
cmd_obj = ExternalCommand.find_command(cmd_name)
132
raise BzrCommandError("unknown command %r" % cmd_name)
135
class Command(object):
136
"""Base class for commands.
138
Commands are the heart of the command-line bzr interface.
140
The command object mostly handles the mapping of command-line
141
parameters into one or more bzrlib operations, and of the results
144
Commands normally don't have any state. All their arguments are
145
passed in to the run method. (Subclasses may take a different
146
policy if the behaviour of the instance needs to depend on e.g. a
147
shell plugin and not just its Python class.)
149
The docstring for an actual command should give a single-line
150
summary, then a complete description of the command. A grammar
151
description will be inserted.
154
Other accepted names for this command.
157
List of argument forms, marked with whether they are optional,
161
List of options that may be given for this command.
164
If true, this command isn't advertised. This is typically
165
for commands intended for expert users.
175
"""Construct an instance of this command."""
176
if self.__doc__ == Command.__doc__:
177
warn("No help message set for %r" % self)
180
def run_argv(self, argv):
181
"""Parse command line and run."""
182
args, opts = parse_args(argv)
184
if 'help' in opts: # e.g. bzr add --help
185
from bzrlib.help import help_on_command
186
help_on_command(self.name())
189
# check options are reasonable
190
allowed = self.takes_options
192
if oname not in allowed:
193
raise BzrCommandError("option '--%s' is not allowed for command %r"
194
% (oname, self.name()))
196
# mix arguments and options into one dictionary
197
cmdargs = _match_argform(self.name(), self.takes_args, args)
199
for k, v in opts.items():
200
cmdopts[k.replace('-', '_')] = v
202
all_cmd_args = cmdargs.copy()
203
all_cmd_args.update(cmdopts)
205
return self.run(**all_cmd_args)
209
"""Actually run the command.
211
This is invoked with the options and arguments bound to
214
Return 0 or None if the command was successful, or a non-zero
215
shell error code if not. It's OK for this method to allow
216
an exception to raise up.
218
raise NotImplementedError()
222
"""Return help message for this class."""
223
if self.__doc__ is Command.__doc__:
228
return _unsquish_command_name(self.__class__.__name__)
231
def parse_spec(spec):
237
>>> parse_spec("../@")
239
>>> parse_spec("../f/@35")
241
>>> parse_spec('./@revid:john@arbash-meinel.com-20050711044610-3ca0327c6a222f67')
242
['.', 'revid:john@arbash-meinel.com-20050711044610-3ca0327c6a222f67']
247
parsed = spec.split('/@')
248
assert len(parsed) == 2
253
parsed[1] = int(parsed[1])
255
pass # We can allow stuff like ./@revid:blahblahblah
259
parsed = [spec, None]
262
def parse_args(argv):
263
"""Parse command line.
265
Arguments and options are parsed at this level before being passed
266
down to specific command handlers. This routine knows, from a
267
lookup table, something about the available options, what optargs
268
they take, and which commands will accept them.
270
>>> parse_args('--help'.split())
272
>>> parse_args('help -- --invalidcmd'.split())
273
(['help', '--invalidcmd'], {})
274
>>> parse_args('--version'.split())
275
([], {'version': True})
276
>>> parse_args('status --all'.split())
277
(['status'], {'all': True})
278
>>> parse_args('commit --message=biter'.split())
279
(['commit'], {'message': u'biter'})
280
>>> parse_args('log -r 500'.split())
281
(['log'], {'revision': [<RevisionSpec_int 500>]})
282
>>> parse_args('log -r500..600'.split())
283
(['log'], {'revision': [<RevisionSpec_int 500>, <RevisionSpec_int 600>]})
284
>>> parse_args('log -vr500..600'.split())
285
(['log'], {'verbose': True, 'revision': [<RevisionSpec_int 500>, <RevisionSpec_int 600>]})
286
>>> parse_args('log -rrevno:500..600'.split()) #the r takes an argument
287
(['log'], {'revision': [<RevisionSpec_revno revno:500>, <RevisionSpec_int 600>]})
295
if not argsover and a[0] == '-':
296
# option names must not be unicode
301
# We've received a standalone -- No more flags
304
mutter(" got option %r" % a)
306
optname, optarg = a[2:].split('=', 1)
309
if optname not in Option.OPTIONS:
310
raise BzrError('unknown long option %r' % a)
313
if shortopt in Option.SHORT_OPTIONS:
314
# Multi-character options must have a space to delimit
316
optname = Option.SHORT_OPTIONS[shortopt].name
318
# Single character short options, can be chained,
319
# and have their value appended to their name
321
if shortopt not in Option.SHORT_OPTIONS:
322
# We didn't find the multi-character name, and we
323
# didn't find the single char name
324
raise BzrError('unknown short option %r' % a)
325
optname = Option.SHORT_OPTIONS[shortopt].name
328
# There are extra things on this option
329
# see if it is the value, or if it is another
331
optargfn = Option.OPTIONS[optname].type
333
# This option does not take an argument, so the
334
# next entry is another short option, pack it back
336
argv.insert(0, '-' + a[2:])
338
# This option takes an argument, so pack it
343
# XXX: Do we ever want to support this, e.g. for -r?
344
raise BzrError('repeated option %r' % a)
346
optargfn = Option.OPTIONS[optname].type
350
raise BzrError('option %r needs an argument' % a)
353
opts[optname] = optargfn(optarg)
356
raise BzrError('option %r takes no argument' % optname)
366
def _match_argform(cmd, takes_args, args):
369
# step through args and takes_args, allowing appropriate 0-many matches
370
for ap in takes_args:
374
argdict[argname] = args.pop(0)
375
elif ap[-1] == '*': # all remaining arguments
377
argdict[argname + '_list'] = args[:]
380
argdict[argname + '_list'] = None
383
raise BzrCommandError("command %r needs one or more %s"
384
% (cmd, argname.upper()))
386
argdict[argname + '_list'] = args[:]
388
elif ap[-1] == '$': # all but one
390
raise BzrCommandError("command %r needs one or more %s"
391
% (cmd, argname.upper()))
392
argdict[argname + '_list'] = args[:-1]
398
raise BzrCommandError("command %r requires argument %s"
399
% (cmd, argname.upper()))
401
argdict[argname] = args.pop(0)
404
raise BzrCommandError("extra argument to command %s: %s"
411
def apply_profiled(the_callable, *args, **kwargs):
415
pffileno, pfname = tempfile.mkstemp()
417
prof = hotshot.Profile(pfname)
419
ret = prof.runcall(the_callable, *args, **kwargs) or 0
422
stats = hotshot.stats.load(pfname)
424
stats.sort_stats('cum') # 'time'
425
## XXX: Might like to write to stderr or the trace file instead but
426
## print_stats seems hardcoded to stdout
427
stats.print_stats(20)
435
"""Execute a command.
437
This is similar to main(), but without all the trappings for
438
logging and error handling.
441
The command-line arguments, without the program name from argv[0]
443
Returns a command status or raises an exception.
445
Special master options: these must come before the command because
446
they control how the command is interpreted.
449
Do not load plugin modules at all
452
Only use builtin commands. (Plugins are still allowed to change
456
Run under the Python profiler.
458
# Load all of the transport methods
459
import bzrlib.transport.local, bzrlib.transport.http
461
argv = [a.decode(bzrlib.user_encoding) for a in argv]
463
opt_profile = opt_no_plugins = opt_builtin = False
465
# --no-plugins is handled specially at a very early stage. We need
466
# to load plugins before doing other command parsing so that they
467
# can override commands, but this needs to happen first.
472
elif a == '--no-plugins':
473
opt_no_plugins = True
474
elif a == '--builtin':
480
if (not argv) or (argv[0] == '--help'):
481
from bzrlib.help import help
488
if argv[0] == '--version':
489
from bzrlib.builtins import show_version
493
if not opt_no_plugins:
494
from bzrlib.plugin import load_plugins
497
cmd = str(argv.pop(0))
499
cmd_obj = get_cmd_object(cmd, plugins_override=not opt_builtin)
502
ret = apply_profiled(cmd_obj.run_argv, argv)
504
ret = cmd_obj.run_argv(argv)
510
bzrlib.trace.log_startup(argv)
511
bzrlib.ui.ui_factory = bzrlib.ui.TextUIFactory()
513
return run_bzr_catch_errors(argv[1:])
516
def run_bzr_catch_errors(argv):
521
# do this here inside the exception wrappers to catch EPIPE
523
except BzrCommandError, e:
524
# command line syntax error, etc
528
bzrlib.trace.log_exception()
530
except AssertionError, e:
531
bzrlib.trace.log_exception('assertion failed: ' + str(e))
533
except KeyboardInterrupt, e:
534
bzrlib.trace.log_exception('interrupted')
538
if (isinstance(e, IOError)
539
and hasattr(e, 'errno')
540
and e.errno == errno.EPIPE):
541
bzrlib.trace.note('broken pipe')
546
bzrlib.trace.log_exception()
549
if __name__ == '__main__':
550
sys.exit(main(sys.argv))