18
18
# TODO: probably should say which arguments are candidates for glob
19
19
# expansion on windows and do that at the command level.
21
# TODO: Help messages for options.
21
23
# TODO: Define arguments by objects, rather than just using names.
22
24
# Those objects can specify the expected type of the argument, which
23
# would help with validation and shell completion. They could also provide
24
# help/explanation for that argument in a structured way.
26
# TODO: Specific "examples" property on commands for consistent formatting.
25
# would help with validation and shell completion.
28
27
# TODO: "--profile=cum", to change sort order. Is there any value in leaving
29
28
# the profile output behind so it can be interactively examined?
34
from bzrlib.lazy_import import lazy_import
35
lazy_import(globals(), """
32
from warnings import warn
33
from inspect import getdoc
38
from warnings import warn
52
from bzrlib.symbol_versioning import (
37
from bzrlib.errors import (BzrError,
57
42
from bzrlib.option import Option
43
from bzrlib.revisionspec import RevisionSpec
44
from bzrlib.symbol_versioning import *
46
from bzrlib.trace import mutter, note, log_error, warning, be_quiet
63
51
def register_command(cmd, decorate=False):
64
"""Utility function to help register a command
66
:param cmd: Command subclass to register
67
:param decorate: If true, allow overriding an existing command
68
of the same name; the old command is returned by this function.
69
Otherwise it is an error to try to override an existing command.
52
"Utility function to help register a command"
73
55
if k.startswith("cmd_"):
74
56
k_unsquished = _unsquish_command_name(k)
77
if k_unsquished not in plugin_cmds:
59
if not plugin_cmds.has_key(k_unsquished):
78
60
plugin_cmds[k_unsquished] = cmd
79
## trace.mutter('registered plugin command %s', k_unsquished)
61
mutter('registered plugin command %s', k_unsquished)
80
62
if decorate and k_unsquished in builtin_command_names():
81
63
return _builtin_commands()[k_unsquished]
251
195
"""Construct an instance of this command."""
252
196
if self.__doc__ == Command.__doc__:
253
197
warn("No help message set for %r" % self)
254
# List of standard options directly supported
255
self.supported_std_options = []
257
def _maybe_expand_globs(self, file_list):
258
"""Glob expand file_list if the platform does not do that itself.
260
:return: A possibly empty list of unicode paths.
262
Introduced in bzrlib 0.18.
266
if sys.platform == 'win32':
267
file_list = win32utils.glob_expand(file_list)
268
return list(file_list)
271
"""Return single-line grammar for this command.
273
Only describes arguments, not options.
275
s = 'bzr ' + self.name() + ' '
276
for aname in self.takes_args:
277
aname = aname.upper()
278
if aname[-1] in ['$', '+']:
279
aname = aname[:-1] + '...'
280
elif aname[-1] == '?':
281
aname = '[' + aname[:-1] + ']'
282
elif aname[-1] == '*':
283
aname = '[' + aname[:-1] + '...]'
285
s = s[:-1] # remove last space
288
def get_help_text(self, additional_see_also=None, plain=True,
289
see_also_as_links=False):
290
"""Return a text string with help for this command.
292
:param additional_see_also: Additional help topics to be
294
:param plain: if False, raw help (reStructuredText) is
295
returned instead of plain text.
296
:param see_also_as_links: if True, convert items in 'See also'
297
list to internal links (used by bzr_man rstx generator)
301
raise NotImplementedError("sorry, no detailed help yet for %r" % self.name())
303
# Extract the summary (purpose) and sections out from the text
304
purpose,sections = self._get_help_parts(doc)
306
# If a custom usage section was provided, use it
307
if sections.has_key('Usage'):
308
usage = sections.pop('Usage')
310
usage = self._usage()
312
# The header is the purpose and usage
314
result += ':Purpose: %s\n' % purpose
315
if usage.find('\n') >= 0:
316
result += ':Usage:\n%s\n' % usage
318
result += ':Usage: %s\n' % usage
322
options = option.get_optparser(self.options()).format_option_help()
323
if options.startswith('Options:'):
324
result += ':' + options
325
elif options.startswith('options:'):
326
# Python 2.4 version of optparse
327
result += ':Options:' + options[len('options:'):]
332
# Add the description, indenting it 2 spaces
333
# to match the indentation of the options
334
if sections.has_key(None):
335
text = sections.pop(None)
336
text = '\n '.join(text.splitlines())
337
result += ':%s:\n %s\n\n' % ('Description',text)
339
# Add the custom sections (e.g. Examples). Note that there's no need
340
# to indent these as they must be indented already in the source.
342
labels = sorted(sections.keys())
344
result += ':%s:\n%s\n\n' % (label,sections[label])
346
# Add the aliases, source (plug-in) and see also links, if any
348
result += ':Aliases: '
349
result += ', '.join(self.aliases) + '\n'
350
plugin_name = self.plugin_name()
351
if plugin_name is not None:
352
result += ':From: plugin "%s"\n' % plugin_name
353
see_also = self.get_see_also(additional_see_also)
355
if not plain and see_also_as_links:
357
for item in see_also:
359
# topics doesn't have an independent section
360
# so don't create a real link
361
see_also_links.append(item)
363
# Use a reST link for this entry
364
see_also_links.append("`%s`_" % (item,))
365
see_also = see_also_links
366
result += ':See also: '
367
result += ', '.join(see_also) + '\n'
369
# If this will be rendered as plain text, convert it
371
import bzrlib.help_topics
372
result = bzrlib.help_topics.help_as_plain_text(result)
376
def _get_help_parts(text):
377
"""Split help text into a summary and named sections.
379
:return: (summary,sections) where summary is the top line and
380
sections is a dictionary of the rest indexed by section name.
381
A section starts with a heading line of the form ":xxx:".
382
Indented text on following lines is the section value.
383
All text found outside a named section is assigned to the
384
default section which is given the key of None.
386
def save_section(sections, label, section):
388
if sections.has_key(label):
389
sections[label] += '\n' + section
391
sections[label] = section
393
lines = text.rstrip().splitlines()
394
summary = lines.pop(0)
396
label,section = None,''
398
if line.startswith(':') and line.endswith(':') and len(line) > 2:
399
save_section(sections, label, section)
400
label,section = line[1:-1],''
401
elif (label is not None) and len(line) > 1 and not line[0].isspace():
402
save_section(sections, label, section)
403
label,section = None,line
406
section += '\n' + line
409
save_section(sections, label, section)
410
return summary, sections
412
def get_help_topic(self):
413
"""Return the commands help topic - its name."""
416
def get_see_also(self, additional_terms=None):
417
"""Return a list of help topics that are related to this command.
419
The list is derived from the content of the _see_also attribute. Any
420
duplicates are removed and the result is in lexical order.
421
:param additional_terms: Additional help topics to cross-reference.
422
:return: A list of help topics.
424
see_also = set(getattr(self, '_see_also', []))
426
see_also.update(additional_terms)
427
return sorted(see_also)
429
199
def options(self):
430
200
"""Return dict of valid options for this command.
432
202
Maps from long option name to option object."""
433
r = Option.STD_OPTIONS.copy()
204
r['help'] = Option.OPTIONS['help']
435
205
for o in self.takes_options:
436
if isinstance(o, basestring):
437
o = option.Option.OPTIONS[o]
206
if not isinstance(o, Option):
207
o = Option.OPTIONS[o]
439
if o.name in std_names:
440
self.supported_std_options.append(o.name)
443
def _setup_outf(self):
444
"""Return a file linked to stdout, which has proper encoding."""
445
# Originally I was using self.stdout, but that looks
446
# *way* too much like sys.stdout
447
if self.encoding_type == 'exact':
448
# force sys.stdout to be binary stream on win32
449
if sys.platform == 'win32':
450
fileno = getattr(sys.stdout, 'fileno', None)
453
msvcrt.setmode(fileno(), os.O_BINARY)
454
self.outf = sys.stdout
457
output_encoding = osutils.get_terminal_encoding()
459
self.outf = codecs.getwriter(output_encoding)(sys.stdout,
460
errors=self.encoding_type)
461
# For whatever reason codecs.getwriter() does not advertise its encoding
462
# it just returns the encoding of the wrapped file, which is completely
463
# bogus. So set the attribute, so we can find the correct encoding later.
464
self.outf.encoding = output_encoding
211
@deprecated_method(zero_eight)
212
def run_argv(self, argv):
213
"""Parse command line and run.
215
See run_argv_aliases for the 0.8 and beyond api.
217
return self.run_argv_aliases(argv)
466
219
def run_argv_aliases(self, argv, alias_argv=None):
467
220
"""Parse the command line and run with extra aliases in alias_argv."""
469
warn("Passing None for [] is deprecated from bzrlib 0.10",
470
DeprecationWarning, stacklevel=2)
472
221
args, opts = parse_args(self, argv, alias_argv)
474
# Process the standard options
475
222
if 'help' in opts: # e.g. bzr add --help
476
sys.stdout.write(self.get_help_text())
223
from bzrlib.help import help_on_command
224
help_on_command(self.name())
478
trace.set_verbosity_level(option._verbosity_level)
479
if 'verbose' in self.supported_std_options:
480
opts['verbose'] = trace.is_verbose()
481
elif opts.has_key('verbose'):
483
if 'quiet' in self.supported_std_options:
484
opts['quiet'] = trace.is_quiet()
485
elif opts.has_key('quiet'):
226
# XXX: This should be handled by the parser
227
allowed_names = self.options().keys()
229
if oname not in allowed_names:
230
raise BzrCommandError("option '--%s' is not allowed for"
231
" command %r" % (oname, self.name()))
488
232
# mix arguments and options into one dictionary
489
233
cmdargs = _match_argform(self.name(), self.takes_args, args)
541
302
lookup table, something about the available options, what optargs
542
303
they take, and which commands will accept them.
544
# TODO: make it a method of the Command?
545
parser = option.get_optparser(command.options())
546
if alias_argv is not None:
547
args = alias_argv + argv
551
options, args = parser.parse_args(args)
552
opts = dict([(k, v) for k, v in options.__dict__.iteritems() if
553
v is not option.OptionParser.DEFAULT_VALUE])
305
# TODO: chop up this beast; make it a method of the Command
310
cmd_options = command.options()
312
proc_aliasarg = True # Are we processing alias_argv now?
313
for proc_argv in alias_argv, argv:
320
# We've received a standalone -- No more flags
324
# option names must not be unicode
328
mutter(" got option %r", a)
330
optname, optarg = a[2:].split('=', 1)
333
if optname not in cmd_options:
334
raise BzrOptionError('unknown long option %r for'
339
if shortopt in Option.SHORT_OPTIONS:
340
# Multi-character options must have a space to delimit
342
# ^^^ what does this mean? mbp 20051014
343
optname = Option.SHORT_OPTIONS[shortopt].name
345
# Single character short options, can be chained,
346
# and have their value appended to their name
348
if shortopt not in Option.SHORT_OPTIONS:
349
# We didn't find the multi-character name, and we
350
# didn't find the single char name
351
raise BzrError('unknown short option %r' % a)
352
optname = Option.SHORT_OPTIONS[shortopt].name
355
# There are extra things on this option
356
# see if it is the value, or if it is another
358
optargfn = Option.OPTIONS[optname].type
360
# This option does not take an argument, so the
361
# next entry is another short option, pack it
363
proc_argv.insert(0, '-' + a[2:])
365
# This option takes an argument, so pack it
369
if optname not in cmd_options:
370
raise BzrOptionError('unknown short option %r for'
372
(shortopt, command.name()))
374
# XXX: Do we ever want to support this, e.g. for -r?
376
raise BzrError('repeated option %r' % a)
377
elif optname in alias_opts:
378
# Replace what's in the alias with what's in the real
380
del alias_opts[optname]
382
proc_argv.insert(0, a)
385
raise BzrError('repeated option %r' % a)
387
option_obj = cmd_options[optname]
388
optargfn = option_obj.type
392
raise BzrError('option %r needs an argument' % a)
394
optarg = proc_argv.pop(0)
395
opts[optname] = optargfn(optarg)
397
alias_opts[optname] = optargfn(optarg)
400
raise BzrError('option %r takes no argument' % optname)
403
alias_opts[optname] = True
406
proc_aliasarg = False # Done with alias argv
554
407
return args, opts
571
424
argdict[argname + '_list'] = None
572
425
elif ap[-1] == '+':
574
raise errors.BzrCommandError("command %r needs one or more %s"
575
% (cmd, argname.upper()))
427
raise BzrCommandError("command %r needs one or more %s"
428
% (cmd, argname.upper()))
577
430
argdict[argname + '_list'] = args[:]
579
432
elif ap[-1] == '$': # all but one
580
433
if len(args) < 2:
581
raise errors.BzrCommandError("command %r needs one or more %s"
582
% (cmd, argname.upper()))
434
raise BzrCommandError("command %r needs one or more %s"
435
% (cmd, argname.upper()))
583
436
argdict[argname + '_list'] = args[:-1]
586
439
# just a plain arg
589
raise errors.BzrCommandError("command %r requires argument %s"
590
% (cmd, argname.upper()))
442
raise BzrCommandError("command %r requires argument %s"
443
% (cmd, argname.upper()))
592
445
argdict[argname] = args.pop(0)
595
raise errors.BzrCommandError("extra argument to command %s: %s"
448
raise BzrCommandError("extra argument to command %s: %s"
600
def apply_coveraged(dirname, the_callable, *args, **kwargs):
601
# Cannot use "import trace", as that would import bzrlib.trace instead of
602
# the standard library's trace.
603
trace = __import__('trace')
605
tracer = trace.Trace(count=1, trace=0)
606
sys.settrace(tracer.globaltrace)
608
ret = the_callable(*args, **kwargs)
611
results = tracer.results()
612
results.write_results(show_missing=1, summary=False,
616
455
def apply_profiled(the_callable, *args, **kwargs):
836
635
from bzrlib.ui.text import TextUIFactory
636
## bzrlib.trace.enable_default_logging()
637
bzrlib.trace.log_startup(argv)
837
638
bzrlib.ui.ui_factory = TextUIFactory()
839
# Is this a final release version? If so, we should suppress warnings
840
if bzrlib.version_info[3] == 'final':
841
from bzrlib import symbol_versioning
842
symbol_versioning.suppress_deprecation_warnings(override=False)
844
argv = [a.decode(bzrlib.user_encoding) for a in argv[1:]]
845
except UnicodeDecodeError:
846
raise errors.BzrError(("Parameter '%r' is unsupported by the current "
848
ret = run_bzr_catch_errors(argv)
849
trace.mutter("return code %d", ret)
639
ret = run_bzr_catch_errors(argv[1:])
640
mutter("return code %d", ret)
853
644
def run_bzr_catch_errors(argv):
854
# Note: The except clause logic below should be kept in sync with the
855
# profile() routine in lsprof.py.
858
except (KeyboardInterrupt, Exception), e:
649
# do this here inside the exception wrappers to catch EPIPE
859
652
# used to handle AssertionError and KeyboardInterrupt
860
653
# specially here, but hopefully they're handled ok by the logger now
861
exitcode = trace.report_exception(sys.exc_info(), sys.stderr)
862
if os.environ.get('BZR_PDB'):
863
print '**** entering debugger'
865
pdb.post_mortem(sys.exc_traceback)
869
def run_bzr_catch_user_errors(argv):
870
"""Run bzr and report user errors, but let internal errors propagate.
872
This is used for the test suite, and might be useful for other programs
873
that want to wrap the commandline interface.
878
if (isinstance(e, (OSError, IOError))
879
or not getattr(e, 'internal_error', True)):
880
trace.report_exception(sys.exc_info(), sys.stderr)
886
class HelpCommandIndex(object):
887
"""A index for bzr help that returns commands."""
890
self.prefix = 'commands/'
892
def get_topics(self, topic):
893
"""Search for topic amongst commands.
895
:param topic: A topic to search for.
896
:return: A list which is either empty or contains a single
899
if topic and topic.startswith(self.prefix):
900
topic = topic[len(self.prefix):]
902
cmd = _get_cmd_object(topic)
909
class Provider(object):
910
'''Generic class to be overriden by plugins'''
912
def plugin_for_command(self, cmd_name):
913
'''Takes a command and returns the information for that plugin
915
:return: A dictionary with all the available information
916
for the requested plugin
918
raise NotImplementedError
921
class ProvidersRegistry(registry.Registry):
922
'''This registry exists to allow other providers to exist'''
925
for key, provider in self.iteritems():
928
command_providers_registry = ProvidersRegistry()
655
if (isinstance(e, IOError)
656
and hasattr(e, 'errno')
657
and e.errno == errno.EPIPE):
658
bzrlib.trace.note('broken pipe')
661
bzrlib.trace.log_exception()
662
if os.environ.get('BZR_PDB'):
663
print '**** entering debugger'
665
pdb.post_mortem(sys.exc_traceback)
931
668
if __name__ == '__main__':
932
669
sys.exit(main(sys.argv))