~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/commands.py

  • Committer: Robert Collins
  • Date: 2007-07-04 08:08:13 UTC
  • mfrom: (2572 +trunk)
  • mto: This revision was merged to the branch mainline in revision 2587.
  • Revision ID: robertc@robertcollins.net-20070704080813-wzebx0r88fvwj5rq
Merge bzr.dev.

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2006 by Canonical Ltd
 
1
# Copyright (C) 2006 Canonical Ltd
2
2
#
3
3
# This program is free software; you can redistribute it and/or modify
4
4
# it under the terms of the GNU General Public License as published by
28
28
# TODO: "--profile=cum", to change sort order.  Is there any value in leaving
29
29
# the profile output behind so it can be interactively examined?
30
30
 
 
31
import os
 
32
import sys
 
33
 
 
34
from bzrlib.lazy_import import lazy_import
 
35
lazy_import(globals(), """
31
36
import codecs
32
37
import errno
33
 
import os
34
38
from warnings import warn
35
 
import sys
36
39
 
37
40
import bzrlib
38
 
import bzrlib.errors as errors
39
 
from bzrlib.errors import (BzrError,
40
 
                           BzrCommandError,
41
 
                           BzrCheckError,
42
 
                           NotBranchError)
43
 
from bzrlib import option
 
41
from bzrlib import (
 
42
    debug,
 
43
    errors,
 
44
    option,
 
45
    osutils,
 
46
    trace,
 
47
    )
 
48
""")
 
49
 
 
50
from bzrlib.symbol_versioning import (
 
51
    deprecated_function,
 
52
    deprecated_method,
 
53
    zero_eight,
 
54
    zero_eleven,
 
55
    )
 
56
# Compatibility
44
57
from bzrlib.option import Option
45
 
import bzrlib.osutils
46
 
from bzrlib.symbol_versioning import (deprecated_method, zero_eight)
47
 
import bzrlib.trace
48
 
from bzrlib.trace import mutter, note, log_error, warning, be_quiet
 
58
 
49
59
 
50
60
plugin_cmds = {}
51
61
 
66
76
        k_unsquished = k
67
77
    if k_unsquished not in plugin_cmds:
68
78
        plugin_cmds[k_unsquished] = cmd
69
 
        mutter('registered plugin command %s', k_unsquished)
 
79
        ## trace.mutter('registered plugin command %s', k_unsquished)
70
80
        if decorate and k_unsquished in builtin_command_names():
71
81
            return _builtin_commands()[k_unsquished]
72
82
    elif decorate:
74
84
        plugin_cmds[k_unsquished] = cmd
75
85
        return result
76
86
    else:
77
 
        log_error('Two plugins defined the same command: %r' % k)
78
 
        log_error('Not loading the one in %r' % sys.modules[cmd.__module__])
 
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__])
79
89
 
80
90
 
81
91
def _squish_command_name(cmd):
127
137
    plugins_override
128
138
        If true, plugin commands can override builtins.
129
139
    """
 
140
    try:
 
141
        return _get_cmd_object(cmd_name, plugins_override)
 
142
    except KeyError:
 
143
        raise errors.BzrCommandError('unknown command "%s"' % cmd_name)
 
144
 
 
145
 
 
146
def _get_cmd_object(cmd_name, plugins_override=True):
 
147
    """Worker for get_cmd_object which raises KeyError rather than BzrCommandError."""
130
148
    from bzrlib.externalcommand import ExternalCommand
131
149
 
132
150
    # We want only 'ascii' command names, but the user may have typed
149
167
    cmd_obj = ExternalCommand.find_command(cmd_name)
150
168
    if cmd_obj:
151
169
        return cmd_obj
152
 
 
153
 
    raise BzrCommandError('unknown command "%s"' % cmd_name)
 
170
    raise KeyError
154
171
 
155
172
 
156
173
class Command(object):
204
221
            replace - put in a bogus character (typically '?')
205
222
            exact - do not encode sys.stdout
206
223
 
 
224
            NOTE: by default on Windows, sys.stdout is opened as a text
 
225
            stream, therefore LF line-endings are converted to CRLF.
 
226
            When a command uses encoding_type = 'exact', then
 
227
            sys.stdout is forced to be a binary stream, and line-endings
 
228
            will not mangled.
 
229
 
207
230
    """
208
231
    aliases = []
209
232
    takes_args = []
217
240
        if self.__doc__ == Command.__doc__:
218
241
            warn("No help message set for %r" % self)
219
242
 
 
243
    def _usage(self):
 
244
        """Return single-line grammar for this command.
 
245
 
 
246
        Only describes arguments, not options.
 
247
        """
 
