/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 bzrlib/commands.py

  • Committer: Aaron Bentley
  • Date: 2008-10-21 14:08:31 UTC
  • mto: This revision was merged to the branch mainline in revision 3789.
  • Revision ID: aaron@aaronbentley.com-20081021140831-a8sqdr5sg8y82z4e
Switch from dict to Registry for plugin_cmds

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
# Copyright (C) 2006 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 
16
 
 
17
 
 
18
# TODO: probably should say which arguments are candidates for glob
 
19
# expansion on windows and do that at the command level.
 
20
 
 
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.
 
25
 
 
26
# TODO: Specific "examples" property on commands for consistent formatting.
 
27
 
 
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?
 
30
 
 
31
import os
 
32
import sys
 
33
 
 
34
from bzrlib.lazy_import import lazy_import
 
35
lazy_import(globals(), """
 
36
import codecs
 
37
import errno
 
38
from warnings import warn
 
39
 
 
40
import bzrlib
 
41
from bzrlib import (
 
42
    debug,
 
43
    errors,
 
44
    option,
 
45
    osutils,
 
46
    trace,
 
47
    win32utils,
 
48
    )
 
49
""")
 
50
 
 
51
from bzrlib import registry
 
52
# Compatibility
 
53
from bzrlib.option import Option
 
54
 
 
55
 
 
56
class CommandRegistry(registry.Registry):
 
57
 
 
58
    def register(self, cmd, decorate=False):
 
59
        """Utility function to help register a command
 
60
 
 
61
        :param cmd: Command subclass to register
 
62
        :param decorate: If true, allow overriding an existing command
 
63
            of the same name; the old command is returned by this function.
 
64
            Otherwise it is an error to try to override an existing command.
 
65
        """
 
66
        k = cmd.__name__
 
67
        if k.startswith("cmd_"):
 
68
            k_unsquished = _unsquish_command_name(k)
 
69
        else:
 
70
            k_unsquished = k
 
71
        try:
 
72
            previous = self.get(k_unsquished)
 
73
        except KeyError:
 
74
            previous = _builtin_commands().get(k_unsquished)
 
75
        try:
 
76
            registry.Registry.register(self, k_unsquished, cmd,
 
77
                                       override_existing=decorate)
 
78
        except KeyError:
 
79
            trace.log_error('Two plugins defined the same command: %r' % k)
 
80
            trace.log_error('Not loading the one in %r' %
 
81
                            sys.modules[cmd.__module__])
 
82
            trace.log_error('Previously this command was registered from %r' %
 
83
                            sys.modules[previous.__module__])
 
84
        return previous
 
85
 
 
86
 
 
87
plugin_cmds = CommandRegistry()
 
88
 
 
89
 
 
90
def register_command(cmd, decorate=False):
 
91
    global plugin_cmds
 
92
    return plugin_cmds.register(cmd, decorate)
 
93
 
 
94
 
 
95
def _squish_command_name(cmd):
 
96
    return 'cmd_' + cmd.replace('-', '_')
 
97
 
 
98
 
 
99
def _unsquish_command_name(cmd):
 
100
    return cmd[4:].replace('_','-')
 
101
 
 
102
 
 
103
def _builtin_commands():
 
104
    import bzrlib.builtins
 
105
    r = {}
 
106
    builtins = bzrlib.builtins.__dict__
 
107
    for name in builtins:
 
108
        if name.startswith("cmd_"):
 
109
            real_name = _unsquish_command_name(name)
 
110
            r[real_name] = builtins[name]
 
111
    return r
 
112
            
 
113
 
 
114
def builtin_command_names():
 
115
    """Return list of builtin command names."""
 
116
    return _builtin_commands().keys()
 
117
    
 
118
 
 
119
def plugin_command_names():
 
120
    return plugin_cmds.keys()
 
121
 
 
122
 
 
123
def _get_cmd_dict(plugins_override=True):
 
124
    """Return name->class mapping for all commands."""
 
125
    d = _builtin_commands()
 
126
    if plugins_override:
 
127
        d.update(plugin_cmds.iteritems())
 
128
    return d
 
129
 
 
130
    
 
131
def get_all_cmds(plugins_override=True):
 
132
    """Return canonical name and class for all registered commands."""
 
133
    for k, v in _get_cmd_dict(plugins_override=plugins_override).iteritems():
 
134
        yield k,v
 
135
 
 
136
 
 
137
def get_cmd_object(cmd_name, plugins_override=True):
 
138
    """Return the canonical name and command class for a command.
 
139
 
 
140
    plugins_override
 
141
        If true, plugin commands can override builtins.
 
142
    """
 
