[PATCH RFC] shellext: automatically add shell scripts as new commands

Matt Mackall mpm at selenic.com
Mon Jan 9 13:24:44 CST 2012


On Sat, 2012-01-07 at 18:56 +0100, Martin Geisler wrote:
> # HG changeset patch
> # User Martin Geisler <mg at aragost.com>
> # Date 1325959011 -3600
> # Node ID 8c34fa2adf476a1e07512207fb18866e7908cf58
> # Parent  3fe39d6d2bd8331932df063d272fff8af327881c
> shellext: automatically add shell scripts as new commands
> 
> People sometimes note that Mercurial extensions must be written in
> Python, whereas extensions for Git can be written in any language.
> Some people see this as an advantage for Git. This extension lets you
> do the same for Mercurial.
> 
> The only required change to core Mercurial is loading the extensions
> before checking for shell aliases -- this is so that the extension has
> a chance to register the new shell aliases.

Interesting. Some musings:

- Is this any different from aliases.foo = !somecommand?

Not yet?

- Do we search for other extensions in ~/.hgext?

Looks like no.

- Should this be built-in functionality?

Probably, if the aim is to making dropping things in trivial. 

- Should we automatically enable everything in .hgext?

Probably, for consistency. But then 'hg showconfig --debug' no longer
completely explains where extensions are coming from. Alternately, the
name needs to be .hgshellext or something to make it clear that it's not
for normal extensions.

- How do we ship a shell extension in a way that a user can enable?

Should --config extensions.shellfoo= find $HGLIB/shellfoo.sh?

- How do we make these things work with the help engine?

By wrapping some RST in a specially-formatted comment block?

- What happens if someone shadows a built-in?

- Can we do anything to facilitate argument parsing?

It'd be extremely helpful to do some option parsing/validation like
fancyopts does and making sure we have the right number of args.

- How does the created environment compare to the generic pre- and post-
command hooks?

It should be the same (or richer). We'll need to be able to find the
current hg executable, for starters.

- What about Windows?

Should we look for .cmd scripts too?

- What if I want to write an extension in Python that's not a normal
Mercurial extension?

Ok, probably don't care about that.


> diff --git a/hgext/shellext.py b/hgext/shellext.py
> new file mode 100644
> --- /dev/null
> +++ b/hgext/shellext.py
> @@ -0,0 +1,50 @@
> +# Copyright 2012  Martin Geisler <mg at aragost.com>
> +#
> +# This software may be used and distributed according to the terms of
> +# the GNU General Public License version 2 or any later version.
> +
> +"""automatically register executables in ~/.hgext as new commands
> +
> +Enable this extension and you'll get a new command for any executable
> +script found in your ``~/.hgext`` folder. A script named ``foo.sh``
> +will give you a new ``hg foo`` command. If you invoke it as::
> +
> +  hg foo -x bar -y baz
> +
> +then ``foo.sh`` will be invoked as
> +
> +  foo.sh -x bar -y baz
> +
> +The above directory is the default and you can change it by setting
> +``shellext.extdir`` to another value.
> +"""
> +
> +import os, sys
> +from mercurial import util, dispatch
> +
> +_earlyopts = ["--config", "--cwd", "-R", "--repository", "--repo"]
> +
> +def uisetup(ui):
> +    args = sys.argv[1:]
> +    # remove the earlyopts that dispatch has already acted upon.
> +    dispatch._earlygetopt(_earlyopts, args)
> +    try:
> +        # remove one layer of "quoting" so that it's possible to pass
> +        # global opts to a shell command by putting them after '--'.
> +        args.remove('--')
> +    except ValueError:
> +        pass
> +
> +    aliasargs = ' '.join(map(util.shellquote, args[1:]))
> +
> +    extdir = ui.configpath('shellext', 'extdir', os.path.expanduser('~/.hgext'))
> +    if not os.path.isdir(extdir):
> +        ui.debug('shellext: %s is not a directory\n' % extdir)
> +        return
> +
> +    for script in os.listdir(extdir):
> +        path = os.path.join(extdir, script)
> +        alias = os.path.splitext(script)[0]
> +        if not os.access(path, os.X_OK):
> +            continue
> +        ui.setconfig('alias', alias, '!%s %s' % (path, aliasargs))
> diff --git a/mercurial/dispatch.py b/mercurial/dispatch.py
> --- a/mercurial/dispatch.py
> +++ b/mercurial/dispatch.py
> @@ -540,12 +540,6 @@
>      rpath = _earlygetopt(["-R", "--repository", "--repo"], args)
>      path, lui = _getlocal(ui, rpath)
>  
> -    # Now that we're operating in the right directory/repository with
> -    # the right config settings, check for shell aliases
> -    shellaliasfn = _checkshellalias(lui, ui, args)
> -    if shellaliasfn:
> -        return shellaliasfn()
> -
>      # Configure extensions in phases: uisetup, extsetup, cmdtable, and
>      # reposetup. Programs like TortoiseHg will call _dispatch several
>      # times so we keep track of configured extensions in _loaded.
> @@ -556,6 +550,12 @@
>  
>      # (uisetup and extsetup are handled in extensions.loadall)
>  
> +    # Now that we're operating in the right directory/repository with
> +    # the right config settings, check for shell aliases
> +    shellaliasfn = _checkshellalias(lui, ui, args)
> +    if shellaliasfn:
> +        return shellaliasfn()
> +
>      for name, module in exts:
>          cmdtable = getattr(module, 'cmdtable', {})
>          overrides = [cmd for cmd in cmdtable if cmd in commands.table]
> _______________________________________________
> Mercurial-devel mailing list
> Mercurial-devel at selenic.com
> http://selenic.com/mailman/listinfo/mercu


-- 
Mathematics is the supreme nostalgia of our time.




More information about the Mercurial-devel mailing list