##// END OF EJS Templates
templater: move hybrid class and functions to templateutil module...
Yuya Nishihara -
r36939:32f9b7e3 default
parent child Browse files
Show More
@@ -143,7 +143,7 b' from mercurial import ('
143 registrar,
143 registrar,
144 revlog,
144 revlog,
145 scmutil,
145 scmutil,
146 templatekw,
146 templateutil,
147 upgrade,
147 upgrade,
148 util,
148 util,
149 vfs as vfsmod,
149 vfs as vfsmod,
@@ -375,12 +375,12 b' def lfsfiles(context, mapping):'
375 makemap = lambda v: {
375 makemap = lambda v: {
376 'file': v,
376 'file': v,
377 'lfsoid': pointers[v].oid() if pointers[v] else None,
377 'lfsoid': pointers[v].oid() if pointers[v] else None,
378 'lfspointer': templatekw.hybriddict(pointer(v)),
378 'lfspointer': templateutil.hybriddict(pointer(v)),
379 }
379 }
380
380
381 # TODO: make the separator ', '?
381 # TODO: make the separator ', '?
382 f = templatekw._showlist('lfs_file', files, templ, mapping)
382 f = templateutil._showlist('lfs_file', files, templ, mapping)
383 return templatekw._hybrid(f, files, makemap, pycompat.identity)
383 return templateutil.hybrid(f, files, makemap, pycompat.identity)
384
384
385 @command('debuglfsupload',
385 @command('debuglfsupload',
386 [('r', 'rev', [], _('upload large files introduced by REV'))])
386 [('r', 'rev', [], _('upload large files introduced by REV'))])
@@ -35,7 +35,7 b' from mercurial import ('
35 registrar,
35 registrar,
36 revsetlang,
36 revsetlang,
37 smartset,
37 smartset,
38 templatekw,
38 templateutil,
39 )
39 )
40
40
41 # Note for extension authors: ONLY specify testedwith = 'ships-with-hg-core' for
41 # Note for extension authors: ONLY specify testedwith = 'ships-with-hg-core' for
@@ -237,8 +237,8 b' def remotenameskw(context, mapping):'
237 if 'remotebranches' in repo.names:
237 if 'remotebranches' in repo.names:
238 remotenames += repo.names['remotebranches'].names(repo, ctx.node())
238 remotenames += repo.names['remotebranches'].names(repo, ctx.node())
239
239
240 return templatekw.compatlist(context, mapping, 'remotename', remotenames,
240 return templateutil.compatlist(context, mapping, 'remotename', remotenames,
241 plural='remotenames')
241 plural='remotenames')
242
242
243 @templatekeyword('remotebookmarks', requires={'repo', 'ctx', 'templ'})
243 @templatekeyword('remotebookmarks', requires={'repo', 'ctx', 'templ'})
244 def remotebookmarkskw(context, mapping):
244 def remotebookmarkskw(context, mapping):
@@ -250,8 +250,8 b' def remotebookmarkskw(context, mapping):'
250 if 'remotebookmarks' in repo.names:
250 if 'remotebookmarks' in repo.names:
251 remotebmarks = repo.names['remotebookmarks'].names(repo, ctx.node())
251 remotebmarks = repo.names['remotebookmarks'].names(repo, ctx.node())
252
252
253 return templatekw.compatlist(context, mapping, 'remotebookmark',
253 return templateutil.compatlist(context, mapping, 'remotebookmark',
254 remotebmarks, plural='remotebookmarks')
254 remotebmarks, plural='remotebookmarks')
255
255
256 @templatekeyword('remotebranches', requires={'repo', 'ctx', 'templ'})
256 @templatekeyword('remotebranches', requires={'repo', 'ctx', 'templ'})
257 def remotebrancheskw(context, mapping):
257 def remotebrancheskw(context, mapping):
@@ -263,8 +263,8 b' def remotebrancheskw(context, mapping):'
263 if 'remotebranches' in repo.names:
263 if 'remotebranches' in repo.names:
264 remotebranches = repo.names['remotebranches'].names(repo, ctx.node())
264 remotebranches = repo.names['remotebranches'].names(repo, ctx.node())
265
265
266 return templatekw.compatlist(context, mapping, 'remotebranch',
266 return templateutil.compatlist(context, mapping, 'remotebranch',
267 remotebranches, plural='remotebranches')
267 remotebranches, plural='remotebranches')
268
268
269 def _revsetutil(repo, subset, x, rtypes):
269 def _revsetutil(repo, subset, x, rtypes):
270 """utility function to return a set of revs based on the rtypes"""
270 """utility function to return a set of revs based on the rtypes"""
@@ -359,14 +359,15 b' class _templateconverter(object):'
359 data = util.sortdict(_iteritems(data))
359 data = util.sortdict(_iteritems(data))
360 def f():
360 def f():
361 yield _plainconverter.formatdict(data, key, value, fmt, sep)
361 yield _plainconverter.formatdict(data, key, value, fmt, sep)
362 return templatekw.hybriddict(data, key=key, value=value, fmt=fmt, gen=f)
362 return templateutil.hybriddict(data, key=key, value=value, fmt=fmt,
363 gen=f)
363 @staticmethod
364 @staticmethod
364 def formatlist(data, name, fmt, sep):
365 def formatlist(data, name, fmt, sep):
365 '''build object that can be evaluated as either plain string or list'''
366 '''build object that can be evaluated as either plain string or list'''
366 data = list(data)
367 data = list(data)
367 def f():
368 def f():
368 yield _plainconverter.formatlist(data, name, fmt, sep)
369 yield _plainconverter.formatlist(data, name, fmt, sep)
369 return templatekw.hybridlist(data, name=name, fmt=fmt, gen=f)
370 return templateutil.hybridlist(data, name=name, fmt=fmt, gen=f)
370
371
371 class templateformatter(baseformatter):
372 class templateformatter(baseformatter):
372 def __init__(self, ui, out, topic, opts):
373 def __init__(self, ui, out, topic, opts):
@@ -17,7 +17,6 b' from . import ('
17 node,
17 node,
18 pycompat,
18 pycompat,
19 registrar,
19 registrar,
20 templatekw,
21 templateutil,
20 templateutil,
22 url,
21 url,
23 util,
22 util,
@@ -366,7 +365,7 b' def slashpath(path):'
366 @templatefilter('splitlines')
365 @templatefilter('splitlines')
367 def splitlines(text):
366 def splitlines(text):
368 """Any text. Split text into a list of lines."""
367 """Any text. Split text into a list of lines."""
369 return templatekw.hybridlist(text.splitlines(), name='line')
368 return templateutil.hybridlist(text.splitlines(), name='line')
370
369
371 @templatefilter('stringescape')
370 @templatefilter('stringescape')
372 def stringescape(text):
371 def stringescape(text):
@@ -23,156 +23,24 b' from . import ('
23 pycompat,
23 pycompat,
24 registrar,
24 registrar,
25 scmutil,
25 scmutil,
26 templateutil,
26 util,
27 util,
27 )
28 )
28
29
29 class _hybrid(object):
30 _hybrid = templateutil.hybrid
30 """Wrapper for list or dict to support legacy template
31 _mappable = templateutil.mappable
31
32 _showlist = templateutil._showlist
32 This class allows us to handle both:
33 hybriddict = templateutil.hybriddict
33 - "{files}" (legacy command-line-specific list hack) and
34 hybridlist = templateutil.hybridlist
34 - "{files % '{file}\n'}" (hgweb-style with inlining and function support)
35 compatdict = templateutil.compatdict
35 and to access raw values:
36 compatlist = templateutil.compatlist
36 - "{ifcontains(file, files, ...)}", "{ifcontains(key, extras, ...)}"
37 - "{get(extras, key)}"
38 - "{files|json}"
39 """
40
41 def __init__(self, gen, values, makemap, joinfmt, keytype=None):
42 if gen is not None:
43 self.gen = gen # generator or function returning generator
44 self._values = values
45 self._makemap = makemap
46 self.joinfmt = joinfmt
47 self.keytype = keytype # hint for 'x in y' where type(x) is unresolved
48 def gen(self):
49 """Default generator to stringify this as {join(self, ' ')}"""
50 for i, x in enumerate(self._values):
51 if i > 0:
52 yield ' '
53 yield self.joinfmt(x)
54 def itermaps(self):
55 makemap = self._makemap
56 for x in self._values:
57 yield makemap(x)
58 def __contains__(self, x):
59 return x in self._values
60 def __getitem__(self, key):
61 return self._values[key]
62 def __len__(self):
63 return len(self._values)
64 def __iter__(self):
65 return iter(self._values)
66 def __getattr__(self, name):
67 if name not in (r'get', r'items', r'iteritems', r'iterkeys',
68 r'itervalues', r'keys', r'values'):
69 raise AttributeError(name)
70 return getattr(self._values, name)
71
72 class _mappable(object):
73 """Wrapper for non-list/dict object to support map operation
74
75 This class allows us to handle both:
76 - "{manifest}"
77 - "{manifest % '{rev}:{node}'}"
78 - "{manifest.rev}"
79
80 Unlike a _hybrid, this does not simulate the behavior of the underling
81 value. Use unwrapvalue() or unwraphybrid() to obtain the inner object.
82 """
83
84 def __init__(self, gen, key, value, makemap):
85 if gen is not None:
86 self.gen = gen # generator or function returning generator
87 self._key = key
88 self._value = value # may be generator of strings
89 self._makemap = makemap
90
91 def gen(self):
92 yield pycompat.bytestr(self._value)
93
94 def tomap(self):
95 return self._makemap(self._key)
96
97 def itermaps(self):
98 yield self.tomap()
99
100 def hybriddict(data, key='key', value='value', fmt=None, gen=None):
101 """Wrap data to support both dict-like and string-like operations"""
102 prefmt = pycompat.identity
103 if fmt is None:
104 fmt = '%s=%s'
105 prefmt = pycompat.bytestr
106 return _hybrid(gen, data, lambda k: {key: k, value: data[k]},
107 lambda k: fmt % (prefmt(k), prefmt(data[k])))
108
109 def hybridlist(data, name, fmt=None, gen=None):
110 """Wrap data to support both list-like and string-like operations"""
111 prefmt = pycompat.identity
112 if fmt is None:
113 fmt = '%s'
114 prefmt = pycompat.bytestr
115 return _hybrid(gen, data, lambda x: {name: x}, lambda x: fmt % prefmt(x))
116
117 def unwraphybrid(thing):
118 """Return an object which can be stringified possibly by using a legacy
119 template"""
120 gen = getattr(thing, 'gen', None)
121 if gen is None:
122 return thing
123 if callable(gen):
124 return gen()
125 return gen
126
127 def unwrapvalue(thing):
128 """Move the inner value object out of the wrapper"""
129 if not util.safehasattr(thing, '_value'):
130 return thing
131 return thing._value
132
133 def wraphybridvalue(container, key, value):
134 """Wrap an element of hybrid container to be mappable
135
136 The key is passed to the makemap function of the given container, which
137 should be an item generated by iter(container).
138 """
139 makemap = getattr(container, '_makemap', None)
140 if makemap is None:
141 return value
142 if util.safehasattr(value, '_makemap'):
143 # a nested hybrid list/dict, which has its own way of map operation
144 return value
145 return _mappable(None, key, value, makemap)
146
147 def compatdict(context, mapping, name, data, key='key', value='value',
148 fmt=None, plural=None, separator=' '):
149 """Wrap data like hybriddict(), but also supports old-style list template
150
151 This exists for backward compatibility with the old-style template. Use
152 hybriddict() for new template keywords.
153 """
154 c = [{key: k, value: v} for k, v in data.iteritems()]
155 t = context.resource(mapping, 'templ')
156 f = _showlist(name, c, t, mapping, plural, separator)
157 return hybriddict(data, key=key, value=value, fmt=fmt, gen=f)
158
159 def compatlist(context, mapping, name, data, element=None, fmt=None,
160 plural=None, separator=' '):
161 """Wrap data like hybridlist(), but also supports old-style list template
162
163 This exists for backward compatibility with the old-style template. Use
164 hybridlist() for new template keywords.
165 """
166 t = context.resource(mapping, 'templ')
167 f = _showlist(name, data, t, mapping, plural, separator)
168 return hybridlist(data, name=element or name, fmt=fmt, gen=f)
169
37
170 def showdict(name, data, mapping, plural=None, key='key', value='value',
38 def showdict(name, data, mapping, plural=None, key='key', value='value',
171 fmt=None, separator=' '):
39 fmt=None, separator=' '):
172 ui = mapping.get('ui')
40 ui = mapping.get('ui')
173 if ui:
41 if ui:
174 ui.deprecwarn("templatekw.showdict() is deprecated, use compatdict()",
42 ui.deprecwarn("templatekw.showdict() is deprecated, use "
175 '4.6')
43 "templateutil.compatdict()", '4.6')
176 c = [{key: k, value: v} for k, v in data.iteritems()]
44 c = [{key: k, value: v} for k, v in data.iteritems()]
177 f = _showlist(name, c, mapping['templ'], mapping, plural, separator)
45 f = _showlist(name, c, mapping['templ'], mapping, plural, separator)
178 return hybriddict(data, key=key, value=value, fmt=fmt, gen=f)
46 return hybriddict(data, key=key, value=value, fmt=fmt, gen=f)
@@ -180,82 +48,13 b' def showdict(name, data, mapping, plural'
180 def showlist(name, values, mapping, plural=None, element=None, separator=' '):
48 def showlist(name, values, mapping, plural=None, element=None, separator=' '):
181 ui = mapping.get('ui')
49 ui = mapping.get('ui')
182 if ui:
50 if ui:
183 ui.deprecwarn("templatekw.showlist() is deprecated, use compatlist()",
51 ui.deprecwarn("templatekw.showlist() is deprecated, use "
184 '4.6')
52 "templateutil.compatlist()", '4.6')
185 if not element:
53 if not element:
186 element = name
54 element = name
187 f = _showlist(name, values, mapping['templ'], mapping, plural, separator)
55 f = _showlist(name, values, mapping['templ'], mapping, plural, separator)
188 return hybridlist(values, name=element, gen=f)
56 return hybridlist(values, name=element, gen=f)
189
57
190 def _showlist(name, values, templ, mapping, plural=None, separator=' '):
191 '''expand set of values.
192 name is name of key in template map.
193 values is list of strings or dicts.
194 plural is plural of name, if not simply name + 's'.
195 separator is used to join values as a string
196
197 expansion works like this, given name 'foo'.
198
199 if values is empty, expand 'no_foos'.
200
201 if 'foo' not in template map, return values as a string,
202 joined by 'separator'.
203
204 expand 'start_foos'.
205
206 for each value, expand 'foo'. if 'last_foo' in template
207 map, expand it instead of 'foo' for last key.
208
209 expand 'end_foos'.
210 '''
211 strmapping = pycompat.strkwargs(mapping)
212 if not plural:
213 plural = name + 's'
214 if not values:
215 noname = 'no_' + plural
216 if noname in templ:
217 yield templ(noname, **strmapping)
218 return
219 if name not in templ:
220 if isinstance(values[0], bytes):
221 yield separator.join(values)
222 else:
223 for v in values:
224 r = dict(v)
225 r.update(mapping)
226 yield r
227 return
228 startname = 'start_' + plural
229 if startname in templ:
230 yield templ(startname, **strmapping)
231 vmapping = mapping.copy()
232 def one(v, tag=name):
233 try:
234 vmapping.update(v)
235 # Python 2 raises ValueError if the type of v is wrong. Python
236 # 3 raises TypeError.
237 except (AttributeError, TypeError, ValueError):
238 try:
239 # Python 2 raises ValueError trying to destructure an e.g.
240 # bytes. Python 3 raises TypeError.
241 for a, b in v:
242 vmapping[a] = b
243 except (TypeError, ValueError):
244 vmapping[name] = v
245 return templ(tag, **pycompat.strkwargs(vmapping))
246 lastname = 'last_' + name
247 if lastname in templ:
248 last = values.pop()
249 else:
250 last = None
251 for v in values:
252 yield one(v)
253 if last is not None:
254 yield one(last, tag=lastname)
255 endname = 'end_' + plural
256 if endname in templ:
257 yield templ(endname, **strmapping)
258
259 def getlatesttags(context, mapping, pattern=None):
58 def getlatesttags(context, mapping, pattern=None):
260 '''return date, distance and name for the latest tag of rev'''
59 '''return date, distance and name for the latest tag of rev'''
261 repo = context.resource(mapping, 'repo')
60 repo = context.resource(mapping, 'repo')
@@ -498,7 +498,7 b' def dict_(context, mapping, args):'
498
498
499 data.update((k, evalfuncarg(context, mapping, v))
499 data.update((k, evalfuncarg(context, mapping, v))
500 for k, v in args['kwargs'].iteritems())
500 for k, v in args['kwargs'].iteritems())
501 return templatekw.hybriddict(data)
501 return templateutil.hybriddict(data)
502
502
503 @templatefunc('diff([includepattern [, excludepattern]])')
503 @templatefunc('diff([includepattern [, excludepattern]])')
504 def diff(context, mapping, args):
504 def diff(context, mapping, args):
@@ -548,7 +548,7 b' def files(context, mapping, args):'
548 ctx = context.resource(mapping, 'ctx')
548 ctx = context.resource(mapping, 'ctx')
549 m = ctx.match([raw])
549 m = ctx.match([raw])
550 files = list(ctx.matches(m))
550 files = list(ctx.matches(m))
551 return templatekw.compatlist(context, mapping, "file", files)
551 return templateutil.compatlist(context, mapping, "file", files)
552
552
553 @templatefunc('fill(text[, width[, initialident[, hangindent]]])')
553 @templatefunc('fill(text[, width[, initialident[, hangindent]]])')
554 def fill(context, mapping, args):
554 def fill(context, mapping, args):
@@ -718,7 +718,7 b' def join(context, mapping, args):'
718 # TODO: perhaps this should be evalfuncarg(), but it can't because hgweb
718 # TODO: perhaps this should be evalfuncarg(), but it can't because hgweb
719 # abuses generator as a keyword that returns a list of dicts.
719 # abuses generator as a keyword that returns a list of dicts.
720 joinset = evalrawexp(context, mapping, args[0])
720 joinset = evalrawexp(context, mapping, args[0])
721 joinset = templatekw.unwrapvalue(joinset)
721 joinset = templateutil.unwrapvalue(joinset)
722 joinfmt = getattr(joinset, 'joinfmt', pycompat.identity)
722 joinfmt = getattr(joinset, 'joinfmt', pycompat.identity)
723 joiner = " "
723 joiner = " "
724 if len(args) > 1:
724 if len(args) > 1:
@@ -808,7 +808,7 b' def max_(context, mapping, args, **kwarg'
808 except (TypeError, ValueError):
808 except (TypeError, ValueError):
809 # i18n: "max" is a keyword
809 # i18n: "max" is a keyword
810 raise error.ParseError(_("max first argument should be an iterable"))
810 raise error.ParseError(_("max first argument should be an iterable"))
811 return templatekw.wraphybridvalue(iterable, x, x)
811 return templateutil.wraphybridvalue(iterable, x, x)
812
812
813 @templatefunc('min(iterable)')
813 @templatefunc('min(iterable)')
814 def min_(context, mapping, args, **kwargs):
814 def min_(context, mapping, args, **kwargs):
@@ -823,7 +823,7 b' def min_(context, mapping, args, **kwarg'
823 except (TypeError, ValueError):
823 except (TypeError, ValueError):
824 # i18n: "min" is a keyword
824 # i18n: "min" is a keyword
825 raise error.ParseError(_("min first argument should be an iterable"))
825 raise error.ParseError(_("min first argument should be an iterable"))
826 return templatekw.wraphybridvalue(iterable, x, x)
826 return templateutil.wraphybridvalue(iterable, x, x)
827
827
828 @templatefunc('mod(a, b)')
828 @templatefunc('mod(a, b)')
829 def mod(context, mapping, args):
829 def mod(context, mapping, args):
@@ -847,7 +847,7 b' def obsfateoperations(context, mapping, '
847
847
848 try:
848 try:
849 data = obsutil.markersoperations(markers)
849 data = obsutil.markersoperations(markers)
850 return templatekw.hybridlist(data, name='operation')
850 return templateutil.hybridlist(data, name='operation')
851 except (TypeError, KeyError):
851 except (TypeError, KeyError):
852 # i18n: "obsfateoperations" is a keyword
852 # i18n: "obsfateoperations" is a keyword
853 errmsg = _("obsfateoperations first argument should be an iterable")
853 errmsg = _("obsfateoperations first argument should be an iterable")
@@ -864,7 +864,7 b' def obsfatedate(context, mapping, args):'
864
864
865 try:
865 try:
866 data = obsutil.markersdates(markers)
866 data = obsutil.markersdates(markers)
867 return templatekw.hybridlist(data, name='date', fmt='%d %d')
867 return templateutil.hybridlist(data, name='date', fmt='%d %d')
868 except (TypeError, KeyError):
868 except (TypeError, KeyError):
869 # i18n: "obsfatedate" is a keyword
869 # i18n: "obsfatedate" is a keyword
870 errmsg = _("obsfatedate first argument should be an iterable")
870 errmsg = _("obsfatedate first argument should be an iterable")
@@ -881,7 +881,7 b' def obsfateusers(context, mapping, args)'
881
881
882 try:
882 try:
883 data = obsutil.markersusers(markers)
883 data = obsutil.markersusers(markers)
884 return templatekw.hybridlist(data, name='user')
884 return templateutil.hybridlist(data, name='user')
885 except (TypeError, KeyError, ValueError):
885 except (TypeError, KeyError, ValueError):
886 # i18n: "obsfateusers" is a keyword
886 # i18n: "obsfateusers" is a keyword
887 msg = _("obsfateusers first argument should be an iterable of "
887 msg = _("obsfateusers first argument should be an iterable of "
@@ -1120,7 +1120,7 b' def expandaliases(tree, aliases):'
1120
1120
1121 def _flatten(thing):
1121 def _flatten(thing):
1122 '''yield a single stream from a possibly nested set of iterators'''
1122 '''yield a single stream from a possibly nested set of iterators'''
1123 thing = templatekw.unwraphybrid(thing)
1123 thing = templateutil.unwraphybrid(thing)
1124 if isinstance(thing, bytes):
1124 if isinstance(thing, bytes):
1125 yield thing
1125 yield thing
1126 elif isinstance(thing, str):
1126 elif isinstance(thing, str):
@@ -1134,7 +1134,7 b' def _flatten(thing):'
1134 yield pycompat.bytestr(thing)
1134 yield pycompat.bytestr(thing)
1135 else:
1135 else:
1136 for i in thing:
1136 for i in thing:
1137 i = templatekw.unwraphybrid(i)
1137 i = templateutil.unwraphybrid(i)
1138 if isinstance(i, bytes):
1138 if isinstance(i, bytes):
1139 yield i
1139 yield i
1140 elif i is None:
1140 elif i is None:
@@ -13,7 +13,6 b' from .i18n import _'
13 from . import (
13 from . import (
14 error,
14 error,
15 pycompat,
15 pycompat,
16 templatekw,
17 util,
16 util,
18 )
17 )
19
18
@@ -23,9 +22,219 b' class ResourceUnavailable(error.Abort):'
23 class TemplateNotFound(error.Abort):
22 class TemplateNotFound(error.Abort):
24 pass
23 pass
25
24
25 class hybrid(object):
26 """Wrapper for list or dict to support legacy template
27
28 This class allows us to handle both:
29 - "{files}" (legacy command-line-specific list hack) and
30 - "{files % '{file}\n'}" (hgweb-style with inlining and function support)
31 and to access raw values:
32 - "{ifcontains(file, files, ...)}", "{ifcontains(key, extras, ...)}"
33 - "{get(extras, key)}"
34 - "{files|json}"
35 """
36
37 def __init__(self, gen, values, makemap, joinfmt, keytype=None):
38 if gen is not None:
39 self.gen = gen # generator or function returning generator
40 self._values = values
41 self._makemap = makemap
42 self.joinfmt = joinfmt
43 self.keytype = keytype # hint for 'x in y' where type(x) is unresolved
44 def gen(self):
45 """Default generator to stringify this as {join(self, ' ')}"""
46 for i, x in enumerate(self._values):
47 if i > 0:
48 yield ' '
49 yield self.joinfmt(x)
50 def itermaps(self):
51 makemap = self._makemap
52 for x in self._values:
53 yield makemap(x)
54 def __contains__(self, x):
55 return x in self._values
56 def __getitem__(self, key):
57 return self._values[key]
58 def __len__(self):
59 return len(self._values)
60 def __iter__(self):
61 return iter(self._values)
62 def __getattr__(self, name):
63 if name not in (r'get', r'items', r'iteritems', r'iterkeys',
64 r'itervalues', r'keys', r'values'):
65 raise AttributeError(name)
66 return getattr(self._values, name)
67
68 class mappable(object):
69 """Wrapper for non-list/dict object to support map operation
70
71 This class allows us to handle both:
72 - "{manifest}"
73 - "{manifest % '{rev}:{node}'}"
74 - "{manifest.rev}"
75
76 Unlike a hybrid, this does not simulate the behavior of the underling
77 value. Use unwrapvalue() or unwraphybrid() to obtain the inner object.
78 """
79
80 def __init__(self, gen, key, value, makemap):
81 if gen is not None:
82 self.gen = gen # generator or function returning generator
83 self._key = key
84 self._value = value # may be generator of strings
85 self._makemap = makemap
86
87 def gen(self):
88 yield pycompat.bytestr(self._value)
89
90 def tomap(self):
91 return self._makemap(self._key)
92
93 def itermaps(self):
94 yield self.tomap()
95
96 def hybriddict(data, key='key', value='value', fmt=None, gen=None):
97 """Wrap data to support both dict-like and string-like operations"""
98 prefmt = pycompat.identity
99 if fmt is None:
100 fmt = '%s=%s'
101 prefmt = pycompat.bytestr
102 return hybrid(gen, data, lambda k: {key: k, value: data[k]},
103 lambda k: fmt % (prefmt(k), prefmt(data[k])))
104
105 def hybridlist(data, name, fmt=None, gen=None):
106 """Wrap data to support both list-like and string-like operations"""
107 prefmt = pycompat.identity
108 if fmt is None:
109 fmt = '%s'
110 prefmt = pycompat.bytestr
111 return hybrid(gen, data, lambda x: {name: x}, lambda x: fmt % prefmt(x))
112
113 def unwraphybrid(thing):
114 """Return an object which can be stringified possibly by using a legacy
115 template"""
116 gen = getattr(thing, 'gen', None)
117 if gen is None:
118 return thing
119 if callable(gen):
120 return gen()
121 return gen
122
123 def unwrapvalue(thing):
124 """Move the inner value object out of the wrapper"""
125 if not util.safehasattr(thing, '_value'):
126 return thing
127 return thing._value
128
129 def wraphybridvalue(container, key, value):
130 """Wrap an element of hybrid container to be mappable
131
132 The key is passed to the makemap function of the given container, which
133 should be an item generated by iter(container).
134 """
135 makemap = getattr(container, '_makemap', None)
136 if makemap is None:
137 return value
138 if util.safehasattr(value, '_makemap'):
139 # a nested hybrid list/dict, which has its own way of map operation
140 return value
141 return mappable(None, key, value, makemap)
142
143 def compatdict(context, mapping, name, data, key='key', value='value',
144 fmt=None, plural=None, separator=' '):
145 """Wrap data like hybriddict(), but also supports old-style list template
146
147 This exists for backward compatibility with the old-style template. Use
148 hybriddict() for new template keywords.
149 """
150 c = [{key: k, value: v} for k, v in data.iteritems()]
151 t = context.resource(mapping, 'templ')
152 f = _showlist(name, c, t, mapping, plural, separator)
153 return hybriddict(data, key=key, value=value, fmt=fmt, gen=f)
154
155 def compatlist(context, mapping, name, data, element=None, fmt=None,
156 plural=None, separator=' '):
157 """Wrap data like hybridlist(), but also supports old-style list template
158
159 This exists for backward compatibility with the old-style template. Use
160 hybridlist() for new template keywords.
161 """
162 t = context.resource(mapping, 'templ')
163 f = _showlist(name, data, t, mapping, plural, separator)
164 return hybridlist(data, name=element or name, fmt=fmt, gen=f)
165
166 def _showlist(name, values, templ, mapping, plural=None, separator=' '):
167 '''expand set of values.
168 name is name of key in template map.
169 values is list of strings or dicts.
170 plural is plural of name, if not simply name + 's'.
171 separator is used to join values as a string
172
173 expansion works like this, given name 'foo'.
174
175 if values is empty, expand 'no_foos'.
176
177 if 'foo' not in template map, return values as a string,
178 joined by 'separator'.
179
180 expand 'start_foos'.
181
182 for each value, expand 'foo'. if 'last_foo' in template
183 map, expand it instead of 'foo' for last key.
184
185 expand 'end_foos'.
186 '''
187 strmapping = pycompat.strkwargs(mapping)
188 if not plural:
189 plural = name + 's'
190 if not values:
191 noname = 'no_' + plural
192 if noname in templ:
193 yield templ(noname, **strmapping)
194 return
195 if name not in templ:
196 if isinstance(values[0], bytes):
197 yield separator.join(values)
198 else:
199 for v in values:
200 r = dict(v)
201 r.update(mapping)
202 yield r
203 return
204 startname = 'start_' + plural
205 if startname in templ:
206 yield templ(startname, **strmapping)
207 vmapping = mapping.copy()
208 def one(v, tag=name):
209 try:
210 vmapping.update(v)
211 # Python 2 raises ValueError if the type of v is wrong. Python
212 # 3 raises TypeError.
213 except (AttributeError, TypeError, ValueError):
214 try:
215 # Python 2 raises ValueError trying to destructure an e.g.
216 # bytes. Python 3 raises TypeError.
217 for a, b in v:
218 vmapping[a] = b
219 except (TypeError, ValueError):
220 vmapping[name] = v
221 return templ(tag, **pycompat.strkwargs(vmapping))
222 lastname = 'last_' + name
223 if lastname in templ:
224 last = values.pop()
225 else:
226 last = None
227 for v in values:
228 yield one(v)
229 if last is not None:
230 yield one(last, tag=lastname)
231 endname = 'end_' + plural
232 if endname in templ:
233 yield templ(endname, **strmapping)
234
26 def stringify(thing):
235 def stringify(thing):
27 """Turn values into bytes by converting into text and concatenating them"""
236 """Turn values into bytes by converting into text and concatenating them"""
28 thing = templatekw.unwraphybrid(thing)
237 thing = unwraphybrid(thing)
29 if util.safehasattr(thing, '__iter__') and not isinstance(thing, bytes):
238 if util.safehasattr(thing, '__iter__') and not isinstance(thing, bytes):
30 if isinstance(thing, str):
239 if isinstance(thing, str):
31 # This is only reachable on Python 3 (otherwise
240 # This is only reachable on Python 3 (otherwise
@@ -59,7 +268,7 b' def evalrawexp(context, mapping, arg):'
59 def evalfuncarg(context, mapping, arg):
268 def evalfuncarg(context, mapping, arg):
60 """Evaluate given argument as value type"""
269 """Evaluate given argument as value type"""
61 thing = evalrawexp(context, mapping, arg)
270 thing = evalrawexp(context, mapping, arg)
62 thing = templatekw.unwrapvalue(thing)
271 thing = unwrapvalue(thing)
63 # evalrawexp() may return string, generator of strings or arbitrary object
272 # evalrawexp() may return string, generator of strings or arbitrary object
64 # such as date tuple, but filter does not want generator.
273 # such as date tuple, but filter does not want generator.
65 if isinstance(thing, types.GeneratorType):
274 if isinstance(thing, types.GeneratorType):
@@ -76,7 +285,7 b' def evalboolean(context, mapping, arg):'
76 thing = util.parsebool(data)
285 thing = util.parsebool(data)
77 else:
286 else:
78 thing = func(context, mapping, data)
287 thing = func(context, mapping, data)
79 thing = templatekw.unwrapvalue(thing)
288 thing = unwrapvalue(thing)
80 if isinstance(thing, bool):
289 if isinstance(thing, bool):
81 return thing
290 return thing
82 # other objects are evaluated as strings, which means 0 is True, but
291 # other objects are evaluated as strings, which means 0 is True, but
@@ -236,4 +445,4 b' def getdictitem(dictarg, key):'
236 val = dictarg.get(key)
445 val = dictarg.get(key)
237 if val is None:
446 if val is None:
238 return
447 return
239 return templatekw.wraphybridvalue(dictarg, key, val)
448 return wraphybridvalue(dictarg, key, val)
General Comments 0
You need to be logged in to leave comments. Login now