143
    try:
 
144
        return _get_cmd_object(cmd_name, plugins_override)
 
145
    except KeyError:
 
146
        raise errors.BzrCommandError('unknown command "%s"' % cmd_name)
 
147
 
 
148
 
 
149
def _get_cmd_object(cmd_name, plugins_override=True):
 
150
    """Worker for get_cmd_object which raises KeyError rather than BzrCommandError."""
 
151
    from bzrlib.externalcommand import ExternalCommand
 
152
 
 
153
    # We want only 'ascii' command names, but the user may have typed
 
154
    # in a Unicode name. In that case, they should just get a
 
155
    # 'command not found' error later.
 
156
    # In the future, we may actually support Unicode command names.
 
157
 
 
158
    # first look up this command under the specified name
 
159
    cmds = _get_cmd_dict(plugins_override=plugins_override)
 
160
    try:
 
161
        return cmds[cmd_name]()
 
162
    except KeyError:
 
163
        pass
 
164
 
 
165
    # look for any command which claims this as an alias
 
166
    for real_cmd_name, cmd_class in cmds.iteritems():
 
167
        if cmd_name in cmd_class.aliases:
 
168
            return cmd_class()
 
169
 
 
170
    cmd_obj = ExternalCommand.find_command(cmd_name)
 
171
    if cmd_obj:
 
172
        return cmd_obj
 
173
 
 
174
    # look for plugins that provide this command but aren't installed
 
175
    for provider in command_providers_registry:
 
176
        try:
 
177
            plugin_metadata = provider.plugin_for_command(cmd_name)
 
178
        except errors.NoPluginAvailable:
 
179
            pass
 
180
        else:
 
181
            raise errors.CommandAvailableInPlugin(cmd_name, 
 
182
                                                  plugin_metadata, provider)
 
183
 
 
184
    raise KeyError
 
185
 
 
186
 
 
187
class Command(object):
 
188
    """Base class for commands.
 
189
 
 
190
    Commands are the heart of the command-line bzr interface.
 
191
 
 
192
    The command object mostly handles the mapping of command-line
 
193
    parameters into one or more bzrlib operations, and of the results
 
194
    into textual output.
 
195
 
 
196
    Commands normally don't have any state.  All their arguments are
 
197
    passed in to the run method.  (Subclasses may take a different
 
198
    policy if the behaviour of the instance needs to depend on e.g. a
 
199
    shell plugin and not just its Python class.)
 
200
 
 
201
    The docstring for an actual command should give a single-line
 
202
    summary, then a complete description of the command.  A grammar
 
203
    description will be inserted.
 
204
 
 
205
    aliases
 
206
        Other accepted names for this command.
 
207
 
 
208
    takes_args
 
209
        List of argument forms, marked with whether they are optional,
 
210
        repeated, etc.
 
211
 
 
212
                Examples:
 
213
 
 
214
                ['to_location', 'from_branch?', 'file*']
 
215
 
 
216
                'to_location' is required
 
217
                'from_branch' is optional
 
218
                'file' can be specified 0 or more times
 
219
 
 
220
    takes_options
 
221
        List of options that may be given for this command.  These can
 
222
        be either strings, referring to globally-defined options,
 
223
        or option objects.  Retrieve through options().
 
224
 
 
225
    hidden
 
226
        If true, this command isn't advertised.  This is typically
 
227
        for commands intended for expert users.
 
228
 
 
229
    encoding_type
 
230
        Command objects will get a 'outf' attribute, which has been
 
231
        setup to properly handle encoding of unicode strings.
 
232
        encoding_type determines what will happen when characters cannot
 
233
        be encoded
 
234
            strict - abort if we cannot decode
 
235
            replace - put in a bogus character (typically '?')
 
236
            exact - do not encode sys.stdout
 
237
 
 
238
            NOTE: by default on Windows, sys.stdout is opened as a text
 
239
            stream, therefore LF line-endings are converted to CRLF.
 
240
            When a command uses encoding_type = 'exact', then
 
241
            sys.stdout is forced to be a binary stream, and line-endings
 
242
            will not mangled.
 
243
 
 
244
    """
 
245
    aliases = []
 
246
    takes_args = []
 
247
    takes_options = []
 
248
    encoding_type = 'strict'
 
249
 
 
250
    hidden = False
 
251
    
 
252
    def __init__(self):
 
253
        """Construct an instance of this command."""
 
254
        if self.__doc__ == Command.__doc__:
 
255
            warn("No help message set for %r" % self)
 
256
        # List of standard options directly supported
 
257
        self.supported_std_options = []
 
