/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: Canonical.com Patch Queue Manager
  • Date: 2008-08-14 17:25:43 UTC
  • mfrom: (3620.2.2 rules.disable)
  • Revision ID: pqm@pqm.ubuntu.com-20080814172543-nl22gdcodusa8rt0
(robertc) Disable .bzrrules from being read from the WT

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