/brz/remove-bazaar

To get this branch, use:
bzr branch http://gegoxaren.bato24.eu/bzr/brz/remove-bazaar

« back to all changes in this revision

Viewing changes to breezy/commands.py

  • Committer: Jelmer Vernooij
  • Date: 2017-06-10 16:40:42 UTC
  • mfrom: (6653.6.7 rename-controldir)
  • mto: This revision was merged to the branch mainline in revision 6690.
  • Revision ID: jelmer@jelmer.uk-20170610164042-zrxqgy2htyduvke2
MergeĀ rename-controldirĀ branch.

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
# Copyright (C) 2005-2011 Canonical Ltd
 
2
#
 
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.
 
7
#
 
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.
 
12
#
 
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
 
16
 
 
17
from __future__ import absolute_import
 
18
 
 
19
# TODO: Define arguments by objects, rather than just using names.
 
20
# Those objects can specify the expected type of the argument, which
 
21
# would help with validation and shell completion.  They could also provide
 
22
# help/explanation for that argument in a structured way.
 
23
 
 
24
# TODO: Specific "examples" property on commands for consistent formatting.
 
25
 
 
26
import os
 
27
import sys
 
28
 
 
29
from .lazy_import import lazy_import
 
30
lazy_import(globals(), """
 
31
import errno
 
32
import threading
 
33
 
 
34
import breezy
 
35
from breezy import (
 
36
    config,
 
37
    cleanup,
 
38
    cmdline,
 
39
    debug,
 
40
    errors,
 
41
    i18n,
 
42
    option,
 
43
    osutils,
 
44
    trace,
 
45
    ui,
 
46
    )
 
47
""")
 
48
 
 
49
from .hooks import Hooks
 
50
from .i18n import gettext
 
51
# Compatibility - Option used to be in commands.
 
52
from .option import Option
 
53
from .plugin import disable_plugins, load_plugins, plugin_name
 
54
from . import registry
 
55
from .sixish import (
 
56
    string_types,
 
57
    )
 
58
 
 
59
 
 
60
class CommandInfo(object):
 
61
    """Information about a command."""
 
62
 
 
63
    def __init__(self, aliases):
 
64
        """The list of aliases for the command."""
 
65
        self.aliases = aliases
 
66
 
 
67
    @classmethod
 
68
    def from_command(klass, command):
 
69
        """Factory to construct a CommandInfo from a command."""
 
70
        return klass(command.aliases)
 
71
 
 
72
 
 
73
class CommandRegistry(registry.Registry):
 
74
    """Special registry mapping command names to command classes.
 
75
    
 
76
    :ivar overridden_registry: Look in this registry for commands being
 
77
        overridden by this registry.  This can be used to tell plugin commands
 
78
        about the builtin they're decorating.
 
79
    """
 
80
 
 
81
    def __init__(self):
 
82
        registry.Registry.__init__(self)
 
83
        self.overridden_registry = None
 
84
        # map from aliases to the real command that implements the name
 
85
        self._alias_dict = {}
 
86
 
 
87
    def get(self, command_name):
 
88
        real_name = self._alias_dict.get(command_name, command_name)
 
89
        return registry.Registry.get(self, real_name)
 
90
 
 
91
    @staticmethod
 
92
    def _get_name(command_name):
 
93
        if command_name.startswith("cmd_"):
 
94
            return _unsquish_command_name(command_name)
 
95
        else:
 
96
            return command_name
 
97
 
 
98
    def register(self, cmd, decorate=False):
 
99
        """Utility function to help register a command
 
100
 
 
101
        :param cmd: Command subclass to register
 
102
        :param decorate: If true, allow overriding an existing command
 
103
            of the same name; the old command is returned by this function.
 
104
            Otherwise it is an error to try to override an existing command.
 
105
        """
 
106
        k = cmd.__name__
 
107
        k_unsquished = self._get_name(k)
 
108
        try:
 
109
            previous = self.get(k_unsquished)
 
110
        except KeyError:
 
111
            previous = None
 
112
            if self.overridden_registry:
 
113
                try:
 
114
                    previous = self.overridden_registry.get(k_unsquished)
 
115
                except KeyError:
 
116
                    pass
 
117
        info = CommandInfo.from_command(cmd)
 
118
        try:
 
119
            registry.Registry.register(self, k_unsquished, cmd,
 
120
                                       override_existing=decorate, info=info)
 
121
        except KeyError:
 
122
            trace.warning('Two plugins defined the same command: %r' % k)
 
123
            trace.warning('Not loading the one in %r' %
 
124
                sys.modules[cmd.__module__])
 
125
            trace.warning('Previously this command was registered from %r' %
 
126
                sys.modules[previous.__module__])
 
127
        for a in cmd.aliases:
 
128
            self._alias_dict[a] = k_unsquished
 
129
        return previous
 
130
 
 
131
    def register_lazy(self, command_name, aliases, module_name):
 
132
        """Register a command without loading its module.
 
133
 
 
134
        :param command_name: The primary name of the command.
 
135
        :param aliases: A list of aliases for the command.
 
136
        :module_name: The module that the command lives in.
 
137
        """
 
138
        key = self._get_name(command_name)
 
139
        registry.Registry.register_lazy(self, key, module_name, command_name,
 
140
                                        info=CommandInfo(aliases))
 
141
        for a in aliases:
 
142
            self._alias_dict[a] = key
 
143
 
 
144
 
 
145
plugin_cmds = CommandRegistry()
 
146
builtin_command_registry = CommandRegistry()
 
147
plugin_cmds.overridden_registry = builtin_command_registry
 
148
 
 
149
 
 
150
def register_command(cmd, decorate=False):
 
151
    """Register a plugin command.
 
152
 
 
153
    Should generally be avoided in favor of lazy registration. 
 
154
    """
 
155
    global plugin_cmds
 
156
    return plugin_cmds.register(cmd, decorate)
 
157
 
 
158
 
 
159
def _squish_command_name(cmd):
 
160
    return 'cmd_' + cmd.replace('-', '_')
 
161
 
 
162
 
 
163
def _unsquish_command_name(cmd):
 
164
    return cmd[4:].replace('_','-')
 
165
 
 
166
 
 
167
def _register_builtin_commands():
 
168
    if builtin_command_registry.keys():
 
169
        # only load once
 
170
        return
 
171
    import breezy.builtins
 
172
    for cmd_class in _scan_module_for_commands(breezy.builtins):
 
173
        builtin_command_registry.register(cmd_class)
 
174
    breezy.builtins._register_lazy_builtins()
 
175
 
 
176
 
 
177
def _scan_module_for_commands(module):
 
