~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/commands.py

  • Committer: Martin Pool
  • Date: 2008-04-30 08:04:11 UTC
  • mto: This revision was merged to the branch mainline in revision 3396.
  • Revision ID: mbp@sourcefrog.net-20080430080411-imrex2wtwpb9eivj
_format_version_tuple can take a 3-tuple

Show diffs side-by-side

added added

removed removed

Lines of Context:
43
43
    errors,
44
44
    option,
45
45
    osutils,
 
46
    registry,
46
47
    trace,
 
48
    win32utils,
47
49
    )
48
50
""")
49
51
 
50
52
from bzrlib.symbol_versioning import (
51
53
    deprecated_function,
52
54
    deprecated_method,
53
 
    zero_eight,
54
 
    zero_eleven,
55
55
    )
56
56
# Compatibility
57
57
from bzrlib.option import Option
86
86
    else:
87
87
        trace.log_error('Two plugins defined the same command: %r' % k)
88
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__])
89
91
 
90
92
 
91
93
def _squish_command_name(cmd):
137
139
    plugins_override
138
140
        If true, plugin commands can override builtins.
139
141
    """
 
142
    try:
 
143
        return _get_cmd_object(cmd_name, plugins_override)
 
144
    except KeyError:
 
145
        raise errors.BzrCommandError('unknown command "%s"' % cmd_name)
 
146
 
 
147
 
 
148
def _get_cmd_object(cmd_name, plugins_override=True):
 
149
    """Worker for get_cmd_object which raises KeyError rather than BzrCommandError."""
140
150
    from bzrlib.externalcommand import ExternalCommand
141
151
 
142
152
    # We want only 'ascii' command names, but the user may have typed
160
170
    if cmd_obj:
161
171
        return cmd_obj
162
172
 
163
 
    raise errors.BzrCommandError('unknown command "%s"' % cmd_name)
 
173
    # look for plugins that provide this command but aren't installed
 
174
    for provider in command_providers_registry:
 
175
        try:
 
176
            plugin_metadata = provider.plugin_for_command(cmd_name)
 
177
        except errors.NoPluginAvailable:
 
178
            pass
 
179
        else:
 
180
            raise errors.CommandAvailableInPlugin(cmd_name, 
 
181
                                                  plugin_metadata, provider)
 
182
 
 
183
    raise KeyError
164
184
 
165
185
 
166
186
class Command(object):
232
252
        """Construct an instance of this command."""
233
253
        if self.__doc__ == Command.__doc__:
234
254
            warn("No help message set for %r" % self)
 
255
        # List of standard options directly supported
 
256
        self.supported_std_options = []
 
257
 
 
258
    def _maybe_expand_globs(self, file_list):
 
259
        """Glob expand file_list if the platform does not do that itself.
 
260
        
 
261
        :return: A possibly empty list of unicode paths.
 
262
 
 
263
        Introduced in bzrlib 0.18.
 
264
        """
 
265
        if not file_list:
 
266
            file_list = []
 
267
        if sys.platform == 'win32':
 
268
            file_list = win32utils.glob_expand(file_list)
 
269
        return list(file_list)
 
270
 
 
271
    def _usage(self):
 
272
        """Return single-line grammar for this command.
 
273
 
 
274
        Only describes arguments, not options.
 
275
        """
 
276
        s = 'bzr ' + self.name() + ' '
 
277
        for aname in self.takes_args:
 
278
            aname = aname.upper()
 
279
            if aname[-1] in ['$', '+']:
 
280
                aname = aname[:-1] + '...'
 
281
            elif aname[-1] == '?':
 
282
                aname = '[' + aname[:-1] + ']'
 
283
            elif aname[-1] == '*':
 
284
                aname = '[' + aname[:-1] + '...]'
 
285
            s += aname + ' '
 
286
                
 
287
        assert s[-1] == ' '
 
288
        s = s[:-1]
 
289
        return s
 
290
 
 
291
    def get_help_text(self, additional_see_also=None, plain=True,
 
292
                      see_also_as_links=False):
 
