[PATCH 1 of 2] templatefuncs: add regexp search() function that extracts substring
Yuya Nishihara
yuya at tcha.org
Thu Dec 13 13:35:16 UTC 2018
# HG changeset patch
# User Yuya Nishihara <yuya at tcha.org>
# Date 1544620797 -32400
# Wed Dec 12 22:19:57 2018 +0900
# Node ID eeb0c3a62092414713689e9dce6e8949bd27692b
# Parent a6ba978d9ffb0dc8b0edb8f892a947776b6377c5
templatefuncs: add regexp search() function that extracts substring
This can be used to extract an issue number from a commit message, for
example:
{search(r'\(issue([0-9]*)\)', desc) % '{1}'}
diff --git a/mercurial/templatefuncs.py b/mercurial/templatefuncs.py
--- a/mercurial/templatefuncs.py
+++ b/mercurial/templatefuncs.py
@@ -20,6 +20,7 @@ from . import (
error,
minirst,
obsutil,
+ pycompat,
registrar,
revset as revsetmod,
revsetlang,
@@ -581,6 +582,40 @@ def rstdoc(context, mapping, args):
return minirst.format(text, style=style, keep=['verbose'])
+ at 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:
+ return
+
+ 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}')
+
@templatefunc('separate(sep, args...)', argspec='sep *args')
def separate(context, mapping, args):
"""Add a separator between non-empty arguments."""
diff --git a/tests/test-template-functions.t b/tests/test-template-functions.t
--- a/tests/test-template-functions.t
+++ b/tests/test-template-functions.t
@@ -603,6 +603,50 @@ Test laziness of if() then/else clause
$ hg debugtemplate '{ifeq(0, 0, "", count(0))}'
$ hg debugtemplate '{ifeq(0, 1, count(0), "")}'
+Test search() function:
+
+ $ hg log -R a -r2 -T '{search(r"p.*", desc)}\n'
+ person
+
+ as bool
+
+ $ hg log -R a -r2 -T '{if(search(r"p.*", desc), "", "not ")}found\n'
+ found
+ $ hg log -R a -r2 -T '{if(search(r"q", desc), "", "not ")}found\n'
+ not found
+
+ match as json
+
+ $ hg log -R a -r2 -T '{search(r"(no) p.*", desc)|json}\n'
+ {"0": "no person", "1": "no"}
+ $ hg log -R a -r2 -T '{search(r"q", desc)|json}\n'
+ null
+
+ group reference
+
+ $ hg log -R a -r2 -T '{search(r"(no) (p.*)", desc) % "{1|upper} {2|hex}"}\n'
+ NO 706572736f6e
+ $ hg log -R a -r2 -T '{search(r"(?P<foo>[a-z]*)", desc) % "{foo}"}\n'
+ no
+ $ hg log -R a -r2 -T '{search(r"(?P<foo>[a-z]*)", desc).foo}\n'
+ no
+
+ group reference with no match
+ (TODO: we'll probably want to map it to an empty value)
+
+ $ hg log -R a -r2 -T '{search(r"q", desc) % "match: {0}"}\n'
+ hg: parse error: None is not iterable of mappings
+ [255]
+
+ bad group names
+
+ $ hg log -R a -r2 -T '{search(r"(?P<0>.)", desc) % "{0}"}\n'
+ hg: parse error: search got an invalid pattern: (?P<0>.)
+ [255]
+ $ hg log -R a -r2 -T '{search(r"(?P<repo>.)", desc) % "{repo}"}\n'
+ hg: parse error: invalid group 'repo' in search pattern: (?P<repo>.)
+ [255]
+
Test the sub function of templating for expansion:
$ hg log -R latesttag -r 10 --template '{sub("[0-9]", "x", "{rev}")}\n'
More information about the Mercurial-devel
mailing list