178
    module_dict = module.__dict__
 
179
    for name in module_dict:
 
180
        if name.startswith("cmd_"):
 
181
            yield module_dict[name]
 
182
 
 
183
 
 
184
def _list_bzr_commands(names):
 
185
    """Find commands from bzr's core and plugins.
 
186
    
 
187
    This is not the public interface, just the default hook called by all_command_names.
 
188
    """
 
189
    # to eliminate duplicates
 
190
    names.update(builtin_command_names())
 
191
    names.update(plugin_command_names())
 
192
    return names
 
193
 
 
194
 
 
195
def all_command_names():
 
196
    """Return a set of all command names."""
 
197
    names = set()
 
198
    for hook in Command.hooks['list_commands']:
 
199
        names = hook(names)
 
200
        if names is None:
 
201
            raise AssertionError(
 
202
                'hook %s returned None' % Command.hooks.get_hook_name(hook))
 
203
    return names
 
204
 
 
205
 
 
206
def builtin_command_names():
 
207
    """Return list of builtin command names.
 
208
    
 
209
    Use of all_command_names() is encouraged rather than builtin_command_names
 
210
    and/or plugin_command_names.
 
211
    """
 
212
    _register_builtin_commands()
 
213
    return builtin_command_registry.keys()
 
214
 
 
215
 
 
216
def plugin_command_names():
 
217
    """Returns command names from commands registered by plugins."""
 
218
    return plugin_cmds.keys()
 
219
 
 
220
 
 
221
def get_cmd_object(cmd_name, plugins_override=True):
 
222
    """Return the command object for a command.
 
223
 
 
224
    plugins_override
 
225
        If true, plugin commands can override builtins.
 
226
    """
 
227
    try:
 
228
        return _get_cmd_object(cmd_name, plugins_override)
 
229
    except KeyError:
 
230
        raise errors.BzrCommandError(gettext('unknown command "%s"') % cmd_name)
 
231
 
 
232
 
 
233
def _get_cmd_object(cmd_name, plugins_override=True, check_missing=True):
 
234
    """Get a command object.
 
235
 
 
236
    :param cmd_name: The name of the command.
 
237
    :param plugins_override: Allow plugins to override builtins.
 
238
    :param check_missing: Look up commands not found in the regular index via
 
239
        the get_missing_command hook.
 
240
    :return: A Command object instance
 
241
    :raises KeyError: If no command is found.
 
242
    """
 
243
    # We want only 'ascii' command names, but the user may have typed
 
244
    # in a Unicode name. In that case, they should just get a
 
245
    # 'command not found' error later.
 
246
    # In the future, we may actually support Unicode command names.
 
247
    cmd = None
 
248
    # Get a command
 
249
    for hook in Command.hooks['get_command']:
 
250
        cmd = hook(cmd, cmd_name)
 
251
        if cmd is not None and not plugins_override and not cmd.plugin_name():
 
252
            # We've found a non-plugin command, don't permit it to be
 
253
            # overridden.
 
254
            break
 
255
    if cmd is None and check_missing:
 
256
        for hook in Command.hooks['get_missing_command']:
 
257
            cmd = hook(cmd_name)
 
258
            if cmd is not None:
 
259
                break
 
260
    if cmd is None:
 
261
        # No command found.
 
262
        raise KeyError
 
263
    # Allow plugins to extend commands
 
264
    for hook in Command.hooks['extend_command']:
 
265
        hook(cmd)
 
266
    if getattr(cmd, 'invoked_as', None) is None:
 
267
        cmd.invoked_as = cmd_name
 
268
    return cmd
 
269
 
 
270
 
 
271
def _try_plugin_provider(cmd_name):
 
272
    """Probe for a plugin provider having cmd_name."""
 
273
    try:
 
274
        plugin_metadata, provider = probe_for_provider(cmd_name)
 
275
        raise errors.CommandAvailableInPlugin(cmd_name,
 
276
            plugin_metadata, provider)
 
277
    except errors.NoPluginAvailable:
 
278
        pass
 
279
 
 
280
 
 
281
def probe_for_provider(cmd_name):
 
282
    """Look for a provider for cmd_name.
 
283
 
 
284
    :param cmd_name: The command name.
 
285
    :return: plugin_metadata, provider for getting cmd_name.
 
286
    :raises NoPluginAvailable: When no provider can supply the plugin.
 
287
    """
 
288
    # look for providers that provide this command but aren't installed
 
289
    for provider in command_providers_registry:
 
290
        try:
 
291
            return provider.plugin_for_command(cmd_name), provider
 
292
        except errors.NoPluginAvailable:
 
293
            pass
 
294
    raise errors.NoPluginAvailable(cmd_name)
 
295
 
 
296
 
 
297
def _get_bzr_command(cmd_or_None, cmd_name):
 
298
    """Get a command from bzr's core."""
 
299
    try:
 
300
        cmd_class = builtin_command_registry.get(cmd_name)
 
301
    except KeyError:
 
302
        pass
 
303
    else:
 
304
        return cmd_class()
 
305
    return cmd_or_None
 
306
 
 
307
 
 
308
def _get_external_command(cmd_or_None, cmd_name):
 
309
    """Lookup a command that is a shell script."""
 
310
    # Only do external command lookups when no command is found so far.
 
311
    if cmd_or_None is not None:
 
312
        return cmd_or_None
 
313
    from breezy.externalcommand import ExternalCommand
 
314
    cmd_obj = ExternalCommand.find_command(cmd_name)
 
315
    if cmd_obj:
 
316
        return cmd_obj
 
317
 
 
318
 
 
319
def _get_plugin_command(cmd_or_None, cmd_name):
 
320
    """Get a command from brz's plugins."""
 
321
    try:
 
322
        return plugin_cmds.get(cmd_name)()
 
323
    except KeyError:
 
324
        pass
 
325
    for key in plugin_cmds.keys():
 
326
        info = plugin_cmds.get_info(key)
 
327
        if cmd_name in info.aliases:
 
328
            return plugin_cmds.get(key)()
 
329
    return cmd_or_None
 
330
 
 
331
 
 
332
class Command(object):
 
