~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/commands.py

  • Committer: Martin Pool
  • Date: 2005-09-01 09:17:38 UTC
  • Revision ID: mbp@sourcefrog.net-20050901091738-5aafcae38c6c945c
- change Command infrastructure to use (mostly stateless) objects to
  represent commands; rather cleaner particularly for representing
  external commands

- clean up the way --profile is done

- various other bzrlib.commands and .help cleanups

Show diffs side-by-side

added added

removed removed

Lines of Context:
25
25
# would help with validation and shell completion.
26
26
 
27
27
 
 
28
 
28
29
import sys
29
30
import os
 
31
from warnings import warn
 
32
from inspect import getdoc
30
33
 
31
34
import bzrlib
32
35
import bzrlib.trace
160
163
        msg = "No known merge type %s. Supported types are:\n%s" %\
161
164
            (typestring, type_list)
162
165
        raise BzrCommandError(msg)
163
 
    
164
 
 
165
 
 
166
 
def _get_cmd_dict(plugins_override=True):
 
166
 
 
167
 
 
168
def _builtin_commands():
167
169
    import bzrlib.builtins
168
 
    
169
 
    d = {}
 
170
    r = {}
170
171
    builtins = bzrlib.builtins.__dict__
171
172
    for name in builtins:
172
173
        if name.startswith("cmd_"):
173
 
            d[_unsquish_command_name(name)] = builtins[name]
174
 
    # If we didn't load plugins, the plugin_cmds dict will be empty
 
174
            real_name = _unsquish_command_name(name)        
 
175
            r[real_name] = builtins[name]
 
176
    return r
 
177
 
 
178
            
 
179
 
 
180
def builtin_command_names():
 
181
    """Return list of builtin command names."""
 
182
    return _builtin_commands().keys()
 
183
    
 
184
 
 
185
def plugin_command_names():
 
186
    return plugin_cmds.keys()
 
187
 
 
188
 
 
189
def _get_cmd_dict(plugins_override=True):
 
190
    """Return name->class mapping for all commands."""
 
191
    d = _builtin_commands()
175
192
    if plugins_override:
176
193
        d.update(plugin_cmds)
177
 
    else:
178
 
        d2 = plugin_cmds.copy()
179
 
        d2.update(d)
180
 
        d = d2
181
194
    return d
182
195
 
183
196
    
187
200
        yield k,v
188
201
 
189
202
 
190
 
def get_cmd_class(cmd, plugins_override=True):
 
203
def get_cmd_object(cmd_name, plugins_override=True):
191
204
    """Return the canonical name and command class for a command.
 
205
 
 
206
    plugins_override
 
207
        If true, plugin commands can override builtins.
192
208
    """
193
 
    cmd = str(cmd)                      # not unicode
 
209
    cmd_name = str(cmd_name)            # not unicode
194
210
 
195
211
    # first look up this command under the specified name
196
212
    cmds = _get_cmd_dict(plugins_override=plugins_override)
197
 
    mutter("all commands: %r", cmds.keys())
198
213
    try:
199
 
        return cmd, cmds[cmd]
 
214
        return cmds[cmd_name]()
200
215
    except KeyError:
201
216
        pass
202
217
 
203
218
    # look for any command which claims this as an alias
204
 
    for cmdname, cmdclass in cmds.iteritems():
205
 
        if cmd in cmdclass.aliases:
206
 
            return cmdname, cmdclass
207
 
 
208
 
    cmdclass = ExternalCommand.find_command(cmd)
209
 
    if cmdclass:
210
 
        return cmd, cmdclass
211
 
 
212
 
    raise BzrCommandError("unknown command %r" % cmd)
 
219
    for real_cmd_name, cmd_class in cmds.iteritems():
 
220
        if cmd_name in cmd_class.aliases:
 
221
            return cmd_class()
 
222
 
 
223
    cmd_obj = ExternalCommand.find_command(cmd_name)
 
224
    if cmd_obj:
 
225
        return cmd_obj
 
226
 
 
227
    raise BzrCommandError("unknown command %r" % cmd_name)
213
228
 
214
229
 
215
230
class Command(object):
216
231
    """Base class for commands.
217
232
 
 
233
    Commands are the heart of the command-line bzr interface.
 
234
 
 
235
    The command object mostly handles the mapping of command-line
 
236
    parameters into one or more bzrlib operations, and of the results
 
237
    into textual output.
 
238
 
 
239
    Commands normally don't have any state.  All their arguments are
 
240
    passed in to the run method.  (Subclasses may take a different
 
241
    policy if the behaviour of the instance needs to depend on e.g. a
 
242
    shell plugin and not just its Python class.)
 
243
 
218
244
    The docstring for an actual command should give a single-line
219
245
    summary, then a complete description of the command.  A grammar
220
246
    description will be inserted.
221
247
 
 
248
    aliases
 
249
        Other accepted names for this command.
 
250
 
222
251
    takes_args
223
252
        List of argument forms, marked with whether they are optional,
224
253
        repeated, etc.
227
256
        List of options that may be given for this command.
228
257
 
229
258
    hidden
230
 
        If true, this command isn't advertised.
 
259
        If true, this command isn't advertised.  This is typically
 
260
        for commands intended for expert users.
231
261
    """
232
262
    aliases = []
233
263
    
236
266
 
237
267
    hidden = False
238
268
    
239
 
    def __init__(self, options, arguments):
240
 
        """Construct and run the command.
241
 
 
242
 
        Sets self.status to the return value of run()."""
