Mercurial > hg
changeset 41044:fe606f2dcae9
extensions: import the exthelper class from evolve 980565468003 (API)
This should help make extensions that wrap a lot of stuff more comprehendible.
It was copied unmodified, except:
- fix up the imports
- rename final_xxxsetup() -> finalxxxsetup() to appease checkcode
- avoid a [] default arg to wrapcommand()
.. api::
Add `exthelper` class to simplify extension writing by allowing functions,
commands, and configitems to be registered via annotations. The previous
APIs are still available for use.
author | Matt Harbison <matt_harbison@yahoo.com> |
---|---|
date | Sat, 22 Dec 2018 21:06:24 -0500 |
parents | ce0bc2952e2a |
children | c1476d095d57 |
files | mercurial/exthelper.py |
diffstat | 1 files changed, 354 insertions(+), 0 deletions(-) [+] |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mercurial/exthelper.py Sat Dec 22 21:06:24 2018 -0500 @@ -0,0 +1,354 @@ +# Copyright 2012 Logilab SA <contact@logilab.fr> +# Pierre-Yves David <pierre-yves.david@ens-lyon.org> +# Octobus <contact@octobus.net> +# +# This software may be used and distributed according to the terms of the +# GNU General Public License version 2 or any later version. + +##################################################################### +### Extension helper ### +##################################################################### + +from __future__ import absolute_import + +from . import ( + commands, + configitems, + extensions, + fileset as filesetmod, + registrar, + revset as revsetmod, + templatekw as templatekwmod, +) + +class exthelper(object): + """Helper for modular extension setup + + A single helper should be instantiated for each extension. Helper + methods are then used as decorators for various purpose. + + All decorators return the original function and may be chained. + """ + + def __init__(self): + self._uipopulatecallables = [] + self._uicallables = [] + self._extcallables = [] + self._repocallables = [] + self._revsetsymbols = [] + self._filesetsymbols = [] + self._templatekws = [] + self._commandwrappers = [] + self._extcommandwrappers = [] + self._functionwrappers = [] + self._duckpunchers = [] + self.cmdtable = {} + self.command = registrar.command(self.cmdtable) + if '^init' in commands.table: + olddoregister = self.command._doregister + + def _newdoregister(self, name, *args, **kwargs): + if kwargs.pop('helpbasic', False): + name = '^' + name + return olddoregister(self, name, *args, **kwargs) + self.command._doregister = _newdoregister + + self.configtable = {} + self._configitem = registrar.configitem(self.configtable) + + def configitem(self, section, config, default=configitems.dynamicdefault): + """Register a config item. + """ + self._configitem(section, config, default=default) + + def merge(self, other): + self._uicallables.extend(other._uicallables) + self._uipopulatecallables.extend(other._uipopulatecallables) + self._extcallables.extend(other._extcallables) + self._repocallables.extend(other._repocallables) + self._revsetsymbols.extend(other._revsetsymbols) + self._filesetsymbols.extend(other._filesetsymbols) + self._templatekws.extend(other._templatekws) + self._commandwrappers.extend(other._commandwrappers) + self._extcommandwrappers.extend(other._extcommandwrappers) + self._functionwrappers.extend(other._functionwrappers) + self._duckpunchers.extend(other._duckpunchers) + self.cmdtable.update(other.cmdtable) + for section, items in other.configtable.iteritems(): + if section in self.configtable: + self.configtable[section].update(items) + else: + self.configtable[section] = items + + def finaluisetup(self, ui): + """Method to be used as the extension uisetup + + The following operations belong here: + + - Changes to ui.__class__ . The ui object that will be used to run the + command has not yet been created. Changes made here will affect ui + objects created after this, and in particular the ui that will be + passed to runcommand + - Command wraps (extensions.wrapcommand) + - Changes that need to be visible to other extensions: because + initialization occurs in phases (all extensions run uisetup, then all + run extsetup), a change made here will be visible to other extensions + during extsetup + - Monkeypatch or wrap function (extensions.wrapfunction) of dispatch + module members + - Setup of pre-* and post-* hooks + - pushkey setup + """ + for cont, funcname, func in self._duckpunchers: + setattr(cont, funcname, func) + for command, wrapper, opts in self._commandwrappers: + entry = extensions.wrapcommand(commands.table, command, wrapper) + if opts: + for short, long, val, msg in opts: + entry[1].append((short, long, val, msg)) + for cont, funcname, wrapper in self._functionwrappers: + extensions.wrapfunction(cont, funcname, wrapper) + for c in self._uicallables: + c(ui) + + def finaluipopulate(self, ui): + """Method to be used as the extension uipopulate + + This is called once per ui instance to: + + - Set up additional ui members + - Update configuration by ``ui.setconfig()`` + - Extend the class dynamically + """ + for c in self._uipopulatecallables: + c(ui) + + def finalextsetup(self, ui): + """Method to be used as a the extension extsetup + + The following operations belong here: + + - Changes depending on the status of other extensions. (if + extensions.find('mq')) + - Add a global option to all commands + - Register revset functions + """ + knownexts = {} + + revsetpredicate = registrar.revsetpredicate() + for name, symbol in self._revsetsymbols: + revsetpredicate(name)(symbol) + revsetmod.loadpredicate(ui, 'evolve', revsetpredicate) + + filesetpredicate = registrar.filesetpredicate() + for name, symbol in self._filesetsymbols: + filesetpredicate(name)(symbol) + # TODO: Figure out the calling extension name + filesetmod.loadpredicate(ui, 'exthelper', filesetpredicate) + + templatekeyword = registrar.templatekeyword() + for name, kw, requires in self._templatekws: + if requires is not None: + templatekeyword(name, requires=requires)(kw) + else: + templatekeyword(name)(kw) + templatekwmod.loadkeyword(ui, 'evolve', templatekeyword) + + for ext, command, wrapper, opts in self._extcommandwrappers: + if ext not in knownexts: + try: + e = extensions.find(ext) + except KeyError: + # Extension isn't enabled, so don't bother trying to wrap + # it. + continue + knownexts[ext] = e.cmdtable + entry = extensions.wrapcommand(knownexts[ext], command, wrapper) + if opts: + for short, long, val, msg in opts: + entry[1].append((short, long, val, msg)) + + for c in self._extcallables: + c(ui) + + def finalreposetup(self, ui, repo): + """Method to be used as the extension reposetup + + The following operations belong here: + + - All hooks but pre-* and post-* + - Modify configuration variables + - Changes to repo.__class__, repo.dirstate.__class__ + """ + for c in self._repocallables: + c(ui, repo) + + def uisetup(self, call): + """Decorated function will be executed during uisetup + + example:: + + @eh.uisetup + def setupbabar(ui): + print 'this is uisetup!' + """ + self._uicallables.append(call) + return call + + def uipopulate(self, call): + """Decorated function will be executed during uipopulate + + example:: + + @eh.uipopulate + def setupfoo(ui): + print 'this is uipopulate!' + """ + self._uipopulatecallables.append(call) + return call + + def extsetup(self, call): + """Decorated function will be executed during extsetup + + example:: + + @eh.extsetup + def setupcelestine(ui): + print 'this is extsetup!' + """ + self._extcallables.append(call) + return call + + def reposetup(self, call): + """Decorated function will be executed during reposetup + + example:: + + @eh.reposetup + def setupzephir(ui, repo): + print 'this is reposetup!' + """ + self._repocallables.append(call) + return call + + def revset(self, symbolname): + """Decorated function is a revset symbol + + The name of the symbol must be given as the decorator argument. + The symbol is added during `extsetup`. + + example:: + + @eh.revset('hidden') + def revsetbabar(repo, subset, x): + args = revset.getargs(x, 0, 0, 'babar accept no argument') + return [r for r in subset if 'babar' in repo[r].description()] + """ + def dec(symbol): + self._revsetsymbols.append((symbolname, symbol)) + return symbol + return dec + + def fileset(self, symbolname): + """Decorated function is a fileset symbol + + The name of the symbol must be given as the decorator argument. + The symbol is added during `extsetup`. + + example:: + + @eh.fileset('lfs()') + def filesetbabar(mctx, x): + return mctx.predicate(...) + """ + def dec(symbol): + self._filesetsymbols.append((symbolname, symbol)) + return symbol + return dec + + def templatekw(self, keywordname, requires=None): + """Decorated function is a template keyword + + The name of the keyword must be given as the decorator argument. + The symbol is added during `extsetup`. + + example:: + + @eh.templatekw('babar') + def kwbabar(ctx): + return 'babar' + """ + def dec(keyword): + self._templatekws.append((keywordname, keyword, requires)) + return keyword + return dec + + def wrapcommand(self, command, extension=None, opts=None): + """Decorated function is a command wrapper + + The name of the command must be given as the decorator argument. + The wrapping is installed during `uisetup`. + + If the second option `extension` argument is provided, the wrapping + will be applied in the extension commandtable. This argument must be a + string that will be searched using `extension.find` if not found and + Abort error is raised. If the wrapping applies to an extension, it is + installed during `extsetup`. + + example:: + + @eh.wrapcommand('summary') + def wrapsummary(orig, ui, repo, *args, **kwargs): + ui.note('Barry!') + return orig(ui, repo, *args, **kwargs) + + The `opts` argument allows specifying additional arguments for the + command. + + """ + if opts is None: + opts = [] + def dec(wrapper): + if extension is None: + self._commandwrappers.append((command, wrapper, opts)) + else: + self._extcommandwrappers.append((extension, command, wrapper, + opts)) + return wrapper + return dec + + def wrapfunction(self, container, funcname): + """Decorated function is a function wrapper + + This function takes two arguments, the container and the name of the + function to wrap. The wrapping is performed during `uisetup`. + (there is no extension support) + + example:: + + @eh.function(discovery, 'checkheads') + def wrapfunction(orig, *args, **kwargs): + ui.note('His head smashed in and his heart cut out') + return orig(*args, **kwargs) + """ + def dec(wrapper): + self._functionwrappers.append((container, funcname, wrapper)) + return wrapper + return dec + + def addattr(self, container, funcname): + """Decorated function is to be added to the container + + This function takes two arguments, the container and the name of the + function to wrap. The wrapping is performed during `uisetup`. + + example:: + + @eh.function(context.changectx, 'babar') + def babar(ctx): + return 'babar' in ctx.description + """ + def dec(func): + self._duckpunchers.append((container, funcname, func)) + return func + return dec