mirror of
https://github.com/facebook/sapling.git
synced 2024-10-07 15:27:13 +03:00
templatekw: add new-style template expansion to {manifest}
The goal is to allow us to easily access to nested data. The dot operator will be introduced later so we can write '{p1.files}' instead of '{revset("p1()") % "{files}"}' for example. In the example above, 'p1' needs to carry a mapping dict along with its string representation. If it were a list or a dict, it could be wrapped semi-transparently with the _hybrid class, but for non-list/dict types, it would be difficult to proxy all necessary functions to underlying value type because several core operations may conflict with the ones of the underlying value: - hash(value) should be different from hash(wrapped(value)), which means dict[wrapped(value)] would be invalid - 'value == wrapped(value)' would be false, breaks 'ifcontains' - len(wrapped(value)) may be either len(value) or len(iter(wrapped(value))) So the wrapper has no proxy functions and its scope designed to be minimal. It's unwrapped at eval*() functions so we don't have to care for a wrapped object unless it's really needed: # most template functions just call evalfuncarg() unwrapped_value = evalfuncarg(context, mapping, args[n]) # if wrapped value is needed, use evalrawexp() maybe_wrapped_value = evalrawexp(context, mapping, args[n]) Another idea was to wrap every template variable with a tagging class, but which seemed uneasy without a static type checker. This patch updates {manifest} to a mappable as an example.
This commit is contained in:
parent
c22459dc5d
commit
7dafbbbbcf
@ -70,6 +70,28 @@ class _hybrid(object):
|
||||
raise AttributeError(name)
|
||||
return getattr(self._values, name)
|
||||
|
||||
class _mappable(object):
|
||||
"""Wrapper for non-list/dict object to support map operation
|
||||
|
||||
This class allows us to handle both:
|
||||
- "{manifest}"
|
||||
- "{manifest % '{rev}:{node}'}"
|
||||
|
||||
Unlike a _hybrid, this does not simulate the behavior of the underling
|
||||
value. Use unwrapvalue() or unwraphybrid() to obtain the inner object.
|
||||
"""
|
||||
|
||||
def __init__(self, gen, value, makemap):
|
||||
self.gen = gen
|
||||
self._value = value # may be generator of strings
|
||||
self._makemap = makemap
|
||||
|
||||
def tomap(self):
|
||||
return self._makemap()
|
||||
|
||||
def itermaps(self):
|
||||
yield self.tomap()
|
||||
|
||||
def hybriddict(data, key='key', value='value', fmt='%s=%s', gen=None):
|
||||
"""Wrap data to support both dict-like and string-like operations"""
|
||||
return _hybrid(gen, data, lambda k: {key: k, value: data[k]},
|
||||
@ -86,6 +108,12 @@ def unwraphybrid(thing):
|
||||
return thing
|
||||
return thing.gen
|
||||
|
||||
def unwrapvalue(thing):
|
||||
"""Move the inner value object out of the wrapper"""
|
||||
if not util.safehasattr(thing, '_value'):
|
||||
return thing
|
||||
return thing._value
|
||||
|
||||
def showdict(name, data, mapping, plural=None, key='key', value='value',
|
||||
fmt='%s=%s', separator=' '):
|
||||
c = [{key: k, value: v} for k, v in data.iteritems()]
|
||||
@ -543,10 +571,14 @@ def showmanifest(**args):
|
||||
if mnode is None:
|
||||
# just avoid crash, we might want to use the 'ff...' hash in future
|
||||
return
|
||||
mrev = repo.manifestlog._revlog.rev(mnode)
|
||||
mhex = hex(mnode)
|
||||
args = args.copy()
|
||||
args.update({r'rev': repo.manifestlog._revlog.rev(mnode),
|
||||
r'node': hex(mnode)})
|
||||
return templ('manifest', **args)
|
||||
args.update({r'rev': mrev, r'node': mhex})
|
||||
f = templ('manifest', **args)
|
||||
# TODO: perhaps 'ctx' should be dropped from mapping because manifest
|
||||
# rev and node are completely different from changeset's.
|
||||
return _mappable(f, f, lambda: {'rev': mrev, 'node': mhex})
|
||||
|
||||
def shownames(namespace, **args):
|
||||
"""helper method to generate a template keyword for a namespace"""
|
||||
|
@ -307,6 +307,7 @@ def evalrawexp(context, mapping, arg):
|
||||
def evalfuncarg(context, mapping, arg):
|
||||
"""Evaluate given argument as value type"""
|
||||
thing = evalrawexp(context, mapping, arg)
|
||||
thing = templatekw.unwrapvalue(thing)
|
||||
# evalrawexp() may return string, generator of strings or arbitrary object
|
||||
# such as date tuple, but filter does not want generator.
|
||||
if isinstance(thing, types.GeneratorType):
|
||||
@ -323,6 +324,7 @@ def evalboolean(context, mapping, arg):
|
||||
thing = util.parsebool(data)
|
||||
else:
|
||||
thing = func(context, mapping, data)
|
||||
thing = templatekw.unwrapvalue(thing)
|
||||
if isinstance(thing, bool):
|
||||
return thing
|
||||
# other objects are evaluated as strings, which means 0 is True, but
|
||||
@ -768,6 +770,7 @@ def join(context, mapping, args):
|
||||
# TODO: perhaps this should be evalfuncarg(), but it can't because hgweb
|
||||
# abuses generator as a keyword that returns a list of dicts.
|
||||
joinset = evalrawexp(context, mapping, args[0])
|
||||
joinset = templatekw.unwrapvalue(joinset)
|
||||
joinfmt = getattr(joinset, 'joinfmt', pycompat.identity)
|
||||
joiner = " "
|
||||
if len(args) > 1:
|
||||
|
@ -3119,6 +3119,20 @@ Test new-style inline templating:
|
||||
hg: parse error: None is not iterable
|
||||
[255]
|
||||
|
||||
Test new-style inline templating of non-list/dict type:
|
||||
|
||||
$ hg log -R latesttag -r tip -T '{manifest}\n'
|
||||
11:2bc6e9006ce2
|
||||
$ hg log -R latesttag -r tip -T 'string length: {manifest|count}\n'
|
||||
string length: 15
|
||||
$ hg log -R latesttag -r tip -T '{manifest % "{rev}:{node}"}\n'
|
||||
11:2bc6e9006ce29882383a22d39fd1f4e66dd3e2fc
|
||||
|
||||
Test manifest can be join()-ed as before, though it's silly:
|
||||
|
||||
$ hg log -R latesttag -r tip -T '{join(manifest, "")}\n'
|
||||
11:2bc6e9006ce2
|
||||
|
||||
Test the sub function of templating for expansion:
|
||||
|
||||
$ hg log -R latesttag -r 10 --template '{sub("[0-9]", "x", "{rev}")}\n'
|
||||
|
Loading…
Reference in New Issue
Block a user