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(verbose=False))
535
trace.set_verbosity_level(option._verbosity_level)
536
if 'verbose' in self.supported_std_options:
537
opts['verbose'] = trace.is_verbose()
538
elif opts.has_key('verbose'):
540
if 'quiet' in self.supported_std_options:
541
opts['quiet'] = trace.is_quiet()
542
elif opts.has_key('quiet'):
545
# mix arguments and options into one dictionary
546
cmdargs = _match_argform(self.name(), self.takes_args, args)
548
for k, v in opts.items():
549
cmdopts[k.replace('-', '_')] = v
551
all_cmd_args = cmdargs.copy()
552
all_cmd_args.update(cmdopts)
556
return self.run(**all_cmd_args)
559
"""Actually run the command.
561
This is invoked with the options and arguments bound to
564
Return 0 or None if the command was successful, or a non-zero
565
shell error code if not. It's OK for this method to allow
566
an exception to raise up.
568
raise NotImplementedError('no implementation of command %r'
572
"""Return help message for this class."""
573
from inspect import getdoc
574
if self.__doc__ is Command.__doc__:
579
return _unsquish_command_name(self.__class__.__name__)
581
def plugin_name(self):
582
"""Get the name of the plugin that provides this command.
584
:return: The name of the plugin or None if the command is builtin.
586
mod_parts = self.__module__.split('.')
587
if len(mod_parts) >= 3 and mod_parts[1] == 'plugins':
593
class CommandHooks(Hooks):
594
"""Hooks related to Command object creation/enumeration."""
597
"""Create the default hooks.
599
These are all empty initially, because by default nothing should get
603
# Introduced in 1.13:
604
# invoked after creating a command object to allow modifications such
605
# as adding or removing options, docs etc. Invoked with the command
607
self['extend_command'] = []
609
Command.hooks = CommandHooks()
612
def parse_args(command, argv, alias_argv=None):
613
"""Parse command line.
615
Arguments and options are parsed at this level before being passed
616
down to specific command handlers. This routine knows, from a
617
lookup table, something about the available options, what optargs
618
they take, and which commands will accept them.
620
# TODO: make it a method of the Command?
621
parser = option.get_optparser(command.options())
622
if alias_argv is not None:
623
args = alias_argv + argv
627
options, args = parser.parse_args(args)
628
opts = dict([(k, v) for k, v in options.__dict__.iteritems() if
629
v is not option.OptionParser.DEFAULT_VALUE])
633
def _match_argform(cmd, takes_args, args):
636
# step through args and takes_args, allowing appropriate 0-many matches
637
for ap in takes_args:
641
argdict[argname] = args.pop(0)
642
elif ap[-1] == '*': # all remaining arguments
644
argdict[argname + '_list'] = args[:]
647
argdict[argname + '_list'] = None
650
raise errors.BzrCommandError("command %r needs one or more %s"
651
% (cmd, argname.upper()))
653
argdict[argname + '_list'] = args[:]
655
elif ap[-1] == '$': # all but one
657
raise errors.BzrCommandError("command %r needs one or more %s"
658
% (cmd, argname.upper()))
659
argdict[argname + '_list'] = args[:-1]
665
raise errors.BzrCommandError("command %r requires argument %s"
666
% (cmd, argname.upper()))
668
argdict[argname] = args.pop(0)
671
raise errors.BzrCommandError("extra argument to command %s: %s"
676
def apply_coveraged(dirname, the_callable, *args, **kwargs):
677
# Cannot use "import trace", as that would import bzrlib.trace instead of
678
# the standard library's trace.
679
trace = __import__('trace')
681
tracer = trace.Trace(count=1, trace=0)
682
sys.settrace(tracer.globaltrace)
684
ret = the_callable(*args, **kwargs)
687
results = tracer.results()
688
results.write_results(show_missing=1, summary=False,
692
def apply_profiled(the_callable, *args, **kwargs):
696
pffileno, pfname = tempfile.mkstemp()
698
prof = hotshot.Profile(pfname)
700
ret = prof.runcall(the_callable, *args, **kwargs) or 0
703
stats = hotshot.stats.load(pfname)
705
stats.sort_stats('cum') # 'time'
706
## XXX: Might like to write to stderr or the trace file instead but
707
## print_stats seems hardcoded to stdout
708
stats.print_stats(20)
715
def apply_lsprofiled(filename, the_callable, *args, **kwargs):
716
from bzrlib.lsprof import profile
717
ret, stats = profile(the_callable, *args, **kwargs)
723
trace.note('Profile data written to "%s".', filename)
727
def shlex_split_unicode(unsplit):
729
return [u.decode('utf-8') for u in shlex.split(unsplit.encode('utf-8'))]
732
def get_alias(cmd, config=None):
733
"""Return an expanded alias, or None if no alias exists.
736
Command to be checked for an alias.
738
Used to specify an alternative config to use,
739
which is especially useful for testing.
740
If it is unspecified, the global config will be used.
744
config = bzrlib.config.GlobalConfig()
745
alias = config.get_alias(cmd)
747
return shlex_split_unicode(alias)
752
"""Execute a command.
755
The command-line arguments, without the program name from argv[0]
756
These should already be decoded. All library/test code calling
757
run_bzr should be passing valid strings (don't need decoding).
759
Returns a command status or raises an exception.
761
Special master options: these must come before the command because
762
they control how the command is interpreted.
765
Do not load plugin modules at all
771
Only use builtin commands. (Plugins are still allowed to change
775
Run under the Python hotshot profiler.
778
Run under the Python lsprof profiler.
781
Generate line coverage report in the specified directory.
784
trace.mutter("bzr arguments: %r", argv)
786
opt_lsprof = opt_profile = opt_no_plugins = opt_builtin = \
787
opt_no_aliases = False
788
opt_lsprof_file = opt_coverage_dir = None
790
# --no-plugins is handled specially at a very early stage. We need
791
# to load plugins before doing other command parsing so that they
792
# can override commands, but this needs to happen first.
800
elif a == '--lsprof':
802
elif a == '--lsprof-file':
804
opt_lsprof_file = argv[i + 1]
806
elif a == '--no-plugins':
807
opt_no_plugins = True
808
elif a == '--no-aliases':
809
opt_no_aliases = True
810
elif a == '--builtin':
812
elif a == '--coverage':
813
opt_coverage_dir = argv[i + 1]
815
elif a.startswith('-D'):
816
debug.debug_flags.add(a[2:])
823
from bzrlib.builtins import cmd_help
824
cmd_help().run_argv_aliases([])
827
if argv[0] == '--version':
828
from bzrlib.builtins import cmd_version
829
cmd_version().run_argv_aliases([])
832
if not opt_no_plugins:
833
from bzrlib.plugin import load_plugins
836
from bzrlib.plugin import disable_plugins
841
if not opt_no_aliases:
842
alias_argv = get_alias(argv[0])
844
user_encoding = osutils.get_user_encoding()
845
alias_argv = [a.decode(user_encoding) for a in alias_argv]
846
argv[0] = alias_argv.pop(0)
849
# We want only 'ascii' command names, but the user may have typed
850
# in a Unicode name. In that case, they should just get a
851
# 'command not found' error later.
853
cmd_obj = get_cmd_object(cmd, plugins_override=not opt_builtin)
854
run = cmd_obj.run_argv_aliases
855
run_argv = [argv, alias_argv]
858
# We can be called recursively (tests for example), but we don't want
859
# the verbosity level to propagate.
860
saved_verbosity_level = option._verbosity_level
861
option._verbosity_level = 0
865
'--coverage ignored, because --lsprof is in use.')
866
ret = apply_lsprofiled(opt_lsprof_file, run, *run_argv)
870
'--coverage ignored, because --profile is in use.')
871
ret = apply_profiled(run, *run_argv)
872
elif opt_coverage_dir:
873
ret = apply_coveraged(opt_coverage_dir, run, *run_argv)
876
if 'memory' in debug.debug_flags:
877
trace.debug_memory('Process status after command:', short=False)
880
# reset, in case we may do other commands later within the same
881
# process. Commands that want to execute sub-commands must propagate
882
# --verbose in their own way.
883
option._verbosity_level = saved_verbosity_level
886
def display_command(func):
887
"""Decorator that suppresses pipe/interrupt errors."""
888
def ignore_pipe(*args, **kwargs):
890
result = func(*args, **kwargs)
894
if getattr(e, 'errno', None) is None:
896
if e.errno != errno.EPIPE:
897
# Win32 raises IOError with errno=0 on a broken pipe
898
if sys.platform != 'win32' or (e.errno not in (0, errno.EINVAL)):
901
except KeyboardInterrupt:
908
bzrlib.ui.ui_factory = bzrlib.ui.make_ui_for_terminal(
909
sys.stdin, sys.stdout, sys.stderr)
911
# Is this a final release version? If so, we should suppress warnings
912
if bzrlib.version_info[3] == 'final':
913
from bzrlib import symbol_versioning
914
symbol_versioning.suppress_deprecation_warnings(override=False)
916
user_encoding = osutils.get_user_encoding()
917
argv = [a.decode(user_encoding) for a in argv[1:]]
918
except UnicodeDecodeError:
919
raise errors.BzrError(("Parameter '%r' is unsupported by the current "
921
ret = run_bzr_catch_errors(argv)
922
trace.mutter("return code %d", ret)
926
def run_bzr_catch_errors(argv):
927
# Note: The except clause logic below should be kept in sync with the
928
# profile() routine in lsprof.py.
931
except (KeyboardInterrupt, Exception), e:
932
# used to handle AssertionError and KeyboardInterrupt
933
# specially here, but hopefully they're handled ok by the logger now
934
exc_info = sys.exc_info()
935
exitcode = trace.report_exception(exc_info, sys.stderr)
936
if os.environ.get('BZR_PDB'):
937
print '**** entering debugger'
940
if sys.version_info[:2] < (2, 6):
942
# pdb.post_mortem(tb)
943
# but because pdb.post_mortem gives bad results for tracebacks
944
# from inside generators, we do it manually.
945
# (http://bugs.python.org/issue4150, fixed in Python 2.6)
947
# Setup pdb on the traceback
950
p.setup(tb.tb_frame, tb)
951
# Point the debugger at the deepest frame of the stack
952
p.curindex = len(p.stack) - 1
953
p.curframe = p.stack[p.curindex]
954
# Start the pdb prompt.
955
p.print_stack_entry(p.stack[p.curindex])
963
def run_bzr_catch_user_errors(argv):
964
"""Run bzr and report user errors, but let internal errors propagate.
966
This is used for the test suite, and might be useful for other programs
967
that want to wrap the commandline interface.
972
if (isinstance(e, (OSError, IOError))
973
or not getattr(e, 'internal_error', True)):
974
trace.report_exception(sys.exc_info(), sys.stderr)
980
class HelpCommandIndex(object):
981
"""A index for bzr help that returns commands."""
984
self.prefix = 'commands/'
986
def get_topics(self, topic):
987
"""Search for topic amongst commands.
989
:param topic: A topic to search for.
990
:return: A list which is either empty or contains a single
993
if topic and topic.startswith(self.prefix):
994
topic = topic[len(self.prefix):]
996
cmd = _get_cmd_object(topic)
1003
class Provider(object):
1004
'''Generic class to be overriden by plugins'''
1006
def plugin_for_command(self, cmd_name):
1007
'''Takes a command and returns the information for that plugin
1009
:return: A dictionary with all the available information
1010
for the requested plugin
1012
raise NotImplementedError
1015
class ProvidersRegistry(registry.Registry):
1016
'''This registry exists to allow other providers to exist'''
1019
for key, provider in self.iteritems():
1022
command_providers_registry = ProvidersRegistry()
1025
if __name__ == '__main__':
1026
sys.exit(main(sys.argv))