293
        """Return a text string with help for this command.
 
294
        
 
295
        :param additional_see_also: Additional help topics to be
 
296
            cross-referenced.
 
297
        :param plain: if False, raw help (reStructuredText) is
 
298
            returned instead of plain text.
 
299
        :param see_also_as_links: if True, convert items in 'See also'
 
300
            list to internal links (used by bzr_man rstx generator)
 
301
        """
 
302
        doc = self.help()
 
303
        if doc is None:
 
304
            raise NotImplementedError("sorry, no detailed help yet for %r" % self.name())
 
305
 
 
306
        # Extract the summary (purpose) and sections out from the text
 
307
        purpose,sections = self._get_help_parts(doc)
 
308
 
 
309
        # If a custom usage section was provided, use it
 
310
        if sections.has_key('Usage'):
 
311
            usage = sections.pop('Usage')
 
312
        else:
 
313
            usage = self._usage()
 
314
 
 
315
        # The header is the purpose and usage
 
316
        result = ""
 
317
        result += ':Purpose: %s\n' % purpose
 
318
        if usage.find('\n') >= 0:
 
319
            result += ':Usage:\n%s\n' % usage
 
320
        else:
 
321
            result += ':Usage:   %s\n' % usage
 
322
        result += '\n'
 
323
 
 
324
        # Add the options
 
325
        options = option.get_optparser(self.options()).format_option_help()
 
326
        if options.startswith('Options:'):
 
327
            result += ':' + options
 
328
        elif options.startswith('options:'):
 
329
            # Python 2.4 version of optparse
 
330
            result += ':Options:' + options[len('options:'):]
 
331
        else:
 
332
            result += options
 
333
        result += '\n'
 
334
 
 
335
        # Add the description, indenting it 2 spaces
 
336
        # to match the indentation of the options
 
337
        if sections.has_key(None):
 
338
            text = sections.pop(None)
 
339
            text = '\n  '.join(text.splitlines())
 
340
            result += ':%s:\n  %s\n\n' % ('Description',text)
 
341
 
 
342
        # Add the custom sections (e.g. Examples). Note that there's no need
 
343
        # to indent these as they must be indented already in the source.
 
344
        if sections:
 
345
            labels = sorted(sections.keys())
 
346
            for label in labels:
 
347
                result += ':%s:\n%s\n\n' % (label,sections[label])
 
348
 
 
349
        # Add the aliases, source (plug-in) and see also links, if any
 
350
        if self.aliases:
 
351
            result += ':Aliases:  '
 
352
            result += ', '.join(self.aliases) + '\n'
 
353
        plugin_name = self.plugin_name()
 
354
        if plugin_name is not None:
 
355
            result += ':From:     plugin "%s"\n' % plugin_name
 
356
        see_also = self.get_see_also(additional_see_also)
 
357
        if see_also:
 
358
            if not plain and see_also_as_links:
 
359
                see_also_links = []
 
360
                for item in see_also:
 
361
                    if item == 'topics':
 
362
                        # topics doesn't have an independent section
 
363
                        # so don't create a real link
 
364
                        see_also_links.append(item)
 
365
                    else:
 
366
                        # Use a reST link for this entry
 
367
                        see_also_links.append("`%s`_" % (item,))
 
368
                see_also = see_also_links
 
369
            result += ':See also: '
 
370
            result += ', '.join(see_also) + '\n'
 
371
 
 
372
        # If this will be rendered as plan text, convert it
 
373
        if plain:
 
374
            import bzrlib.help_topics
 
375
            result = bzrlib.help_topics.help_as_plain_text(result)
 
376
        return result
 
377
 
 
378
    @staticmethod
 
379
    def _get_help_parts(text):
 
380
        """Split help text into a summary and named sections.
 
381
 
 
382
        :return: (summary,sections) where summary is the top line and
 
383
            sections is a dictionary of the rest indexed by section name.
 
384
            A section starts with a heading line of the form ":xxx:".
 
385
            Indented text on following lines is the section value.
 
386
            All text found outside a named section is assigned to the
 
387
            default section which is given the key of None.
 
388
        """
 