333
    """Base class for commands.
 
334
 
 
335
    Commands are the heart of the command-line brz interface.
 
336
 
 
337
    The command object mostly handles the mapping of command-line
 
338
    parameters into one or more breezy operations, and of the results
 
339
    into textual output.
 
340
 
 
341
    Commands normally don't have any state.  All their arguments are
 
342
    passed in to the run method.  (Subclasses may take a different
 
343
    policy if the behaviour of the instance needs to depend on e.g. a
 
344
    shell plugin and not just its Python class.)
 
345
 
 
346
    The docstring for an actual command should give a single-line
 
347
    summary, then a complete description of the command.  A grammar
 
348
    description will be inserted.
 
349
 
 
350
    :cvar aliases: Other accepted names for this command.
 
351
 
 
352
    :cvar takes_args: List of argument forms, marked with whether they are
 
353
        optional, repeated, etc.  Examples::
 
354
 
 
355
            ['to_location', 'from_branch?', 'file*']
 
356
 
 
357
        * 'to_location' is required
 
358
        * 'from_branch' is optional
 
359
        * 'file' can be specified 0 or more times
 
360
 
 
361
    :cvar takes_options: List of options that may be given for this command.
 
362
        These can be either strings, referring to globally-defined options, or
 
363
        option objects.  Retrieve through options().
 
364
 
 
365
    :cvar hidden: If true, this command isn't advertised.  This is typically
 
366
        for commands intended for expert users.
 
367
 
 
368
    :cvar encoding_type: Command objects will get a 'outf' attribute, which has
 
369
        been setup to properly handle encoding of unicode strings.
 
370
        encoding_type determines what will happen when characters cannot be
 
371
        encoded:
 
372
 
 
373
        * strict - abort if we cannot decode
 
374
        * replace - put in a bogus character (typically '?')
 
375
        * exact - do not encode sys.stdout
 
376
 
 
377
        NOTE: by default on Windows, sys.stdout is opened as a text stream,
 
378
        therefore LF line-endings are converted to CRLF.  When a command uses
 
379
        encoding_type = 'exact', then sys.stdout is forced to be a binary
 
380
        stream, and line-endings will not mangled.
 
381
 
 
382
    :cvar invoked_as:
 
383
        A string indicating the real name under which this command was
 
384
        invoked, before expansion of aliases.
 
385
        (This may be None if the command was constructed and run in-process.)
 
386
 
 
387
    :cvar hooks: An instance of CommandHooks.
 
388
 
 
389
    :cvar __doc__: The help shown by 'brz help command' for this command.
 
390
        This is set by assigning explicitly to __doc__ so that -OO can
 
391
        be used::
 
392
 
 
393
            class Foo(Command):
 
394
                __doc__ = "My help goes here"
 
395
    """
 
396
    aliases = []
 
397
    takes_args = []
 
398
    takes_options = []
 
399
    encoding_type = 'strict'
 
400
    invoked_as = None
 
401
    l10n = True
 
402
 
 
403
    hidden = False
 
404
 
 
405
    def __init__(self):
 
406
        """Construct an instance of this command."""
 
407
        # List of standard options directly supported
 
408
        self.supported_std_options = []
 
409
        self._setup_run()
 
410
 
 
411
    def add_cleanup(self, cleanup_func, *args, **kwargs):
 
412
        """Register a function to call after self.run returns or raises.
 
413
 
 
414
        Functions will be called in LIFO order.
 
415
        """
 
416
        self._operation.add_cleanup(cleanup_func, *args, **kwargs)
 
417
 
 
418
    def cleanup_now(self):
 
419
        """Execute and empty pending cleanup functions immediately.
 
420
 
 
421
        After cleanup_now all registered cleanups are forgotten.  add_cleanup
 
422
        may be called again after cleanup_now; these cleanups will be called
 
423
        after self.run returns or raises (or when cleanup_now is next called).
 
424
 
 
425
        This is useful for releasing expensive or contentious resources (such
 
426
        as write locks) before doing further work that does not require those
 
427
        resources (such as writing results to self.outf). Note though, that
 
428
        as it releases all resources, this may release locks that the command
 
429
        wants to hold, so use should be done with care.
 
430
        """
 
431
        self._operation.cleanup_now()
 
432
 
 
433
    def _usage(self):
 
434
        """Return single-line grammar for this command.
 
435
 
 
436
        Only describes arguments, not options.
 
437
        """
 
438
        s = 'brz ' + self.name() + ' '
 
439
        for aname in self.takes_args:
 
440
            aname = aname.upper()
 
441
            if aname[-1] in ['$', '+']:
 
442
                aname = aname[:-1] + '...'
 
443
            elif aname[-1] == '?':
 
444
                aname = '[' + aname[:-1] + ']'
 
445
            elif aname[-1] == '*':
 
446
                aname = '[' + aname[:-1] + '...]'
 
447
            s += aname + ' '
 
448
        s = s[:-1]      # remove last space
 
449
        return s
 
450
 
 
451
    def get_help_text(self, additional_see_also=None, plain=True,
 
452
                      see_also_as_links=False, verbose=True):
 
453
        """Return a text string with help for this command.
 
454
 
 
455
        :param additional_see_also: Additional help topics to be
 
456
            cross-referenced.
 
457
        :param plain: if False, raw help (reStructuredText) is
 
458
            returned instead of plain text.
 
459
        :param see_also_as_links: if True, convert items in 'See also'
 
460
            list to internal links (used by bzr_man rstx generator)
 
461
        :param verbose: if True, display the full help, otherwise
 
462
            leave out the descriptive sections and just display
 
463
            usage help (e.g. Purpose, Usage, Options) with a
 
464
            message explaining how to obtain full help.
 
465
        """
 
466
        if self.l10n:
 
467
            i18n.install()  # Install i18n only for get_help_text for now.
 
468
        doc = self.help()
 
469
        if doc:
 
470
            # Note: If self.gettext() translates ':Usage:\n', the section will
 
471
            # be shown after "Description" section and we don't want to
 
472
            # translate the usage string.
 
473
            # Though, brz export-pot don't exports :Usage: section and it must
 
474
            # not be translated.
 
475
            doc = self.gettext(doc)
 
476
        else:
 
477
            doc = gettext("No help for this command.")
 
478
 
 
479
        # Extract the summary (purpose) and sections out from the text
 
480
        purpose,sections,order = self._get_help_parts(doc)
 
481
 
 
482
        # If a custom usage section was provided, use it
 
483
        if 'Usage' in sections:
 
484
            usage = sections.pop('Usage')
 
485
        else:
 
486
            usage = self._usage()
 
487
 
 
488
        # The header is the purpose and usage
 
489
        result = ""
 
490
        result += gettext(':Purpose: %s\n') % (purpose,)
 
491
        if usage.find('\n') >= 0:
 
492
            result += gettext(':Usage:\n%s\n') % (usage,)
 
493
        else:
 
494
            result += gettext(':Usage:   %s\n') % (usage,)
 
495
        result += '\n'
 
496
 
 
497
        # Add the options
 
498
        #
 
499
        # XXX: optparse implicitly rewraps the help, and not always perfectly,
 