258
 
 
259
    def _maybe_expand_globs(self, file_list):
 
260
        """Glob expand file_list if the platform does not do that itself.
 
261
        
 
262
        :return: A possibly empty list of unicode paths.
 
263
 
 
264
        Introduced in bzrlib 0.18.
 
265
        """
 
266
        if not file_list:
 
267
            file_list = []
 
268
        if sys.platform == 'win32':
 
269
            file_list = win32utils.glob_expand(file_list)
 
270
        return list(file_list)
 
271
 
 
272
    def _usage(self):
 
273
        """Return single-line grammar for this command.
 
274
 
 
275
        Only describes arguments, not options.
 
276
        """
 
277
        s = 'bzr ' + self.name() + ' '
 
278
        for aname in self.takes_args:
 
279
            aname = aname.upper()
 
280
            if aname[-1] in ['$', '+']:
 
281
                aname = aname[:-1] + '...'
 
282
            elif aname[-1] == '?':
 
283
                aname = '[' + aname[:-1] + ']'
 
284
            elif aname[-1] == '*':
 
285
                aname = '[' + aname[:-1] + '...]'
 
286
            s += aname + ' '
 
287
        s = s[:-1]      # remove last space
 
288
        return s
 
289
 
 
290
    def get_help_text(self, additional_see_also=None, plain=True,
 
291
                      see_also_as_links=False):
 
292
        """Return a text string with help for this command.
 
293
        
 
294
        :param additional_see_also: Additional help topics to be
 
295
            cross-referenced.
 
296
        :param plain: if False, raw help (reStructuredText) is
 
297
            returned instead of plain text.
 
298
        :param see_also_as_links: if True, convert items in 'See also'
 
299
            list to internal links (used by bzr_man rstx generator)
 
300
        """
 
301
        doc = self.help()
 
302
        if doc is None:
 
303
            raise NotImplementedError("sorry, no detailed help yet for %r" % self.name())
 
304
 
 
305
        # Extract the summary (purpose) and sections out from the text
 
306
        purpose,sections = self._get_help_parts(doc)
 
307
 
 
308
        # If a custom usage section was provided, use it
 
309
        if sections.has_key('Usage'):
 
310
            usage = sections.pop('Usage')
 
311
        else:
 
312
            usage = self._usage()
 
313
 
 
314
        # The header is the purpose and usage
 
315
        result = ""
 
316
        result += ':Purpose: %s\n' % purpose
 
317
        if usage.find('\n') >= 0:
 
318
            result += ':Usage:\n%s\n' % usage
 
319
        else:
 
320
            result += ':Usage:   %s\n' % usage
 
321
        result += '\n'
 
322
 
 
323
        # Add the options
 
324
        options = option.get_optparser(self.options()).format_option_help()
 
325
        if options.startswith('Options:'):
 
326
            result += ':' + options
 
327
        elif options.startswith('options:'):
 
328
            # Python 2.4 version of optparse
 
329
            result += ':Options:' + options[len('options:'):]
 
330
        else:
 
331
            result += options
 
332
        result += '\n'
 
333
 
 
334
        # Add the description, indenting it 2 spaces
 
335
        # to match the indentation of the options
 
336
        if sections.has_key(None):
 
337
            text = sections.pop(None)
 
338
            text = '\n  '.join(text.splitlines())
 
339
            result += ':%s:\n  %s\n\n' % ('Description',text)
 
340
 
 
341
        # Add the custom sections (e.g. Examples). Note that there's no need
 
342
        # to indent these as they must be indented already in the source.
 
343
        if sections:
 
344
            labels = sorted(sections.keys())
 
345
            for label in labels:
 
346
                result += ':%s:\n%s\n\n' % (label,sections[label])
 
347
 
 
348
        # Add the aliases, source (plug-in) and see also links, if any
 
349
        if self.aliases:
 
350
            result += ':Aliases:  '
 
351
            result += ', '.join(self.aliases) + '\n'
 
352
        plugin_name = self.plugin_name()
 
353
        if plugin_name is not None:
 
354
            result += ':From:     plugin "%s"\n' % plugin_name
 
355
        see_also = self.get_see_also(additional_see_also)
 
356
        if see_also:
 
357
            if not plain and see_also_as_links:
 
358
                see_also_links = []
 
359
                for item in see_also:
 
360
                    if item == 'topics':
 
361
                        # topics doesn't have an independent section
 
362
                        # so don't create a real link
 
363
                        see_also_links.append(item)
 
364
                    else:
 
365
                        # Use a reST link for this entry
 
366
                        see_also_links.append("`%s`_" % (item,))
 
