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., 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: 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 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
concise help (e.g. Purpose, Usage, Options) with a
346
message pointing to "help -v" for more information.
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
# Introduced in 1.13:
607
# invoked after creating a command object to allow modifications such
608
# as adding or removing options, docs etc. Invoked with the command
610
self['extend_command'] = []
612
Command.hooks = CommandHooks()
615
def parse_args(command, argv, alias_argv=None):
616
"""Parse command line.
618
Arguments and options are parsed at this level before being passed
619
down to specific command handlers. This routine knows, from a
620
lookup table, something about the available options, what optargs
621
they take, and which commands will accept them.
623
# TODO: make it a method of the Command?
624
parser = option.get_optparser(command.options())
625
if alias_argv is not None:
626
args = alias_argv + argv
630
options, args = parser.parse_args(args)
631
opts = dict([(k, v) for k, v in options.__dict__.iteritems() if
632
v is not option.OptionParser.DEFAULT_VALUE])
636
def _match_argform(cmd, takes_args, args):
639
# step through args and takes_args, allowing appropriate 0-many matches
640
for ap in takes_args:
644
argdict[argname] = args.pop(0)
645
elif ap[-1] == '*': # all remaining arguments
647
argdict[argname + '_list'] = args[:]
650
argdict[argname + '_list'] = None
653
raise errors.BzrCommandError("command %r needs one or more %s"
654
% (cmd, argname.upper()))
656
argdict[argname + '_list'] = args[:]
658
elif ap[-1] == '$': # all but one
660
raise errors.BzrCommandError("command %r needs one or more %s"
661
% (cmd, argname.upper()))
662
argdict[argname + '_list'] = args[:-1]
668
raise errors.BzrCommandError("command %r requires argument %s"
669
% (cmd, argname.upper()))
671
argdict[argname] = args.pop(0)
674
raise errors.BzrCommandError("extra argument to command %s: %s"
679
def apply_coveraged(dirname, the_callable, *args, **kwargs):
680
# Cannot use "import trace", as that would import bzrlib.trace instead of
681
# the standard library's trace.
682
trace = __import__('trace')
684
tracer = trace.Trace(count=1, trace=0)
685
sys.settrace(tracer.globaltrace)
687
ret = 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(the_callable, *args, **kwargs) or 0
706
stats = hotshot.stats.load(pfname)
708
stats.sort_stats('cum') # 'time'
709
## XXX: Might like to write to stderr or the trace file instead but
710
## print_stats seems hardcoded to stdout
711
stats.print_stats(20)
718
def apply_lsprofiled(filename, the_callable, *args, **kwargs):
719
from bzrlib.lsprof import profile
720
ret, stats = profile(the_callable, *args, **kwargs)
726
trace.note('Profile data written to "%s".', filename)
730
def shlex_split_unicode(unsplit):
732
return [u.decode('utf-8') for u in shlex.split(unsplit.encode('utf-8'))]
735
def get_alias(cmd, config=None):
736
"""Return an expanded alias, or None if no alias exists.
739
Command to be checked for an alias.
741
Used to specify an alternative config to use,
742
which is especially useful for testing.
743
If it is unspecified, the global config will be used.
747
config = bzrlib.config.GlobalConfig()
748
alias = config.get_alias(cmd)
750
return shlex_split_unicode(alias)
755
"""Execute a command.
758
The command-line arguments, without the program name from argv[0]
759
These should already be decoded. All library/test code calling
760
run_bzr should be passing valid strings (don't need decoding).
762
Returns a command status or raises an exception.
764
Special master options: these must come before the command because
765
they control how the command is interpreted.
768
Do not load plugin modules at all
774
Only use builtin commands. (Plugins are still allowed to change
778
Run under the Python hotshot profiler.
781
Run under the Python lsprof profiler.
784
Generate line coverage report in the specified directory.
787
trace.mutter("bzr arguments: %r", argv)
789
opt_lsprof = opt_profile = opt_no_plugins = opt_builtin = \
790
opt_no_aliases = False
791
opt_lsprof_file = opt_coverage_dir = None
793
# --no-plugins is handled specially at a very early stage. We need
794
# to load plugins before doing other command parsing so that they
795
# can override commands, but this needs to happen first.
803
elif a == '--lsprof':
805
elif a == '--lsprof-file':
807
opt_lsprof_file = argv[i + 1]
809
elif a == '--no-plugins':
810
opt_no_plugins = True
811
elif a == '--no-aliases':
812
opt_no_aliases = True
813
elif a == '--builtin':
815
elif a == '--coverage':
816
opt_coverage_dir = argv[i + 1]
818
elif a.startswith('-D'):
819
debug.debug_flags.add(a[2:])
826
from bzrlib.builtins import cmd_help
827
cmd_help().run_argv_aliases([])
830
if argv[0] == '--version':
831
from bzrlib.builtins import cmd_version
832
cmd_version().run_argv_aliases([])
835
if not opt_no_plugins:
836
from bzrlib.plugin import load_plugins
839
from bzrlib.plugin import disable_plugins
844
if not opt_no_aliases:
845
alias_argv = get_alias(argv[0])
847
user_encoding = osutils.get_user_encoding()
848
alias_argv = [a.decode(user_encoding) for a in alias_argv]
849
argv[0] = alias_argv.pop(0)
852
# We want only 'ascii' command names, but the user may have typed
853
# in a Unicode name. In that case, they should just get a
854
# 'command not found' error later.
856
cmd_obj = get_cmd_object(cmd, plugins_override=not opt_builtin)
857
run = cmd_obj.run_argv_aliases
858
run_argv = [argv, alias_argv]
861
# We can be called recursively (tests for example), but we don't want
862
# the verbosity level to propagate.
863
saved_verbosity_level = option._verbosity_level
864
option._verbosity_level = 0
868
'--coverage ignored, because --lsprof is in use.')
869
ret = apply_lsprofiled(opt_lsprof_file, run, *run_argv)
873
'--coverage ignored, because --profile is in use.')
874
ret = apply_profiled(run, *run_argv)
875
elif opt_coverage_dir:
876
ret = apply_coveraged(opt_coverage_dir, run, *run_argv)
879
if 'memory' in debug.debug_flags:
880
trace.debug_memory('Process status after command:', short=False)
883
# reset, in case we may do other commands later within the same
884
# process. Commands that want to execute sub-commands must propagate
885
# --verbose in their own way.
886
option._verbosity_level = saved_verbosity_level
889
def display_command(func):
890
"""Decorator that suppresses pipe/interrupt errors."""
891
def ignore_pipe(*args, **kwargs):
893
result = func(*args, **kwargs)
897
if getattr(e, 'errno', None) is None:
899
if e.errno != errno.EPIPE:
900
# Win32 raises IOError with errno=0 on a broken pipe
901
if sys.platform != 'win32' or (e.errno not in (0, errno.EINVAL)):
904
except KeyboardInterrupt:
911
bzrlib.ui.ui_factory = bzrlib.ui.make_ui_for_terminal(
912
sys.stdin, sys.stdout, sys.stderr)
914
# Is this a final release version? If so, we should suppress warnings
915
if bzrlib.version_info[3] == 'final':
916
from bzrlib import symbol_versioning
917
symbol_versioning.suppress_deprecation_warnings(override=False)
919
user_encoding = osutils.get_user_encoding()
920
argv = [a.decode(user_encoding) for a in argv[1:]]
921
except UnicodeDecodeError:
922
raise errors.BzrError(("Parameter '%r' is unsupported by the current "
924
ret = run_bzr_catch_errors(argv)
925
trace.mutter("return code %d", ret)
929
def run_bzr_catch_errors(argv):
930
# Note: The except clause logic below should be kept in sync with the
931
# profile() routine in lsprof.py.
934
except (KeyboardInterrupt, Exception), e:
935
# used to handle AssertionError and KeyboardInterrupt
936
# specially here, but hopefully they're handled ok by the logger now
937
exc_info = sys.exc_info()
938
exitcode = trace.report_exception(exc_info, sys.stderr)
939
if os.environ.get('BZR_PDB'):
940
print '**** entering debugger'
943
if sys.version_info[:2] < (2, 6):
945
# pdb.post_mortem(tb)
946
# but because pdb.post_mortem gives bad results for tracebacks
947
# from inside generators, we do it manually.
948
# (http://bugs.python.org/issue4150, fixed in Python 2.6)
950
# Setup pdb on the traceback
953
p.setup(tb.tb_frame, tb)
954
# Point the debugger at the deepest frame of the stack
955
p.curindex = len(p.stack) - 1
956
p.curframe = p.stack[p.curindex]
957
# Start the pdb prompt.
958
p.print_stack_entry(p.stack[p.curindex])
966
def run_bzr_catch_user_errors(argv):
967
"""Run bzr and report user errors, but let internal errors propagate.
969
This is used for the test suite, and might be useful for other programs
970
that want to wrap the commandline interface.
975
if (isinstance(e, (OSError, IOError))
976
or not getattr(e, 'internal_error', True)):
977
trace.report_exception(sys.exc_info(), sys.stderr)
983
class HelpCommandIndex(object):
984
"""A index for bzr help that returns commands."""
987
self.prefix = 'commands/'
989
def get_topics(self, topic):
990
"""Search for topic amongst commands.
992
:param topic: A topic to search for.
993
:return: A list which is either empty or contains a single
996
if topic and topic.startswith(self.prefix):
997
topic = topic[len(self.prefix):]
999
cmd = _get_cmd_object(topic)
1006
class Provider(object):
1007
'''Generic class to be overriden by plugins'''
1009
def plugin_for_command(self, cmd_name):
1010
'''Takes a command and returns the information for that plugin
1012
:return: A dictionary with all the available information
1013
for the requested plugin
1015
raise NotImplementedError
1018
class ProvidersRegistry(registry.Registry):
1019
'''This registry exists to allow other providers to exist'''
1022
for key, provider in self.iteritems():
1025
command_providers_registry = ProvidersRegistry()
1028
if __name__ == '__main__':
1029
sys.exit(main(sys.argv))