500
        # so we get <https://bugs.launchpad.net/bzr/+bug/249908>.  -- mbp
 
501
        # 20090319
 
502
        parser = option.get_optparser(self.options())
 
503
        options = parser.format_option_help()
 
504
        # FIXME: According to the spec, ReST option lists actually don't
 
505
        # support options like --1.14 so that causes syntax errors (in Sphinx
 
506
        # at least).  As that pattern always appears in the commands that
 
507
        # break, we trap on that and then format that block of 'format' options
 
508
        # as a literal block. We use the most recent format still listed so we
 
509
        # don't have to do that too often -- vila 20110514
 
510
        if not plain and options.find('  --1.14  ') != -1:
 
511
            options = options.replace(' format:\n', ' format::\n\n', 1)
 
512
        if options.startswith('Options:'):
 
513
            result += gettext(':Options:%s') % (options[len('options:'):],)
 
514
        else:
 
515
            result += options
 
516
        result += '\n'
 
517
 
 
518
        if verbose:
 
519
            # Add the description, indenting it 2 spaces
 
520
            # to match the indentation of the options
 
521
            if None in sections:
 
522
                text = sections.pop(None)
 
523
                text = '\n  '.join(text.splitlines())
 
524
                result += gettext(':Description:\n  %s\n\n') % (text,)
 
525
 
 
526
            # Add the custom sections (e.g. Examples). Note that there's no need
 
527
            # to indent these as they must be indented already in the source.
 
528
            if sections:
 
529
                for label in order:
 
530
                    if label in sections:
 
531
                        result += ':%s:\n%s\n' % (label, sections[label])
 
532
                result += '\n'
 
533
        else:
 
534
            result += (gettext("See brz help %s for more details and examples.\n\n")
 
535
                % self.name())
 
536
 
 
537
        # Add the aliases, source (plug-in) and see also links, if any
 
538
        if self.aliases:
 
539
            result += gettext(':Aliases:  ')
 
540
            result += ', '.join(self.aliases) + '\n'
 
541
        plugin_name = self.plugin_name()
 
542
        if plugin_name is not None:
 
543
            result += gettext(':From:     plugin "%s"\n') % plugin_name
 
544
        see_also = self.get_see_also(additional_see_also)
 
545
        if see_also:
 
546
            if not plain and see_also_as_links:
 
547
                see_also_links = []
 
548
                for item in see_also:
 
549
                    if item == 'topics':
 
550
                        # topics doesn't have an independent section
 
551
                        # so don't create a real link
 
552
                        see_also_links.append(item)
 
553
                    else:
 
554
                        # Use a Sphinx link for this entry
 
555
                        link_text = gettext(":doc:`{0} <{1}-help>`").format(
 
556
                                                                    item, item)
 
557
                        see_also_links.append(link_text)
 
558
                see_also = see_also_links
 
559
            result += gettext(':See also: %s') % ', '.join(see_also) + '\n'
 
560
 
 
561
        # If this will be rendered as plain text, convert it
 
562
        if plain:
 
563
            import breezy.help_topics
 
564
            result = breezy.help_topics.help_as_plain_text(result)
 
565
        return result
 
566
 
 
567
    @staticmethod
 
568
    def _get_help_parts(text):
 
569
        """Split help text into a summary and named sections.
 
570
 
 
571
        :return: (summary,sections,order) where summary is the top line and
 
572
            sections is a dictionary of the rest indexed by section name.
 
573
            order is the order the section appear in the text.
 
574
            A section starts with a heading line of the form ":xxx:".
 
575
            Indented text on following lines is the section value.
 
576
            All text found outside a named section is assigned to the
 
577
            default section which is given the key of None.
 
578
        """
 
579
        def save_section(sections, order, label, section):
 
580
            if len(section) > 0:
 
581
                if label in sections:
 
582
                    sections[label] += '\n' + section
 
583
                else:
 
584
                    order.append(label)
 
585
                    sections[label] = section
 
586
 
 
587
        lines = text.rstrip().splitlines()
 
588
        summary = lines.pop(0)
 
589
        sections = {}
 
590
        order = []
 
591
        label,section = None,''
 
592
        for line in lines:
 
593
            if line.startswith(':') and line.endswith(':') and len(line) > 2:
 
594
                save_section(sections, order, label, section)
 
595
                label,section = line[1:-1],''
 
596
            elif (label is not None) and len(line) > 1 and not line[0].isspace():
 
597
                save_section(sections, order, label, section)
 
598
                label,section = None,line
 
599
            else:
 
600
                if len(section) > 0:
 
601
                    section += '\n' + line
 
602
                else:
 
603
                    section = line
 
604
        save_section(sections, order, label, section)
 
605
        return summary, sections, order
 
606
 
 
607
    def get_help_topic(self):
 
608
        """Return the commands help topic - its name."""
 
609
        return self.name()
 
610
 
 
611
    def get_see_also(self, additional_terms=None):
 
612
        """Return a list of help topics that are related to this command.
 
613
 
 
614
        The list is derived from the content of the _see_also attribute. Any
 
615
        duplicates are removed and the result is in lexical order.
 
616
        :param additional_terms: Additional help topics to cross-reference.
 
617
        :return: A list of help topics.
 
618
        """
 
619
        see_also = set(getattr(self, '_see_also', []))
 
620
        if additional_terms:
 
621
            see_also.update(additional_terms)
 
622
        return sorted(see_also)
 
623
 
 
624
    def options(self):
 
625
        """Return dict of valid options for this command.
 
626
 
 
627
        Maps from long option name to option object."""
 
628
        r = Option.STD_OPTIONS.copy()
 
629
        std_names = set(r)
 
630
        for o in self.takes_options:
 
631
            if isinstance(o, string_types):
 
632
                o = option.Option.OPTIONS[o]
 
633
            r[o.name] = o
 
634
            if o.name in std_names:
 
635
                self.supported_std_options.append(o.name)
 
636
        return r
 
637
 
 
638
    def _setup_outf(self):
 
639
        """Return a file linked to stdout, which has proper encoding."""
 
640
        self.outf = ui.ui_factory.make_output_stream(
 
641
            encoding_type=self.encoding_type)
 
642
 
 
643
    def run_argv_aliases(self, argv, alias_argv=None):
 
644
        """Parse the command line and run with extra aliases in alias_argv."""
 
645
        args, opts = parse_args(self, argv, alias_argv)
 
646
        self._setup_outf()
 
647
 
 
648
        # Process the standard options
 
649
        if 'help' in opts:  # e.g. brz add --help
 
650
            self.outf.write(self.get_help_text())
 
651
            return 0
 