367
                see_also = see_also_links
 
368
            result += ':See also: '
 
369
            result += ', '.join(see_also) + '\n'
 
370
 
 
371
        # If this will be rendered as plain text, convert it
 
372
        if plain:
 
373
            import bzrlib.help_topics
 
374
            result = bzrlib.help_topics.help_as_plain_text(result)
 
375
        return result
 
376
 
 
377
    @staticmethod
 
378
    def _get_help_parts(text):
 
379
        """Split help text into a summary and named sections.
 
380
 
 
381
        :return: (summary,sections) where summary is the top line and
 
382
            sections is a dictionary of the rest indexed by section name.
 
383
            A section starts with a heading line of the form ":xxx:".
 
384
            Indented text on following lines is the section value.
 
385
            All text found outside a named section is assigned to the
 
386
            default section which is given the key of None.
 
387
        """
 
388
        def save_section(sections, label, section):
 
389
            if len(section) > 0:
 
390
                if sections.has_key(label):
 
391
                    sections[label] += '\n' + section
 
392
                else:
 
393
                    sections[label] = section
 
394
 
 
395
        lines = text.rstrip().splitlines()
 
396
        summary = lines.pop(0)
 
397
        sections = {}
 
398
        label,section = None,''
 
399
        for line in lines:
 
400
            if line.startswith(':') and line.endswith(':') and len(line) > 2:
 
401
                save_section(sections, label, section)
 
402
                label,section = line[1:-1],''
 
403
            elif (label is not None) and len(line) > 1 and not line[0].isspace():
 
404
                save_section(sections, label, section)
 
405
                label,section = None,line
 
406
            else:
 
407
                if len(section) > 0:
 
408
                    section += '\n' + line
 
409
                else:
 
410
                    section = line
 
411
        save_section(sections, label, section)
 
412
        return summary, sections
 
413
 
 
414
    def get_help_topic(self):
 
415
        """Return the commands help topic - its name."""
 
416
        return self.name()
 
417
 
 
418
    def get_see_also(self, additional_terms=None):
 
419
        """Return a list of help topics that are related to this command.
 
420
        
 
421
        The list is derived from the content of the _see_also attribute. Any
 
422
        duplicates are removed and the result is in lexical order.
 
423
        :param additional_terms: Additional help topics to cross-reference.
 
424
        :return: A list of help topics.
 
425
        """
 
426
        see_also = set(getattr(self, '_see_also', []))
 
427
        if additional_terms:
 
428
            see_also.update(additional_terms)
 
429
        return sorted(see_also)
 
430
 
 
431
    def options(self):
 
432
        """Return dict of valid options for this command.
 
433
 
 
434
        Maps from long option name to option object."""
 
435
        r = Option.STD_OPTIONS.copy()
 
436
        std_names = r.keys()
 
437
        for o in self.takes_options:
 
438
            if isinstance(o, basestring):
 
439
                o = option.Option.OPTIONS[o]
 
440
            r[o.name] = o
 
441
            if o.name in std_names:
 
442
                self.supported_std_options.append(o.name)
 
443
        return r
 
444
 
 
445
    def _setup_outf(self):
 
446
        """Return a file linked to stdout, which has proper encoding."""
 
447
        # Originally I was using self.stdout, but that looks
 
448
        # *way* too much like sys.stdout
 
449
        if self.encoding_type == 'exact':
 
450
            # force sys.stdout to be binary stream on win32
 
451
            if sys.platform == 'win32':
 
452
                fileno = getattr(sys.stdout, 'fileno', None)
 
453
                if fileno:
 
454
                    import msvcrt
 
455
                    msvcrt.setmode(fileno(), os.O_BINARY)
 
456
            self.outf = sys.stdout
 
457
            return
 
458
 
 
459
        output_encoding = osutils.get_terminal_encoding()
 
460
 
 
461
        self.outf = codecs.getwriter(output_encoding)(sys.stdout,
 
462
                        errors=self.encoding_type)
 
463
        # For whatever reason codecs.getwriter() does not advertise its encoding
 
464
        # it just returns the encoding of the wrapped file, which is completely
 
465
        # bogus. So set the attribute, so we can find the correct encoding later.
 
466
        self.outf.encoding = output_encoding
 
467
 
 
468
    def run_argv_aliases(self, argv, alias_argv=None):
 
469
        """Parse the command line and run with extra aliases in alias_argv."""
 
470
        if argv is None:
 
471
            warn("Passing None for [] is deprecated from bzrlib 0.10",
 
472
                 DeprecationWarning, stacklevel=2)
 
