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