652
        if 'usage' in opts:  # e.g. brz add --usage
 
653
            self.outf.write(self.get_help_text(verbose=False))
 
654
            return 0
 
655
        trace.set_verbosity_level(option._verbosity_level)
 
656
        if 'verbose' in self.supported_std_options:
 
657
            opts['verbose'] = trace.is_verbose()
 
658
        elif 'verbose' in opts:
 
659
            del opts['verbose']
 
660
        if 'quiet' in self.supported_std_options:
 
661
            opts['quiet'] = trace.is_quiet()
 
662
        elif 'quiet' in opts:
 
663
            del opts['quiet']
 
664
        # mix arguments and options into one dictionary
 
665
        cmdargs = _match_argform(self.name(), self.takes_args, args)
 
666
        cmdopts = {}
 
667
        for k, v in opts.items():
 
668
            cmdopts[k.replace('-', '_')] = v
 
669
 
 
670
        all_cmd_args = cmdargs.copy()
 
671
        all_cmd_args.update(cmdopts)
 
672
 
 
673
        try:
 
674
            return self.run(**all_cmd_args)
 
675
        finally:
 
676
            # reset it, so that other commands run in the same process won't
 
677
            # inherit state. Before we reset it, log any activity, so that it
 
678
            # gets properly tracked.
 
679
            ui.ui_factory.log_transport_activity(
 
680
                display=('bytes' in debug.debug_flags))
 
681
            trace.set_verbosity_level(0)
 
682
 
 
683
    def _setup_run(self):
 
684
        """Wrap the defined run method on self with a cleanup.
 
685
 
 
686
        This is called by __init__ to make the Command be able to be run
 
687
        by just calling run(), as it could be before cleanups were added.
 
688
 
 
689
        If a different form of cleanups are in use by your Command subclass,
 
690
        you can override this method.
 
691
        """
 
692
        class_run = self.run
 
693
        def run(*args, **kwargs):
 
694
            for hook in Command.hooks['pre_command']:
 
695
                hook(self)
 
696
            self._operation = cleanup.OperationWithCleanups(class_run)
 
697
            try:
 
698
                return self._operation.run_simple(*args, **kwargs)
 
699
            finally:
 
700
                del self._operation
 
701
                for hook in Command.hooks['post_command']:
 
702
                    hook(self)
 
703
        self.run = run
 
704
 
 
705
    def run(self):
 
706
        """Actually run the command.
 
707
 
 
708
        This is invoked with the options and arguments bound to
 
709
        keyword parameters.
 
710
 
 
711
        Return 0 or None if the command was successful, or a non-zero
 
712
        shell error code if not.  It's OK for this method to allow
 
713
        an exception to raise up.
 
714
 
 
715
        This method is automatically wrapped by Command.__init__ with a 
 
716
        cleanup operation, stored as self._operation. This can be used
 
717
        via self.add_cleanup to perform automatic cleanups at the end of
 
718
        run().
 
719
 
 
720
        The argument for run are assembled by introspection. So for instance,
 
721
        if your command takes an argument files, you would declare::
 
722
 
 
723
            def run(self, files=None):
 
724
                pass
 
725
        """
 
726
        raise NotImplementedError('no implementation of command %r'
 
727
                                  % self.name())
 
728
 
 
729
    def help(self):
 
730
        """Return help message for this class."""
 
731
        from inspect import getdoc
 
732
        if self.__doc__ is Command.__doc__:
 
733
            return None
 
734
        return getdoc(self)
 
735
 
 
736
    def gettext(self, message):
 
737
        """Returns the gettext function used to translate this command's help.
 
738
 
 
739
        Commands provided by plugins should override this to use their
 
740
        own i18n system.
 
741
        """
 
742
        return i18n.gettext_per_paragraph(message)
 
743
 
 
744
    def name(self):
 
745
        """Return the canonical name for this command.
 
746
 
 
747
        The name under which it was actually invoked is available in invoked_as.
 
748
        """
 
749
        return _unsquish_command_name(self.__class__.__name__)
 
750
 
 
751
    def plugin_name(self):
 
752
        """Get the name of the plugin that provides this command.
 
753
 
 
754
        :return: The name of the plugin or None if the command is builtin.
 
755
        """
 
756
        return plugin_name(self.__module__)
 
757
 
 
758
 
 
759
class CommandHooks(Hooks):
 
760
    """Hooks related to Command object creation/enumeration."""
 
761
 
 
762
    def __init__(self):
 
763
        """Create the default hooks.
 
764
 
 
765
        These are all empty initially, because by default nothing should get
 
766
        notified.
 
767
        """
 
768
        Hooks.__init__(self, "breezy.commands", "Command.hooks")
 
769
        self.add_hook('extend_command',
 
770
            "Called after creating a command object to allow modifications "
 
771
            "such as adding or removing options, docs etc. Called with the "
 
772
            "new breezy.commands.Command object.", (1, 13))
 
773
        self.add_hook('get_command',
 
774
            "Called when creating a single command. Called with "
 
775
            "(cmd_or_None, command_name). get_command should either return "
 
776
            "the cmd_or_None parameter, or a replacement Command object that "
 
777
            "should be used for the command. Note that the Command.hooks "
 
778
            "hooks are core infrastructure. Many users will prefer to use "
 
779
            "breezy.commands.register_command or plugin_cmds.register_lazy.",
 
780
            (1, 17))
 
781
        self.add_hook('get_missing_command',
 
782
            "Called when creating a single command if no command could be "
 
783
            "found. Called with (command_name). get_missing_command should "
 
784
            "either return None, or a Command object to be used for the "
 
785
            "command.", (1, 17))
 
786
        self.add_hook('list_commands',
 
787
            "Called when enumerating commands. Called with a set of "
 
788
            "cmd_name strings for all the commands found so far. This set "
 
789
            " is safe to mutate - e.g. to remove a command. "
 
790
            "list_commands should return the updated set of command names.",
 
791
            (1, 17))
 
792
        self.add_hook('pre_command',
 
793
            "Called prior to executing a command. Called with the command "
 
794
            "object.", (2, 6))
 
795
        self.add_hook('post_command',
 
796
            "Called after executing a command. Called with the command "
 
797
            "object.", (2, 6))
 
798
 
 
799
Command.hooks = CommandHooks()
 
800
 
 
801
 
 
802
def parse_args(command, argv, alias_argv=None):
 
803
    """Parse command line.
 
804
 
 
805
    Arguments and options are parsed at this level before being passed
 
806
    down to specific command handlers.  This routine knows, from a
 
807
    lookup table, something about the available options, what optargs
 
808
    they take, and which commands will accept them.
 
809
    """
 