389
        def save_section(sections, label, section):
 
390
            if len(section) > 0:
 
391
                if sections.has_key(label):
 
392
                    sections[label] += '\n' + section
 
393
                else:
 
394
                    sections[label] = section
 
395
            
 
396
        lines = text.rstrip().splitlines()
 
397
        summary = lines.pop(0)
 
398
        sections = {}
 
399
        label,section = None,''
 
400
        for line in lines:
 
401
            if line.startswith(':') and line.endswith(':') and len(line) > 2:
 
402
                save_section(sections, label, section)
 
403
                label,section = line[1:-1],''
 
404
            elif label != None and len(line) > 1 and not line[0].isspace():
 
405
                save_section(sections, label, section)
 
406
                label,section = None,line
 
407
            else:
 
408
                if len(section) > 0:
 
409
                    section += '\n' + line
 
410
                else:
 
411
                    section = line
 
412
        save_section(sections, label, section)
 
413
        return summary, sections
 
414
 
 
415
    def get_help_topic(self):
 
416
        """Return the commands help topic - its name."""
 
417
        return self.name()
 
418
 
 
419
    def get_see_also(self, additional_terms=None):
 
420
        """Return a list of help topics that are related to this command.
 
421
        
 
422
        The list is derived from the content of the _see_also attribute. Any
 
423
        duplicates are removed and the result is in lexical order.
 
424
        :param additional_terms: Additional help topics to cross-reference.
 
425
        :return: A list of help topics.
 
426
        """
 
427
        see_also = set(getattr(self, '_see_also', []))
 
428
        if additional_terms:
 
429
            see_also.update(additional_terms)
 
430
        return sorted(see_also)
235
431
 
236
432
    def options(self):
237
433
        """Return dict of valid options for this command.
238
434
 
239
435
        Maps from long option name to option object."""
240
 
        r = dict()
241
 
        r['help'] = option.Option.OPTIONS['help']
 
436
        r = Option.STD_OPTIONS.copy()
 
437
        std_names = r.keys()
242
438
        for o in self.takes_options:
243
439
            if isinstance(o, basestring):
244
440
                o = option.Option.OPTIONS[o]
245
441
            r[o.name] = o
 
442
            if o.name in std_names:
 
443
                self.supported_std_options.append(o.name)
246
444
        return r
247
445
 
248
446
    def _setup_outf(self):
263
461
 
264
462
        output_encoding = osutils.get_terminal_encoding()
265
463
 
266
 
        # use 'replace' so that we don't abort if trying to write out
267
 
        # in e.g. the default C locale.
268
 
        self.outf = codecs.getwriter(output_encoding)(sys.stdout, errors=self.encoding_type)
 
464
        self.outf = codecs.getwriter(output_encoding)(sys.stdout,
 
465
                        errors=self.encoding_type)
269
466
        # For whatever reason codecs.getwriter() does not advertise its encoding
270
467
        # it just returns the encoding of the wrapped file, which is completely
271
468
        # bogus. So set the attribute, so we can find the correct encoding later.
272
469
        self.outf.encoding = output_encoding
273
470
 
274
 
    @deprecated_method(zero_eight)
275
 
    def run_argv(self, argv):
276
 
        """Parse command line and run.
277
 
        
278
 
        See run_argv_aliases for the 0.8 and beyond api.
279
 
        """
280
 
        return self.run_argv_aliases(argv)
281
 
 
282
471
    def run_argv_aliases(self, argv, alias_argv=None):
283
472
        """Parse the command line and run with extra aliases in alias_argv."""
284
473
        if argv is None:
286
475
                 DeprecationWarning, stacklevel=2)
287
476
            argv = []
288
477
        args, opts = parse_args(self, argv, alias_argv)
 
478
 
 
479
        # Process the standard options
289
480
        if 'help' in opts:  # e.g. bzr add --help
290
 
            from bzrlib.help import help_on_command