473
            argv = []
 
474
        args, opts = parse_args(self, argv, alias_argv)
 
475
 
 
476
        # Process the standard options
 
477
        if 'help' in opts:  # e.g. bzr add --help
 
478
            sys.stdout.write(self.get_help_text())
 
479
            return 0
 
480
        trace.set_verbosity_level(option._verbosity_level)
 
481
        if 'verbose' in self.supported_std_options:
 
482
            opts['verbose'] = trace.is_verbose()
 
483
        elif opts.has_key('verbose'):
 
484
            del opts['verbose']
 
485
        if 'quiet' in self.supported_std_options:
 
486
            opts['quiet'] = trace.is_quiet()
 
487
        elif opts.has_key('quiet'):
 
488
            del opts['quiet']
 
489
 
 
490
        # mix arguments and options into one dictionary
 
491
        cmdargs = _match_argform(self.name(), self.takes_args, args)
 
492
        cmdopts = {}
 
493
        for k, v in opts.items():
 
494
            cmdopts[k.replace('-', '_')] = v
 
495
 
 
496
        all_cmd_args = cmdargs.copy()
 
497
        all_cmd_args.update(cmdopts)
 
498
 
 
499
        self._setup_outf()
 
500
 
 
501
        return self.run(**all_cmd_args)
 
502
 
 
503
    def run(self):
 
504
        """Actually run the command.
 
505
 
 
506
        This is invoked with the options and arguments bound to
 
507
        keyword parameters.
 
508
 
 
509
        Return 0 or None if the command was successful, or a non-zero
 
510
        shell error code if not.  It's OK for this method to allow
 
511
        an exception to raise up.
 
512
        """
 
513
        raise NotImplementedError('no implementation of command %r'
 
514
                                  % self.name())
 
515
 
 
516
    def help(self):
 
517
        """Return help message for this class."""
 
518
        from inspect import getdoc
 
519
        if self.__doc__ is Command.__doc__:
 
520
            return None
 
521
        return getdoc(self)
 
522
 
 
523
    def name(self):
 
524
        return _unsquish_command_name(self.__class__.__name__)
 
525
 
 
526
    def plugin_name(self):
 
527
        """Get the name of the plugin that provides this command.
 
528
 
 
529
        :return: The name of the plugin or None if the command is builtin.
 
530
        """
 
531
        mod_parts = self.__module__.split('.')
 
532
        if len(mod_parts) >= 3 and mod_parts[1] == 'plugins':
 
533
            return mod_parts[2]
 
534
        else:
 
535
            return None
 
536
 
 
537
 
 
538
def parse_args(command, argv, alias_argv=None):
 
539
    """Parse command line.
 
540
    
 
541
    Arguments and options are parsed at this level before being passed
 
542
    down to specific command handlers.  This routine knows, from a
 
543
    lookup table, something about the available options, what optargs
 
544
    they take, and which commands will accept them.
 
545
    """
 
546
    # TODO: make it a method of the Command?
 
547
    parser = option.get_optparser(command.options())
 
548
    if alias_argv is not None:
 
549
        args = alias_argv + argv
 
550
    else:
 
551
        args = argv
 
552
 
 
553
    options, args = parser.parse_args(args)
 
554
    opts = dict([(k, v) for k, v in options.__dict__.iteritems() if
 
555
                 v is not option.OptionParser.DEFAULT_VALUE])
 
556
    return args, opts
 
557
 
 
558
 
 
559
def _match_argform(cmd, takes_args, args):
 
560
    argdict = {}
 
561
 
 
562
    # step through args and takes_args, allowing appropriate 0-many matches
 
563
    for ap in takes_args:
 
564
        argname = ap[:-1]
 
565
        if ap[-1] == '?':
 
566
            if args:
 
567
                argdict[argname] = args.pop(0)
 
568
        elif ap[-1] == '*': # all remaining arguments
 
569
            if args:
 
570
                argdict[argname + '_list'] = args[:]
 
571
                args = []
 
572
            else:
 
573
                argdict[argname + '_list'] = None
 
574
        elif ap[-1] == '+':
 
575
            if not args:
 
576
                raise errors.BzrCommandError("command %r needs one or more %s"
 
577
                                             % (cmd, argname.upper()))
 
578
            else:
 
579
                argdict[argname + '_list'] = args[:]
 
580
                args = []
 
581
        elif ap[-1] == '$': # all but one
 
582
            if len(args) < 2:
 
583
                raise errors.BzrCommandError("command %r needs one or more %s"
 
584
                                             % (cmd, argname.upper()))
 
585
            argdict[argname + '_list'] = args[:-1]
 
