1
# Copyright (C) 2006, 2008 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 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: Define arguments by objects, rather than just using names.
22
# 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.
28
# TODO: "--profile=cum", to change sort order. Is there any value in leaving
29
# the profile output behind so it can be interactively examined?
34
from bzrlib.lazy_import import lazy_import
35
lazy_import(globals(), """
38
from warnings import warn
51
from bzrlib import registry
53
from bzrlib.hooks import HookPoint, Hooks
54
from bzrlib.option import Option
57
class CommandInfo(object):
58
"""Information about a command."""
60
def __init__(self, aliases):
61
"""The list of aliases for the command."""
62
self.aliases = aliases
65
def from_command(klass, command):
66
"""Factory to construct a CommandInfo from a command."""
67
return klass(command.aliases)
70
class CommandRegistry(registry.Registry):
73
def _get_name(command_name):
74
if command_name.startswith("cmd_"):
75
return _unsquish_command_name(command_name)
79
def register(self, cmd, decorate=False):
80
"""Utility function to help register a command
82
:param cmd: Command subclass to register
83
:param decorate: If true, allow overriding an existing command
84
of the same name; the old command is returned by this function.
85
Otherwise it is an error to try to override an existing command.
88
k_unsquished = self._get_name(k)
90
previous = self.get(k_unsquished)
92
previous = _builtin_commands().get(k_unsquished)
93
info = CommandInfo.from_command(cmd)
95
registry.Registry.register(self, k_unsquished, cmd,
96
override_existing=decorate, info=info)
98
trace.log_error('Two plugins defined the same command: %r' % k)
99
trace.log_error('Not loading the one in %r' %
100
sys.modules[cmd.__module__])
101
trace.log_error('Previously this command was registered from %r' %
102
sys.modules[previous.__module__])
105
def register_lazy(self, command_name, aliases, module_name):
106
"""Register a command without loading its module.
108
:param command_name: The primary name of the command.
109
:param aliases: A list of aliases for the command.
110
:module_name: The module that the command lives in.
112
key = self._get_name(command_name)
113
registry.Registry.register_lazy(self, key, module_name, command_name,
114
info=CommandInfo(aliases))
117
plugin_cmds = CommandRegistry()
120
def register_command(cmd, decorate=False):
122
return plugin_cmds.register(cmd, decorate)
125
def _squish_command_name(cmd):
126
return 'cmd_' + cmd.replace('-', '_')
129
def _unsquish_command_name(cmd):
130
return cmd[4:].replace('_','-')
133
def _builtin_commands():
134
import bzrlib.builtins
136
builtins = bzrlib.builtins.__dict__
137
for name in builtins:
138
if name.startswith("cmd_"):
139
real_name = _unsquish_command_name(name)
140
r[real_name] = builtins[name]
144
def builtin_command_names():
145
"""Return list of builtin command names."""
146
return _builtin_commands().keys()
149
def plugin_command_names():
150
return plugin_cmds.keys()
153
def _get_cmd_dict(plugins_override=True):
154
"""Return name->class mapping for all commands."""
155
d = _builtin_commands()
157
d.update(plugin_cmds.iteritems())
161
def get_all_cmds(plugins_override=True):
162
"""Return canonical name and class for all registered commands."""
163
for k, v in _get_cmd_dict(plugins_override=plugins_override).iteritems():
167
def get_cmd_object(cmd_name, plugins_override=True):
168
"""Return the canonical name and command class for a command.
171
If true, plugin commands can override builtins.
174
cmd = _get_cmd_object(cmd_name, plugins_override)
175
# Allow plugins to extend commands
176
for hook in Command.hooks['extend_command']:
180
raise errors.BzrCommandError('unknown command "%s"' % cmd_name)
183
def _get_cmd_object(cmd_name, plugins_override=True):
184
"""Worker for get_cmd_object which raises KeyError rather than BzrCommandError."""
185
from bzrlib.externalcommand import ExternalCommand
187
# We want only 'ascii' command names, but the user may have typed
188
# in a Unicode name. In that case, they should just get a
189
# 'command not found' error later.
190
# In the future, we may actually support Unicode command names.
192
# first look up this command under the specified name
195
return plugin_cmds.get(cmd_name)()
198
cmds = _get_cmd_dict(plugins_override=False)
200
return cmds[cmd_name]()
204
for key in plugin_cmds.keys():
205
info = plugin_cmds.get_info(key)
206
if cmd_name in info.aliases:
207
return plugin_cmds.get(key)()
208
# look for any command which claims this as an alias
209
for real_cmd_name, cmd_class in cmds.iteritems():
210
if cmd_name in cmd_class.aliases:
213
cmd_obj = ExternalCommand.find_command(cmd_name)
217
# look for plugins that provide this command but aren't installed
218
for provider in command_providers_registry:
220
plugin_metadata = provider.plugin_for_command(cmd_name)
221
except errors.NoPluginAvailable:
224
raise errors.CommandAvailableInPlugin(cmd_name,
225
plugin_metadata, provider)
229
class Command(object):
230
"""Base class for commands.
232
Commands are the heart of the command-line bzr interface.
234
The command object mostly handles the mapping of command-line
235
parameters into one or more bzrlib operations, and of the results
238
Commands normally don't have any state. All their arguments are
239
passed in to the run method. (Subclasses may take a different
240
policy if the behaviour of the instance needs to depend on e.g. a
241
shell plugin and not just its Python class.)
243
The docstring for an actual command should give a single-line
244
summary, then a complete description of the command. A grammar
245
description will be inserted.
248
Other accepted names for this command.
251
List of argument forms, marked with whether they are optional,
256
['to_location', 'from_branch?', 'file*']
258
'to_location' is required
259
'from_branch' is optional
260
'file' can be specified 0 or more times
263
List of options that may be given for this command. These can
264
be either strings, referring to globally-defined options,
265
or option objects. Retrieve through options().
268
If true, this command isn't advertised. This is typically
269
for commands intended for expert users.
272
Command objects will get a 'outf' attribute, which has been
273
setup to properly handle encoding of unicode strings.
274
encoding_type determines what will happen when characters cannot
276
strict - abort if we cannot decode
277
replace - put in a bogus character (typically '?')
278
exact - do not encode sys.stdout
280
NOTE: by default on Windows, sys.stdout is opened as a text
281
stream, therefore LF line-endings are converted to CRLF.
282
When a command uses encoding_type = 'exact', then
283
sys.stdout is forced to be a binary stream, and line-endings
286
:cvar hooks: An instance of CommandHooks.
291
encoding_type = 'strict'
296
"""Construct an instance of this command."""
297
if self.__doc__ == Command.__doc__:
298
warn("No help message set for %r" % self)
299
# List of standard options directly supported
300
self.supported_std_options = []
302
def _maybe_expand_globs(self, file_list):
303
"""Glob expand file_list if the platform does not do that itself.
305
:return: A possibly empty list of unicode paths.
307
Introduced in bzrlib 0.18.
311
if sys.platform == 'win32':
312
file_list = win32utils.glob_expand(file_list)
313
return list(file_list)
316
"""Return single-line grammar for this command.
318
Only describes arguments, not options.
320
s = 'bzr ' + self.name() + ' '
321
for aname in self.takes_args:
322
aname = aname.upper()
323
if aname[-1] in ['$', '+']:
324
aname = aname[:-1] + '...'
325
elif aname[-1] == '?':
326
aname = '[' + aname[:-1] + ']'
327
elif aname[-1] == '*':
328
aname = '[' + aname[:-1] + '...]'
330
s = s[:-1] # remove last space
333
def get_help_text(self, additional_see_also=None, plain=True,
334
see_also_as_links=False, verbose=True):
335
"""Return a text string with help for this command.
337
:param additional_see_also: Additional help topics to be
339
:param plain: if False, raw help (reStructuredText) is
340
returned instead of plain text.
341
:param see_also_as_links: if True, convert items in 'See also'
342
list to internal links (used by bzr_man rstx generator)
343
:param verbose: if True, display the full help, otherwise
344
leave out the descriptive sections and just display
345
usage help (e.g. Purpose, Usage, Options) with a
346
message explaining how to obtain full help.
350
raise NotImplementedError("sorry, no detailed help yet for %r" % self.name())
352
# Extract the summary (purpose) and sections out from the text
353
purpose,sections,order = self._get_help_parts(doc)
355
# If a custom usage section was provided, use it
356
if sections.has_key('Usage'):
357
usage = sections.pop('Usage')
359
usage = self._usage()
361
# The header is the purpose and usage
363
result += ':Purpose: %s\n' % purpose
364
if usage.find('\n') >= 0:
365
result += ':Usage:\n%s\n' % usage
367
result += ':Usage: %s\n' % usage
371
options = option.get_optparser(self.options()).format_option_help()
372
if options.startswith('Options:'):
373
result += ':' + options
374
elif options.startswith('options:'):
375
# Python 2.4 version of optparse
376
result += ':Options:' + options[len('options:'):]
382
# Add the description, indenting it 2 spaces
383
# to match the indentation of the options
384
if sections.has_key(None):
385
text = sections.pop(None)
386
text = '\n '.join(text.splitlines())
387
result += ':%s:\n %s\n\n' % ('Description',text)
389
# Add the custom sections (e.g. Examples). Note that there's no need
390
# to indent these as they must be indented already in the source.
393
if sections.has_key(label):
394
result += ':%s:\n%s\n' % (label,sections[label])
397
result += ("See bzr help %s for more details and examples.\n\n"
400
# Add the aliases, source (plug-in) and see also links, if any
402
result += ':Aliases: '
403
result += ', '.join(self.aliases) + '\n'
404
plugin_name = self.plugin_name()
405
if plugin_name is not None:
406
result += ':From: plugin "%s"\n' % plugin_name
407
see_also = self.get_see_also(additional_see_also)
409
if not plain and see_also_as_links:
411
for item in see_also:
413
# topics doesn't have an independent section
414
# so don't create a real link
415
see_also_links.append(item)
417
# Use a reST link for this entry
418
see_also_links.append("`%s`_" % (item,))
419
see_also = see_also_links
420
result += ':See also: '
421
result += ', '.join(see_also) + '\n'
423
# If this will be rendered as plain text, convert it
425
import bzrlib.help_topics
426
result = bzrlib.help_topics.help_as_plain_text(result)
430
def _get_help_parts(text):
431
"""Split help text into a summary and named sections.
433
:return: (summary,sections,order) where summary is the top line and
434
sections is a dictionary of the rest indexed by section name.
435
order is the order the section appear in the text.
436
A section starts with a heading line of the form ":xxx:".
437
Indented text on following lines is the section value.
438
All text found outside a named section is assigned to the
439
default section which is given the key of None.
441
def save_section(sections, order, label, section):
443
if sections.has_key(label):
444
sections[label] += '\n' + section
447
sections[label] = section
449
lines = text.rstrip().splitlines()
450
summary = lines.pop(0)
453
label,section = None,''
455
if line.startswith(':') and line.endswith(':') and len(line) > 2:
456
save_section(sections, order, label, section)
457
label,section = line[1:-1],''
458
elif (label is not None) and len(line) > 1 and not line[0].isspace():
459
save_section(sections, order, label, section)
460
label,section = None,line
463
section += '\n' + line
466
save_section(sections, order, label, section)
467
return summary, sections, order
469
def get_help_topic(self):
470
"""Return the commands help topic - its name."""
473
def get_see_also(self, additional_terms=None):
474
"""Return a list of help topics that are related to this command.
476
The list is derived from the content of the _see_also attribute. Any
477
duplicates are removed and the result is in lexical order.
478
:param additional_terms: Additional help topics to cross-reference.
479
:return: A list of help topics.
481
see_also = set(getattr(self, '_see_also', []))
483
see_also.update(additional_terms)
484
return sorted(see_also)
487
"""Return dict of valid options for this command.
489
Maps from long option name to option object."""
490
r = Option.STD_OPTIONS.copy()
492
for o in self.takes_options:
493
if isinstance(o, basestring):
494
o = option.Option.OPTIONS[o]
496
if o.name in std_names:
497
self.supported_std_options.append(o.name)
500
def _setup_outf(self):
501
"""Return a file linked to stdout, which has proper encoding."""
502
# Originally I was using self.stdout, but that looks
503
# *way* too much like sys.stdout
504
if self.encoding_type == 'exact':
505
# force sys.stdout to be binary stream on win32
506
if sys.platform == 'win32':
507
fileno = getattr(sys.stdout, 'fileno', None)
510
msvcrt.setmode(fileno(), os.O_BINARY)
511
self.outf = sys.stdout
514
output_encoding = osutils.get_terminal_encoding()
516
self.outf = codecs.getwriter(output_encoding)(sys.stdout,
517
errors=self.encoding_type)
518
# For whatever reason codecs.getwriter() does not advertise its encoding
519
# it just returns the encoding of the wrapped file, which is completely
520
# bogus. So set the attribute, so we can find the correct encoding later.
521
self.outf.encoding = output_encoding
523
def run_argv_aliases(self, argv, alias_argv=None):
524
"""Parse the command line and run with extra aliases in alias_argv."""
526
warn("Passing None for [] is deprecated from bzrlib 0.10",
527
DeprecationWarning, stacklevel=2)
529
args, opts = parse_args(self, argv, alias_argv)
531
# Process the standard options
532
if 'help' in opts: # e.g. bzr add --help
533
sys.stdout.write(self.get_help_text())
535
if 'usage' in opts: # e.g. bzr add --usage
536
sys.stdout.write(self.get_help_text(verbose=False))
538
trace.set_verbosity_level(option._verbosity_level)
539
if 'verbose' in self.supported_std_options:
540
opts['verbose'] = trace.is_verbose()
541
elif opts.has_key('verbose'):
543
if 'quiet' in self.supported_std_options:
544
opts['quiet'] = trace.is_quiet()
545
elif opts.has_key('quiet'):
548
# mix arguments and options into one dictionary
549
cmdargs = _match_argform(self.name(), self.takes_args, args)
551
for k, v in opts.items():
552
cmdopts[k.replace('-', '_')] = v
554
all_cmd_args = cmdargs.copy()
555
all_cmd_args.update(cmdopts)
559
return self.run(**all_cmd_args)
562
"""Actually run the command.
564
This is invoked with the options and arguments bound to
567
Return 0 or None if the command was successful, or a non-zero
568
shell error code if not. It's OK for this method to allow
569
an exception to raise up.
571
raise NotImplementedError('no implementation of command %r'
575
"""Return help message for this class."""
576
from inspect import getdoc
577
if self.__doc__ is Command.__doc__:
582
return _unsquish_command_name(self.__class__.__name__)
584
def plugin_name(self):
585
"""Get the name of the plugin that provides this command.
587
:return: The name of the plugin or None if the command is builtin.
589
mod_parts = self.__module__.split('.')
590
if len(mod_parts) >= 3 and mod_parts[1] == 'plugins':
596
class CommandHooks(Hooks):
597
"""Hooks related to Command object creation/enumeration."""
600
"""Create the default hooks.
602
These are all empty initially, because by default nothing should get
606
self.create_hook(HookPoint('extend_command',
607
"Called after creating a command object to allow modifications "
608
"such as adding or removing options, docs etc. Called with the "
609
"new bzrlib.commands.Command object.", (1, 13), None))
611
Command.hooks = CommandHooks()
614
def parse_args(command, argv, alias_argv=None):
615
"""Parse command line.
617
Arguments and options are parsed at this level before being passed
618
down to specific command handlers. This routine knows, from a
619
lookup table, something about the available options, what optargs
620
they take, and which commands will accept them.
622
# TODO: make it a method of the Command?
623
parser = option.get_optparser(command.options())
624
if alias_argv is not None:
625
args = alias_argv + argv
629
options, args = parser.parse_args(args)
630
opts = dict([(k, v) for k, v in options.__dict__.iteritems() if
631
v is not option.OptionParser.DEFAULT_VALUE])
635
def _match_argform(cmd, takes_args, args):
638
# step through args and takes_args, allowing appropriate 0-many matches
639
for ap in takes_args:
643
argdict[argname] = args.pop(0)
644
elif ap[-1] == '*': # all remaining arguments
646
argdict[argname + '_list'] = args[:]
649
argdict[argname + '_list'] = None
652
raise errors.BzrCommandError("command %r needs one or more %s"
653
% (cmd, argname.upper()))
655
argdict[argname + '_list'] = args[:]
657
elif ap[-1] == '$': # all but one
659
raise errors.BzrCommandError("command %r needs one or more %s"
660
% (cmd, argname.upper()))
661
argdict[argname + '_list'] = args[:-1]
667
raise errors.BzrCommandError("command %r requires argument %s"
668
% (cmd, argname.upper()))
670
argdict[argname] = args.pop(0)
673
raise errors.BzrCommandError("extra argument to command %s: %s"
678
def apply_coveraged(dirname, the_callable, *args, **kwargs):
679
# Cannot use "import trace", as that would import bzrlib.trace instead of
680
# the standard library's trace.
681
trace = __import__('trace')
683
tracer = trace.Trace(count=1, trace=0)
684
sys.settrace(tracer.globaltrace)
687
return exception_to_return_code(the_callable, *args, **kwargs)
690
results = tracer.results()
691
results.write_results(show_missing=1, summary=False,
695
def apply_profiled(the_callable, *args, **kwargs):
699
pffileno, pfname = tempfile.mkstemp()
701
prof = hotshot.Profile(pfname)
703
ret = prof.runcall(exception_to_return_code, the_callable, *args,
707
stats = hotshot.stats.load(pfname)
709
stats.sort_stats('cum') # 'time'
710
## XXX: Might like to write to stderr or the trace file instead but
711
## print_stats seems hardcoded to stdout
712
stats.print_stats(20)
719
def exception_to_return_code(the_callable, *args, **kwargs):
720
"""UI level helper for profiling and coverage.
722
This transforms exceptions into a return value of 3. As such its only
723
relevant to the UI layer, and should never be called where catching
724
exceptions may be desirable.
727
return the_callable(*args, **kwargs)
728
except (KeyboardInterrupt, Exception), e:
729
# used to handle AssertionError and KeyboardInterrupt
730
# specially here, but hopefully they're handled ok by the logger now
731
exc_info = sys.exc_info()
732
exitcode = trace.report_exception(exc_info, sys.stderr)
733
if os.environ.get('BZR_PDB'):
734
print '**** entering debugger'
737
if sys.version_info[:2] < (2, 6):
739
# pdb.post_mortem(tb)
740
# but because pdb.post_mortem gives bad results for tracebacks
741
# from inside generators, we do it manually.
742
# (http://bugs.python.org/issue4150, fixed in Python 2.6)
744
# Setup pdb on the traceback
747
p.setup(tb.tb_frame, tb)
748
# Point the debugger at the deepest frame of the stack
749
p.curindex = len(p.stack) - 1
750
p.curframe = p.stack[p.curindex][0]
751
# Start the pdb prompt.
752
p.print_stack_entry(p.stack[p.curindex])
760
def apply_lsprofiled(filename, the_callable, *args, **kwargs):
761
from bzrlib.lsprof import profile
762
ret, stats = profile(exception_to_return_code, the_callable, *args, **kwargs)
768
trace.note('Profile data written to "%s".', filename)
772
def shlex_split_unicode(unsplit):
774
return [u.decode('utf-8') for u in shlex.split(unsplit.encode('utf-8'))]
777
def get_alias(cmd, config=None):
778
"""Return an expanded alias, or None if no alias exists.
781
Command to be checked for an alias.
783
Used to specify an alternative config to use,
784
which is especially useful for testing.
785
If it is unspecified, the global config will be used.
789
config = bzrlib.config.GlobalConfig()
790
alias = config.get_alias(cmd)
792
return shlex_split_unicode(alias)
797
"""Execute a command.
800
The command-line arguments, without the program name from argv[0]
801
These should already be decoded. All library/test code calling
802
run_bzr should be passing valid strings (don't need decoding).
804
Returns a command status or raises an exception.
806
Special master options: these must come before the command because
807
they control how the command is interpreted.
810
Do not load plugin modules at all
816
Only use builtin commands. (Plugins are still allowed to change
820
Run under the Python hotshot profiler.
823
Run under the Python lsprof profiler.
826
Generate line coverage report in the specified directory.
829
trace.mutter("bzr arguments: %r", argv)
831
opt_lsprof = opt_profile = opt_no_plugins = opt_builtin = \
832
opt_no_aliases = False
833
opt_lsprof_file = opt_coverage_dir = None
835
# --no-plugins is handled specially at a very early stage. We need
836
# to load plugins before doing other command parsing so that they
837
# can override commands, but this needs to happen first.
845
elif a == '--lsprof':
847
elif a == '--lsprof-file':
849
opt_lsprof_file = argv[i + 1]
851
elif a == '--no-plugins':
852
opt_no_plugins = True
853
elif a == '--no-aliases':
854
opt_no_aliases = True
855
elif a == '--builtin':
857
elif a == '--coverage':
858
opt_coverage_dir = argv[i + 1]
860
elif a.startswith('-D'):
861
debug.debug_flags.add(a[2:])
866
debug.set_debug_flags_from_config()
870
from bzrlib.builtins import cmd_help
871
cmd_help().run_argv_aliases([])
874
if argv[0] == '--version':
875
from bzrlib.builtins import cmd_version
876
cmd_version().run_argv_aliases([])
879
if not opt_no_plugins:
880
from bzrlib.plugin import load_plugins
883
from bzrlib.plugin import disable_plugins
888
if not opt_no_aliases:
889
alias_argv = get_alias(argv[0])
891
user_encoding = osutils.get_user_encoding()
892
alias_argv = [a.decode(user_encoding) for a in alias_argv]
893
argv[0] = alias_argv.pop(0)
896
# We want only 'ascii' command names, but the user may have typed
897
# in a Unicode name. In that case, they should just get a
898
# 'command not found' error later.
900
cmd_obj = get_cmd_object(cmd, plugins_override=not opt_builtin)
901
run = cmd_obj.run_argv_aliases
902
run_argv = [argv, alias_argv]
905
# We can be called recursively (tests for example), but we don't want
906
# the verbosity level to propagate.
907
saved_verbosity_level = option._verbosity_level
908
option._verbosity_level = 0
912
'--coverage ignored, because --lsprof is in use.')
913
ret = apply_lsprofiled(opt_lsprof_file, run, *run_argv)
917
'--coverage ignored, because --profile is in use.')
918
ret = apply_profiled(run, *run_argv)
919
elif opt_coverage_dir:
920
ret = apply_coveraged(opt_coverage_dir, run, *run_argv)
923
if 'memory' in debug.debug_flags:
924
trace.debug_memory('Process status after command:', short=False)
927
# reset, in case we may do other commands later within the same
928
# process. Commands that want to execute sub-commands must propagate
929
# --verbose in their own way.
930
option._verbosity_level = saved_verbosity_level
933
def display_command(func):
934
"""Decorator that suppresses pipe/interrupt errors."""
935
def ignore_pipe(*args, **kwargs):
937
result = func(*args, **kwargs)
941
if getattr(e, 'errno', None) is None:
943
if e.errno != errno.EPIPE:
944
# Win32 raises IOError with errno=0 on a broken pipe
945
if sys.platform != 'win32' or (e.errno not in (0, errno.EINVAL)):
948
except KeyboardInterrupt:
955
bzrlib.ui.ui_factory = bzrlib.ui.make_ui_for_terminal(
956
sys.stdin, sys.stdout, sys.stderr)
958
# Is this a final release version? If so, we should suppress warnings
959
if bzrlib.version_info[3] == 'final':
960
from bzrlib import symbol_versioning
961
symbol_versioning.suppress_deprecation_warnings(override=False)
963
user_encoding = osutils.get_user_encoding()
964
argv = [a.decode(user_encoding) for a in argv[1:]]
965
except UnicodeDecodeError:
966
raise errors.BzrError(("Parameter '%r' is unsupported by the current "
968
ret = run_bzr_catch_errors(argv)
969
trace.mutter("return code %d", ret)
973
def run_bzr_catch_errors(argv):
974
"""Run a bzr command with parameters as described by argv.
976
This function assumed that that UI layer is setup, that symbol deprecations
977
are already applied, and that unicode decoding has already been performed on argv.
979
return exception_to_return_code(run_bzr, argv)
982
def run_bzr_catch_user_errors(argv):
983
"""Run bzr and report user errors, but let internal errors propagate.
985
This is used for the test suite, and might be useful for other programs
986
that want to wrap the commandline interface.
991
if (isinstance(e, (OSError, IOError))
992
or not getattr(e, 'internal_error', True)):
993
trace.report_exception(sys.exc_info(), sys.stderr)
999
class HelpCommandIndex(object):
1000
"""A index for bzr help that returns commands."""
1003
self.prefix = 'commands/'
1005
def get_topics(self, topic):
1006
"""Search for topic amongst commands.
1008
:param topic: A topic to search for.
1009
:return: A list which is either empty or contains a single
1012
if topic and topic.startswith(self.prefix):
1013
topic = topic[len(self.prefix):]
1015
cmd = _get_cmd_object(topic)
1022
class Provider(object):
1023
'''Generic class to be overriden by plugins'''
1025
def plugin_for_command(self, cmd_name):
1026
'''Takes a command and returns the information for that plugin
1028
:return: A dictionary with all the available information
1029
for the requested plugin
1031
raise NotImplementedError
1034
class ProvidersRegistry(registry.Registry):
1035
'''This registry exists to allow other providers to exist'''
1038
for key, provider in self.iteritems():
1041
command_providers_registry = ProvidersRegistry()
1044
if __name__ == '__main__':
1045
sys.exit(main(sys.argv))