291
 
            help_on_command(self.name())
 
481
            sys.stdout.write(self.get_help_text())
292
482
            return 0
 
483
        trace.set_verbosity_level(option._verbosity_level)
 
484
        if 'verbose' in self.supported_std_options:
 
485
            opts['verbose'] = trace.is_verbose()
 
486
        elif opts.has_key('verbose'):
 
487
            del opts['verbose']
 
488
        if 'quiet' in self.supported_std_options:
 
489
            opts['quiet'] = trace.is_quiet()
 
490
        elif opts.has_key('quiet'):
 
491
            del opts['quiet']
 
492
 
293
493
        # mix arguments and options into one dictionary
294
494
        cmdargs = _match_argform(self.name(), self.takes_args, args)
295
495
        cmdopts = {}
338
538
            return None
339
539
 
340
540
 
341
 
# Technically, this function hasn't been use in a *really* long time
342
 
# but we are only deprecating it now.
343
 
@deprecated_function(zero_eleven)
344
 
def parse_spec(spec):
345
 
    """
346
 
    >>> parse_spec(None)
347
 
    [None, None]
348
 
    >>> parse_spec("./")
349
 
    ['./', None]
350
 
    >>> parse_spec("../@")
351
 
    ['..', -1]
352
 
    >>> parse_spec("../f/@35")
353
 
    ['../f', 35]
354
 
    >>> parse_spec('./@revid:john@arbash-meinel.com-20050711044610-3ca0327c6a222f67')
355
 
    ['.', 'revid:john@arbash-meinel.com-20050711044610-3ca0327c6a222f67']
356
 
    """
357
 
    if spec is None:
358
 
        return [None, None]
359
 
    if '/@' in spec:
360
 
        parsed = spec.split('/@')
361
 
        assert len(parsed) == 2
362
 
        if parsed[1] == "":
363
 
            parsed[1] = -1
364
 
        else:
365
 
            try:
366
 
                parsed[1] = int(parsed[1])
367
 
            except ValueError:
368
 
                pass # We can allow stuff like ./@revid:blahblahblah
369
 
            else:
370
 
                assert parsed[1] >=0
371
 
    else:
372
 
        parsed = [spec, None]
373
 
    return parsed
374
 
 
375
541
def parse_args(command, argv, alias_argv=None):
376
542
    """Parse command line.
377
543
    
436
602
 
437
603
    return argdict
438
604
 
 
605
def apply_coveraged(dirname, the_callable, *args, **kwargs):
 
606
    # Cannot use "import trace", as that would import bzrlib.trace instead of
 
607
    # the standard library's trace.
 
608
    trace = __import__('trace')
 
609
 
 
610
    tracer = trace.Trace(count=1, trace=0)
 
611
    sys.settrace(tracer.globaltrace)
 
612
 
 
613
    ret = the_callable(*args, **kwargs)
 
614
 
 
615
    sys.settrace(None)
 
616
    results = tracer.results()
 
617
    results.write_results(show_missing=1, summary=False,
 
618
                          coverdir=dirname)
439
619
 
440
620
 
441
621
def apply_profiled(the_callable, *args, **kwargs):
463
643
 
464
644
def apply_lsprofiled(filename, the_callable, *args, **kwargs):
465
645
    from bzrlib.lsprof import profile
466
 
    import cPickle
467
646
    ret, stats = profile(the_callable, *args, **kwargs)
468
647
    stats.sort()
469
648
    if filename is None:
470
649
        stats.pprint()
471
650
    else:
472
 
        stats.freeze()
473
 
        cPickle.dump(stats, open(filename, 'w'), 2)
474
 
        print 'Profile data written to %r.' % filename
 
651
        stats.save(filename)
 
652
        trace.note('Profile data written to "%s".', filename)
475
653
    return ret
476
654
 
477
655
 
 
656
def shlex_split_unicode(unsplit):
 
657
    import shlex
 
658
    return [u.decode('utf-8') for u in shlex.split(unsplit.encode('utf-8'))]
 
659
 
 
660
 
478
661
def get_alias(cmd, config=None):
479
662
    """Return an expanded alias, or None if no alias exists.
