Mercurial > hg
comparison mercurial/templatekw.py @ 36921:32f9b7e3f056
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.
author | Yuya Nishihara <yuya@tcha.org> |
---|---|
date | Thu, 08 Mar 2018 23:15:09 +0900 |
parents | cafd0586876b |
children | c97b936d8bb5 |
comparison
equal
deleted
inserted
replaced
36920:6ff6e1d6b5b8 | 36921:32f9b7e3f056 |
---|---|
21 obsutil, | 21 obsutil, |
22 patch, | 22 patch, |
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) |
179 | 47 |
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 | |
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 | 57 |
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') |
262 ctx = context.resource(mapping, 'ctx') | 61 ctx = context.resource(mapping, 'ctx') |