810
    # TODO: make it a method of the Command?
 
811
    parser = option.get_optparser(command.options())
 
812
    if alias_argv is not None:
 
813
        args = alias_argv + argv
 
814
    else:
 
815
        args = argv
 
816
 
 
817
    # for python 2.5 and later, optparse raises this exception if a non-ascii
 
818
    # option name is given.  See http://bugs.python.org/issue2931
 
819
    try:
 
820
        options, args = parser.parse_args(args)
 
821
    except UnicodeEncodeError as e:
 
822
        raise errors.BzrCommandError(
 
823
            gettext('Only ASCII permitted in option names'))
 
824
 
 
825
    opts = dict((k, v) for k, v in options.__dict__.items() if
 
826
                v is not option.OptionParser.DEFAULT_VALUE)
 
827
    return args, opts
 
828
 
 
829
 
 
830
def _match_argform(cmd, takes_args, args):
 
831
    argdict = {}
 
832
 
 
833
    # step through args and takes_args, allowing appropriate 0-many matches
 
834
    for ap in takes_args:
 
835
        argname = ap[:-1]
 
836
        if ap[-1] == '?':
 
837
            if args:
 
838
                argdict[argname] = args.pop(0)
 
839
        elif ap[-1] == '*': # all remaining arguments
 
840
            if args:
 
841
                argdict[argname + '_list'] = args[:]
 
842
                args = []
 
843
            else:
 
844
                argdict[argname + '_list'] = None
 
845
        elif ap[-1] == '+':
 
846
            if not args:
 
847
                raise errors.BzrCommandError(gettext(
 
848
                      "command {0!r} needs one or more {1}").format(
 
849
                      cmd, argname.upper()))
 
850
            else:
 
851
                argdict[argname + '_list'] = args[:]
 
852
                args = []
 
853
        elif ap[-1] == '$': # all but one
 
854
            if len(args) < 2:
 
855
                raise errors.BzrCommandError(
 
856
                      gettext("command {0!r} needs one or more {1}").format(
 
857
                                             cmd, argname.upper()))
 
858
            argdict[argname + '_list'] = args[:-1]
 
859
            args[:-1] = []
 
860
        else:
 
861
            # just a plain arg
 
862
            argname = ap
 
863
            if not args:
 
864
                raise errors.BzrCommandError(
 
865
                     gettext("command {0!r} requires argument {1}").format(
 
866
                               cmd, argname.upper()))
 
867
            else:
 
868
                argdict[argname] = args.pop(0)
 
869
 
 
870
    if args:
 
871
        raise errors.BzrCommandError( gettext(
 
872
                              "extra argument to command {0}: {1}").format(
 
873
                                       cmd, args[0]) )
 
874
 
 
875
    return argdict
 
876
 
 
877
def apply_coveraged(dirname, the_callable, *args, **kwargs):
 
878
    # Cannot use "import trace", as that would import breezy.trace instead of
 
879
    # the standard library's trace.
 
880
    trace = __import__('trace')
 
881
 
 
882
    tracer = trace.Trace(count=1, trace=0)
 
883
    sys.settrace(tracer.globaltrace)
 
884
    threading.settrace(tracer.globaltrace)
 
885
 
 
886
    try:
 
887
        return exception_to_return_code(the_callable, *args, **kwargs)
 
888
    finally:
 
889
        sys.settrace(None)
 
890
        results = tracer.results()
 
891
        results.write_results(show_missing=1, summary=False,
 
892
                              coverdir=dirname)
 
893
 
 
894
 
 
895
def apply_profiled(the_callable, *args, **kwargs):
 
896
    import hotshot
 
897
    import tempfile
 
898
    import hotshot.stats
 
899
    pffileno, pfname = tempfile.mkstemp()
 
900
    try:
 
901
        prof = hotshot.Profile(pfname)
 
902
        try:
 
903
            ret = prof.runcall(exception_to_return_code, the_callable, *args,
 
904
                **kwargs) or 0
 
905
        finally:
 
906
            prof.close()
 
907
        stats = hotshot.stats.load(pfname)
 
908
        stats.strip_dirs()
 
909
        stats.sort_stats('cum')   # 'time'
 
910
        ## XXX: Might like to write to stderr or the trace file instead but
 
911
        ## print_stats seems hardcoded to stdout
 
912
        stats.print_stats(20)
 
913
        return ret
 
914
    finally:
 
915
        os.close(pffileno)
 
916
        os.remove(pfname)
 
917
 
 
918
 
 
919
def exception_to_return_code(the_callable, *args, **kwargs):
 
920
    """UI level helper for profiling and coverage.
 
921
 
 
922
    This transforms exceptions into a return value of 3. As such its only
 
923
    relevant to the UI layer, and should never be called where catching
 
924
    exceptions may be desirable.
 
925
    """
 
926
    try:
 
927
        return the_callable(*args, **kwargs)
 
928
    except (KeyboardInterrupt, Exception) as e:
 
929
        # used to handle AssertionError and KeyboardInterrupt
 
930
        # specially here, but hopefully they're handled ok by the logger now
 
931
        exc_info = sys.exc_info()
 
932
        exitcode = trace.report_exception(exc_info, sys.stderr)
 
933
        if os.environ.get('BRZ_PDB'):
 
934
            print('**** entering debugger')
 
935
            import pdb
 
936
            pdb.post_mortem(exc_info[2])
 
937
        return exitcode
 
938
 
 
939
 
 
940
def apply_lsprofiled(filename, the_callable, *args, **kwargs):
 
941
    from breezy.lsprof import profile
 
942
    ret, stats = profile(exception_to_return_code, the_callable,
 
943
                         *args, **kwargs)
 
944
    stats.sort()
 
945
    if filename is None:
 
946
        stats.pprint()
 
947
    else:
 
948
        stats.save(filename)
 
949
        trace.note(gettext('Profile data written to "%s".'), filename)
 
950
    return ret
 
951
 
 
952
 
 
953
def get_alias(cmd, config=None):
 
954
    """Return an expanded alias, or None if no alias exists.
 
955
 
 
956
    cmd
 
957
        Command to be checked for an alias.
 
958
    config
 
959
        Used to specify an alternative config to use,
 
960
        which is especially useful for testing.
 
961
        If it is unspecified, the global config will be used.
 
962
    """
 
963
    if config is None:
 
964
        import breezy.config
 
965
        config = breezy.config.GlobalConfig()
 
966
    alias = config.get_alias(cmd)
 
967
    if (alias):
 
968
        return cmdline.split(alias)
 
969
    return None
 
970
 
 
971
 
 
972
def run_bzr(argv, load_plugins=load_plugins, disable_plugins=disable_plugins):
 