248
        s = 'bzr ' + self.name() + ' '
 
249
        for aname in self.takes_args:
 
250
            aname = aname.upper()
 
251
            if aname[-1] in ['$', '+']:
 
252
                aname = aname[:-1] + '...'
 
253
            elif aname[-1] == '?':
 
254
                aname = '[' + aname[:-1] + ']'
 
255
            elif aname[-1] == '*':
 
256
                aname = '[' + aname[:-1] + '...]'
 
257
            s += aname + ' '
 
258
                
 
259
        assert s[-1] == ' '
 
260
        s = s[:-1]
 
261
        return s
 
262
 
 
263
    def get_help_text(self, additional_see_also=None):
 
264
        """Return a text string with help for this command.
 
265
        
 
266
        :param additional_see_also: Additional help topics to be
 
267
            cross-referenced.
 
268
        """
 
269
        doc = self.help()
 
270
        if doc is None:
 
271
            raise NotImplementedError("sorry, no detailed help yet for %r" % self.name())
 
272
 
 
273
        result = ""
 
274
        result += 'usage: %s\n' % self._usage()
 
275
 
 
276
        if self.aliases:
 
277
            result += 'aliases: '
 
278
            result += ', '.join(self.aliases) + '\n'
 
279
 
 
280
        result += '\n'
 
281
 
 
282
        plugin_name = self.plugin_name()
 
283
        if plugin_name is not None:
 
284
            result += '(From plugin "%s")' % plugin_name
 
285
            result += '\n\n'
 
286
 
 
287
        result += doc
 
288
        if result[-1] != '\n':
 
289
            result += '\n'
 
290
        result += '\n'
 
291
        result += option.get_optparser(self.options()).format_option_help()
 
292
        see_also = self.get_see_also(additional_see_also)
 
293
        if see_also:
 
294
            result += '\nSee also: '
 
295
            result += ', '.join(see_also)
 
296
            result += '\n'
 
297
        return result
 
298
 
 
299
    def get_help_topic(self):
 
300
        """Return the commands help topic - its name."""
 
301
        return self.name()
 
302
 
 
303
    def get_see_also(self, additional_terms=None):
 
304
        """Return a list of help topics that are related to this ommand.
 
305
        
 
306
        The list is derived from the content of the _see_also attribute. Any
 
307
        duplicates are removed and the result is in lexical order.
 
308
        :param additional_terms: Additional help topics to cross-reference.
 
309
        :return: A list of help topics.
 
310
        """
 
311
        see_also = set(getattr(self, '_see_also', []))
 
312
        if additional_terms:
 
313
            see_also.update(additional_terms)
 
314
        return sorted(see_also)
 
315
 
220
316
    def options(self):
221
317
        """Return dict of valid options for this command.
222
318
 
223
319
        Maps from long option name to option object."""
224
320
        r = dict()
225
 
        r['help'] = Option.OPTIONS['help']
 
321
        r['help'] = option.Option.OPTIONS['help']
226
322
        for o in self.takes_options:
227
323
            if isinstance(o, basestring):
228
 
                o = Option.OPTIONS[o]
 
324
                o = option.Option.OPTIONS[o]
229
325
            r[o.name] = o
230
326
        return r
231
327
 
236
332
        # Originally I was using self.stdout, but that looks
237
333
        # *way* too much like sys.stdout
238
334
        if self.encoding_type == 'exact':
 
335
            # force sys.stdout to be binary stream on win32
 
336
            if sys.platform == 'win32':
 
337
                fileno = getattr(sys.stdout, 'fileno', None)
 
338
                if fileno:
 
339
                    import msvcrt
 
340
                    msvcrt.setmode(fileno(), os.O_BINARY)
239
341
            self.outf = sys.stdout
240
342
            return
241
343
 
242
 
        output_encoding = bzrlib.osutils.get_terminal_encoding()
 
344
        output_encoding = osutils.get_terminal_encoding()
243
345
 
244
346
        # use 'replace' so that we don't abort if trying to write out
245
347
        # in e.g. the default C locale.
249
351
        # bogus. So set the attribute, so we can find the correct encoding later.
250
352
        self.outf.encoding = output_encoding
251
353
 
252
 
    @deprecated_method(zero_eight)
253
 
    def run_argv(self, argv):
254
 
        """Parse command line and run.
255
 
        
256
 
        See run_argv_aliases for the 0.8 and beyond api.
257
 
        """
258
 
        return self.run_argv_aliases(argv)
259
 
 
260
354
    def run_argv_aliases(self, argv, alias_argv=None):