586
            args[:-1] = []
 
587
        else:
 
588
            # just a plain arg
 
589
            argname = ap
 
590
            if not args:
 
591
                raise errors.BzrCommandError("command %r requires argument %s"
 
592
                               % (cmd, argname.upper()))
 
593
            else:
 
594
                argdict[argname] = args.pop(0)
 
595
            
 
596
    if args:
 
597
        raise errors.BzrCommandError("extra argument to command %s: %s"
 
598
                                     % (cmd, args[0]))
 
599
 
 
600
    return argdict
 
601
 
 
602
def apply_coveraged(dirname, the_callable, *args, **kwargs):
 
603
    # Cannot use "import trace", as that would import bzrlib.trace instead of
 
604
    # the standard library's trace.
 
605
    trace = __import__('trace')
 
606
 
 
607
    tracer = trace.Trace(count=1, trace=0)
 
608
    sys.settrace(tracer.globaltrace)
 
609
 
 
610
    ret = the_callable(*args, **kwargs)
 
611
 
 
612
    sys.settrace(None)
 
613
    results = tracer.results()
 
614
    results.write_results(show_missing=1, summary=False,
 
615
                          coverdir=dirname)
 
616
 
 
617
 
 
618
def apply_profiled(the_callable, *args, **kwargs):
 
619
    import hotshot
 
620
    import tempfile
 
621
    import hotshot.stats
 
622
    pffileno, pfname = tempfile.mkstemp()
 
623
    try:
 
624
        prof = hotshot.Profile(pfname)
 
625
        try:
 
626
            ret = prof.runcall(the_callable, *args, **kwargs) or 0
 
627
        finally:
 
628
            prof.close()
 
629
        stats = hotshot.stats.load(pfname)
 
630
        stats.strip_dirs()
 
631
        stats.sort_stats('cum')   # 'time'
 
632
        ## XXX: Might like to write to stderr or the trace file instead but
 
633
        ## print_stats seems hardcoded to stdout
 
634
        stats.print_stats(20)
 
635
        return ret
 
636
    finally:
 
637
        os.close(pffileno)
 
638
        os.remove(pfname)
 
639
 
 
640
 
 
641
def apply_lsprofiled(filename, the_callable, *args, **kwargs):
 
642
    from bzrlib.lsprof import profile
 
643
    ret, stats = profile(the_callable, *args, **kwargs)
 
644
    stats.sort()
 
645
    if filename is None:
 
646
        stats.pprint()
 
647
    else:
 
648
        stats.save(filename)
 
649
        trace.note('Profile data written to "%s".', filename)
 
650
    return ret
 
651
 
 
652
 
 
653
def shlex_split_unicode(unsplit):
 
654
    import shlex
 
655
    return [u.decode('utf-8') for u in shlex.split(unsplit.encode('utf-8'))]
 
656
 
 
657
 
 
658
def get_alias(cmd, config=None):
 
659
    """Return an expanded alias, or None if no alias exists.
 
660
 
 
661
    cmd
 
662
        Command to be checked for an alias.
 
663
    config
 
664
        Used to specify an alternative config to use,
 
665
        which is especially useful for testing.
 
666
        If it is unspecified, the global config will be used.
 
667
    """
 
668
    if config is None:
 
669
        import bzrlib.config
 
670
        config = bzrlib.config.GlobalConfig()
 
671
    alias = config.get_alias(cmd)
 
672
    if (alias):
 
673
        return shlex_split_unicode(alias)
 
674
    return None
 
675
 
 
676
 
 
677
def run_bzr(argv):
 
678
    """Execute a command.
 
679
 
 
680
    argv
 
681
       The command-line arguments, without the program name from argv[0]
 
682
       These should already be decoded. All library/test code calling
 
683
       run_bzr should be passing valid strings (don't need decoding).
 
684
    
 
685
    Returns a command status or raises an exception.
 
686
 
 
687
    Special master options: these must come before the command because
 
688
    they control how the command is interpreted.
 
689
 
 
690
    --no-plugins
 
691
        Do not load plugin modules at all
 
692
 
 
693
    --no-aliases
 
694
        Do not allow aliases
 
695
 
 
696
    --builtin
 
697
        Only use builtin commands.  (Plugins are still allowed to change
 
698
        other behaviour.)
 
699
 
 
700
    --profile
 
701
        Run under the Python hotshot profiler.
 
702
 
 
703
    --lsprof
 
704
        Run under the Python lsprof profiler.
 
705
 
 
706
    --coverage
 
707
        Generate line coverage report in the specified directory.
 
708
    """
 
