registrar.py
486 lines
| 17.0 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 | |||
Gregory Szorc
|
r37734 | The `intents` argument defines a set of intended actions or capabilities | ||
the command is taking. These intents can be used to affect the construction | ||||
of the repository object passed to the command. For example, commands | ||||
declaring that they are read-only could receive a repository that doesn't | ||||
have any methods allowing repository mutation. Other intents could be used | ||||
to prevent the command from running if the requested intent could not be | ||||
fulfilled. | ||||
Pulkit Goyal
|
r34782 | |||
rdamazio@google.com
|
r40327 | If `helpcategory` is set (usually to one of the constants in the help | ||
module), the command will be displayed under that category in the help's | ||||
list of commands. | ||||
Gregory Szorc
|
r37734 | The following intents are defined: | ||
readonly | ||||
The command is read-only | ||||
rlevasseur@google.com
|
r35106 | |||
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 | |||
rdamazio@google.com
|
r40327 | # Command categories for grouping them in help output. | ||
rdamazio@google.com
|
r40329 | CATEGORY_REPO_CREATION = 'repo' | ||
CATEGORY_REMOTE_REPO_MANAGEMENT = 'remote' | ||||
CATEGORY_COMMITTING = 'commit' | ||||
CATEGORY_CHANGE_MANAGEMENT = 'management' | ||||
CATEGORY_CHANGE_ORGANIZATION = 'organization' | ||||
CATEGORY_FILE_CONTENTS = 'files' | ||||
CATEGORY_CHANGE_NAVIGATION = 'navigation' | ||||
CATEGORY_WORKING_DIRECTORY = 'wdir' | ||||
CATEGORY_IMPORT_EXPORT = 'import' | ||||
CATEGORY_MAINTENANCE = 'maintenance' | ||||
CATEGORY_HELP = 'help' | ||||
CATEGORY_MISC = 'misc' | ||||
rdamazio@google.com
|
r40327 | CATEGORY_NONE = 'none' | ||
Yuya Nishihara
|
r32338 | def _doregister(self, func, name, options=(), synopsis=None, | ||
Pulkit Goyal
|
r34782 | norepo=False, optionalrepo=False, inferrepo=False, | ||
rdamazio@google.com
|
r40327 | intents=None, helpcategory=None): | ||
Yuya Nishihara
|
r32339 | func.norepo = norepo | ||
func.optionalrepo = optionalrepo | ||||
func.inferrepo = inferrepo | ||||
Gregory Szorc
|
r37734 | func.intents = intents or set() | ||
rdamazio@google.com
|
r40327 | func.helpcategory = helpcategory | ||
Yuya Nishihara
|
r32339 | if synopsis: | ||
self._table[name] = func, list(options), synopsis | ||||
else: | ||||
self._table[name] = func, list(options) | ||||
return func | ||||
Yuya Nishihara
|
r32337 | |||
Gregory Szorc
|
r37734 | INTENT_READONLY = b'readonly' | ||
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). | ||||
Yuya Nishihara
|
r38865 | Optional argument 'weight' indicates the estimated run-time cost, useful | ||
for static optimization, default is 1. Higher weight means more expensive. | ||||
Yuya Nishihara
|
r38899 | There are predefined weights in the 'filesetlang' module. | ||
Yuya Nishihara
|
r38865 | |||
Yuya Nishihara
|
r38866 | ====== ============================================================= | ||
Weight Description and examples | ||||
====== ============================================================= | ||||
0.5 basic match patterns (e.g. a symbol) | ||||
10 computing status (e.g. added()) or accessing a few files | ||||
30 reading file content for each (e.g. grep()) | ||||
50 scanning working directory (ignored()) | ||||
====== ============================================================= | ||||
FUJIWARA Katsunori
|
r28447 | '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 | |||
Yuya Nishihara
|
r38865 | def _extrasetup(self, name, func, callstatus=False, weight=1): | ||
FUJIWARA Katsunori
|
r28447 | func._callstatus = callstatus | ||
Yuya Nishihara
|
r38865 | func._weight = weight | ||
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 | ||||
Yuya Nishihara
|
r38966 | # old API (DEPRECATED) | ||
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
|
r38447 | @templatefunc('myfunc(arg1, arg2[, arg3])', argspec='arg1 arg2 arg3', | ||
requires={'ctx'}) | ||||
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. | ||||
Yuya Nishihara
|
r38447 | Optional argument 'requires' should be a collection of resource names | ||
which the template function depends on. | ||||
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 | |||
Yuya Nishihara
|
r38447 | def _extrasetup(self, name, func, argspec=None, requires=()): | ||
Yuya Nishihara
|
r31886 | func._argspec = argspec | ||
Yuya Nishihara
|
r38447 | func._requires = requires | ||
FUJIWARA Katsunori
|
r33663 | |||
class internalmerge(_funcregistrarbase): | ||||
"""Decorator to register in-process merge tool | ||||
Usage:: | ||||
internalmerge = registrar.internalmerge() | ||||
@internalmerge('mymerge', internalmerge.mergeonly, | ||||
FUJIWARA Katsunori
|
r39158 | onfailure=None, precheck=None, | ||
binary=False, symlink=False): | ||||
FUJIWARA Katsunori
|
r33663 | 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"). | ||||
FUJIWARA Katsunori
|
r39158 | Optional argument 'binary' is a binary files capability of internal | ||
merge tool. 'nomerge' merge type implies binary=True. | ||||
Optional argument 'symlink' is a symlinks capability of inetrnal | ||||
merge function. 'nomerge' merge type implies symlink=True. | ||||
FUJIWARA Katsunori
|
r33663 | '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, | ||||
FUJIWARA Katsunori
|
r39158 | onfailure=None, precheck=None, | ||
binary=False, symlink=False): | ||||
FUJIWARA Katsunori
|
r33663 | func.mergetype = mergetype | ||
func.onfailure = onfailure | ||||
func.precheck = precheck | ||||
FUJIWARA Katsunori
|
r39158 | |||
binarycap = binary or mergetype == self.nomerge | ||||
symlinkcap = symlink or mergetype == self.nomerge | ||||
# actual capabilities, which this internal merge tool has | ||||
func.capabilities = {"binary": binarycap, "symlink": symlinkcap} | ||||