480
663
 
490
673
        config = bzrlib.config.GlobalConfig()
491
674
    alias = config.get_alias(cmd)
492
675
    if (alias):
493
 
        import shlex
494
 
        return [a.decode('utf-8') for a in shlex.split(alias.encode('utf-8'))]
 
676
        return shlex_split_unicode(alias)
495
677
    return None
496
678
 
497
679
 
526
708
 
527
709
    --lsprof
528
710
        Run under the Python lsprof profiler.
 
711
 
 
712
    --coverage
 
713
        Generate line coverage report in the specified directory.
529
714
    """
530
715
    argv = list(argv)
531
716
    trace.mutter("bzr arguments: %r", argv)
532
717
 
533
718
    opt_lsprof = opt_profile = opt_no_plugins = opt_builtin =  \
534
719
                opt_no_aliases = False
535
 
    opt_lsprof_file = None
 
720
    opt_lsprof_file = opt_coverage_dir = None
536
721
 
537
722
    # --no-plugins is handled specially at a very early stage. We need
538
723
    # to load plugins before doing other command parsing so that they
556
741
            opt_no_aliases = True
557
742
        elif a == '--builtin':
558
743
            opt_builtin = True
559
 
        elif a in ('--quiet', '-q'):
560
 
            trace.be_quiet()
 
744
        elif a == '--coverage':
 
745
            opt_coverage_dir = argv[i + 1]
 
746
            i += 1
561
747
        elif a.startswith('-D'):
562
748
            debug.debug_flags.add(a[2:])
563
749
        else:
571
757
        return 0
572
758
 
573
759
    if argv[0] == '--version':
574
 
        from bzrlib.version import show_version
575
 
        show_version()
 
760
        from bzrlib.builtins import cmd_version
 
761
        cmd_version().run_argv_aliases([])
576
762
        return 0
577
763
        
578
764
    if not opt_no_plugins:
596
782
    # 'command not found' error later.
597
783
 
598
784
    cmd_obj = get_cmd_object(cmd, plugins_override=not opt_builtin)
599
 
    if not getattr(cmd_obj.run_argv, 'is_deprecated', False):
600
 
        run = cmd_obj.run_argv
601
 
        run_argv = [argv]
602
 
    else:
603
 
        run = cmd_obj.run_argv_aliases
604
 
        run_argv = [argv, alias_argv]
 
785
    run = cmd_obj.run_argv_aliases
 
786
    run_argv = [argv, alias_argv]
605
787
 
606
788
    try:
607
789
        if opt_lsprof:
 
790
            if opt_coverage_dir:
 
791
                trace.warning(
 
792
                    '--coverage ignored, because --lsprof is in use.')
608
793
            ret = apply_lsprofiled(opt_lsprof_file, run, *run_argv)
609
794
        elif opt_profile:
 
795
            if opt_coverage_dir:
 
796
                trace.warning(
 
797
                    '--coverage ignored, because --profile is in use.')
610
798
            ret = apply_profiled(run, *run_argv)
 
799
        elif opt_coverage_dir:
 
800
            ret = apply_coveraged(opt_coverage_dir, run, *run_argv)
611
801
        else:
612
802
            ret = run(*run_argv)
613
803
        return ret or 0
614
804
    finally:
615
805
        # reset, in case we may do other commands later within the same process
616
 
        trace.be_quiet(False)
 
806
        option._verbosity_level = 0
617
807
 
618
808
def display_command(func):
619
809
    """Decorator that suppresses pipe/interrupt errors."""
627
817
                raise
628
818
            if e.errno != errno.EPIPE:
629
819
                # Win32 raises IOError with errno=0 on a broken pipe
630
 
                if sys.platform != 'win32' or e.errno != 0:
 
820
                if sys.platform != 'win32' or (e.errno not in (0, errno.EINVAL)):
631
821
                    raise
632
822
            pass
633
823
        except KeyboardInterrupt:
639
829
    import bzrlib.ui
640
830
    from bzrlib.ui.text import TextUIFactory
641
831
    bzrlib.ui.ui_factory = TextUIFactory()
642
 
    argv = [a.decode(bzrlib.user_encoding) for a in argv[1:]]
 
832
    try:
 
833
        argv = [a.decode(bzrlib.user_encoding) for a in argv[1:]]
 
834
    except UnicodeDecodeError:
 
835
        raise errors.BzrError(("Parameter '%r' is unsupported by the current "
 
836
                                                            "encoding." % a))
643
837
    ret = run_bzr_catch_errors(argv)
644
838
    trace.mutter("return code %d", ret)
645
839
    return ret
646
840
 
647
841
 
648
842
def run_bzr_catch_errors(argv):
 
843
    # Note: The except clause logic below should be kept in sync with the
 
844
    # profile() routine in lsprof.py.
649
845
    try:
650
846
        return run_bzr(argv)
651
 
        # do this here inside the exception wrappers to catch EPIPE
652
 
        sys.stdout.flush()
653
847
    except (KeyboardInterrupt, Exception), e:
654
848
        # used to handle AssertionError and KeyboardInterrupt
655
849
        # specially here, but hopefully they're handled ok by the logger now
656
 
        trace.report_exception(sys.exc_info(), sys.stderr)
 
850
        exitcode = trace.report_exception(sys.exc_info(), sys.stderr)
657
851
        if os.environ.get('BZR_PDB'):
658
852
            print '**** entering debugger'
659
853
            import pdb
660
854
            pdb.post_mortem(sys.exc_traceback)
661
 
        return 3
 
855
        return exitcode
 
856
 
 
857
 
 
858
def run_bzr_catch_user_errors(argv):
 
859
    """Run bzr and report user errors, but let internal errors propagate.
 
