##// END OF EJS Templates
discovery: slowly increase sampling size...
discovery: slowly increase sampling size Some pathological discovery runs can requires many roundtrip. When this happens things can get very slow. To make the algorithm more resilience again such pathological case. We slowly increase the sample size with each roundtrip (+5%). This will have a negligible impact on "normal" discovery with few roundtrips, but a large positive impact of case with many roundtrips. Asking more question per roundtrip helps to reduce the undecided set faster. Instead of reducing the undecided set a linear speed (in the worst case), we reduce it as a guaranteed (small) exponential rate. The data below show this slow ramp up in sample size: round trip | 1 | 5 | 10 | 20 | 50 | 100 | 130 | sample size | 200 | 254 | 321 | 517 | 2 199 | 25 123 | 108 549 | covered nodes | 200 | 1 357 | 2 821 | 7 031 | 42 658 | 524 530 | 2 276 755 | To be a bit more concrete, lets take a very pathological case as an example. We are doing discovery from a copy of Mozilla-try to a more recent version of mozilla-unified. Mozilla-unified heads are unknown to the mozilla-try repo and there are over 1 million "missing" changesets. (the discovery is "local" to avoid network interference) Without this change, the discovery: - last 1858 seconds (31 minutes), - does 1700 round trip, - asking about 340 000 nodes. With this change, the discovery: - last 218 seconds (3 minutes, 38 seconds a -88% improvement), - does 94 round trip (-94%), - asking about 344 211 nodes (+1%). Of course, this is an extreme case (and 3 minutes is still slow). However this give a good example of how this sample size increase act as a safety net catching any bad situations. We could image a steeper increase than 5%. For example 10% would give the following number: round trip | 1 | 5 | 10 | 20 | 50 | 75 | 100 | sample size | 200 | 321 | 514 | 1 326 | 23 060 | 249 812 | 2 706 594 | covered nodes | 200 | 1 541 | 3 690 | 12 671 | 251 871 | 2 746 254 | 29 770 966 | In parallel, it is useful to understand these pathological cases and improve them. However the current change provides a general purpose safety net to smooth the impact of pathological cases. To avoid issue with older http server, the increase in sample size only occurs if the protocol has not limit on command argument size.

File last commit:

r41869:67046961 default
r42546:dbd0fcca default
Show More
templatefuncs.py
790 lines | 28.1 KiB | text/x-python | PythonLexer
Yuya Nishihara
templater: split template functions to new module...
r36940 # templatefuncs.py - common template functions
#
# Copyright 2005, 2006 Matt Mackall <mpm@selenic.com>
#
# 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
import re
from .i18n import _
Martin von Zweigbergk
scmutil: make shortesthexnodeidprefix() take a full binary nodeid...
r37727 from .node import (
bin,
Martin von Zweigbergk
shortest: make {shortest("fffffffff")} work again...
r37876 wdirid,
Martin von Zweigbergk
scmutil: make shortesthexnodeidprefix() take a full binary nodeid...
r37727 )
Yuya Nishihara
templater: split template functions to new module...
r36940 from . import (
color,
encoding,
error,
minirst,
obsutil,
Yuya Nishihara
templatefuncs: add regexp search() function that extracts substring...
r40970 pycompat,
Yuya Nishihara
templater: split template functions to new module...
r36940 registrar,
revset as revsetmod,
revsetlang,
scmutil,
templatefilters,
templatekw,
templateutil,
util,
)
Connor Sheehan
templatefuncs: add mailmap template function...
r37227 from .utils import (
dateutil,
stringutil,
)
Yuya Nishihara
templater: split template functions to new module...
r36940
evalrawexp = templateutil.evalrawexp
Yuya Nishihara
templater: always join() over a wrapped object (BC)...
r38229 evalwrapped = templateutil.evalwrapped
Yuya Nishihara
templater: split template functions to new module...
r36940 evalfuncarg = templateutil.evalfuncarg
evalboolean = templateutil.evalboolean
Yuya Nishihara
templater: factor out function that parses argument as date tuple
r37241 evaldate = templateutil.evaldate
Yuya Nishihara
templater: split template functions to new module...
r36940 evalinteger = templateutil.evalinteger
evalstring = templateutil.evalstring
evalstringliteral = templateutil.evalstringliteral
# dict of template built-in functions
funcs = {}
templatefunc = registrar.templatefunc(funcs)
@templatefunc('date(date[, fmt])')
def date(context, mapping, args):
"""Format a date. See :hg:`help dates` for formatting
strings. The default is a Unix date format, including the timezone:
"Mon Sep 04 15:13:13 2006 0700"."""
if not (1 <= len(args) <= 2):
# i18n: "date" is a keyword
raise error.ParseError(_("date expects one or two arguments"))
Yuya Nishihara
templatefuncs: use evaldate() where seems appropriate...
r37242 date = evaldate(context, mapping, args[0],
# i18n: "date" is a keyword
_("date expects a date information"))
Yuya Nishihara
templater: split template functions to new module...
r36940 fmt = None
if len(args) == 2:
fmt = evalstring(context, mapping, args[1])
Yuya Nishihara
templatefuncs: use evaldate() where seems appropriate...
r37242 if fmt is None:
return dateutil.datestr(date)
else:
return dateutil.datestr(date, fmt)
Yuya Nishihara
templater: split template functions to new module...
r36940
@templatefunc('dict([[key=]value...])', argspec='*args **kwargs')
def dict_(context, mapping, args):
"""Construct a dict from key-value pairs. A key may be omitted if
a value expression can provide an unambiguous name."""
data = util.sortdict()
for v in args['args']:
k = templateutil.findsymbolicname(v)
if not k:
raise error.ParseError(_('dict key cannot be inferred'))
if k in data or k in args['kwargs']:
raise error.ParseError(_("duplicated dict key '%s' inferred") % k)
data[k] = evalfuncarg(context, mapping, v)
data.update((k, evalfuncarg(context, mapping, v))
for k, v in args['kwargs'].iteritems())
return templateutil.hybriddict(data)
Yuya Nishihara
templatefuncs: declare resource requirements for future use
r38447 @templatefunc('diff([includepattern [, excludepattern]])', requires={'ctx'})
Yuya Nishihara
templater: split template functions to new module...
r36940 def diff(context, mapping, args):
"""Show a diff, optionally
specifying files to include or exclude."""
if len(args) > 2:
# i18n: "diff" is a keyword
raise error.ParseError(_("diff expects zero, one, or two arguments"))
def getpatterns(i):
if i < len(args):
s = evalstring(context, mapping, args[i]).strip()
if s:
return [s]
return []
ctx = context.resource(mapping, 'ctx')
chunks = ctx.diff(match=ctx.match([], getpatterns(0), getpatterns(1)))
return ''.join(chunks)
Yuya Nishihara
templatefuncs: declare resource requirements for future use
r38447 @templatefunc('extdata(source)', argspec='source', requires={'ctx', 'cache'})
Yuya Nishihara
templater: split template functions to new module...
r36940 def extdata(context, mapping, args):
"""Show a text read from the specified extdata source. (EXPERIMENTAL)"""
if 'source' not in args:
# i18n: "extdata" is a keyword
raise error.ParseError(_('extdata expects one argument'))
source = evalstring(context, mapping, args['source'])
Yuya Nishihara
templatefuncs: show hint if extdata source is evaluated to empty (issue5843)
r37950 if not source:
sym = templateutil.findsymbolicname(args['source'])
if sym:
raise error.ParseError(_('empty data source specified'),
hint=_("did you mean extdata('%s')?") % sym)
else:
raise error.ParseError(_('empty data source specified'))
Yuya Nishihara
templater: split template functions to new module...
r36940 cache = context.resource(mapping, 'cache').setdefault('extdata', {})
ctx = context.resource(mapping, 'ctx')
if source in cache:
data = cache[source]
else:
data = cache[source] = scmutil.extdatasource(ctx.repo(), source)
return data.get(ctx.rev(), '')
Yuya Nishihara
templatefuncs: declare resource requirements for future use
r38447 @templatefunc('files(pattern)', requires={'ctx'})
Yuya Nishihara
templater: split template functions to new module...
r36940 def files(context, mapping, args):
"""All files of the current changeset matching the pattern. See
:hg:`help patterns`."""
if not len(args) == 1:
# i18n: "files" is a keyword
raise error.ParseError(_("files expects one argument"))
raw = evalstring(context, mapping, args[0])
ctx = context.resource(mapping, 'ctx')
m = ctx.match([raw])
files = list(ctx.matches(m))
Yuya Nishihara
templatekw: alias {file} of files list to {path}...
r39403 return templateutil.compatfileslist(context, mapping, "file", files)
Yuya Nishihara
templater: split template functions to new module...
r36940
@templatefunc('fill(text[, width[, initialident[, hangindent]]])')
def fill(context, mapping, args):
"""Fill many
paragraphs with optional indentation. See the "fill" filter."""
if not (1 <= len(args) <= 4):
# i18n: "fill" is a keyword
raise error.ParseError(_("fill expects one to four arguments"))
text = evalstring(context, mapping, args[0])
width = 76
initindent = ''
hangindent = ''
if 2 <= len(args) <= 4:
width = evalinteger(context, mapping, args[1],
# i18n: "fill" is a keyword
_("fill expects an integer width"))
try:
initindent = evalstring(context, mapping, args[2])
hangindent = evalstring(context, mapping, args[3])
except IndexError:
pass
return templatefilters.fill(text, width, initindent, hangindent)
Yuya Nishihara
templater: extend filter() to accept template expression for emptiness test...
r38468 @templatefunc('filter(iterable[, expr])')
Yuya Nishihara
templater: introduce filter() function to remove empty items from list...
r38467 def filter_(context, mapping, args):
Yuya Nishihara
templater: extend filter() to accept template expression for emptiness test...
r38468 """Remove empty elements from a list or a dict. If expr specified, it's
applied to each element to test emptiness."""
if not (1 <= len(args) <= 2):
Yuya Nishihara
templater: introduce filter() function to remove empty items from list...
r38467 # i18n: "filter" is a keyword
Yuya Nishihara
templater: extend filter() to accept template expression for emptiness test...
r38468 raise error.ParseError(_("filter expects one or two arguments"))
Yuya Nishihara
templater: introduce filter() function to remove empty items from list...
r38467 iterable = evalwrapped(context, mapping, args[0])
Yuya Nishihara
templater: extend filter() to accept template expression for emptiness test...
r38468 if len(args) == 1:
def select(w):
return w.tobool(context, mapping)
else:
def select(w):
if not isinstance(w, templateutil.mappable):
raise error.ParseError(_("not filterable by expression"))
lm = context.overlaymap(mapping, w.tomap(context))
return evalboolean(context, lm, args[1])
Yuya Nishihara
templater: introduce filter() function to remove empty items from list...
r38467 return iterable.filter(context, mapping, select)
Yuya Nishihara
templatefuncs: declare resource requirements for future use
r38447 @templatefunc('formatnode(node)', requires={'ui'})
Yuya Nishihara
templater: split template functions to new module...
r36940 def formatnode(context, mapping, args):
"""Obtain the preferred form of a changeset hash. (DEPRECATED)"""
if len(args) != 1:
# i18n: "formatnode" is a keyword
raise error.ParseError(_("formatnode expects one argument"))
ui = context.resource(mapping, 'ui')
node = evalstring(context, mapping, args[0])
if ui.debugflag:
return node
return templatefilters.short(node)
Yuya Nishihara
templatefuncs: declare resource requirements for future use
r38447 @templatefunc('mailmap(author)', requires={'repo', 'cache'})
Connor Sheehan
templatefuncs: add mailmap template function...
r37227 def mailmap(context, mapping, args):
"""Return the author, updated according to the value
set in the .mailmap file"""
if len(args) != 1:
raise error.ParseError(_("mailmap expects one argument"))
Yuya Nishihara
templatefuncs: do not crash because of invalid value fed to mailmap()
r37277 author = evalstring(context, mapping, args[0])
Connor Sheehan
templatefuncs: add mailmap template function...
r37227
cache = context.resource(mapping, 'cache')
repo = context.resource(mapping, 'repo')
if 'mailmap' not in cache:
data = repo.wvfs.tryread('.mailmap')
cache['mailmap'] = stringutil.parsemailmap(data)
Connor Sheehan
templatefuncs: remove redundant "or author" from mailmap return statement...
r37261 return stringutil.mapname(cache['mailmap'], author)
Connor Sheehan
templatefuncs: add mailmap template function...
r37227
Mark Thomas
templatefuncs: add truncate parameter to pad...
r40225 @templatefunc(
'pad(text, width[, fillchar=\' \'[, left=False[, truncate=False]]])',
argspec='text width fillchar left truncate')
Yuya Nishihara
templater: split template functions to new module...
r36940 def pad(context, mapping, args):
"""Pad text with a
fill character."""
if 'text' not in args or 'width' not in args:
# i18n: "pad" is a keyword
raise error.ParseError(_("pad() expects two to four arguments"))
width = evalinteger(context, mapping, args['width'],
# i18n: "pad" is a keyword
_("pad() expects an integer width"))
text = evalstring(context, mapping, args['text'])
Mark Thomas
templatefuncs: add truncate parameter to pad...
r40225 truncate = False
Yuya Nishihara
templater: split template functions to new module...
r36940 left = False
fillchar = ' '
if 'fillchar' in args:
fillchar = evalstring(context, mapping, args['fillchar'])
if len(color.stripeffects(fillchar)) != 1:
# i18n: "pad" is a keyword
raise error.ParseError(_("pad() expects a single fill character"))
if 'left' in args:
left = evalboolean(context, mapping, args['left'])
Mark Thomas
templatefuncs: add truncate parameter to pad...
r40225 if 'truncate' in args:
truncate = evalboolean(context, mapping, args['truncate'])
Yuya Nishihara
templater: split template functions to new module...
r36940
fillwidth = width - encoding.colwidth(color.stripeffects(text))
Mark Thomas
templatefuncs: add truncate parameter to pad...
r40225 if fillwidth < 0 and truncate:
return encoding.trim(color.stripeffects(text), width, leftside=left)
Yuya Nishihara
templater: split template functions to new module...
r36940 if fillwidth <= 0:
return text
if left:
return fillchar * fillwidth + text
else:
return text + fillchar * fillwidth
@templatefunc('indent(text, indentchars[, firstline])')
def indent(context, mapping, args):
"""Indents all non-empty lines
with the characters given in the indentchars string. An optional
third parameter will override the indent for the first line only
if present."""
if not (2 <= len(args) <= 3):
# i18n: "indent" is a keyword
raise error.ParseError(_("indent() expects two or three arguments"))
text = evalstring(context, mapping, args[0])
indent = evalstring(context, mapping, args[1])
if len(args) == 3:
firstline = evalstring(context, mapping, args[2])
else:
firstline = indent
# the indent function doesn't indent the first line, so we do it here
return templatefilters.indent(firstline + text, indent)
@templatefunc('get(dict, key)')
def get(context, mapping, args):
"""Get an attribute/key from an object. Some keywords
are complex types. This function allows you to obtain the value of an
attribute on these types."""
if len(args) != 2:
# i18n: "get" is a keyword
raise error.ParseError(_("get() expects two arguments"))
Yuya Nishihara
templater: do dict lookup over a wrapped object...
r38258 dictarg = evalwrapped(context, mapping, args[0])
Yuya Nishihara
templater: resolve type of dict key in getmember()...
r38262 key = evalrawexp(context, mapping, args[1])
Yuya Nishihara
templater: promote getmember() to an interface of wrapped types
r38261 try:
return dictarg.getmember(context, mapping, key)
except error.ParseError as err:
Yuya Nishihara
templater: split template functions to new module...
r36940 # i18n: "get" is a keyword
Yuya Nishihara
templater: promote getmember() to an interface of wrapped types
r38261 hint = _("get() expects a dict as first argument")
raise error.ParseError(bytes(err), hint=hint)
Yuya Nishihara
templater: split template functions to new module...
r36940
rdamazio@google.com
templates: adding a config() function for template customization...
r41869 @templatefunc('config(section, name[, default])', requires={'ui'})
def config(context, mapping, args):
"""Returns the requested hgrc config option as a string."""
fn = context.resource(mapping, 'ui').config
return _config(context, mapping, args, fn, evalstring)
@templatefunc('configbool(section, name[, default])', requires={'ui'})
def configbool(context, mapping, args):
"""Returns the requested hgrc config option as a boolean."""
fn = context.resource(mapping, 'ui').configbool
return _config(context, mapping, args, fn, evalboolean)
@templatefunc('configint(section, name[, default])', requires={'ui'})
def configint(context, mapping, args):
"""Returns the requested hgrc config option as an integer."""
fn = context.resource(mapping, 'ui').configint
return _config(context, mapping, args, fn, evalinteger)
def _config(context, mapping, args, configfn, defaultfn):
if not (2 <= len(args) <= 3):
raise error.ParseError(_("config expects two or three arguments"))
# The config option can come from any section, though we specifically
# reserve the [templateconfig] section for dynamically defining options
# for this function without also requiring an extension.
section = evalstringliteral(context, mapping, args[0])
name = evalstringliteral(context, mapping, args[1])
if len(args) == 3:
default = defaultfn(context, mapping, args[2])
return configfn(section, name, default)
else:
return configfn(section, name)
Yuya Nishihara
templater: split template functions to new module...
r36940 @templatefunc('if(expr, then[, else])')
def if_(context, mapping, args):
"""Conditionally execute based on the result of
an expression."""
if not (2 <= len(args) <= 3):
# i18n: "if" is a keyword
raise error.ParseError(_("if expects two or three arguments"))
test = evalboolean(context, mapping, args[0])
if test:
Yuya Nishihara
templatefuncs: do not stringify result of if*() expression...
r37033 return evalrawexp(context, mapping, args[1])
Yuya Nishihara
templater: split template functions to new module...
r36940 elif len(args) == 3:
Yuya Nishihara
templatefuncs: do not stringify result of if*() expression...
r37033 return evalrawexp(context, mapping, args[2])
Yuya Nishihara
templater: split template functions to new module...
r36940
@templatefunc('ifcontains(needle, haystack, then[, else])')
def ifcontains(context, mapping, args):
"""Conditionally execute based
on whether the item "needle" is in "haystack"."""
if not (3 <= len(args) <= 4):
# i18n: "ifcontains" is a keyword
raise error.ParseError(_("ifcontains expects three or four arguments"))
Yuya Nishihara
templater: abstract ifcontains() over wrapped types...
r38286 haystack = evalwrapped(context, mapping, args[1])
Yuya Nishihara
templater: split template functions to new module...
r36940 try:
Yuya Nishihara
templater: factor out unwrapastype() from evalastype()...
r37180 needle = evalrawexp(context, mapping, args[0])
Yuya Nishihara
templater: abstract ifcontains() over wrapped types...
r38286 found = haystack.contains(context, mapping, needle)
Yuya Nishihara
templater: split template functions to new module...
r36940 except error.ParseError:
found = False
if found:
Yuya Nishihara
templatefuncs: do not stringify result of if*() expression...
r37033 return evalrawexp(context, mapping, args[2])
Yuya Nishihara
templater: split template functions to new module...
r36940 elif len(args) == 4:
Yuya Nishihara
templatefuncs: do not stringify result of if*() expression...
r37033 return evalrawexp(context, mapping, args[3])
Yuya Nishihara
templater: split template functions to new module...
r36940
@templatefunc('ifeq(expr1, expr2, then[, else])')
def ifeq(context, mapping, args):
"""Conditionally execute based on
whether 2 items are equivalent."""
if not (3 <= len(args) <= 4):
# i18n: "ifeq" is a keyword
raise error.ParseError(_("ifeq expects three or four arguments"))
test = evalstring(context, mapping, args[0])
match = evalstring(context, mapping, args[1])
if test == match:
Yuya Nishihara
templatefuncs: do not stringify result of if*() expression...
r37033 return evalrawexp(context, mapping, args[2])
Yuya Nishihara
templater: split template functions to new module...
r36940 elif len(args) == 4:
Yuya Nishihara
templatefuncs: do not stringify result of if*() expression...
r37033 return evalrawexp(context, mapping, args[3])
Yuya Nishihara
templater: split template functions to new module...
r36940
@templatefunc('join(list, sep)')
def join(context, mapping, args):
"""Join items in a list with a delimiter."""
if not (1 <= len(args) <= 2):
# i18n: "join" is a keyword
raise error.ParseError(_("join expects one or two arguments"))
Yuya Nishihara
templater: always join() over a wrapped object (BC)...
r38229 joinset = evalwrapped(context, mapping, args[0])
Yuya Nishihara
templater: split template functions to new module...
r36940 joiner = " "
if len(args) > 1:
joiner = evalstring(context, mapping, args[1])
Yuya Nishihara
templater: always join() over a wrapped object (BC)...
r38229 return joinset.join(context, mapping, joiner)
Yuya Nishihara
templater: split template functions to new module...
r36940
Yuya Nishihara
templatefuncs: declare resource requirements for future use
r38447 @templatefunc('label(label, expr)', requires={'ui'})
Yuya Nishihara
templater: split template functions to new module...
r36940 def label(context, mapping, args):
"""Apply a label to generated content. Content with
a label applied can result in additional post-processing, such as
automatic colorization."""
if len(args) != 2:
# i18n: "label" is a keyword
raise error.ParseError(_("label expects two arguments"))
ui = context.resource(mapping, 'ui')
thing = evalstring(context, mapping, args[1])
# preserve unknown symbol as literal so effects like 'red', 'bold',
# etc. don't need to be quoted
label = evalstringliteral(context, mapping, args[0])
return ui.label(thing, label)
@templatefunc('latesttag([pattern])')
def latesttag(context, mapping, args):
"""The global tags matching the given pattern on the
most recent globally tagged ancestor of this changeset.
If no such tags exist, the "{tag}" template resolves to
Yuya Nishihara
help: mention pattern syntax of latesttag() template function
r38161 the string "null". See :hg:`help revisions.patterns` for the pattern
syntax.
"""
Yuya Nishihara
templater: split template functions to new module...
r36940 if len(args) > 1:
# i18n: "latesttag" is a keyword
raise error.ParseError(_("latesttag expects at most one argument"))
pattern = None
if len(args) == 1:
pattern = evalstring(context, mapping, args[0])
return templatekw.showlatesttags(context, mapping, pattern)
@templatefunc('localdate(date[, tz])')
def localdate(context, mapping, args):
"""Converts a date to the specified timezone.
The default is local date."""
if not (1 <= len(args) <= 2):
# i18n: "localdate" is a keyword
raise error.ParseError(_("localdate expects one or two arguments"))
Yuya Nishihara
templater: factor out function that parses argument as date tuple
r37241 date = evaldate(context, mapping, args[0],
# i18n: "localdate" is a keyword
_("localdate expects a date information"))
Yuya Nishihara
templater: split template functions to new module...
r36940 if len(args) >= 2:
tzoffset = None
tz = evalfuncarg(context, mapping, args[1])
if isinstance(tz, bytes):
tzoffset, remainder = dateutil.parsetimezone(tz)
if remainder:
tzoffset = None
if tzoffset is None:
try:
tzoffset = int(tz)
except (TypeError, ValueError):
# i18n: "localdate" is a keyword
raise error.ParseError(_("localdate expects a timezone"))
else:
tzoffset = dateutil.makedate()[1]
Yuya Nishihara
templater: introduce a wrapper for date tuple (BC)...
r38304 return templateutil.date((date[0], tzoffset))
Yuya Nishihara
templater: split template functions to new module...
r36940
@templatefunc('max(iterable)')
def max_(context, mapping, args, **kwargs):
"""Return the max of an iterable"""
if len(args) != 1:
# i18n: "max" is a keyword
raise error.ParseError(_("max expects one argument"))
Yuya Nishihara
templater: abstract min/max away...
r38284 iterable = evalwrapped(context, mapping, args[0])
Yuya Nishihara
templater: split template functions to new module...
r36940 try:
Yuya Nishihara
templater: abstract min/max away...
r38284 return iterable.getmax(context, mapping)
except error.ParseError as err:
Yuya Nishihara
templater: split template functions to new module...
r36940 # i18n: "max" is a keyword
Yuya Nishihara
templater: abstract min/max away...
r38284 hint = _("max first argument should be an iterable")
raise error.ParseError(bytes(err), hint=hint)
Yuya Nishihara
templater: split template functions to new module...
r36940
@templatefunc('min(iterable)')
def min_(context, mapping, args, **kwargs):
"""Return the min of an iterable"""
if len(args) != 1:
# i18n: "min" is a keyword
raise error.ParseError(_("min expects one argument"))
Yuya Nishihara
templater: abstract min/max away...
r38284 iterable = evalwrapped(context, mapping, args[0])
Yuya Nishihara
templater: split template functions to new module...
r36940 try:
Yuya Nishihara
templater: abstract min/max away...
r38284 return iterable.getmin(context, mapping)
except error.ParseError as err:
Yuya Nishihara
templater: split template functions to new module...
r36940 # i18n: "min" is a keyword
Yuya Nishihara
templater: abstract min/max away...
r38284 hint = _("min first argument should be an iterable")
raise error.ParseError(bytes(err), hint=hint)
Yuya Nishihara
templater: split template functions to new module...
r36940
@templatefunc('mod(a, b)')
def mod(context, mapping, args):
"""Calculate a mod b such that a / b + a mod b == a"""
if not len(args) == 2:
# i18n: "mod" is a keyword
raise error.ParseError(_("mod expects two arguments"))
func = lambda a, b: a % b
return templateutil.runarithmetic(context, mapping,
(func, args[0], args[1]))
@templatefunc('obsfateoperations(markers)')
def obsfateoperations(context, mapping, args):
"""Compute obsfate related information based on markers (EXPERIMENTAL)"""
if len(args) != 1:
# i18n: "obsfateoperations" is a keyword
raise error.ParseError(_("obsfateoperations expects one argument"))
markers = evalfuncarg(context, mapping, args[0])
try:
data = obsutil.markersoperations(markers)
return templateutil.hybridlist(data, name='operation')
except (TypeError, KeyError):
# i18n: "obsfateoperations" is a keyword
errmsg = _("obsfateoperations first argument should be an iterable")
raise error.ParseError(errmsg)
@templatefunc('obsfatedate(markers)')
def obsfatedate(context, mapping, args):
"""Compute obsfate related information based on markers (EXPERIMENTAL)"""
if len(args) != 1:
# i18n: "obsfatedate" is a keyword
raise error.ParseError(_("obsfatedate expects one argument"))
markers = evalfuncarg(context, mapping, args[0])
try:
Yuya Nishihara
templater: introduce a wrapper for date tuple (BC)...
r38304 # TODO: maybe this has to be a wrapped list of date wrappers?
Yuya Nishihara
templater: split template functions to new module...
r36940 data = obsutil.markersdates(markers)
return templateutil.hybridlist(data, name='date', fmt='%d %d')
except (TypeError, KeyError):
# i18n: "obsfatedate" is a keyword
errmsg = _("obsfatedate first argument should be an iterable")
raise error.ParseError(errmsg)
@templatefunc('obsfateusers(markers)')
def obsfateusers(context, mapping, args):
"""Compute obsfate related information based on markers (EXPERIMENTAL)"""
if len(args) != 1:
# i18n: "obsfateusers" is a keyword
raise error.ParseError(_("obsfateusers expects one argument"))
markers = evalfuncarg(context, mapping, args[0])
try:
data = obsutil.markersusers(markers)
return templateutil.hybridlist(data, name='user')
except (TypeError, KeyError, ValueError):
# i18n: "obsfateusers" is a keyword
msg = _("obsfateusers first argument should be an iterable of "
"obsmakers")
raise error.ParseError(msg)
@templatefunc('obsfateverb(successors, markers)')
def obsfateverb(context, mapping, args):
"""Compute obsfate related information based on successors (EXPERIMENTAL)"""
if len(args) != 2:
# i18n: "obsfateverb" is a keyword
raise error.ParseError(_("obsfateverb expects two arguments"))
successors = evalfuncarg(context, mapping, args[0])
markers = evalfuncarg(context, mapping, args[1])
try:
return obsutil.obsfateverb(successors, markers)
except TypeError:
# i18n: "obsfateverb" is a keyword
errmsg = _("obsfateverb first argument should be countable")
raise error.ParseError(errmsg)
Yuya Nishihara
templatefuncs: declare resource requirements for future use
r38447 @templatefunc('relpath(path)', requires={'repo'})
Yuya Nishihara
templater: split template functions to new module...
r36940 def relpath(context, mapping, args):
"""Convert a repository-absolute path into a filesystem path relative to
the current working directory."""
if len(args) != 1:
# i18n: "relpath" is a keyword
raise error.ParseError(_("relpath expects one argument"))
Yuya Nishihara
templatefuncs: minimize resource requirements
r38446 repo = context.resource(mapping, 'repo')
Yuya Nishihara
templater: split template functions to new module...
r36940 path = evalstring(context, mapping, args[0])
return repo.pathto(path)
Yuya Nishihara
templatefuncs: declare resource requirements for future use
r38447 @templatefunc('revset(query[, formatargs...])', requires={'repo', 'cache'})
Yuya Nishihara
templater: split template functions to new module...
r36940 def revset(context, mapping, args):
"""Execute a revision set query. See
:hg:`help revset`."""
if not len(args) > 0:
# i18n: "revset" is a keyword
raise error.ParseError(_("revset expects one or more arguments"))
raw = evalstring(context, mapping, args[0])
Yuya Nishihara
templatefuncs: minimize resource requirements
r38446 repo = context.resource(mapping, 'repo')
Yuya Nishihara
templater: split template functions to new module...
r36940
def query(expr):
Yuya Nishihara
revset: pass in lookup function instead of repo (API)...
r37692 m = revsetmod.match(repo.ui, expr, lookup=revsetmod.lookupfn(repo))
Yuya Nishihara
templater: split template functions to new module...
r36940 return m(repo)
if len(args) > 1:
formatargs = [evalfuncarg(context, mapping, a) for a in args[1:]]
revs = query(revsetlang.formatspec(raw, *formatargs))
else:
cache = context.resource(mapping, 'cache')
revsetcache = cache.setdefault("revsetcache", {})
if raw in revsetcache:
revs = revsetcache[raw]
else:
revs = query(raw)
revsetcache[raw] = revs
return templatekw.showrevslist(context, mapping, "revision", revs)
@templatefunc('rstdoc(text, style)')
def rstdoc(context, mapping, args):
"""Format reStructuredText."""
if len(args) != 2:
# i18n: "rstdoc" is a keyword
raise error.ParseError(_("rstdoc expects two arguments"))
text = evalstring(context, mapping, args[0])
style = evalstring(context, mapping, args[1])
Yuya Nishihara
minirst: make format() simply return a formatted text...
r39346 return minirst.format(text, style=style, keep=['verbose'])
Yuya Nishihara
templater: split template functions to new module...
r36940
Yuya Nishihara
templatefuncs: add regexp search() function that extracts substring...
r40970 @templatefunc('search(pattern, text)')
def search(context, mapping, args):
"""Look for the first text matching the regular expression pattern.
Groups are accessible as ``{1}``, ``{2}``, ... in %-mapped template."""
if len(args) != 2:
# i18n: "search" is a keyword
raise error.ParseError(_(b'search expects two arguments'))
pat = evalstring(context, mapping, args[0])
src = evalstring(context, mapping, args[1])
try:
patre = re.compile(pat)
except re.error:
# i18n: "search" is a keyword
raise error.ParseError(_(b'search got an invalid pattern: %s') % pat)
# named groups shouldn't shadow *reserved* resource keywords
badgroups = (context.knownresourcekeys()
& set(pycompat.byteskwargs(patre.groupindex)))
if badgroups:
raise error.ParseError(
# i18n: "search" is a keyword
_(b'invalid group %(group)s in search pattern: %(pat)s')
% {b'group': b', '.join("'%s'" % g for g in sorted(badgroups)),
b'pat': pat})
match = patre.search(src)
if not match:
Yuya Nishihara
templatefuncs: specialize "no match" value of search() to allow % operation...
r40971 return templateutil.mappingnone()
Yuya Nishihara
templatefuncs: add regexp search() function that extracts substring...
r40970
lm = {b'0': match.group(0)}
lm.update((b'%d' % i, v) for i, v in enumerate(match.groups(), 1))
lm.update(pycompat.byteskwargs(match.groupdict()))
return templateutil.mappingdict(lm, tmpl=b'{0}')
Yuya Nishihara
help: correct signature of separate() template function...
r38179 @templatefunc('separate(sep, args...)', argspec='sep *args')
Yuya Nishihara
templater: split template functions to new module...
r36940 def separate(context, mapping, args):
"""Add a separator between non-empty arguments."""
if 'sep' not in args:
# i18n: "separate" is a keyword
raise error.ParseError(_("separate expects at least one argument"))
sep = evalstring(context, mapping, args['sep'])
first = True
for arg in args['args']:
argstr = evalstring(context, mapping, arg)
if not argstr:
continue
if first:
first = False
else:
yield sep
yield argstr
Martin von Zweigbergk
shortest: cache disambiguation revset...
r38889 @templatefunc('shortest(node, minlength=4)', requires={'repo', 'cache'})
Yuya Nishihara
templater: split template functions to new module...
r36940 def shortest(context, mapping, args):
"""Obtain the shortest representation of
a node."""
if not (1 <= len(args) <= 2):
# i18n: "shortest" is a keyword
raise error.ParseError(_("shortest() expects one or two arguments"))
Martin von Zweigbergk
scmutil: make shortesthexnodeidprefix() take a full binary nodeid...
r37727 hexnode = evalstring(context, mapping, args[0])
Yuya Nishihara
templater: split template functions to new module...
r36940
minlength = 4
if len(args) > 1:
minlength = evalinteger(context, mapping, args[1],
# i18n: "shortest" is a keyword
_("shortest() expects an integer minlength"))
Yuya Nishihara
templatefuncs: minimize resource requirements
r38446 repo = context.resource(mapping, 'repo')
Martin von Zweigbergk
scmutil: make shortesthexnodeidprefix() take a full binary nodeid...
r37727 if len(hexnode) > 40:
return hexnode
elif len(hexnode) == 40:
try:
node = bin(hexnode)
except TypeError:
return hexnode
else:
try:
node = scmutil.resolvehexnodeidprefix(repo, hexnode)
Martin von Zweigbergk
shortest: make {shortest("fffffffff")} work again...
r37876 except error.WdirUnsupported:
node = wdirid
except error.LookupError:
Martin von Zweigbergk
scmutil: make shortesthexnodeidprefix() take a full binary nodeid...
r37727 return hexnode
if not node:
return hexnode
Martin von Zweigbergk
shortest: cache disambiguation revset...
r38889 cache = context.resource(mapping, 'cache')
Martin von Zweigbergk
shortest: don't keep checking for longer prefix if node doesn't exist (API)...
r37882 try:
Martin von Zweigbergk
shortest: cache disambiguation revset...
r38889 return scmutil.shortesthexnodeidprefix(repo, node, minlength, cache)
Martin von Zweigbergk
shortest: don't keep checking for longer prefix if node doesn't exist (API)...
r37882 except error.RepoLookupError:
return hexnode
Yuya Nishihara
templater: split template functions to new module...
r36940
@templatefunc('strip(text[, chars])')
def strip(context, mapping, args):
"""Strip characters from a string. By default,
strips all leading and trailing whitespace."""
if not (1 <= len(args) <= 2):
# i18n: "strip" is a keyword
raise error.ParseError(_("strip expects one or two arguments"))
text = evalstring(context, mapping, args[0])
if len(args) == 2:
chars = evalstring(context, mapping, args[1])
return text.strip(chars)
return text.strip()
@templatefunc('sub(pattern, replacement, expression)')
def sub(context, mapping, args):
"""Perform text substitution
using regular expressions."""
if len(args) != 3:
# i18n: "sub" is a keyword
raise error.ParseError(_("sub expects three arguments"))
pat = evalstring(context, mapping, args[0])
rpl = evalstring(context, mapping, args[1])
src = evalstring(context, mapping, args[2])
try:
patre = re.compile(pat)
except re.error:
# i18n: "sub" is a keyword
raise error.ParseError(_("sub got an invalid pattern: %s") % pat)
try:
yield patre.sub(rpl, src)
except re.error:
# i18n: "sub" is a keyword
raise error.ParseError(_("sub got an invalid replacement: %s") % rpl)
@templatefunc('startswith(pattern, text)')
def startswith(context, mapping, args):
"""Returns the value from the "text" argument
if it begins with the content from the "pattern" argument."""
if len(args) != 2:
# i18n: "startswith" is a keyword
raise error.ParseError(_("startswith expects two arguments"))
patn = evalstring(context, mapping, args[0])
text = evalstring(context, mapping, args[1])
if text.startswith(patn):
return text
return ''
@templatefunc('word(number, text[, separator])')
def word(context, mapping, args):
"""Return the nth word from a string."""
if not (2 <= len(args) <= 3):
# i18n: "word" is a keyword
raise error.ParseError(_("word expects two or three arguments, got %d")
% len(args))
num = evalinteger(context, mapping, args[0],
# i18n: "word" is a keyword
_("word expects an integer index"))
text = evalstring(context, mapping, args[1])
if len(args) == 3:
splitter = evalstring(context, mapping, args[2])
else:
splitter = None
tokens = text.split(splitter)
if num >= len(tokens) or num < -len(tokens):
return ''
else:
return tokens[num]
def loadfunction(ui, extname, registrarobj):
"""Load template function from specified registrarobj
"""
for name, func in registrarobj._table.iteritems():
funcs[name] = func
# tell hggettext to extract docstrings from these functions:
i18nfunctions = funcs.values()