# HG changeset patch # User Yuya Nishihara # Date 1520518509 -32400 # Node ID 32f9b7e3f05678c13fd174651a18437511480606 # Parent 6ff6e1d6b5b8f992bc0276f2cd948e8501629b0e templater: move hybrid class and functions to templateutil module And make _hybrid and _mappable classes public. _showlist() is still marked as private since it's weird and third-party codes shouldn't depend on it. diff -r 6ff6e1d6b5b8 -r 32f9b7e3f056 hgext/lfs/__init__.py --- a/hgext/lfs/__init__.py Thu Mar 08 23:10:46 2018 +0900 +++ b/hgext/lfs/__init__.py Thu Mar 08 23:15:09 2018 +0900 @@ -143,7 +143,7 @@ registrar, revlog, scmutil, - templatekw, + templateutil, upgrade, util, vfs as vfsmod, @@ -375,12 +375,12 @@ makemap = lambda v: { 'file': v, 'lfsoid': pointers[v].oid() if pointers[v] else None, - 'lfspointer': templatekw.hybriddict(pointer(v)), + 'lfspointer': templateutil.hybriddict(pointer(v)), } # TODO: make the separator ', '? - f = templatekw._showlist('lfs_file', files, templ, mapping) - return templatekw._hybrid(f, files, makemap, pycompat.identity) + f = templateutil._showlist('lfs_file', files, templ, mapping) + return templateutil.hybrid(f, files, makemap, pycompat.identity) @command('debuglfsupload', [('r', 'rev', [], _('upload large files introduced by REV'))]) diff -r 6ff6e1d6b5b8 -r 32f9b7e3f056 hgext/remotenames.py --- a/hgext/remotenames.py Thu Mar 08 23:10:46 2018 +0900 +++ b/hgext/remotenames.py Thu Mar 08 23:15:09 2018 +0900 @@ -35,7 +35,7 @@ registrar, revsetlang, smartset, - templatekw, + templateutil, ) # Note for extension authors: ONLY specify testedwith = 'ships-with-hg-core' for @@ -237,8 +237,8 @@ if 'remotebranches' in repo.names: remotenames += repo.names['remotebranches'].names(repo, ctx.node()) - return templatekw.compatlist(context, mapping, 'remotename', remotenames, - plural='remotenames') + return templateutil.compatlist(context, mapping, 'remotename', remotenames, + plural='remotenames') @templatekeyword('remotebookmarks', requires={'repo', 'ctx', 'templ'}) def remotebookmarkskw(context, mapping): @@ -250,8 +250,8 @@ if 'remotebookmarks' in repo.names: remotebmarks = repo.names['remotebookmarks'].names(repo, ctx.node()) - return templatekw.compatlist(context, mapping, 'remotebookmark', - remotebmarks, plural='remotebookmarks') + return templateutil.compatlist(context, mapping, 'remotebookmark', + remotebmarks, plural='remotebookmarks') @templatekeyword('remotebranches', requires={'repo', 'ctx', 'templ'}) def remotebrancheskw(context, mapping): @@ -263,8 +263,8 @@ if 'remotebranches' in repo.names: remotebranches = repo.names['remotebranches'].names(repo, ctx.node()) - return templatekw.compatlist(context, mapping, 'remotebranch', - remotebranches, plural='remotebranches') + return templateutil.compatlist(context, mapping, 'remotebranch', + remotebranches, plural='remotebranches') def _revsetutil(repo, subset, x, rtypes): """utility function to return a set of revs based on the rtypes""" diff -r 6ff6e1d6b5b8 -r 32f9b7e3f056 mercurial/formatter.py --- a/mercurial/formatter.py Thu Mar 08 23:10:46 2018 +0900 +++ b/mercurial/formatter.py Thu Mar 08 23:15:09 2018 +0900 @@ -359,14 +359,15 @@ data = util.sortdict(_iteritems(data)) def f(): yield _plainconverter.formatdict(data, key, value, fmt, sep) - return templatekw.hybriddict(data, key=key, value=value, fmt=fmt, gen=f) + return templateutil.hybriddict(data, key=key, value=value, fmt=fmt, + gen=f) @staticmethod def formatlist(data, name, fmt, sep): '''build object that can be evaluated as either plain string or list''' data = list(data) def f(): yield _plainconverter.formatlist(data, name, fmt, sep) - return templatekw.hybridlist(data, name=name, fmt=fmt, gen=f) + return templateutil.hybridlist(data, name=name, fmt=fmt, gen=f) class templateformatter(baseformatter): def __init__(self, ui, out, topic, opts): diff -r 6ff6e1d6b5b8 -r 32f9b7e3f056 mercurial/templatefilters.py --- a/mercurial/templatefilters.py Thu Mar 08 23:10:46 2018 +0900 +++ b/mercurial/templatefilters.py Thu Mar 08 23:15:09 2018 +0900 @@ -17,7 +17,6 @@ node, pycompat, registrar, - templatekw, templateutil, url, util, @@ -366,7 +365,7 @@ @templatefilter('splitlines') def splitlines(text): """Any text. Split text into a list of lines.""" - return templatekw.hybridlist(text.splitlines(), name='line') + return templateutil.hybridlist(text.splitlines(), name='line') @templatefilter('stringescape') def stringescape(text): diff -r 6ff6e1d6b5b8 -r 32f9b7e3f056 mercurial/templatekw.py --- a/mercurial/templatekw.py Thu Mar 08 23:10:46 2018 +0900 +++ b/mercurial/templatekw.py Thu Mar 08 23:15:09 2018 +0900 @@ -23,156 +23,24 @@ pycompat, registrar, scmutil, + templateutil, util, ) -class _hybrid(object): - """Wrapper for list or dict to support legacy template - - This class allows us to handle both: - - "{files}" (legacy command-line-specific list hack) and - - "{files % '{file}\n'}" (hgweb-style with inlining and function support) - and to access raw values: - - "{ifcontains(file, files, ...)}", "{ifcontains(key, extras, ...)}" - - "{get(extras, key)}" - - "{files|json}" - """ - - def __init__(self, gen, values, makemap, joinfmt, keytype=None): - if gen is not None: - self.gen = gen # generator or function returning generator - self._values = values - self._makemap = makemap - self.joinfmt = joinfmt - self.keytype = keytype # hint for 'x in y' where type(x) is unresolved - def gen(self): - """Default generator to stringify this as {join(self, ' ')}""" - for i, x in enumerate(self._values): - if i > 0: - yield ' ' - yield self.joinfmt(x) - def itermaps(self): - makemap = self._makemap - for x in self._values: - yield makemap(x) - def __contains__(self, x): - return x in self._values - def __getitem__(self, key): - return self._values[key] - def __len__(self): - return len(self._values) - def __iter__(self): - return iter(self._values) - def __getattr__(self, name): - if name not in (r'get', r'items', r'iteritems', r'iterkeys', - r'itervalues', r'keys', r'values'): - 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}'}" - - "{manifest.rev}" - - 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, key, value, makemap): - if gen is not None: - self.gen = gen # generator or function returning generator - self._key = key - self._value = value # may be generator of strings - self._makemap = makemap - - def gen(self): - yield pycompat.bytestr(self._value) - - def tomap(self): - return self._makemap(self._key) - - def itermaps(self): - yield self.tomap() - -def hybriddict(data, key='key', value='value', fmt=None, gen=None): - """Wrap data to support both dict-like and string-like operations""" - prefmt = pycompat.identity - if fmt is None: - fmt = '%s=%s' - prefmt = pycompat.bytestr - return _hybrid(gen, data, lambda k: {key: k, value: data[k]}, - lambda k: fmt % (prefmt(k), prefmt(data[k]))) - -def hybridlist(data, name, fmt=None, gen=None): - """Wrap data to support both list-like and string-like operations""" - prefmt = pycompat.identity - if fmt is None: - fmt = '%s' - prefmt = pycompat.bytestr - return _hybrid(gen, data, lambda x: {name: x}, lambda x: fmt % prefmt(x)) - -def unwraphybrid(thing): - """Return an object which can be stringified possibly by using a legacy - template""" - gen = getattr(thing, 'gen', None) - if gen is None: - return thing - if callable(gen): - return gen() - return 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 wraphybridvalue(container, key, value): - """Wrap an element of hybrid container to be mappable - - The key is passed to the makemap function of the given container, which - should be an item generated by iter(container). - """ - makemap = getattr(container, '_makemap', None) - if makemap is None: - return value - if util.safehasattr(value, '_makemap'): - # a nested hybrid list/dict, which has its own way of map operation - return value - return _mappable(None, key, value, makemap) - -def compatdict(context, mapping, name, data, key='key', value='value', - fmt=None, plural=None, separator=' '): - """Wrap data like hybriddict(), but also supports old-style list template - - This exists for backward compatibility with the old-style template. Use - hybriddict() for new template keywords. - """ - c = [{key: k, value: v} for k, v in data.iteritems()] - t = context.resource(mapping, 'templ') - f = _showlist(name, c, t, mapping, plural, separator) - return hybriddict(data, key=key, value=value, fmt=fmt, gen=f) - -def compatlist(context, mapping, name, data, element=None, fmt=None, - plural=None, separator=' '): - """Wrap data like hybridlist(), but also supports old-style list template - - This exists for backward compatibility with the old-style template. Use - hybridlist() for new template keywords. - """ - t = context.resource(mapping, 'templ') - f = _showlist(name, data, t, mapping, plural, separator) - return hybridlist(data, name=element or name, fmt=fmt, gen=f) +_hybrid = templateutil.hybrid +_mappable = templateutil.mappable +_showlist = templateutil._showlist +hybriddict = templateutil.hybriddict +hybridlist = templateutil.hybridlist +compatdict = templateutil.compatdict +compatlist = templateutil.compatlist def showdict(name, data, mapping, plural=None, key='key', value='value', fmt=None, separator=' '): ui = mapping.get('ui') if ui: - ui.deprecwarn("templatekw.showdict() is deprecated, use compatdict()", - '4.6') + ui.deprecwarn("templatekw.showdict() is deprecated, use " + "templateutil.compatdict()", '4.6') c = [{key: k, value: v} for k, v in data.iteritems()] f = _showlist(name, c, mapping['templ'], mapping, plural, separator) return hybriddict(data, key=key, value=value, fmt=fmt, gen=f) @@ -180,82 +48,13 @@ def showlist(name, values, mapping, plural=None, element=None, separator=' '): ui = mapping.get('ui') if ui: - ui.deprecwarn("templatekw.showlist() is deprecated, use compatlist()", - '4.6') + ui.deprecwarn("templatekw.showlist() is deprecated, use " + "templateutil.compatlist()", '4.6') if not element: element = name f = _showlist(name, values, mapping['templ'], mapping, plural, separator) return hybridlist(values, name=element, gen=f) -def _showlist(name, values, templ, mapping, plural=None, separator=' '): - '''expand set of values. - name is name of key in template map. - values is list of strings or dicts. - plural is plural of name, if not simply name + 's'. - separator is used to join values as a string - - expansion works like this, given name 'foo'. - - if values is empty, expand 'no_foos'. - - if 'foo' not in template map, return values as a string, - joined by 'separator'. - - expand 'start_foos'. - - for each value, expand 'foo'. if 'last_foo' in template - map, expand it instead of 'foo' for last key. - - expand 'end_foos'. - ''' - strmapping = pycompat.strkwargs(mapping) - if not plural: - plural = name + 's' - if not values: - noname = 'no_' + plural - if noname in templ: - yield templ(noname, **strmapping) - return - if name not in templ: - if isinstance(values[0], bytes): - yield separator.join(values) - else: - for v in values: - r = dict(v) - r.update(mapping) - yield r - return - startname = 'start_' + plural - if startname in templ: - yield templ(startname, **strmapping) - vmapping = mapping.copy() - def one(v, tag=name): - try: - vmapping.update(v) - # Python 2 raises ValueError if the type of v is wrong. Python - # 3 raises TypeError. - except (AttributeError, TypeError, ValueError): - try: - # Python 2 raises ValueError trying to destructure an e.g. - # bytes. Python 3 raises TypeError. - for a, b in v: - vmapping[a] = b - except (TypeError, ValueError): - vmapping[name] = v - return templ(tag, **pycompat.strkwargs(vmapping)) - lastname = 'last_' + name - if lastname in templ: - last = values.pop() - else: - last = None - for v in values: - yield one(v) - if last is not None: - yield one(last, tag=lastname) - endname = 'end_' + plural - if endname in templ: - yield templ(endname, **strmapping) - def getlatesttags(context, mapping, pattern=None): '''return date, distance and name for the latest tag of rev''' repo = context.resource(mapping, 'repo') diff -r 6ff6e1d6b5b8 -r 32f9b7e3f056 mercurial/templater.py --- a/mercurial/templater.py Thu Mar 08 23:10:46 2018 +0900 +++ b/mercurial/templater.py Thu Mar 08 23:15:09 2018 +0900 @@ -498,7 +498,7 @@ data.update((k, evalfuncarg(context, mapping, v)) for k, v in args['kwargs'].iteritems()) - return templatekw.hybriddict(data) + return templateutil.hybriddict(data) @templatefunc('diff([includepattern [, excludepattern]])') def diff(context, mapping, args): @@ -548,7 +548,7 @@ ctx = context.resource(mapping, 'ctx') m = ctx.match([raw]) files = list(ctx.matches(m)) - return templatekw.compatlist(context, mapping, "file", files) + return templateutil.compatlist(context, mapping, "file", files) @templatefunc('fill(text[, width[, initialident[, hangindent]]])') def fill(context, mapping, args): @@ -718,7 +718,7 @@ # 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) + joinset = templateutil.unwrapvalue(joinset) joinfmt = getattr(joinset, 'joinfmt', pycompat.identity) joiner = " " if len(args) > 1: @@ -808,7 +808,7 @@ except (TypeError, ValueError): # i18n: "max" is a keyword raise error.ParseError(_("max first argument should be an iterable")) - return templatekw.wraphybridvalue(iterable, x, x) + return templateutil.wraphybridvalue(iterable, x, x) @templatefunc('min(iterable)') def min_(context, mapping, args, **kwargs): @@ -823,7 +823,7 @@ except (TypeError, ValueError): # i18n: "min" is a keyword raise error.ParseError(_("min first argument should be an iterable")) - return templatekw.wraphybridvalue(iterable, x, x) + return templateutil.wraphybridvalue(iterable, x, x) @templatefunc('mod(a, b)') def mod(context, mapping, args): @@ -847,7 +847,7 @@ try: data = obsutil.markersoperations(markers) - return templatekw.hybridlist(data, name='operation') + return templateutil.hybridlist(data, name='operation') except (TypeError, KeyError): # i18n: "obsfateoperations" is a keyword errmsg = _("obsfateoperations first argument should be an iterable") @@ -864,7 +864,7 @@ try: data = obsutil.markersdates(markers) - return templatekw.hybridlist(data, name='date', fmt='%d %d') + 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") @@ -881,7 +881,7 @@ try: data = obsutil.markersusers(markers) - return templatekw.hybridlist(data, name='user') + return templateutil.hybridlist(data, name='user') except (TypeError, KeyError, ValueError): # i18n: "obsfateusers" is a keyword msg = _("obsfateusers first argument should be an iterable of " @@ -1120,7 +1120,7 @@ def _flatten(thing): '''yield a single stream from a possibly nested set of iterators''' - thing = templatekw.unwraphybrid(thing) + thing = templateutil.unwraphybrid(thing) if isinstance(thing, bytes): yield thing elif isinstance(thing, str): @@ -1134,7 +1134,7 @@ yield pycompat.bytestr(thing) else: for i in thing: - i = templatekw.unwraphybrid(i) + i = templateutil.unwraphybrid(i) if isinstance(i, bytes): yield i elif i is None: diff -r 6ff6e1d6b5b8 -r 32f9b7e3f056 mercurial/templateutil.py --- a/mercurial/templateutil.py Thu Mar 08 23:10:46 2018 +0900 +++ b/mercurial/templateutil.py Thu Mar 08 23:15:09 2018 +0900 @@ -13,7 +13,6 @@ from . import ( error, pycompat, - templatekw, util, ) @@ -23,9 +22,219 @@ class TemplateNotFound(error.Abort): pass +class hybrid(object): + """Wrapper for list or dict to support legacy template + + This class allows us to handle both: + - "{files}" (legacy command-line-specific list hack) and + - "{files % '{file}\n'}" (hgweb-style with inlining and function support) + and to access raw values: + - "{ifcontains(file, files, ...)}", "{ifcontains(key, extras, ...)}" + - "{get(extras, key)}" + - "{files|json}" + """ + + def __init__(self, gen, values, makemap, joinfmt, keytype=None): + if gen is not None: + self.gen = gen # generator or function returning generator + self._values = values + self._makemap = makemap + self.joinfmt = joinfmt + self.keytype = keytype # hint for 'x in y' where type(x) is unresolved + def gen(self): + """Default generator to stringify this as {join(self, ' ')}""" + for i, x in enumerate(self._values): + if i > 0: + yield ' ' + yield self.joinfmt(x) + def itermaps(self): + makemap = self._makemap + for x in self._values: + yield makemap(x) + def __contains__(self, x): + return x in self._values + def __getitem__(self, key): + return self._values[key] + def __len__(self): + return len(self._values) + def __iter__(self): + return iter(self._values) + def __getattr__(self, name): + if name not in (r'get', r'items', r'iteritems', r'iterkeys', + r'itervalues', r'keys', r'values'): + 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}'}" + - "{manifest.rev}" + + 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, key, value, makemap): + if gen is not None: + self.gen = gen # generator or function returning generator + self._key = key + self._value = value # may be generator of strings + self._makemap = makemap + + def gen(self): + yield pycompat.bytestr(self._value) + + def tomap(self): + return self._makemap(self._key) + + def itermaps(self): + yield self.tomap() + +def hybriddict(data, key='key', value='value', fmt=None, gen=None): + """Wrap data to support both dict-like and string-like operations""" + prefmt = pycompat.identity + if fmt is None: + fmt = '%s=%s' + prefmt = pycompat.bytestr + return hybrid(gen, data, lambda k: {key: k, value: data[k]}, + lambda k: fmt % (prefmt(k), prefmt(data[k]))) + +def hybridlist(data, name, fmt=None, gen=None): + """Wrap data to support both list-like and string-like operations""" + prefmt = pycompat.identity + if fmt is None: + fmt = '%s' + prefmt = pycompat.bytestr + return hybrid(gen, data, lambda x: {name: x}, lambda x: fmt % prefmt(x)) + +def unwraphybrid(thing): + """Return an object which can be stringified possibly by using a legacy + template""" + gen = getattr(thing, 'gen', None) + if gen is None: + return thing + if callable(gen): + return gen() + return 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 wraphybridvalue(container, key, value): + """Wrap an element of hybrid container to be mappable + + The key is passed to the makemap function of the given container, which + should be an item generated by iter(container). + """ + makemap = getattr(container, '_makemap', None) + if makemap is None: + return value + if util.safehasattr(value, '_makemap'): + # a nested hybrid list/dict, which has its own way of map operation + return value + return mappable(None, key, value, makemap) + +def compatdict(context, mapping, name, data, key='key', value='value', + fmt=None, plural=None, separator=' '): + """Wrap data like hybriddict(), but also supports old-style list template + + This exists for backward compatibility with the old-style template. Use + hybriddict() for new template keywords. + """ + c = [{key: k, value: v} for k, v in data.iteritems()] + t = context.resource(mapping, 'templ') + f = _showlist(name, c, t, mapping, plural, separator) + return hybriddict(data, key=key, value=value, fmt=fmt, gen=f) + +def compatlist(context, mapping, name, data, element=None, fmt=None, + plural=None, separator=' '): + """Wrap data like hybridlist(), but also supports old-style list template + + This exists for backward compatibility with the old-style template. Use + hybridlist() for new template keywords. + """ + t = context.resource(mapping, 'templ') + f = _showlist(name, data, t, mapping, plural, separator) + return hybridlist(data, name=element or name, fmt=fmt, gen=f) + +def _showlist(name, values, templ, mapping, plural=None, separator=' '): + '''expand set of values. + name is name of key in template map. + values is list of strings or dicts. + plural is plural of name, if not simply name + 's'. + separator is used to join values as a string + + expansion works like this, given name 'foo'. + + if values is empty, expand 'no_foos'. + + if 'foo' not in template map, return values as a string, + joined by 'separator'. + + expand 'start_foos'. + + for each value, expand 'foo'. if 'last_foo' in template + map, expand it instead of 'foo' for last key. + + expand 'end_foos'. + ''' + strmapping = pycompat.strkwargs(mapping) + if not plural: + plural = name + 's' + if not values: + noname = 'no_' + plural + if noname in templ: + yield templ(noname, **strmapping) + return + if name not in templ: + if isinstance(values[0], bytes): + yield separator.join(values) + else: + for v in values: + r = dict(v) + r.update(mapping) + yield r + return + startname = 'start_' + plural + if startname in templ: + yield templ(startname, **strmapping) + vmapping = mapping.copy() + def one(v, tag=name): + try: + vmapping.update(v) + # Python 2 raises ValueError if the type of v is wrong. Python + # 3 raises TypeError. + except (AttributeError, TypeError, ValueError): + try: + # Python 2 raises ValueError trying to destructure an e.g. + # bytes. Python 3 raises TypeError. + for a, b in v: + vmapping[a] = b + except (TypeError, ValueError): + vmapping[name] = v + return templ(tag, **pycompat.strkwargs(vmapping)) + lastname = 'last_' + name + if lastname in templ: + last = values.pop() + else: + last = None + for v in values: + yield one(v) + if last is not None: + yield one(last, tag=lastname) + endname = 'end_' + plural + if endname in templ: + yield templ(endname, **strmapping) + def stringify(thing): """Turn values into bytes by converting into text and concatenating them""" - thing = templatekw.unwraphybrid(thing) + thing = unwraphybrid(thing) if util.safehasattr(thing, '__iter__') and not isinstance(thing, bytes): if isinstance(thing, str): # This is only reachable on Python 3 (otherwise @@ -59,7 +268,7 @@ def evalfuncarg(context, mapping, arg): """Evaluate given argument as value type""" thing = evalrawexp(context, mapping, arg) - thing = templatekw.unwrapvalue(thing) + thing = 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): @@ -76,7 +285,7 @@ thing = util.parsebool(data) else: thing = func(context, mapping, data) - thing = templatekw.unwrapvalue(thing) + thing = unwrapvalue(thing) if isinstance(thing, bool): return thing # other objects are evaluated as strings, which means 0 is True, but @@ -236,4 +445,4 @@ val = dictarg.get(key) if val is None: return - return templatekw.wraphybridvalue(dictarg, key, val) + return wraphybridvalue(dictarg, key, val)