Show More
@@ -143,7 +143,7 b' from mercurial import (' | |||
|
143 | 143 | registrar, |
|
144 | 144 | revlog, |
|
145 | 145 | scmutil, |
|
146 |
template |
|
|
146 | templateutil, | |
|
147 | 147 | upgrade, |
|
148 | 148 | util, |
|
149 | 149 | vfs as vfsmod, |
@@ -375,12 +375,12 b' def lfsfiles(context, mapping):' | |||
|
375 | 375 | makemap = lambda v: { |
|
376 | 376 | 'file': v, |
|
377 | 377 | 'lfsoid': pointers[v].oid() if pointers[v] else None, |
|
378 |
'lfspointer': template |
|
|
378 | 'lfspointer': templateutil.hybriddict(pointer(v)), | |
|
379 | 379 | } |
|
380 | 380 | |
|
381 | 381 | # TODO: make the separator ', '? |
|
382 |
f = template |
|
|
383 |
return template |
|
|
382 | f = templateutil._showlist('lfs_file', files, templ, mapping) | |
|
383 | return templateutil.hybrid(f, files, makemap, pycompat.identity) | |
|
384 | 384 | |
|
385 | 385 | @command('debuglfsupload', |
|
386 | 386 | [('r', 'rev', [], _('upload large files introduced by REV'))]) |
@@ -35,7 +35,7 b' from mercurial import (' | |||
|
35 | 35 | registrar, |
|
36 | 36 | revsetlang, |
|
37 | 37 | smartset, |
|
38 |
template |
|
|
38 | templateutil, | |
|
39 | 39 | ) |
|
40 | 40 | |
|
41 | 41 | # Note for extension authors: ONLY specify testedwith = 'ships-with-hg-core' for |
@@ -237,7 +237,7 b' def remotenameskw(context, mapping):' | |||
|
237 | 237 | if 'remotebranches' in repo.names: |
|
238 | 238 | remotenames += repo.names['remotebranches'].names(repo, ctx.node()) |
|
239 | 239 | |
|
240 |
return template |
|
|
240 | return templateutil.compatlist(context, mapping, 'remotename', remotenames, | |
|
241 | 241 | plural='remotenames') |
|
242 | 242 | |
|
243 | 243 | @templatekeyword('remotebookmarks', requires={'repo', 'ctx', 'templ'}) |
@@ -250,7 +250,7 b' def remotebookmarkskw(context, mapping):' | |||
|
250 | 250 | if 'remotebookmarks' in repo.names: |
|
251 | 251 | remotebmarks = repo.names['remotebookmarks'].names(repo, ctx.node()) |
|
252 | 252 | |
|
253 |
return template |
|
|
253 | return templateutil.compatlist(context, mapping, 'remotebookmark', | |
|
254 | 254 | remotebmarks, plural='remotebookmarks') |
|
255 | 255 | |
|
256 | 256 | @templatekeyword('remotebranches', requires={'repo', 'ctx', 'templ'}) |
@@ -263,7 +263,7 b' def remotebrancheskw(context, mapping):' | |||
|
263 | 263 | if 'remotebranches' in repo.names: |
|
264 | 264 | remotebranches = repo.names['remotebranches'].names(repo, ctx.node()) |
|
265 | 265 | |
|
266 |
return template |
|
|
266 | return templateutil.compatlist(context, mapping, 'remotebranch', | |
|
267 | 267 | remotebranches, plural='remotebranches') |
|
268 | 268 | |
|
269 | 269 | def _revsetutil(repo, subset, x, rtypes): |
@@ -359,14 +359,15 b' class _templateconverter(object):' | |||
|
359 | 359 | data = util.sortdict(_iteritems(data)) |
|
360 | 360 | def f(): |
|
361 | 361 | yield _plainconverter.formatdict(data, key, value, fmt, sep) |
|
362 |
return template |
|
|
362 | return templateutil.hybriddict(data, key=key, value=value, fmt=fmt, | |
|
363 | gen=f) | |
|
363 | 364 | @staticmethod |
|
364 | 365 | def formatlist(data, name, fmt, sep): |
|
365 | 366 | '''build object that can be evaluated as either plain string or list''' |
|
366 | 367 | data = list(data) |
|
367 | 368 | def f(): |
|
368 | 369 | yield _plainconverter.formatlist(data, name, fmt, sep) |
|
369 |
return template |
|
|
370 | return templateutil.hybridlist(data, name=name, fmt=fmt, gen=f) | |
|
370 | 371 | |
|
371 | 372 | class templateformatter(baseformatter): |
|
372 | 373 | def __init__(self, ui, out, topic, opts): |
@@ -17,7 +17,6 b' from . import (' | |||
|
17 | 17 | node, |
|
18 | 18 | pycompat, |
|
19 | 19 | registrar, |
|
20 | templatekw, | |
|
21 | 20 | templateutil, |
|
22 | 21 | url, |
|
23 | 22 | util, |
@@ -366,7 +365,7 b' def slashpath(path):' | |||
|
366 | 365 | @templatefilter('splitlines') |
|
367 | 366 | def splitlines(text): |
|
368 | 367 | """Any text. Split text into a list of lines.""" |
|
369 |
return template |
|
|
368 | return templateutil.hybridlist(text.splitlines(), name='line') | |
|
370 | 369 | |
|
371 | 370 | @templatefilter('stringescape') |
|
372 | 371 | def stringescape(text): |
@@ -23,156 +23,24 b' from . import (' | |||
|
23 | 23 | pycompat, |
|
24 | 24 | registrar, |
|
25 | 25 | scmutil, |
|
26 | templateutil, | |
|
26 | 27 | util, |
|
27 | 28 | ) |
|
28 | 29 | |
|
29 | class _hybrid(object): | |
|
30 | """Wrapper for list or dict to support legacy template | |
|
31 | ||
|
32 | This class allows us to handle both: | |
|
33 | - "{files}" (legacy command-line-specific list hack) and | |
|
34 | - "{files % '{file}\n'}" (hgweb-style with inlining and function support) | |
|
35 | and to access raw values: | |
|
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) | |
|
30 | _hybrid = templateutil.hybrid | |
|
31 | _mappable = templateutil.mappable | |
|
32 | _showlist = templateutil._showlist | |
|
33 | hybriddict = templateutil.hybriddict | |
|
34 | hybridlist = templateutil.hybridlist | |
|
35 | compatdict = templateutil.compatdict | |
|
36 | compatlist = templateutil.compatlist | |
|
169 | 37 | |
|
170 | 38 | def showdict(name, data, mapping, plural=None, key='key', value='value', |
|
171 | 39 | fmt=None, separator=' '): |
|
172 | 40 | ui = mapping.get('ui') |
|
173 | 41 | if ui: |
|
174 |
ui.deprecwarn("templatekw.showdict() is deprecated, use |
|
|
175 | '4.6') | |
|
42 | ui.deprecwarn("templatekw.showdict() is deprecated, use " | |
|
43 | "templateutil.compatdict()", '4.6') | |
|
176 | 44 | c = [{key: k, value: v} for k, v in data.iteritems()] |
|
177 | 45 | f = _showlist(name, c, mapping['templ'], mapping, plural, separator) |
|
178 | 46 | return hybriddict(data, key=key, value=value, fmt=fmt, gen=f) |
@@ -180,82 +48,13 b' def showdict(name, data, mapping, plural' | |||
|
180 | 48 | def showlist(name, values, mapping, plural=None, element=None, separator=' '): |
|
181 | 49 | ui = mapping.get('ui') |
|
182 | 50 | if ui: |
|
183 |
ui.deprecwarn("templatekw.showlist() is deprecated, use |
|
|
184 | '4.6') | |
|
51 | ui.deprecwarn("templatekw.showlist() is deprecated, use " | |
|
52 | "templateutil.compatlist()", '4.6') | |
|
185 | 53 | if not element: |
|
186 | 54 | element = name |
|
187 | 55 | f = _showlist(name, values, mapping['templ'], mapping, plural, separator) |
|
188 | 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 | 58 | def getlatesttags(context, mapping, pattern=None): |
|
260 | 59 | '''return date, distance and name for the latest tag of rev''' |
|
261 | 60 | repo = context.resource(mapping, 'repo') |
@@ -498,7 +498,7 b' def dict_(context, mapping, args):' | |||
|
498 | 498 | |
|
499 | 499 | data.update((k, evalfuncarg(context, mapping, v)) |
|
500 | 500 | for k, v in args['kwargs'].iteritems()) |
|
501 |
return template |
|
|
501 | return templateutil.hybriddict(data) | |
|
502 | 502 | |
|
503 | 503 | @templatefunc('diff([includepattern [, excludepattern]])') |
|
504 | 504 | def diff(context, mapping, args): |
@@ -548,7 +548,7 b' def files(context, mapping, args):' | |||
|
548 | 548 | ctx = context.resource(mapping, 'ctx') |
|
549 | 549 | m = ctx.match([raw]) |
|
550 | 550 | files = list(ctx.matches(m)) |
|
551 |
return template |
|
|
551 | return templateutil.compatlist(context, mapping, "file", files) | |
|
552 | 552 | |
|
553 | 553 | @templatefunc('fill(text[, width[, initialident[, hangindent]]])') |
|
554 | 554 | def fill(context, mapping, args): |
@@ -718,7 +718,7 b' def join(context, mapping, args):' | |||
|
718 | 718 | # TODO: perhaps this should be evalfuncarg(), but it can't because hgweb |
|
719 | 719 | # abuses generator as a keyword that returns a list of dicts. |
|
720 | 720 | joinset = evalrawexp(context, mapping, args[0]) |
|
721 |
joinset = template |
|
|
721 | joinset = templateutil.unwrapvalue(joinset) | |
|
722 | 722 | joinfmt = getattr(joinset, 'joinfmt', pycompat.identity) |
|
723 | 723 | joiner = " " |
|
724 | 724 | if len(args) > 1: |
@@ -808,7 +808,7 b' def max_(context, mapping, args, **kwarg' | |||
|
808 | 808 | except (TypeError, ValueError): |
|
809 | 809 | # i18n: "max" is a keyword |
|
810 | 810 | raise error.ParseError(_("max first argument should be an iterable")) |
|
811 |
return template |
|
|
811 | return templateutil.wraphybridvalue(iterable, x, x) | |
|
812 | 812 | |
|
813 | 813 | @templatefunc('min(iterable)') |
|
814 | 814 | def min_(context, mapping, args, **kwargs): |
@@ -823,7 +823,7 b' def min_(context, mapping, args, **kwarg' | |||
|
823 | 823 | except (TypeError, ValueError): |
|
824 | 824 | # i18n: "min" is a keyword |
|
825 | 825 | raise error.ParseError(_("min first argument should be an iterable")) |
|
826 |
return template |
|
|
826 | return templateutil.wraphybridvalue(iterable, x, x) | |
|
827 | 827 | |
|
828 | 828 | @templatefunc('mod(a, b)') |
|
829 | 829 | def mod(context, mapping, args): |
@@ -847,7 +847,7 b' def obsfateoperations(context, mapping, ' | |||
|
847 | 847 | |
|
848 | 848 | try: |
|
849 | 849 | data = obsutil.markersoperations(markers) |
|
850 |
return template |
|
|
850 | return templateutil.hybridlist(data, name='operation') | |
|
851 | 851 | except (TypeError, KeyError): |
|
852 | 852 | # i18n: "obsfateoperations" is a keyword |
|
853 | 853 | errmsg = _("obsfateoperations first argument should be an iterable") |
@@ -864,7 +864,7 b' def obsfatedate(context, mapping, args):' | |||
|
864 | 864 | |
|
865 | 865 | try: |
|
866 | 866 | data = obsutil.markersdates(markers) |
|
867 |
return template |
|
|
867 | return templateutil.hybridlist(data, name='date', fmt='%d %d') | |
|
868 | 868 | except (TypeError, KeyError): |
|
869 | 869 | # i18n: "obsfatedate" is a keyword |
|
870 | 870 | errmsg = _("obsfatedate first argument should be an iterable") |
@@ -881,7 +881,7 b' def obsfateusers(context, mapping, args)' | |||
|
881 | 881 | |
|
882 | 882 | try: |
|
883 | 883 | data = obsutil.markersusers(markers) |
|
884 |
return template |
|
|
884 | return templateutil.hybridlist(data, name='user') | |
|
885 | 885 | except (TypeError, KeyError, ValueError): |
|
886 | 886 | # i18n: "obsfateusers" is a keyword |
|
887 | 887 | msg = _("obsfateusers first argument should be an iterable of " |
@@ -1120,7 +1120,7 b' def expandaliases(tree, aliases):' | |||
|
1120 | 1120 | |
|
1121 | 1121 | def _flatten(thing): |
|
1122 | 1122 | '''yield a single stream from a possibly nested set of iterators''' |
|
1123 |
thing = template |
|
|
1123 | thing = templateutil.unwraphybrid(thing) | |
|
1124 | 1124 | if isinstance(thing, bytes): |
|
1125 | 1125 | yield thing |
|
1126 | 1126 | elif isinstance(thing, str): |
@@ -1134,7 +1134,7 b' def _flatten(thing):' | |||
|
1134 | 1134 | yield pycompat.bytestr(thing) |
|
1135 | 1135 | else: |
|
1136 | 1136 | for i in thing: |
|
1137 |
i = template |
|
|
1137 | i = templateutil.unwraphybrid(i) | |
|
1138 | 1138 | if isinstance(i, bytes): |
|
1139 | 1139 | yield i |
|
1140 | 1140 | elif i is None: |
@@ -13,7 +13,6 b' from .i18n import _' | |||
|
13 | 13 | from . import ( |
|
14 | 14 | error, |
|
15 | 15 | pycompat, |
|
16 | templatekw, | |
|
17 | 16 | util, |
|
18 | 17 | ) |
|
19 | 18 | |
@@ -23,9 +22,219 b' class ResourceUnavailable(error.Abort):' | |||
|
23 | 22 | class TemplateNotFound(error.Abort): |
|
24 | 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 | 235 | def stringify(thing): |
|
27 | 236 | """Turn values into bytes by converting into text and concatenating them""" |
|
28 |
thing = |
|
|
237 | thing = unwraphybrid(thing) | |
|
29 | 238 | if util.safehasattr(thing, '__iter__') and not isinstance(thing, bytes): |
|
30 | 239 | if isinstance(thing, str): |
|
31 | 240 | # This is only reachable on Python 3 (otherwise |
@@ -59,7 +268,7 b' def evalrawexp(context, mapping, arg):' | |||
|
59 | 268 | def evalfuncarg(context, mapping, arg): |
|
60 | 269 | """Evaluate given argument as value type""" |
|
61 | 270 | thing = evalrawexp(context, mapping, arg) |
|
62 |
thing = |
|
|
271 | thing = unwrapvalue(thing) | |
|
63 | 272 | # evalrawexp() may return string, generator of strings or arbitrary object |
|
64 | 273 | # such as date tuple, but filter does not want generator. |
|
65 | 274 | if isinstance(thing, types.GeneratorType): |
@@ -76,7 +285,7 b' def evalboolean(context, mapping, arg):' | |||
|
76 | 285 | thing = util.parsebool(data) |
|
77 | 286 | else: |
|
78 | 287 | thing = func(context, mapping, data) |
|
79 |
thing = |
|
|
288 | thing = unwrapvalue(thing) | |
|
80 | 289 | if isinstance(thing, bool): |
|
81 | 290 | return thing |
|
82 | 291 | # other objects are evaluated as strings, which means 0 is True, but |
@@ -236,4 +445,4 b' def getdictitem(dictarg, key):' | |||
|
236 | 445 | val = dictarg.get(key) |
|
237 | 446 | if val is None: |
|
238 | 447 | return |
|
239 |
return |
|
|
448 | return wraphybridvalue(dictarg, key, val) |
General Comments 0
You need to be logged in to leave comments.
Login now