973
    """Execute a command.
 
974
 
 
975
    :param argv: The command-line arguments, without the program name from
 
976
        argv[0] These should already be decoded. All library/test code calling
 
977
        run_bzr should be passing valid strings (don't need decoding).
 
978
    :param load_plugins: What function to call when triggering plugin loading.
 
979
        This function should take no arguments and cause all plugins to be
 
980
        loaded.
 
981
    :param disable_plugins: What function to call when disabling plugin
 
982
        loading. This function should take no arguments and cause all plugin
 
983
        loading to be prohibited (so that code paths in your application that
 
984
        know about some plugins possibly being present will fail to import
 
985
        those plugins even if they are installed.)
 
986
    :return: Returns a command exit code or raises an exception.
 
987
 
 
988
    Special master options: these must come before the command because
 
989
    they control how the command is interpreted.
 
990
 
 
991
    --no-plugins
 
992
        Do not load plugin modules at all
 
993
 
 
994
    --no-aliases
 
995
        Do not allow aliases
 
996
 
 
997
    --builtin
 
998
        Only use builtin commands.  (Plugins are still allowed to change
 
999
        other behaviour.)
 
1000
 
 
1001
    --profile
 
1002
        Run under the Python hotshot profiler.
 
1003
 
 
1004
    --lsprof
 
1005
        Run under the Python lsprof profiler.
 
1006
 
 
1007
    --coverage
 
1008
        Generate line coverage report in the specified directory.
 
1009
 
 
1010
    --concurrency
 
1011
        Specify the number of processes that can be run concurrently (selftest).
 
1012
    """
 
1013
    trace.mutter("breezy version: " + breezy.__version__)
 
1014
    argv = _specified_or_unicode_argv(argv)
 
1015
    trace.mutter("brz arguments: %r", argv)
 
1016
 
 
1017
    opt_lsprof = opt_profile = opt_no_plugins = opt_builtin = \
 
1018
            opt_no_l10n = opt_no_aliases = False
 
1019
    opt_lsprof_file = opt_coverage_dir = None
 
1020
 
 
1021
    # --no-plugins is handled specially at a very early stage. We need
 
1022
    # to load plugins before doing other command parsing so that they
 
1023
    # can override commands, but this needs to happen first.
 
1024
 
 
1025
    argv_copy = []
 
1026
    i = 0
 
1027
    override_config = []
 
1028
    while i < len(argv):
 
1029
        a = argv[i]
 
1030
        if a == '--profile':
 
1031
            opt_profile = True
 
1032
        elif a == '--lsprof':
 
1033
            opt_lsprof = True
 
1034
        elif a == '--lsprof-file':
 
1035
            opt_lsprof = True
 
1036
            opt_lsprof_file = argv[i + 1]
 
1037
            i += 1
 
1038
        elif a == '--no-plugins':
 
1039
            opt_no_plugins = True
 
1040
        elif a == '--no-aliases':
 
1041
            opt_no_aliases = True
 
1042
        elif a == '--no-l10n':
 
1043
            opt_no_l10n = True
 
1044
        elif a == '--builtin':
 
1045
            opt_builtin = True
 
1046
        elif a == '--concurrency':
 
1047
            os.environ['BRZ_CONCURRENCY'] = argv[i + 1]
 
1048
            i += 1
 
1049
        elif a == '--coverage':
 
1050
            opt_coverage_dir = argv[i + 1]
 
1051
            i += 1
 
1052
        elif a == '--profile-imports':
 
1053
            pass # already handled in startup script Bug #588277
 
1054
        elif a.startswith('-D'):
 
1055
            debug.debug_flags.add(a[2:])
 
1056
        elif a.startswith('-O'):
 
1057
            override_config.append(a[2:])
 
1058
        else:
 
1059
            argv_copy.append(a)
 
1060
        i += 1
 
1061
 
 
1062
    if breezy.global_state is None:
 
1063
        # FIXME: Workaround for users that imported breezy but didn't call
 
1064
        # breezy.initialize -- vila 2012-01-19
 
1065
        cmdline_overrides = config.CommandLineStore()
 
1066
    else:
 
1067
        cmdline_overrides = breezy.global_state.cmdline_overrides
 
1068
    cmdline_overrides._from_cmdline(override_config)
 
1069
 
 
1070
    debug.set_debug_flags_from_config()
 
1071
 
 
1072
    if not opt_no_plugins:
 
1073
        load_plugins()
 
1074
    else:
 
1075
        disable_plugins()
 
1076
 
 
1077
    argv = argv_copy
 
1078
    if (not argv):
 
1079
        get_cmd_object('help').run_argv_aliases([])
 
1080
        return 0
 
1081
 
 
1082
    if argv[0] == '--version':
 
1083
        get_cmd_object('version').run_argv_aliases([])
 
1084
        return 0
 
1085
 
 
1086
    alias_argv = None
 
1087
 
 
1088
    if not opt_no_aliases:
 
1089
        alias_argv = get_alias(argv[0])
 
1090
        if alias_argv:
 
1091
            argv[0] = alias_argv.pop(0)
 
1092
 
 
1093
    cmd = argv.pop(0)
 
1094
    cmd_obj = get_cmd_object(cmd, plugins_override=not opt_builtin)
 
1095
    if opt_no_l10n:
 
1096
        cmd_obj.l10n = False
 
1097
    run = cmd_obj.run_argv_aliases
 
1098
    run_argv = [argv, alias_argv]
 
1099
 
 
1100
    try:
 
1101
        # We can be called recursively (tests for example), but we don't want
 
1102
        # the verbosity level to propagate.
 
1103
        saved_verbosity_level = option._verbosity_level
 
1104
        option._verbosity_level = 0
 
1105
        if opt_lsprof:
 
1106
            if opt_coverage_dir:
 
1107
                trace.warning(
 
1108
                    '--coverage ignored, because --lsprof is in use.')
 
1109
            ret = apply_lsprofiled(opt_lsprof_file, run, *run_argv)
 
1110
        elif opt_profile:
 
1111
            if opt_coverage_dir:
 
1112
                trace.warning(
 
1113
                    '--coverage ignored, because --profile is in use.')
 
1114
            ret = apply_profiled(run, *run_argv)
 
1115
        elif opt_coverage_dir:
 
1116
            ret = apply_coveraged(opt_coverage_dir, run, *run_argv)
 
1117
        else:
 
1118
            ret = run(*run_argv)
 
1119
        return ret or 0
 
1120
    finally:
 
1121
        # reset, in case we may do other commands later within the same
 
1122
        # process. Commands that want to execute sub-commands must propagate
 
