exthelper.py
339 lines
| 11.8 KiB
| text/x-python
|
PythonLexer
/ mercurial / exthelper.py
Matt Harbison
|
r41074 | # 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 ### | ||||
##################################################################### | ||||
Matt Harbison
|
r52756 | from __future__ import annotations | ||
Matt Harbison
|
r41074 | |||
from . import ( | ||||
commands, | ||||
Matt Harbison
|
r41090 | error, | ||
Matt Harbison
|
r41074 | extensions, | ||
registrar, | ||||
) | ||||
Augie Fackler
|
r42507 | from hgdemandimport import tracing | ||
Augie Fackler
|
r43346 | |||
Gregory Szorc
|
r49801 | class exthelper: | ||
Matt Harbison
|
r41074 | """Helper for modular extension setup | ||
Matt Harbison
|
r41101 | A single helper should be instantiated for each module of an | ||
extension, where a command or function needs to be wrapped, or a | ||||
command, extension hook, fileset, revset or template needs to be | ||||
registered. Helper methods are then used as decorators for | ||||
these various purposes. If an extension spans multiple modules, | ||||
all helper instances should be merged in the main module. | ||||
Matt Harbison
|
r41074 | |||
All decorators return the original function and may be chained. | ||||
Matt Harbison
|
r41101 | |||
Aside from the helper functions with examples below, several | ||||
registrar method aliases are available for adding commands, | ||||
configitems, filesets, revsets, and templates. Simply decorate | ||||
the appropriate methods, and assign the corresponding exthelper | ||||
variable to a module level variable of the extension. The | ||||
extension loading mechanism will handle the rest. | ||||
example:: | ||||
# ext.py | ||||
eh = exthelper.exthelper() | ||||
Kyle Lippincott
|
r47630 | # As needed (failure to do this will mean your registration will not | ||
# happen): | ||||
Matt Harbison
|
r41101 | cmdtable = eh.cmdtable | ||
configtable = eh.configtable | ||||
filesetpredicate = eh.filesetpredicate | ||||
revsetpredicate = eh.revsetpredicate | ||||
templatekeyword = eh.templatekeyword | ||||
Kyle Lippincott
|
r47630 | # As needed (failure to do this will mean your eh.wrap*-decorated | ||
# functions will not wrap, and/or your eh.*setup-decorated functions | ||||
# will not execute): | ||||
uisetup = eh.finaluisetup | ||||
extsetup = eh.finalextsetup | ||||
reposetup = eh.finalreposetup | ||||
uipopulate = eh.finaluipopulate | ||||
Matt Harbison
|
r46544 | @eh.command(b'mynewcommand', | ||
[(b'r', b'rev', [], _(b'operate on these revisions'))], | ||||
_(b'-r REV...'), | ||||
Matt Harbison
|
r41101 | helpcategory=command.CATEGORY_XXX) | ||
def newcommand(ui, repo, *revs, **opts): | ||||
# implementation goes here | ||||
Matt Harbison
|
r46544 | eh.configitem(b'experimental', b'foo', | ||
Matt Harbison
|
r41101 | default=False, | ||
) | ||||
Matt Harbison
|
r46544 | @eh.filesetpredicate(b'lfs()') | ||
Matt Harbison
|
r41101 | def filesetbabar(mctx, x): | ||
return mctx.predicate(...) | ||||
Matt Harbison
|
r46544 | @eh.revsetpredicate(b'hidden') | ||
Matt Harbison
|
r41101 | def revsetbabar(repo, subset, x): | ||
Matt Harbison
|
r46544 | args = revset.getargs(x, 0, 0, b'babar accept no argument') | ||
return [r for r in subset if b'babar' in repo[r].description()] | ||||
Matt Harbison
|
r41101 | |||
Matt Harbison
|
r46544 | @eh.templatekeyword(b'babar') | ||
Matt Harbison
|
r41101 | def kwbabar(ctx): | ||
Matt Harbison
|
r46544 | return b'babar' | ||
Matt Harbison
|
r41074 | """ | ||
def __init__(self): | ||||
self._uipopulatecallables = [] | ||||
self._uicallables = [] | ||||
self._extcallables = [] | ||||
self._repocallables = [] | ||||
self._commandwrappers = [] | ||||
self._extcommandwrappers = [] | ||||
self._functionwrappers = [] | ||||
self.cmdtable = {} | ||||
self.command = registrar.command(self.cmdtable) | ||||
self.configtable = {} | ||||
Matt Harbison
|
r41075 | self.configitem = registrar.configitem(self.configtable) | ||
Matt Harbison
|
r41100 | self.filesetpredicate = registrar.filesetpredicate() | ||
Matt Harbison
|
r41096 | self.revsetpredicate = registrar.revsetpredicate() | ||
Matt Harbison
|
r41099 | self.templatekeyword = registrar.templatekeyword() | ||
Matt Harbison
|
r41074 | |||
def merge(self, other): | ||||
self._uicallables.extend(other._uicallables) | ||||
self._uipopulatecallables.extend(other._uipopulatecallables) | ||||
self._extcallables.extend(other._extcallables) | ||||
self._repocallables.extend(other._repocallables) | ||||
Matt Harbison
|
r41113 | self.filesetpredicate._merge(other.filesetpredicate) | ||
self.revsetpredicate._merge(other.revsetpredicate) | ||||
self.templatekeyword._merge(other.templatekeyword) | ||||
Matt Harbison
|
r41074 | self._commandwrappers.extend(other._commandwrappers) | ||
self._extcommandwrappers.extend(other._extcommandwrappers) | ||||
self._functionwrappers.extend(other._functionwrappers) | ||||
self.cmdtable.update(other.cmdtable) | ||||
Gregory Szorc
|
r49768 | for section, items in other.configtable.items(): | ||
Matt Harbison
|
r41074 | 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 command, wrapper, opts in self._commandwrappers: | ||||
entry = extensions.wrapcommand(commands.table, command, wrapper) | ||||
if opts: | ||||
Matt Harbison
|
r41090 | for opt in opts: | ||
entry[1].append(opt) | ||||
Matt Harbison
|
r41074 | for cont, funcname, wrapper in self._functionwrappers: | ||
extensions.wrapfunction(cont, funcname, wrapper) | ||||
for c in self._uicallables: | ||||
Augie Fackler
|
r43532 | with tracing.log('finaluisetup: %s', repr(c)): | ||
Augie Fackler
|
r42507 | c(ui) | ||
Matt Harbison
|
r41074 | |||
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): | ||||
Kyle Lippincott
|
r47630 | """Method to be used as the extension extsetup | ||
Matt Harbison
|
r41074 | |||
The following operations belong here: | ||||
- Changes depending on the status of other extensions. (if | ||||
Matt Harbison
|
r46544 | extensions.find(b'mq')) | ||
Matt Harbison
|
r41074 | - Add a global option to all commands | ||
""" | ||||
knownexts = {} | ||||
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: | ||||
Matt Harbison
|
r41090 | for opt in opts: | ||
entry[1].append(opt) | ||||
Matt Harbison
|
r41074 | |||
for c in self._extcallables: | ||||
Augie Fackler
|
r43532 | with tracing.log('finalextsetup: %s', repr(c)): | ||
Augie Fackler
|
r42507 | c(ui) | ||
Matt Harbison
|
r41074 | |||
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: | ||||
Augie Fackler
|
r43532 | with tracing.log('finalreposetup: %s', repr(c)): | ||
Augie Fackler
|
r42507 | c(ui, repo) | ||
Matt Harbison
|
r41074 | |||
def uisetup(self, call): | ||||
"""Decorated function will be executed during uisetup | ||||
example:: | ||||
Kyle Lippincott
|
r47630 | # Required, otherwise your uisetup function(s) will not execute. | ||
uisetup = eh.finaluisetup | ||||
Matt Harbison
|
r41074 | @eh.uisetup | ||
def setupbabar(ui): | ||||
Matt Harbison
|
r46544 | print('this is uisetup!') | ||
Matt Harbison
|
r41074 | """ | ||
self._uicallables.append(call) | ||||
return call | ||||
def uipopulate(self, call): | ||||
"""Decorated function will be executed during uipopulate | ||||
example:: | ||||
Kyle Lippincott
|
r47630 | # Required, otherwise your uipopulate function(s) will not execute. | ||
uipopulate = eh.finaluipopulate | ||||
Matt Harbison
|
r41074 | @eh.uipopulate | ||
def setupfoo(ui): | ||||
Matt Harbison
|
r46544 | print('this is uipopulate!') | ||
Matt Harbison
|
r41074 | """ | ||
self._uipopulatecallables.append(call) | ||||
return call | ||||
def extsetup(self, call): | ||||
"""Decorated function will be executed during extsetup | ||||
example:: | ||||
Kyle Lippincott
|
r47630 | # Required, otherwise your extsetup function(s) will not execute. | ||
extsetup = eh.finalextsetup | ||||
Matt Harbison
|
r41074 | @eh.extsetup | ||
def setupcelestine(ui): | ||||
Matt Harbison
|
r46544 | print('this is extsetup!') | ||
Matt Harbison
|
r41074 | """ | ||
self._extcallables.append(call) | ||||
return call | ||||
def reposetup(self, call): | ||||
"""Decorated function will be executed during reposetup | ||||
example:: | ||||
Kyle Lippincott
|
r47630 | # Required, otherwise your reposetup function(s) will not execute. | ||
reposetup = eh.finalreposetup | ||||
Matt Harbison
|
r41074 | @eh.reposetup | ||
def setupzephir(ui, repo): | ||||
Matt Harbison
|
r46544 | print('this is reposetup!') | ||
Matt Harbison
|
r41074 | """ | ||
self._repocallables.append(call) | ||||
return call | ||||
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:: | ||||
Kyle Lippincott
|
r47630 | # Required if `extension` is not provided | ||
uisetup = eh.finaluisetup | ||||
# Required if `extension` is provided | ||||
extsetup = eh.finalextsetup | ||||
Matt Harbison
|
r46544 | @eh.wrapcommand(b'summary') | ||
Matt Harbison
|
r41074 | def wrapsummary(orig, ui, repo, *args, **kwargs): | ||
Matt Harbison
|
r46544 | ui.note(b'Barry!') | ||
Matt Harbison
|
r41074 | return orig(ui, repo, *args, **kwargs) | ||
Matt Harbison
|
r41090 | The `opts` argument allows specifying a list of tuples for additional | ||
arguments for the command. See ``mercurial.fancyopts.fancyopts()`` for | ||||
the format of the tuple. | ||||
Matt Harbison
|
r41074 | |||
""" | ||||
if opts is None: | ||||
opts = [] | ||||
Matt Harbison
|
r41090 | else: | ||
for opt in opts: | ||||
if not isinstance(opt, tuple): | ||||
Augie Fackler
|
r43347 | raise error.ProgrammingError(b'opts must be list of tuples') | ||
Matt Harbison
|
r41090 | if len(opt) not in (4, 5): | ||
Augie Fackler
|
r43347 | msg = b'each opt tuple must contain 4 or 5 values' | ||
Matt Harbison
|
r41090 | raise error.ProgrammingError(msg) | ||
Matt Harbison
|
r41074 | def dec(wrapper): | ||
if extension is None: | ||||
self._commandwrappers.append((command, wrapper, opts)) | ||||
else: | ||||
Augie Fackler
|
r43346 | self._extcommandwrappers.append( | ||
(extension, command, wrapper, opts) | ||||
) | ||||
Matt Harbison
|
r41074 | return wrapper | ||
Augie Fackler
|
r43346 | |||
Matt Harbison
|
r41074 | 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:: | ||||
Kyle Lippincott
|
r47630 | # Required, otherwise the function will not be wrapped | ||
uisetup = eh.finaluisetup | ||||
r51691 | @eh.wrapfunction(discovery, 'checkheads') | |||
Kyle Lippincott
|
r47630 | def wrapcheckheads(orig, *args, **kwargs): | ||
Matt Harbison
|
r46544 | ui.note(b'His head smashed in and his heart cut out') | ||
Matt Harbison
|
r41074 | return orig(*args, **kwargs) | ||
""" | ||||
Augie Fackler
|
r43346 | |||
Matt Harbison
|
r41074 | def dec(wrapper): | ||
self._functionwrappers.append((container, funcname, wrapper)) | ||||
return wrapper | ||||
Augie Fackler
|
r43346 | |||
Matt Harbison
|
r41074 | return dec | ||