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')