[Patch] Updated support for external command handlers

Michael Ellerman michael+bazaar at ellerman.id.au
Tue May 10 00:11:35 BST 2005


Before anyone picks me up on it .. I didn't add support for external commands 
in 'bzr help commands', because that would require blindly running everything 
in the $BZRPATH, which could be dangerous, eg.

$ export BZRPATH=~/bin
$ cat ~/bin/delete-home-directory
#!/bin/bash
rm -rf ~
$ bzr help commands
...
calls ~/bin/delete-home-directory --bzr-description
ouch!

If anyone can think of a nice way around this let me know.

cheers

On Tue, 10 May 2005 01:37, Michael Ellerman wrote:
> Hi Martin,
>
> Here's an updated patch for external command handlers.
>
> Rusty will write you some test cases in the morning =D
>
> External scripts need to handle two special options, here's an example.
>
> 8<---------------------
> #!/bin/bash
>
> if [[ $1 == '--bzr-usage' ]]; then
> 	# Options we accept, these are the bzr names as found in OPTIONS in
> commands.py echo "verbose message"
> 	# And arguments we accept, same format as for other commands, space
> seperated echo "file*"
>         exit 0
> elif [[ $1 == '--bzr-help' ]]; then
>         echo "This is an external bzr script."
>         echo ""
>         echo "More help about this command"
>         exit 0
> fi
>
> # do stuff
> exit 0
> 8<---------------------
>
> The script will get called with the appropriate arguments, bzr does all the
> parsing and so on. The script is always called with long options. Options
> and arguments will always be passed in alphabetical order of the
> option/argument name.
>
> And the patch ..
>
> cheers
>
> *** modified file 'bzrlib/commands.py'
> --- bzrlib/commands.py
> +++ bzrlib/commands.py
> @@ -58,8 +58,12 @@
>      for cmdname, cmdclass in get_all_cmds():
>          if cmd in cmdclass.aliases:
>              return cmdname, cmdclass
> -    else:
> -        raise BzrCommandError("unknown command %r" % cmd)
> +
> +    cmdclass = ExternalCommand.find_command(cmd)
> +    if cmdclass:
> +        return cmd, cmdclass
> +
> +    raise BzrCommandError("unknown command %r" % cmd)
>
>
>  class Command:
> @@ -110,6 +114,73 @@
>          """
>          return 0
>
> +
> +class ExternalCommand(Command):
> +    """Class to wrap external commands.
> +
> +    We cheat a little here, when get_cmd_class() calls us we actually give
> it back +    an object we construct that has the appropriate path, help,
> options etc for the +    specified command.
> +
> +    When run_bzr() tries to instantiate that 'class' it gets caught by the
> __call__ +    method, which we override to call the Command.__init__
> method. That then calls +    our run method which is pretty straight
> forward.
> +
> +    The only wrinkle is that we have to map bzr's dictionary of options
> and arguments +    back into command line options and arguments for the
> script.
> +    """
> +
> +    def find_command(cls, cmd):
> +        bzrpath = os.environ.get('BZRPATH', '')
> +
> +        for dir in bzrpath.split(':'):
> +            path = os.path.join(dir, cmd)
> +            if os.path.isfile(path):
> +                return ExternalCommand(path)
> +
> +        return None
> +
> +    find_command = classmethod(find_command)
> +
> +    def __init__(self, path):
> +        self.path = path
> +
> +        pipe = os.popen('%s --bzr-usage' % path, 'r')
> +        self.takes_options = pipe.readline().split()
> +        self.takes_args = pipe.readline().split()
> +        pipe.close()
> +
> +        pipe = os.popen('%s --bzr-help' % path, 'r')
> +        self.__doc__ = pipe.read()
> +        pipe.close()
> +
> +    def __call__(self, options, arguments):
> +        Command.__init__(self, options, arguments)
> +        return self
> +
> +    def run(self, **kargs):
> +        opts = []
> +        args = []
> +
> +        keys = kargs.keys()
> +        keys.sort()
> +        for name in keys:
> +            value = kargs[name]
> +            if OPTIONS.has_key(name):
> +                # it's an option
> +                opts.append('--%s' % name)
> +                if value is not None and value is not True:
> +                    opts.append(str(value))
> +            else:
> +                # it's an arg, or arg list
> +                if type(value) is not list:
> +                    value = [value]
> +                for v in value:
> +                    if v is not None:
> +                        args.append(str(v))
> +
> +        self.status = os.spawnv(os.P_WAIT, self.path, [self.path] + opts +
> args) +        return self.status
>
>
>  class cmd_status(Command):

-- 
Michael Ellerman
IBM OzLabs

email: michael:ellerman.id.au
inmsg: mpe:jabber.org
wwweb: http://michael.ellerman.id.au
phone: +61 2 6212 1183 (tie line 70 21183)

We do not inherit the earth from our ancestors,
we borrow it from our children. - S.M.A.R.T Person




More information about the bazaar mailing list