registrar.py
443 lines
| 15.1 KiB
| text/x-python
|
PythonLexer
/ mercurial / registrar.py
FUJIWARA Katsunori
|
r27583 | # registrar.py - utilities to register function for specific purpose | ||
# | ||||
# Copyright FUJIWARA Katsunori <foozy@lares.dti.ne.jp> and others | ||||
# | ||||
# This software may be used and distributed according to the terms of the | ||||
# GNU General Public License version 2 or any later version. | ||||
from __future__ import absolute_import | ||||
from . import ( | ||||
r33127 | configitems, | |||
Pierre-Yves David
|
r30608 | error, | ||
Augie Fackler
|
r30059 | pycompat, | ||
FUJIWARA Katsunori
|
r27583 | util, | ||
) | ||||
r33127 | # unlike the other registered items, config options are neither functions or | |||
# classes. Registering the option is just small function call. | ||||
# | ||||
# We still add the official API to the registrar module for consistency with | ||||
# the other items extensions want might to register. | ||||
configitem = configitems.getitemregister | ||||
FUJIWARA Katsunori
|
r28392 | class _funcregistrarbase(object): | ||
Mads Kiilerich
|
r30332 | """Base of decorator to register a function for specific purpose | ||
FUJIWARA Katsunori
|
r28392 | |||
This decorator stores decorated functions into own dict 'table'. | ||||
The least derived class can be defined by overriding 'formatdoc', | ||||
for example:: | ||||
class keyword(_funcregistrarbase): | ||||
_docformat = ":%s: %s" | ||||
This should be used as below: | ||||
keyword = registrar.keyword() | ||||
@keyword('bar') | ||||
def barfunc(*args, **kwargs): | ||||
'''Explanation of bar keyword .... | ||||
''' | ||||
pass | ||||
In this case: | ||||
- 'barfunc' is stored as 'bar' in '_table' of an instance 'keyword' above | ||||
- 'barfunc.__doc__' becomes ":bar: Explanation of bar keyword" | ||||
""" | ||||
def __init__(self, table=None): | ||||
if table is None: | ||||
self._table = {} | ||||
else: | ||||
self._table = table | ||||
def __call__(self, decl, *args, **kwargs): | ||||
return lambda func: self._doregister(func, decl, *args, **kwargs) | ||||
def _doregister(self, func, decl, *args, **kwargs): | ||||
name = self._getname(decl) | ||||
Pierre-Yves David
|
r30608 | if name in self._table: | ||
msg = 'duplicate registration for name: "%s"' % name | ||||
raise error.ProgrammingError(msg) | ||||
FUJIWARA Katsunori
|
r28392 | if func.__doc__ and not util.safehasattr(func, '_origdoc'): | ||
Yuya Nishihara
|
r31820 | doc = pycompat.sysbytes(func.__doc__).strip() | ||
FUJIWARA Katsunori
|
r28392 | func._origdoc = doc | ||
Yuya Nishihara
|
r31820 | func.__doc__ = pycompat.sysstr(self._formatdoc(decl, doc)) | ||
FUJIWARA Katsunori
|
r28392 | |||
self._table[name] = func | ||||
self._extrasetup(name, func, *args, **kwargs) | ||||
return func | ||||
def _parsefuncdecl(self, decl): | ||||
"""Parse function declaration and return the name of function in it | ||||
""" | ||||
i = decl.find('(') | ||||
if i >= 0: | ||||
return decl[:i] | ||||
else: | ||||
return decl | ||||
def _getname(self, decl): | ||||
"""Return the name of the registered function from decl | ||||
Derived class should override this, if it allows more | ||||
descriptive 'decl' string than just a name. | ||||
""" | ||||
return decl | ||||
_docformat = None | ||||
def _formatdoc(self, decl, doc): | ||||
"""Return formatted document of the registered function for help | ||||
'doc' is '__doc__.strip()' of the registered function. | ||||
""" | ||||
return self._docformat % (decl, doc) | ||||
def _extrasetup(self, name, func): | ||||
"""Execute exra setup for registered function, if needed | ||||
""" | ||||
FUJIWARA Katsunori
|
r28393 | |||
Yuya Nishihara
|
r32338 | class command(_funcregistrarbase): | ||
"""Decorator to register a command function to table | ||||
Yuya Nishihara
|
r32337 | |||
Yuya Nishihara
|
r32338 | This class receives a command table as its argument. The table should | ||
Yuya Nishihara
|
r32337 | be a dict. | ||
Yuya Nishihara
|
r32338 | The created object can be used as a decorator for adding commands to | ||
that command table. This accepts multiple arguments to define a command. | ||||
Yuya Nishihara
|
r32337 | |||
rlevasseur@google.com
|
r35106 | The first argument is the command name (as bytes). | ||
Yuya Nishihara
|
r32337 | |||
rlevasseur@google.com
|
r35106 | The `options` keyword argument is an iterable of tuples defining command | ||
arguments. See ``mercurial.fancyopts.fancyopts()`` for the format of each | ||||
tuple. | ||||
Yuya Nishihara
|
r32337 | |||
rlevasseur@google.com
|
r35106 | The `synopsis` argument defines a short, one line summary of how to use the | ||
Yuya Nishihara
|
r32337 | command. This shows up in the help output. | ||
rlevasseur@google.com
|
r35106 | There are three arguments that control what repository (if any) is found | ||
and passed to the decorated function: `norepo`, `optionalrepo`, and | ||||
`inferrepo`. | ||||
The `norepo` argument defines whether the command does not require a | ||||
Yuya Nishihara
|
r32337 | local repository. Most commands operate against a repository, thus the | ||
rlevasseur@google.com
|
r35106 | default is False. When True, no repository will be passed. | ||
Yuya Nishihara
|
r32337 | |||
rlevasseur@google.com
|
r35106 | The `optionalrepo` argument defines whether the command optionally requires | ||
a local repository. If no repository can be found, None will be passed | ||||
to the decorated function. | ||||
Yuya Nishihara
|
r32337 | |||
rlevasseur@google.com
|
r35106 | The `inferrepo` argument defines whether to try to find a repository from | ||
the command line arguments. If True, arguments will be examined for | ||||
potential repository locations. See ``findrepo()``. If a repository is | ||||
found, it will be used and passed to the decorated function. | ||||
Pulkit Goyal
|
r34782 | |||
There are three constants in the class which tells what type of the command | ||||
that is. That information will be helpful at various places. It will be also | ||||
be used to decide what level of access the command has on hidden commits. | ||||
The constants are: | ||||
rlevasseur@google.com
|
r35106 | `unrecoverablewrite` is for those write commands which can't be recovered | ||
like push. | ||||
`recoverablewrite` is for write commands which can be recovered like commit. | ||||
`readonly` is for commands which are read only. | ||||
The signature of the decorated function looks like this: | ||||
def cmd(ui[, repo] [, <args>] [, <options>]) | ||||
`repo` is required if `norepo` is False. | ||||
`<args>` are positional args (or `*args`) arguments, of non-option | ||||
arguments from the command line. | ||||
`<options>` are keyword arguments (or `**options`) of option arguments | ||||
from the command line. | ||||
See the WritingExtensions and MercurialApi documentation for more exhaustive | ||||
descriptions and examples. | ||||
Yuya Nishihara
|
r32337 | """ | ||
Yuya Nishihara
|
r32338 | |||
Pulkit Goyal
|
r34782 | unrecoverablewrite = "unrecoverable" | ||
recoverablewrite = "recoverable" | ||||
readonly = "readonly" | ||||
Martin von Zweigbergk
|
r34896 | possiblecmdtypes = {unrecoverablewrite, recoverablewrite, readonly} | ||
Yuya Nishihara
|
r32338 | def _doregister(self, func, name, options=(), synopsis=None, | ||
Pulkit Goyal
|
r34782 | norepo=False, optionalrepo=False, inferrepo=False, | ||
cmdtype=unrecoverablewrite): | ||||
Martin von Zweigbergk
|
r34896 | if cmdtype not in self.possiblecmdtypes: | ||
Martin von Zweigbergk
|
r34897 | raise error.ProgrammingError("unknown cmdtype value '%s' for " | ||
"'%s' command" % (cmdtype, name)) | ||||
Yuya Nishihara
|
r32339 | func.norepo = norepo | ||
func.optionalrepo = optionalrepo | ||||
func.inferrepo = inferrepo | ||||
Pulkit Goyal
|
r34782 | func.cmdtype = cmdtype | ||
Yuya Nishihara
|
r32339 | if synopsis: | ||
self._table[name] = func, list(options), synopsis | ||||
else: | ||||
self._table[name] = func, list(options) | ||||
return func | ||||
Yuya Nishihara
|
r32337 | |||
FUJIWARA Katsunori
|
r28393 | class revsetpredicate(_funcregistrarbase): | ||
"""Decorator to register revset predicate | ||||
Usage:: | ||||
revsetpredicate = registrar.revsetpredicate() | ||||
@revsetpredicate('mypredicate(arg1, arg2[, arg3])') | ||||
def mypredicatefunc(repo, subset, x): | ||||
'''Explanation of this revset predicate .... | ||||
''' | ||||
pass | ||||
The first string argument is used also in online help. | ||||
Optional argument 'safe' indicates whether a predicate is safe for | ||||
DoS attack (False by default). | ||||
Yuya Nishihara
|
r29933 | Optional argument 'takeorder' indicates whether a predicate function | ||
takes ordering policy as the last argument. | ||||
Jun Wu
|
r34274 | Optional argument 'weight' indicates the estimated run-time cost, useful | ||
for static optimization, default is 1. Higher weight means more expensive. | ||||
Usually, revsets that are fast and return only one revision has a weight of | ||||
0.5 (ex. a symbol); revsets with O(changelog) complexity and read only the | ||||
changelog have weight 10 (ex. author); revsets reading manifest deltas have | ||||
weight 30 (ex. adds); revset reading manifest contents have weight 100 | ||||
(ex. contains). Note: those values are flexible. If the revset has a | ||||
same big-O time complexity as 'contains', but with a smaller constant, it | ||||
might have a weight of 90. | ||||
FUJIWARA Katsunori
|
r28393 | 'revsetpredicate' instance in example above can be used to | ||
decorate multiple functions. | ||||
Decorated functions are registered automatically at loading | ||||
extension, if an instance named as 'revsetpredicate' is used for | ||||
decorating in extension. | ||||
Otherwise, explicit 'revset.loadpredicate()' is needed. | ||||
""" | ||||
_getname = _funcregistrarbase._parsefuncdecl | ||||
Yuya Nishihara
|
r31820 | _docformat = "``%s``\n %s" | ||
FUJIWARA Katsunori
|
r28393 | |||
Jun Wu
|
r34274 | def _extrasetup(self, name, func, safe=False, takeorder=False, weight=1): | ||
FUJIWARA Katsunori
|
r28393 | func._safe = safe | ||
Yuya Nishihara
|
r29933 | func._takeorder = takeorder | ||
Jun Wu
|
r34274 | func._weight = weight | ||
FUJIWARA Katsunori
|
r28447 | |||
class filesetpredicate(_funcregistrarbase): | ||||
"""Decorator to register fileset predicate | ||||
Usage:: | ||||
filesetpredicate = registrar.filesetpredicate() | ||||
@filesetpredicate('mypredicate()') | ||||
def mypredicatefunc(mctx, x): | ||||
'''Explanation of this fileset predicate .... | ||||
''' | ||||
pass | ||||
The first string argument is used also in online help. | ||||
Optional argument 'callstatus' indicates whether a predicate | ||||
implies 'matchctx.status()' at runtime or not (False, by | ||||
default). | ||||
Optional argument 'callexisting' indicates whether a predicate | ||||
implies 'matchctx.existing()' at runtime or not (False, by | ||||
default). | ||||
'filesetpredicate' instance in example above can be used to | ||||
decorate multiple functions. | ||||
Decorated functions are registered automatically at loading | ||||
extension, if an instance named as 'filesetpredicate' is used for | ||||
decorating in extension. | ||||
Otherwise, explicit 'fileset.loadpredicate()' is needed. | ||||
""" | ||||
_getname = _funcregistrarbase._parsefuncdecl | ||||
Yuya Nishihara
|
r31820 | _docformat = "``%s``\n %s" | ||
FUJIWARA Katsunori
|
r28447 | |||
def _extrasetup(self, name, func, callstatus=False, callexisting=False): | ||||
func._callstatus = callstatus | ||||
func._callexisting = callexisting | ||||
FUJIWARA Katsunori
|
r28538 | |||
class _templateregistrarbase(_funcregistrarbase): | ||||
"""Base of decorator to register functions as template specific one | ||||
""" | ||||
Yuya Nishihara
|
r31820 | _docformat = ":%s: %s" | ||
FUJIWARA Katsunori
|
r28538 | |||
class templatekeyword(_templateregistrarbase): | ||||
"""Decorator to register template keyword | ||||
Usage:: | ||||
Mads Kiilerich
|
r30332 | templatekeyword = registrar.templatekeyword() | ||
FUJIWARA Katsunori
|
r28538 | |||
Yuya Nishihara
|
r36463 | # new API (since Mercurial 4.6) | ||
@templatekeyword('mykeyword', requires={'repo', 'ctx'}) | ||||
def mykeywordfunc(context, mapping): | ||||
'''Explanation of this template keyword .... | ||||
''' | ||||
pass | ||||
# old API | ||||
FUJIWARA Katsunori
|
r28538 | @templatekeyword('mykeyword') | ||
def mykeywordfunc(repo, ctx, templ, cache, revcache, **args): | ||||
'''Explanation of this template keyword .... | ||||
''' | ||||
pass | ||||
The first string argument is used also in online help. | ||||
Yuya Nishihara
|
r36463 | Optional argument 'requires' should be a collection of resource names | ||
which the template keyword depends on. This also serves as a flag to | ||||
switch to the new API. If 'requires' is unspecified, all template | ||||
keywords and resources are expanded to the function arguments. | ||||
FUJIWARA Katsunori
|
r28538 | 'templatekeyword' instance in example above can be used to | ||
decorate multiple functions. | ||||
Decorated functions are registered automatically at loading | ||||
extension, if an instance named as 'templatekeyword' is used for | ||||
decorating in extension. | ||||
Otherwise, explicit 'templatekw.loadkeyword()' is needed. | ||||
""" | ||||
FUJIWARA Katsunori
|
r28692 | |||
Yuya Nishihara
|
r36463 | def _extrasetup(self, name, func, requires=None): | ||
func._requires = requires | ||||
FUJIWARA Katsunori
|
r28692 | class templatefilter(_templateregistrarbase): | ||
"""Decorator to register template filer | ||||
Usage:: | ||||
templatefilter = registrar.templatefilter() | ||||
Yuya Nishihara
|
r37239 | @templatefilter('myfilter', intype=bytes) | ||
FUJIWARA Katsunori
|
r28692 | def myfilterfunc(text): | ||
'''Explanation of this template filter .... | ||||
''' | ||||
pass | ||||
The first string argument is used also in online help. | ||||
Yuya Nishihara
|
r37239 | Optional argument 'intype' defines the type of the input argument, | ||
Yuya Nishihara
|
r37244 | which should be (bytes, int, templateutil.date, or None for any.) | ||
Yuya Nishihara
|
r37239 | |||
FUJIWARA Katsunori
|
r28692 | 'templatefilter' instance in example above can be used to | ||
decorate multiple functions. | ||||
Decorated functions are registered automatically at loading | ||||
extension, if an instance named as 'templatefilter' is used for | ||||
decorating in extension. | ||||
Otherwise, explicit 'templatefilters.loadkeyword()' is needed. | ||||
""" | ||||
FUJIWARA Katsunori
|
r28695 | |||
Yuya Nishihara
|
r37239 | def _extrasetup(self, name, func, intype=None): | ||
func._intype = intype | ||||
FUJIWARA Katsunori
|
r28695 | class templatefunc(_templateregistrarbase): | ||
"""Decorator to register template function | ||||
Usage:: | ||||
templatefunc = registrar.templatefunc() | ||||
Yuya Nishihara
|
r31886 | @templatefunc('myfunc(arg1, arg2[, arg3])', argspec='arg1 arg2 arg3') | ||
FUJIWARA Katsunori
|
r28695 | def myfuncfunc(context, mapping, args): | ||
'''Explanation of this template function .... | ||||
''' | ||||
pass | ||||
The first string argument is used also in online help. | ||||
Yuya Nishihara
|
r31886 | If optional 'argspec' is defined, the function will receive 'args' as | ||
a dict of named arguments. Otherwise 'args' is a list of positional | ||||
arguments. | ||||
FUJIWARA Katsunori
|
r28695 | 'templatefunc' instance in example above can be used to | ||
decorate multiple functions. | ||||
Decorated functions are registered automatically at loading | ||||
extension, if an instance named as 'templatefunc' is used for | ||||
decorating in extension. | ||||
Yuya Nishihara
|
r36940 | Otherwise, explicit 'templatefuncs.loadfunction()' is needed. | ||
FUJIWARA Katsunori
|
r28695 | """ | ||
_getname = _funcregistrarbase._parsefuncdecl | ||||
Yuya Nishihara
|
r31886 | |||
def _extrasetup(self, name, func, argspec=None): | ||||
func._argspec = argspec | ||||
FUJIWARA Katsunori
|
r33663 | |||
class internalmerge(_funcregistrarbase): | ||||
"""Decorator to register in-process merge tool | ||||
Usage:: | ||||
internalmerge = registrar.internalmerge() | ||||
@internalmerge('mymerge', internalmerge.mergeonly, | ||||
onfailure=None, precheck=None): | ||||
def mymergefunc(repo, mynode, orig, fcd, fco, fca, | ||||
toolconf, files, labels=None): | ||||
'''Explanation of this internal merge tool .... | ||||
''' | ||||
return 1, False # means "conflicted", "no deletion needed" | ||||
The first string argument is used to compose actual merge tool name, | ||||
":name" and "internal:name" (the latter is historical one). | ||||
The second argument is one of merge types below: | ||||
========== ======== ======== ========= | ||||
merge type precheck premerge fullmerge | ||||
========== ======== ======== ========= | ||||
nomerge x x x | ||||
mergeonly o x o | ||||
fullmerge o o o | ||||
========== ======== ======== ========= | ||||
Saurabh Singh
|
r34437 | Optional argument 'onfailure' is the format of warning message | ||
FUJIWARA Katsunori
|
r33663 | to be used at failure of merging (target filename is specified | ||
at formatting). Or, None or so, if warning message should be | ||||
suppressed. | ||||
Optional argument 'precheck' is the function to be used | ||||
before actual invocation of internal merge tool itself. | ||||
It takes as same arguments as internal merge tool does, other than | ||||
'files' and 'labels'. If it returns false value, merging is aborted | ||||
immediately (and file is marked as "unresolved"). | ||||
'internalmerge' instance in example above can be used to | ||||
decorate multiple functions. | ||||
Decorated functions are registered automatically at loading | ||||
extension, if an instance named as 'internalmerge' is used for | ||||
decorating in extension. | ||||
Otherwise, explicit 'filemerge.loadinternalmerge()' is needed. | ||||
""" | ||||
_docformat = "``:%s``\n %s" | ||||
# merge type definitions: | ||||
nomerge = None | ||||
mergeonly = 'mergeonly' # just the full merge, no premerge | ||||
fullmerge = 'fullmerge' # both premerge and merge | ||||
def _extrasetup(self, name, func, mergetype, | ||||
onfailure=None, precheck=None): | ||||
func.mergetype = mergetype | ||||
func.onfailure = onfailure | ||||
func.precheck = precheck | ||||