261
355
        """Parse the command line and run with extra aliases in alias_argv."""
262
356
        if argv is None:
263
 
            warn("Passing None for [] is deprecated from bzrlib 0.10", 
 
357
            warn("Passing None for [] is deprecated from bzrlib 0.10",
264
358
                 DeprecationWarning, stacklevel=2)
265
359
            argv = []
266
360
        args, opts = parse_args(self, argv, alias_argv)
267
361
        if 'help' in opts:  # e.g. bzr add --help
268
 
            from bzrlib.help import help_on_command
269
 
            help_on_command(self.name())
 
362
            sys.stdout.write(self.get_help_text())
270
363
            return 0
271
364
        # mix arguments and options into one dictionary
272
365
        cmdargs = _match_argform(self.name(), self.takes_args, args)
291
384
        shell error code if not.  It's OK for this method to allow
292
385
        an exception to raise up.
293
386
        """
294
 
        raise NotImplementedError('no implementation of command %r' 
 
387
        raise NotImplementedError('no implementation of command %r'
295
388
                                  % self.name())
296
389
 
297
390
    def help(self):
316
409
            return None
317
410
 
318
411
 
 
412
# Technically, this function hasn't been use in a *really* long time
 
413
# but we are only deprecating it now.
 
414
@deprecated_function(zero_eleven)
319
415
def parse_spec(spec):
320
416
    """
321
417
    >>> parse_spec(None)
363
459
        args = argv
364
460
 
365
461
    options, args = parser.parse_args(args)
366
 
    opts = dict([(k, v) for k, v in options.__dict__.iteritems() if 
 
462
    opts = dict([(k, v) for k, v in options.__dict__.iteritems() if
367
463
                 v is not option.OptionParser.DEFAULT_VALUE])
368
464
    return args, opts
369
465
 
385
481
                argdict[argname + '_list'] = None
386
482
        elif ap[-1] == '+':
387
483
            if not args:
388
 
                raise BzrCommandError("command %r needs one or more %s"
389
 
                        % (cmd, argname.upper()))
 
484
                raise errors.BzrCommandError("command %r needs one or more %s"
 
485
                                             % (cmd, argname.upper()))
390
486
            else:
391
487
                argdict[argname + '_list'] = args[:]
392
488
                args = []
393
489
        elif ap[-1] == '$': # all but one
394
490
            if len(args) < 2:
395
 
                raise BzrCommandError("command %r needs one or more %s"
396
 
                        % (cmd, argname.upper()))
 
491
                raise errors.BzrCommandError("command %r needs one or more %s"
 
492
                                             % (cmd, argname.upper()))
397
493
            argdict[argname + '_list'] = args[:-1]
398
494
            args[:-1] = []
399
495
        else:
400
496
            # just a plain arg
401
497
            argname = ap
402
498
            if not args:
403
 
                raise BzrCommandError("command %r requires argument %s"
404
 
                        % (cmd, argname.upper()))
 
499
                raise errors.BzrCommandError("command %r requires argument %s"
 
500
                               % (cmd, argname.upper()))
405
501
            else:
406
502
                argdict[argname] = args.pop(0)
407
503
            
408
504
    if args:
409
 
        raise BzrCommandError("extra argument to command %s: %s"
410
 
                              % (cmd, args[0]))
 
505
        raise errors.BzrCommandError("extra argument to command %s: %s"
 
506
                                     % (cmd, args[0]))
411
507
 
412
508
    return argdict
413
509
 
438
534
 
439
535
def apply_lsprofiled(filename, the_callable, *args, **kwargs):
440
536
    from bzrlib.lsprof import profile
441
 
    import cPickle
442
537
    ret, stats = profile(the_callable, *args, **kwargs)
443
538
    stats.sort()
444
539
    if filename is None:
445
540
        stats.pprint()
446
541
    else:
447
 
        stats.freeze()
448
 
        cPickle.dump(stats, open(filename, 'w'), 2)
449
 
        print 'Profile data written to %r.' % filename
 
542
        stats.save(filename)
 
543
        trace.note('Profile data written to "%s".', filename)
450
544
    return ret
451
545
 
452
546
 
453
 
def get_alias(cmd):
454
 
    """Return an expanded alias, or None if no alias exists"""
455
 
    import bzrlib.config
456
 
    alias = bzrlib.config.GlobalConfig().get_alias(cmd)
 
547
def get_alias(cmd, config=None):
 
548
    """Return an expanded alias, or None if no alias exists.
 
549
 
 
550
    cmd
 
551
        Command to be checked for an alias.
 
552
    config
 
553
        Used to specify an alternative config to use,
 
554
        which is especially useful for testing.
 
555
        If it is unspecified, the global config will be used.
 
556
    """
 
557
    if config is None:
 
558
        import bzrlib.config
 
559
        config = bzrlib.config.GlobalConfig()
 
560
    alias = config.get_alias(cmd)
457
561
    if (alias):
458
 
        return alias.split(' ')
 
562
        import shlex
 
563
        return [a.decode('utf-8') for a in shlex.split(alias.encode('utf-8'))]
459
564
    return None
460
565
 
461
566
 
492
597
        Run under the Python lsprof profiler.
493
598
    """
494
599
    argv = list(argv)
 
600
    trace.mutter("bzr arguments: %r", argv)
495
601
 
496
602
    opt_lsprof = opt_profile = opt_no_plugins = opt_builtin =  \
497
603
                opt_no_aliases = False
520
626
        elif a == '--builtin':
521
627
            opt_builtin = True
522
628
        elif a in ('--quiet', '-q'):
523
 
            be_quiet()
 
629
            trace.be_quiet()
 
630
        elif a.startswith('-D'):
 
631
            debug.debug_flags.add(a[2:])
524
632
        else:
525
633
            argv_copy.append(a)
526
634
        i += 1
557
665
    # 'command not found' error later.
558
666
 
559
667
    cmd_obj = get_cmd_object(cmd, plugins_override=not opt_builtin)
560
 
    if not getattr(cmd_obj.run_argv, 'is_deprecated', False):
561
 
        run = cmd_obj.run_argv
562
 
        run_argv = [argv]
563
 
    else:
564
 
        run = cmd_obj.run_argv_aliases
565
 
        run_argv = [argv, alias_argv]
 
668
    run = cmd_obj.run_argv_aliases
 
669
    run_argv = [argv, alias_argv]
566
670
 
567
671
    try:
568
672
        if opt_lsprof:
574
678
        return ret or 0
575
679
    finally:
576
680
        # reset, in case we may do other commands later within the same process
577
 
        be_quiet(False)
 
681
        trace.be_quiet(False)
578
682
 
579
683
def display_command(func):
580
684
    """Decorator that suppresses pipe/interrupt errors."""
588
692
                raise
589
693
            if e.errno != errno.EPIPE:
590
694
                # Win32 raises IOError with errno=0 on a broken pipe
591
 
                if sys.platform != 'win32' or e.errno != 0:
 
695
                if sys.platform != 'win32' or (e.errno not in (0, errno.EINVAL)):
592
696
                    raise
593
697
            pass
594
698
        except KeyboardInterrupt:
602
706
    bzrlib.ui.ui_factory = TextUIFactory()
603
707
    argv = [a.decode(bzrlib.user_encoding) for a in argv[1:]]
604
708
    ret = run_bzr_catch_errors(argv)
605
 
    mutter("return code %d", ret)
 
709
    trace.mutter("return code %d", ret)
606
710
    return ret
607
711
 
608
712
 
609
713
def run_bzr_catch_errors(argv):
610
714
    try:
611
715
        return run_bzr(argv)
612
 
        # do this here inside the exception wrappers to catch EPIPE
613
 
        sys.stdout.flush()
614
716
    except (KeyboardInterrupt, Exception), e:
615
717
        # used to handle AssertionError and KeyboardInterrupt
616
718
        # specially here, but hopefully they're handled ok by the logger now
617
 
        bzrlib.trace.report_exception(sys.exc_info(), sys.stderr)
 
719
        trace.report_exception(sys.exc_info(), sys.stderr)
618
720
        if os.environ.get('BZR_PDB'):
619
721
            print '**** entering debugger'
620
722
            import pdb
621
723
            pdb.post_mortem(sys.exc_traceback)
622
724
        return 3
623
725
 
 
726
 
 
727
class HelpCommandIndex(object):
 
728
    """A index for bzr help that returns commands."""
 
729
 
 
730
    def __init__(self):
 
731
        self.prefix = 'commands/'
 
732
 
 
733
    def get_topics(self, topic):
 
734
        """Search for topic amongst commands.
 
735
 
 
736
        :param topic: A topic to search for.
 
737
        :return: A list which is either empty or contains a single
 
738
            Command entry.
 
739
        """
 
740
        if topic and topic.startswith(self.prefix):
 
741
            topic = topic[len(self.prefix):]
 
742
        try:
 
743
            cmd = _get_cmd_object(topic)
 
744
        except KeyError:
 
745
            return []
 
746
        else:
 
747
            return [cmd]
 
748
 
 
749
 
624
750
if __name__ == '__main__':
625
751
    sys.exit(main(sys.argv))