709
    argv = list(argv)
 
710
    trace.mutter("bzr arguments: %r", argv)
 
711
 
 
712
    opt_lsprof = opt_profile = opt_no_plugins = opt_builtin =  \
 
713
                opt_no_aliases = False
 
714
    opt_lsprof_file = opt_coverage_dir = None
 
715
 
 
716
    # --no-plugins is handled specially at a very early stage. We need
 
717
    # to load plugins before doing other command parsing so that they
 
718
    # can override commands, but this needs to happen first.
 
719
 
 
720
    argv_copy = []
 
721
    i = 0
 
722
    while i < len(argv):
 
723
        a = argv[i]
 
724
        if a == '--profile':
 
725
            opt_profile = True
 
726
        elif a == '--lsprof':
 
727
            opt_lsprof = True
 
728
        elif a == '--lsprof-file':
 
729
            opt_lsprof = True
 
730
            opt_lsprof_file = argv[i + 1]
 
731
            i += 1
 
732
        elif a == '--no-plugins':
 
733
            opt_no_plugins = True
 
734
        elif a == '--no-aliases':
 
735
            opt_no_aliases = True
 
736
        elif a == '--builtin':
 
737
            opt_builtin = True
 
738
        elif a == '--coverage':
 
739
            opt_coverage_dir = argv[i + 1]
 
740
            i += 1
 
741
        elif a.startswith('-D'):
 
742
            debug.debug_flags.add(a[2:])
 
743
        else:
 
744
            argv_copy.append(a)
 
745
        i += 1
 
746
 
 
747
    argv = argv_copy
 
748
    if (not argv):
 
749
        from bzrlib.builtins import cmd_help
 
750
        cmd_help().run_argv_aliases([])
 
751
        return 0
 
752
 
 
753
    if argv[0] == '--version':
 
754
        from bzrlib.builtins import cmd_version
 
755
        cmd_version().run_argv_aliases([])
 
756
        return 0
 
757
 
 
758
    if not opt_no_plugins:
 
759
        from bzrlib.plugin import load_plugins
 
760
        load_plugins()
 
761
    else:
 
762
        from bzrlib.plugin import disable_plugins
 
763
        disable_plugins()
 
764
 
 
765
    alias_argv = None
 
766
 
 
767
    if not opt_no_aliases:
 
768
        alias_argv = get_alias(argv[0])
 
769
        if alias_argv:
 
770
            user_encoding = osutils.get_user_encoding()
 
771
            alias_argv = [a.decode(user_encoding) for a in alias_argv]
 
772
            argv[0] = alias_argv.pop(0)
 
773
 
 
774
    cmd = argv.pop(0)
 
775
    # We want only 'ascii' command names, but the user may have typed
 
776
    # in a Unicode name. In that case, they should just get a
 
777
    # 'command not found' error later.
 
778
 
 
779
    cmd_obj = get_cmd_object(cmd, plugins_override=not opt_builtin)
 
780
    run = cmd_obj.run_argv_aliases
 
781
    run_argv = [argv, alias_argv]
 
782
 
 
783
    try:
 
784
        # We can be called recursively (tests for example), but we don't want
 
785
        # the verbosity level to propagate.
 
786
        saved_verbosity_level = option._verbosity_level
 
787
        option._verbosity_level = 0
 
788
        if opt_lsprof:
 
789
            if opt_coverage_dir:
 
790
                trace.warning(
 
791
                    '--coverage ignored, because --lsprof is in use.')
 
792
            ret = apply_lsprofiled(opt_lsprof_file, run, *run_argv)
 
793
        elif opt_profile:
 
794
            if opt_coverage_dir:
 
795
                trace.warning(
 
796
                    '--coverage ignored, because --profile is in use.')
 
797
            ret = apply_profiled(run, *run_argv)
 
798
        elif opt_coverage_dir:
 
799
            ret = apply_coveraged(opt_coverage_dir, run, *run_argv)
 
800
        else:
 
801
            ret = run(*run_argv)
 
802
        if 'memory' in debug.debug_flags:
 
803
            trace.debug_memory('Process status after command:', short=False)
 
804
        return ret or 0
 
805
    finally:
 
806
        # reset, in case we may do other commands later within the same
 
807
        # process. Commands that want to execute sub-commands must propagate
 
808
        # --verbose in their own way.
 
809
        option._verbosity_level = saved_verbosity_level
 
810
 
 
811
def display_command(func):
 
812
    """Decorator that suppresses pipe/interrupt errors."""
 
813
    def ignore_pipe(*args, **kwargs):
 
814
        try:
 