243
 
        assert isinstance(options, dict)
244
 
        assert isinstance(arguments, dict)
245
 
        cmdargs = options.copy()
246
 
        cmdargs.update(arguments)
 
269
    def __init__(self):
 
270
        """Construct an instance of this command."""
247
271
        if self.__doc__ == Command.__doc__:
248
 
            from warnings import warn
249
272
            warn("No help message set for %r" % self)
250
 
        self.status = self.run(**cmdargs)
251
 
        if self.status is None:
252
 
            self.status = 0
253
273
 
254
274
    
255
 
    def run(self, *args, **kwargs):
256
 
        """Override this in sub-classes.
 
275
    def run(self):
 
276
        """Actually run the command.
257
277
 
258
278
        This is invoked with the options and arguments bound to
259
279
        keyword parameters.
260
280
 
261
 
        Return 0 or None if the command was successful, or a shell
262
 
        error code if not.
 
281
        Return 0 or None if the command was successful, or a non-zero
 
282
        shell error code if not.  It's OK for this method to allow
 
283
        an exception to raise up.
263
284
        """
264
285
        raise NotImplementedError()
265
286
 
266
287
 
 
288
    def help(self):
 
289
        """Return help message for this class."""
 
290
        if self.__doc__ is Command.__doc__:
 
291
            return None
 
292
        return getdoc(self)
 
293
 
 
294
    def name(self):
 
295
        return _unsquish_command_name(self.__class__.__name__)
 
296
 
 
297
 
267
298
class ExternalCommand(Command):
268
299
    """Class to wrap external commands.
269
300
 
320
351
        Command.__init__(self, options, arguments)
321
352
        return self
322
353
 
 
354
    def name(self):
 
355
        raise NotImplementedError()
 
356
 
323
357
    def run(self, **kargs):
 
358
        raise NotImplementedError()
 
359
        
324
360
        opts = []
325
361
        args = []
326
362
 
570
606
 
571
607
 
572
608
 
 
609
def apply_profiled(the_callable, *args, **kwargs):
 
610
    import hotshot
 
611
    import tempfile
 
612
    pffileno, pfname = tempfile.mkstemp()
 
613
    try:
 
614
        prof = hotshot.Profile(pfname)
 
615
        try:
 
616
            ret = prof.runcall(the_callable, *args, **kwargs) or 0
 
617
        finally:
 
618
            prof.close()
 
619
 
 
620
        import hotshot.stats
 
621
        stats = hotshot.stats.load(pfname)
 
622
        #stats.strip_dirs()
 
623
        stats.sort_stats('time')
 
624
        ## XXX: Might like to write to stderr or the trace file instead but
 
625
        ## print_stats seems hardcoded to stdout
 
626
        stats.print_stats(20)
 
627
 
 
628
        return ret
 
629
    finally:
 
630
        os.close(pffileno)
 
631
        os.remove(pfname)
 
632
 
 
633
 
573
634
def run_bzr(argv):
574
635
    """Execute a command.
575
636
 
640
701
    
641
702
    cmd = str(args.pop(0))
642
703
 
643
 
    canonical_cmd, cmd_class = \
644
 
                   get_cmd_class(cmd, plugins_override=not opt_builtin)
 
704
    cmd_obj = get_cmd_object(cmd, plugins_override=not opt_builtin)
645
705
 
646
706
    # check options are reasonable
647
 
    allowed = cmd_class.takes_options
 
707
    allowed = cmd_obj.takes_options
648
708
    for oname in opts:
649
709
        if oname not in allowed:
650
710
            raise BzrCommandError("option '--%s' is not allowed for command %r"
651
711
                                  % (oname, cmd))
652
712
 
653
713
    # mix arguments and options into one dictionary
654
 
    cmdargs = _match_argform(cmd, cmd_class.takes_args, args)
 
714
    cmdargs = _match_argform(cmd, cmd_obj.takes_args, args)
655
715
    cmdopts = {}
656
716
    for k, v in opts.items():
657
717
        cmdopts[k.replace('-', '_')] = v
658
718
 
 
719
    all_cmd_args = cmdargs.copy()
 
720
    all_cmd_args.update(cmdopts)
 
721
 
659
722
    if opt_profile:
660
 
        import hotshot, tempfile
661
 
        pffileno, pfname = tempfile.mkstemp()
662
 
        try:
663
 
            prof = hotshot.Profile(pfname)
664
 
            ret = prof.runcall(cmd_class, cmdopts, cmdargs) or 0
665
 
            prof.close()
666
 
 
667
 
            import hotshot.stats
668
 
            stats = hotshot.stats.load(pfname)
669
 
            #stats.strip_dirs()
670
 
            stats.sort_stats('time')
671
 
            ## XXX: Might like to write to stderr or the trace file instead but
672
 
            ## print_stats seems hardcoded to stdout
673
 
            stats.print_stats(20)
674
 
            
675
 
            return ret.status
676
 
 
677
 
        finally:
678
 
            os.close(pffileno)
679
 
            os.remove(pfname)
 
723
        ret = apply_profiled(cmd_obj.run, **all_cmd_args)
680
724
    else:
681
 
        return cmd_class(cmdopts, cmdargs).status 
 
725
        ret = cmd_obj.run(**all_cmd_args)
 
726
 
 
727
    if ret is None:
 
728
        ret = 0
 
729
    return ret
682
730
 
683
731
 
684
732
def main(argv):