860
 
 
861
    This is used for the test suite, and might be useful for other programs
 
862
    that want to wrap the commandline interface.
 
863
    """
 
864
    try:
 
865
        return run_bzr(argv)
 
866
    except Exception, e:
 
867
        if (isinstance(e, (OSError, IOError))
 
868
            or not getattr(e, 'internal_error', True)):
 
869
            trace.report_exception(sys.exc_info(), sys.stderr)
 
870
            return 3
 
871
        else:
 
872
            raise
 
873
 
 
874
 
 
875
class HelpCommandIndex(object):
 
876
    """A index for bzr help that returns commands."""
 
877
 
 
878
    def __init__(self):
 
879
        self.prefix = 'commands/'
 
880
 
 
881
    def get_topics(self, topic):
 
882
        """Search for topic amongst commands.
 
883
 
 
884
        :param topic: A topic to search for.
 
885
        :return: A list which is either empty or contains a single
 
886
            Command entry.
 
887
        """
 
888
        if topic and topic.startswith(self.prefix):
 
889
            topic = topic[len(self.prefix):]
 
890
        try:
 
891
            cmd = _get_cmd_object(topic)
 
892
        except KeyError:
 
893
            return []
 
894
        else:
 
895
            return [cmd]
 
896
 
 
897
 
 
898
class Provider(object):
 
899
    '''Generic class to be overriden by plugins'''
 
900
 
 
901
    def plugin_for_command(self, cmd_name):
 
902
        '''Takes a command and returns the information for that plugin
 
903
        
 
904
        :return: A dictionary with all the available information 
 
905
        for the requested plugin
 
906
        '''
 
907
        raise NotImplementedError
 
908
 
 
909
 
 
910
class ProvidersRegistry(registry.Registry):
 
911
    '''This registry exists to allow other providers to exist'''
 
912
 
 
913
    def __iter__(self):
 
914
        for key, provider in self.iteritems():
 
915
            yield provider
 
916
 
 
917
command_providers_registry = ProvidersRegistry()
 
918
 
662
919
 
663
920
if __name__ == '__main__':
664
921
    sys.exit(main(sys.argv))