815
            result = func(*args, **kwargs)
 
816
            sys.stdout.flush()
 
817
            return result
 
818
        except IOError, e:
 
819
            if getattr(e, 'errno', None) is None:
 
820
                raise
 
821
            if e.errno != errno.EPIPE:
 
822
                # Win32 raises IOError with errno=0 on a broken pipe
 
823
                if sys.platform != 'win32' or (e.errno not in (0, errno.EINVAL)):
 
824
                    raise
 
825
            pass
 
826
        except KeyboardInterrupt:
 
827
            pass
 
828
    return ignore_pipe
 
829
 
 
830
 
 
831
def main(argv):
 
832
    import bzrlib.ui
 
833
    from bzrlib.ui.text import TextUIFactory
 
834
    bzrlib.ui.ui_factory = TextUIFactory()
 
835
 
 
836
    # Is this a final release version? If so, we should suppress warnings
 
837
    if bzrlib.version_info[3] == 'final':
 
838
        from bzrlib import symbol_versioning
 
839
        symbol_versioning.suppress_deprecation_warnings(override=False)
 
840
    try:
 
841
        user_encoding = osutils.get_user_encoding()
 
842
        argv = [a.decode(user_encoding) for a in argv[1:]]
 
843
    except UnicodeDecodeError:
 
844
        raise errors.BzrError(("Parameter '%r' is unsupported by the current "
 
845
                                                            "encoding." % a))
 
846
    ret = run_bzr_catch_errors(argv)
 
847
    trace.mutter("return code %d", ret)
 
848
    return ret
 
849
 
 
850
 
 
851
def run_bzr_catch_errors(argv):
 
852
    # Note: The except clause logic below should be kept in sync with the
 
853
    # profile() routine in lsprof.py.
 
854
    try:
 
855
        return run_bzr(argv)
 
856
    except (KeyboardInterrupt, Exception), e:
 
857
        # used to handle AssertionError and KeyboardInterrupt
 
858
        # specially here, but hopefully they're handled ok by the logger now
 
859
        exitcode = trace.report_exception(sys.exc_info(), sys.stderr)
 
860
        if os.environ.get('BZR_PDB'):
 
861
            print '**** entering debugger'
 
862
            import pdb
 
863
            pdb.post_mortem(sys.exc_traceback)
 
864
        return exitcode
 
865
 
 
866
 
 
867
def run_bzr_catch_user_errors(argv):
 
868
    """Run bzr and report user errors, but let internal errors propagate.
 
869
 
 
870
    This is used for the test suite, and might be useful for other programs
 
871
    that want to wrap the commandline interface.
 
872
    """
 
873
    try:
 
874
        return run_bzr(argv)
 
875
    except Exception, e:
 
876
        if (isinstance(e, (OSError, IOError))
 
877
            or not getattr(e, 'internal_error', True)):
 
878
            trace.report_exception(sys.exc_info(), sys.stderr)
 
879
            return 3
 
880
        else:
 
881
            raise
 
882
 
 
883
 
 
884
class HelpCommandIndex(object):
 
885
    """A index for bzr help that returns commands."""
 
886
 
 
887
    def __init__(self):
 
888
        self.prefix = 'commands/'
 
889
 
 
890
    def get_topics(self, topic):
 
891
        """Search for topic amongst commands.
 
892
 
 
893
        :param topic: A topic to search for.
 
894
        :return: A list which is either empty or contains a single
 
895
            Command entry.
 
896
        """
 
897
        if topic and topic.startswith(self.prefix):
 
898
            topic = topic[len(self.prefix):]
 
899
        try:
 
900
            cmd = _get_cmd_object(topic)
 
901
        except KeyError:
 
902
            return []
 
903
        else:
 
904
            return [cmd]
 
905
 
 
906
 
 
907
class Provider(object):
 
908
    '''Generic class to be overriden by plugins'''
 
909
 
 
910
    def plugin_for_command(self, cmd_name):
 
911
        '''Takes a command and returns the information for that plugin
 
912
        
 
913
        :return: A dictionary with all the available information 
 
914
        for the requested plugin
 
915
        '''
 
916
        raise NotImplementedError
 
917
 
 
918
 
 
919
class ProvidersRegistry(registry.Registry):
 
920
    '''This registry exists to allow other providers to exist'''
 
921
 
 
922
    def __iter__(self):
 
923
        for key, provider in self.iteritems():
 
924
            yield provider
 
925
 
 
926
command_providers_registry = ProvidersRegistry()
 
927
 
 
928
 
 
929
if __name__ == '__main__':
 
930
    sys.exit(main(sys.argv))