1123
        # --verbose in their own way.
 
1124
        if 'memory' in debug.debug_flags:
 
1125
            trace.debug_memory('Process status after command:', short=False)
 
1126
        option._verbosity_level = saved_verbosity_level
 
1127
        # Reset the overrides 
 
1128
        cmdline_overrides._reset()
 
1129
 
 
1130
 
 
1131
def display_command(func):
 
1132
    """Decorator that suppresses pipe/interrupt errors."""
 
1133
    def ignore_pipe(*args, **kwargs):
 
1134
        try:
 
1135
            result = func(*args, **kwargs)
 
1136
            sys.stdout.flush()
 
1137
            return result
 
1138
        except IOError as e:
 
1139
            if getattr(e, 'errno', None) is None:
 
1140
                raise
 
1141
            if e.errno != errno.EPIPE:
 
1142
                # Win32 raises IOError with errno=0 on a broken pipe
 
1143
                if sys.platform != 'win32' or (e.errno not in (0, errno.EINVAL)):
 
1144
                    raise
 
1145
            pass
 
1146
        except KeyboardInterrupt:
 
1147
            pass
 
1148
    return ignore_pipe
 
1149
 
 
1150
 
 
1151
def install_bzr_command_hooks():
 
1152
    """Install the hooks to supply bzr's own commands."""
 
1153
    if _list_bzr_commands in Command.hooks["list_commands"]:
 
1154
        return
 
1155
    Command.hooks.install_named_hook("list_commands", _list_bzr_commands,
 
1156
        "bzr commands")
 
1157
    Command.hooks.install_named_hook("get_command", _get_bzr_command,
 
1158
        "bzr commands")
 
1159
    Command.hooks.install_named_hook("get_command", _get_plugin_command,
 
1160
        "bzr plugin commands")
 
1161
    Command.hooks.install_named_hook("get_command", _get_external_command,
 
1162
        "bzr external command lookup")
 
1163
    Command.hooks.install_named_hook("get_missing_command",
 
1164
                                     _try_plugin_provider,
 
1165
                                     "bzr plugin-provider-db check")
 
1166
 
 
1167
 
 
1168
 
 
1169
def _specified_or_unicode_argv(argv):
 
1170
    # For internal or testing use, argv can be passed.  Otherwise, get it from
 
1171
    # the process arguments in a unicode-safe way.
 
1172
    if argv is None:
 
1173
        return osutils.get_unicode_argv()
 
1174
    else:
 
1175
        new_argv = []
 
1176
        try:
 
1177
            # ensure all arguments are unicode strings
 
1178
            for a in argv:
 
1179
                if isinstance(a, unicode):
 
1180
                    new_argv.append(a)
 
1181
                else:
 
1182
                    new_argv.append(a.decode('ascii'))
 
1183
        except UnicodeDecodeError:
 
1184
            raise errors.BzrError("argv should be list of unicode strings.")
 
1185
        return new_argv
 
1186
 
 
1187
 
 
1188
def main(argv=None):
 
1189
    """Main entry point of command-line interface.
 
1190
 
 
1191
    Typically `breezy.initialize` should be called first.
 
1192
 
 
1193
    :param argv: list of unicode command-line arguments similar to sys.argv.
 
1194
        argv[0] is script name usually, it will be ignored.
 
1195
        Don't pass here sys.argv because this list contains plain strings
 
1196
        and not unicode; pass None instead.
 
1197
 
 
1198
    :return: exit code of brz command.
 
1199
    """
 
1200
    if argv is not None:
 
1201
        argv = argv[1:]
 
1202
    _register_builtin_commands()
 
1203
    ret = run_bzr_catch_errors(argv)
 
1204
    trace.mutter("return code %d", ret)
 
1205
    return ret
 
1206
 
 
1207
 
 
1208
def run_bzr_catch_errors(argv):
 
1209
    """Run a bzr command with parameters as described by argv.
 
1210
 
 
1211
    This function assumed that that UI layer is setup, that symbol deprecations
 
1212
    are already applied, and that unicode decoding has already been performed on argv.
 
1213
    """
 
1214
    # done here so that they're covered for every test run
 
1215
    install_bzr_command_hooks()
 
1216
    return exception_to_return_code(run_bzr, argv)
 
1217
 
 
1218
 
 
1219
def run_bzr_catch_user_errors(argv):
 
1220
    """Run brz and report user errors, but let internal errors propagate.
 
1221
 
 
1222
    This is used for the test suite, and might be useful for other programs
 
1223
    that want to wrap the commandline interface.
 
1224
    """
 
1225
    # done here so that they're covered for every test run
 
1226
    install_bzr_command_hooks()
 
1227
    try:
 
1228
        return run_bzr(argv)
 
1229
    except Exception as e:
 
1230
        if (isinstance(e, (OSError, IOError))
 
1231
            or not getattr(e, 'internal_error', True)):
 
1232
            trace.report_exception(sys.exc_info(), sys.stderr)
 
1233
            return 3
 
1234
        else:
 
1235
            raise
 
1236
 
 
1237
 
 
1238
class HelpCommandIndex(object):
 
1239
    """A index for bzr help that returns commands."""
 
1240
 
 
1241
    def __init__(self):
 
1242
        self.prefix = 'commands/'
 
1243
 
 
1244
    def get_topics(self, topic):
 
1245
        """Search for topic amongst commands.
 
1246
 
 
1247
        :param topic: A topic to search for.
 
1248
        :return: A list which is either empty or contains a single
 
1249
            Command entry.
 
1250
        """
 
1251
        if topic and topic.startswith(self.prefix):
 
1252
            topic = topic[len(self.prefix):]
 
1253
        try:
 
1254
            cmd = _get_cmd_object(topic, check_missing=False)
 
1255
        except KeyError:
 
1256
            return []
 
1257
        else:
 
1258
            return [cmd]
 
1259
 
 
1260
 
 
1261
class Provider(object):
 
1262
    """Generic class to be overriden by plugins"""
 
1263
 
 
1264
    def plugin_for_command(self, cmd_name):
 
1265
        """Takes a command and returns the information for that plugin
 
1266
 
 
1267
        :return: A dictionary with all the available information
 
1268
            for the requested plugin
 
1269
        """
 
1270
        raise NotImplementedError
 
1271
 
 
1272
 
 
1273
class ProvidersRegistry(registry.Registry):
 
1274
    """This registry exists to allow other providers to exist"""
 
1275
 
 
1276
    def __iter__(self):
 
1277
        for key, provider in self.items():
 
1278
            yield provider
 
1279
 
 
1280
command_providers_registry = ProvidersRegistry()