##// END OF EJS Templates
templater: fix truth testing of integer 0 taken from a list/dict...
Yuya Nishihara -
r38466:b6294c11 default
parent child Browse files
Show More
@@ -1,886 +1,887 b''
1 # templateutil.py - utility for template evaluation
1 # templateutil.py - utility for template evaluation
2 #
2 #
3 # Copyright 2005, 2006 Matt Mackall <mpm@selenic.com>
3 # Copyright 2005, 2006 Matt Mackall <mpm@selenic.com>
4 #
4 #
5 # This software may be used and distributed according to the terms of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version.
6 # GNU General Public License version 2 or any later version.
7
7
8 from __future__ import absolute_import
8 from __future__ import absolute_import
9
9
10 import abc
10 import abc
11 import types
11 import types
12
12
13 from .i18n import _
13 from .i18n import _
14 from . import (
14 from . import (
15 error,
15 error,
16 pycompat,
16 pycompat,
17 util,
17 util,
18 )
18 )
19 from .utils import (
19 from .utils import (
20 dateutil,
20 dateutil,
21 stringutil,
21 stringutil,
22 )
22 )
23
23
24 class ResourceUnavailable(error.Abort):
24 class ResourceUnavailable(error.Abort):
25 pass
25 pass
26
26
27 class TemplateNotFound(error.Abort):
27 class TemplateNotFound(error.Abort):
28 pass
28 pass
29
29
30 class wrapped(object):
30 class wrapped(object):
31 """Object requiring extra conversion prior to displaying or processing
31 """Object requiring extra conversion prior to displaying or processing
32 as value
32 as value
33
33
34 Use unwrapvalue() or unwrapastype() to obtain the inner object.
34 Use unwrapvalue() or unwrapastype() to obtain the inner object.
35 """
35 """
36
36
37 __metaclass__ = abc.ABCMeta
37 __metaclass__ = abc.ABCMeta
38
38
39 @abc.abstractmethod
39 @abc.abstractmethod
40 def contains(self, context, mapping, item):
40 def contains(self, context, mapping, item):
41 """Test if the specified item is in self
41 """Test if the specified item is in self
42
42
43 The item argument may be a wrapped object.
43 The item argument may be a wrapped object.
44 """
44 """
45
45
46 @abc.abstractmethod
46 @abc.abstractmethod
47 def getmember(self, context, mapping, key):
47 def getmember(self, context, mapping, key):
48 """Return a member item for the specified key
48 """Return a member item for the specified key
49
49
50 The key argument may be a wrapped object.
50 The key argument may be a wrapped object.
51 A returned object may be either a wrapped object or a pure value
51 A returned object may be either a wrapped object or a pure value
52 depending on the self type.
52 depending on the self type.
53 """
53 """
54
54
55 @abc.abstractmethod
55 @abc.abstractmethod
56 def getmin(self, context, mapping):
56 def getmin(self, context, mapping):
57 """Return the smallest item, which may be either a wrapped or a pure
57 """Return the smallest item, which may be either a wrapped or a pure
58 value depending on the self type"""
58 value depending on the self type"""
59
59
60 @abc.abstractmethod
60 @abc.abstractmethod
61 def getmax(self, context, mapping):
61 def getmax(self, context, mapping):
62 """Return the largest item, which may be either a wrapped or a pure
62 """Return the largest item, which may be either a wrapped or a pure
63 value depending on the self type"""
63 value depending on the self type"""
64
64
65 @abc.abstractmethod
65 @abc.abstractmethod
66 def itermaps(self, context):
66 def itermaps(self, context):
67 """Yield each template mapping"""
67 """Yield each template mapping"""
68
68
69 @abc.abstractmethod
69 @abc.abstractmethod
70 def join(self, context, mapping, sep):
70 def join(self, context, mapping, sep):
71 """Join items with the separator; Returns a bytes or (possibly nested)
71 """Join items with the separator; Returns a bytes or (possibly nested)
72 generator of bytes
72 generator of bytes
73
73
74 A pre-configured template may be rendered per item if this container
74 A pre-configured template may be rendered per item if this container
75 holds unprintable items.
75 holds unprintable items.
76 """
76 """
77
77
78 @abc.abstractmethod
78 @abc.abstractmethod
79 def show(self, context, mapping):
79 def show(self, context, mapping):
80 """Return a bytes or (possibly nested) generator of bytes representing
80 """Return a bytes or (possibly nested) generator of bytes representing
81 the underlying object
81 the underlying object
82
82
83 A pre-configured template may be rendered if the underlying object is
83 A pre-configured template may be rendered if the underlying object is
84 not printable.
84 not printable.
85 """
85 """
86
86
87 @abc.abstractmethod
87 @abc.abstractmethod
88 def tobool(self, context, mapping):
88 def tobool(self, context, mapping):
89 """Return a boolean representation of the inner value"""
89 """Return a boolean representation of the inner value"""
90
90
91 @abc.abstractmethod
91 @abc.abstractmethod
92 def tovalue(self, context, mapping):
92 def tovalue(self, context, mapping):
93 """Move the inner value object out or create a value representation
93 """Move the inner value object out or create a value representation
94
94
95 A returned value must be serializable by templaterfilters.json().
95 A returned value must be serializable by templaterfilters.json().
96 """
96 """
97
97
98 class mappable(object):
98 class mappable(object):
99 """Object which can be converted to a single template mapping"""
99 """Object which can be converted to a single template mapping"""
100
100
101 def itermaps(self, context):
101 def itermaps(self, context):
102 yield self.tomap(context)
102 yield self.tomap(context)
103
103
104 @abc.abstractmethod
104 @abc.abstractmethod
105 def tomap(self, context):
105 def tomap(self, context):
106 """Create a single template mapping representing this"""
106 """Create a single template mapping representing this"""
107
107
108 class wrappedbytes(wrapped):
108 class wrappedbytes(wrapped):
109 """Wrapper for byte string"""
109 """Wrapper for byte string"""
110
110
111 def __init__(self, value):
111 def __init__(self, value):
112 self._value = value
112 self._value = value
113
113
114 def contains(self, context, mapping, item):
114 def contains(self, context, mapping, item):
115 item = stringify(context, mapping, item)
115 item = stringify(context, mapping, item)
116 return item in self._value
116 return item in self._value
117
117
118 def getmember(self, context, mapping, key):
118 def getmember(self, context, mapping, key):
119 raise error.ParseError(_('%r is not a dictionary')
119 raise error.ParseError(_('%r is not a dictionary')
120 % pycompat.bytestr(self._value))
120 % pycompat.bytestr(self._value))
121
121
122 def getmin(self, context, mapping):
122 def getmin(self, context, mapping):
123 return self._getby(context, mapping, min)
123 return self._getby(context, mapping, min)
124
124
125 def getmax(self, context, mapping):
125 def getmax(self, context, mapping):
126 return self._getby(context, mapping, max)
126 return self._getby(context, mapping, max)
127
127
128 def _getby(self, context, mapping, func):
128 def _getby(self, context, mapping, func):
129 if not self._value:
129 if not self._value:
130 raise error.ParseError(_('empty string'))
130 raise error.ParseError(_('empty string'))
131 return func(pycompat.iterbytestr(self._value))
131 return func(pycompat.iterbytestr(self._value))
132
132
133 def itermaps(self, context):
133 def itermaps(self, context):
134 raise error.ParseError(_('%r is not iterable of mappings')
134 raise error.ParseError(_('%r is not iterable of mappings')
135 % pycompat.bytestr(self._value))
135 % pycompat.bytestr(self._value))
136
136
137 def join(self, context, mapping, sep):
137 def join(self, context, mapping, sep):
138 return joinitems(pycompat.iterbytestr(self._value), sep)
138 return joinitems(pycompat.iterbytestr(self._value), sep)
139
139
140 def show(self, context, mapping):
140 def show(self, context, mapping):
141 return self._value
141 return self._value
142
142
143 def tobool(self, context, mapping):
143 def tobool(self, context, mapping):
144 return bool(self._value)
144 return bool(self._value)
145
145
146 def tovalue(self, context, mapping):
146 def tovalue(self, context, mapping):
147 return self._value
147 return self._value
148
148
149 class wrappedvalue(wrapped):
149 class wrappedvalue(wrapped):
150 """Generic wrapper for pure non-list/dict/bytes value"""
150 """Generic wrapper for pure non-list/dict/bytes value"""
151
151
152 def __init__(self, value):
152 def __init__(self, value):
153 self._value = value
153 self._value = value
154
154
155 def contains(self, context, mapping, item):
155 def contains(self, context, mapping, item):
156 raise error.ParseError(_("%r is not iterable") % self._value)
156 raise error.ParseError(_("%r is not iterable") % self._value)
157
157
158 def getmember(self, context, mapping, key):
158 def getmember(self, context, mapping, key):
159 raise error.ParseError(_('%r is not a dictionary') % self._value)
159 raise error.ParseError(_('%r is not a dictionary') % self._value)
160
160
161 def getmin(self, context, mapping):
161 def getmin(self, context, mapping):
162 raise error.ParseError(_("%r is not iterable") % self._value)
162 raise error.ParseError(_("%r is not iterable") % self._value)
163
163
164 def getmax(self, context, mapping):
164 def getmax(self, context, mapping):
165 raise error.ParseError(_("%r is not iterable") % self._value)
165 raise error.ParseError(_("%r is not iterable") % self._value)
166
166
167 def itermaps(self, context):
167 def itermaps(self, context):
168 raise error.ParseError(_('%r is not iterable of mappings')
168 raise error.ParseError(_('%r is not iterable of mappings')
169 % self._value)
169 % self._value)
170
170
171 def join(self, context, mapping, sep):
171 def join(self, context, mapping, sep):
172 raise error.ParseError(_('%r is not iterable') % self._value)
172 raise error.ParseError(_('%r is not iterable') % self._value)
173
173
174 def show(self, context, mapping):
174 def show(self, context, mapping):
175 if self._value is None:
175 if self._value is None:
176 return b''
176 return b''
177 return pycompat.bytestr(self._value)
177 return pycompat.bytestr(self._value)
178
178
179 def tobool(self, context, mapping):
179 def tobool(self, context, mapping):
180 if self._value is None:
180 if self._value is None:
181 return False
181 return False
182 if isinstance(self._value, bool):
182 if isinstance(self._value, bool):
183 return self._value
183 return self._value
184 # otherwise evaluate as string, which means 0 is True
184 # otherwise evaluate as string, which means 0 is True
185 return bool(pycompat.bytestr(self._value))
185 return bool(pycompat.bytestr(self._value))
186
186
187 def tovalue(self, context, mapping):
187 def tovalue(self, context, mapping):
188 return self._value
188 return self._value
189
189
190 class date(mappable, wrapped):
190 class date(mappable, wrapped):
191 """Wrapper for date tuple"""
191 """Wrapper for date tuple"""
192
192
193 def __init__(self, value, showfmt='%d %d'):
193 def __init__(self, value, showfmt='%d %d'):
194 # value may be (float, int), but public interface shouldn't support
194 # value may be (float, int), but public interface shouldn't support
195 # floating-point timestamp
195 # floating-point timestamp
196 self._unixtime, self._tzoffset = map(int, value)
196 self._unixtime, self._tzoffset = map(int, value)
197 self._showfmt = showfmt
197 self._showfmt = showfmt
198
198
199 def contains(self, context, mapping, item):
199 def contains(self, context, mapping, item):
200 raise error.ParseError(_('date is not iterable'))
200 raise error.ParseError(_('date is not iterable'))
201
201
202 def getmember(self, context, mapping, key):
202 def getmember(self, context, mapping, key):
203 raise error.ParseError(_('date is not a dictionary'))
203 raise error.ParseError(_('date is not a dictionary'))
204
204
205 def getmin(self, context, mapping):
205 def getmin(self, context, mapping):
206 raise error.ParseError(_('date is not iterable'))
206 raise error.ParseError(_('date is not iterable'))
207
207
208 def getmax(self, context, mapping):
208 def getmax(self, context, mapping):
209 raise error.ParseError(_('date is not iterable'))
209 raise error.ParseError(_('date is not iterable'))
210
210
211 def join(self, context, mapping, sep):
211 def join(self, context, mapping, sep):
212 raise error.ParseError(_("date is not iterable"))
212 raise error.ParseError(_("date is not iterable"))
213
213
214 def show(self, context, mapping):
214 def show(self, context, mapping):
215 return self._showfmt % (self._unixtime, self._tzoffset)
215 return self._showfmt % (self._unixtime, self._tzoffset)
216
216
217 def tomap(self, context):
217 def tomap(self, context):
218 return {'unixtime': self._unixtime, 'tzoffset': self._tzoffset}
218 return {'unixtime': self._unixtime, 'tzoffset': self._tzoffset}
219
219
220 def tobool(self, context, mapping):
220 def tobool(self, context, mapping):
221 return True
221 return True
222
222
223 def tovalue(self, context, mapping):
223 def tovalue(self, context, mapping):
224 return (self._unixtime, self._tzoffset)
224 return (self._unixtime, self._tzoffset)
225
225
226 class hybrid(wrapped):
226 class hybrid(wrapped):
227 """Wrapper for list or dict to support legacy template
227 """Wrapper for list or dict to support legacy template
228
228
229 This class allows us to handle both:
229 This class allows us to handle both:
230 - "{files}" (legacy command-line-specific list hack) and
230 - "{files}" (legacy command-line-specific list hack) and
231 - "{files % '{file}\n'}" (hgweb-style with inlining and function support)
231 - "{files % '{file}\n'}" (hgweb-style with inlining and function support)
232 and to access raw values:
232 and to access raw values:
233 - "{ifcontains(file, files, ...)}", "{ifcontains(key, extras, ...)}"
233 - "{ifcontains(file, files, ...)}", "{ifcontains(key, extras, ...)}"
234 - "{get(extras, key)}"
234 - "{get(extras, key)}"
235 - "{files|json}"
235 - "{files|json}"
236 """
236 """
237
237
238 def __init__(self, gen, values, makemap, joinfmt, keytype=None):
238 def __init__(self, gen, values, makemap, joinfmt, keytype=None):
239 self._gen = gen # generator or function returning generator
239 self._gen = gen # generator or function returning generator
240 self._values = values
240 self._values = values
241 self._makemap = makemap
241 self._makemap = makemap
242 self._joinfmt = joinfmt
242 self._joinfmt = joinfmt
243 self._keytype = keytype # hint for 'x in y' where type(x) is unresolved
243 self._keytype = keytype # hint for 'x in y' where type(x) is unresolved
244
244
245 def contains(self, context, mapping, item):
245 def contains(self, context, mapping, item):
246 item = unwrapastype(context, mapping, item, self._keytype)
246 item = unwrapastype(context, mapping, item, self._keytype)
247 return item in self._values
247 return item in self._values
248
248
249 def getmember(self, context, mapping, key):
249 def getmember(self, context, mapping, key):
250 # TODO: maybe split hybrid list/dict types?
250 # TODO: maybe split hybrid list/dict types?
251 if not util.safehasattr(self._values, 'get'):
251 if not util.safehasattr(self._values, 'get'):
252 raise error.ParseError(_('not a dictionary'))
252 raise error.ParseError(_('not a dictionary'))
253 key = unwrapastype(context, mapping, key, self._keytype)
253 key = unwrapastype(context, mapping, key, self._keytype)
254 return self._wrapvalue(key, self._values.get(key))
254 return self._wrapvalue(key, self._values.get(key))
255
255
256 def getmin(self, context, mapping):
256 def getmin(self, context, mapping):
257 return self._getby(context, mapping, min)
257 return self._getby(context, mapping, min)
258
258
259 def getmax(self, context, mapping):
259 def getmax(self, context, mapping):
260 return self._getby(context, mapping, max)
260 return self._getby(context, mapping, max)
261
261
262 def _getby(self, context, mapping, func):
262 def _getby(self, context, mapping, func):
263 if not self._values:
263 if not self._values:
264 raise error.ParseError(_('empty sequence'))
264 raise error.ParseError(_('empty sequence'))
265 val = func(self._values)
265 val = func(self._values)
266 return self._wrapvalue(val, val)
266 return self._wrapvalue(val, val)
267
267
268 def _wrapvalue(self, key, val):
268 def _wrapvalue(self, key, val):
269 if val is None:
269 if val is None:
270 return
270 return
271 if util.safehasattr(val, '_makemap'):
271 if util.safehasattr(val, '_makemap'):
272 # a nested hybrid list/dict, which has its own way of map operation
272 # a nested hybrid list/dict, which has its own way of map operation
273 return val
273 return val
274 return hybriditem(None, key, val, self._makemap)
274 return hybriditem(None, key, val, self._makemap)
275
275
276 def itermaps(self, context):
276 def itermaps(self, context):
277 makemap = self._makemap
277 makemap = self._makemap
278 for x in self._values:
278 for x in self._values:
279 yield makemap(x)
279 yield makemap(x)
280
280
281 def join(self, context, mapping, sep):
281 def join(self, context, mapping, sep):
282 # TODO: switch gen to (context, mapping) API?
282 # TODO: switch gen to (context, mapping) API?
283 return joinitems((self._joinfmt(x) for x in self._values), sep)
283 return joinitems((self._joinfmt(x) for x in self._values), sep)
284
284
285 def show(self, context, mapping):
285 def show(self, context, mapping):
286 # TODO: switch gen to (context, mapping) API?
286 # TODO: switch gen to (context, mapping) API?
287 gen = self._gen
287 gen = self._gen
288 if gen is None:
288 if gen is None:
289 return self.join(context, mapping, ' ')
289 return self.join(context, mapping, ' ')
290 if callable(gen):
290 if callable(gen):
291 return gen()
291 return gen()
292 return gen
292 return gen
293
293
294 def tobool(self, context, mapping):
294 def tobool(self, context, mapping):
295 return bool(self._values)
295 return bool(self._values)
296
296
297 def tovalue(self, context, mapping):
297 def tovalue(self, context, mapping):
298 # TODO: make it non-recursive for trivial lists/dicts
298 # TODO: make it non-recursive for trivial lists/dicts
299 xs = self._values
299 xs = self._values
300 if util.safehasattr(xs, 'get'):
300 if util.safehasattr(xs, 'get'):
301 return {k: unwrapvalue(context, mapping, v)
301 return {k: unwrapvalue(context, mapping, v)
302 for k, v in xs.iteritems()}
302 for k, v in xs.iteritems()}
303 return [unwrapvalue(context, mapping, x) for x in xs]
303 return [unwrapvalue(context, mapping, x) for x in xs]
304
304
305 class hybriditem(mappable, wrapped):
305 class hybriditem(mappable, wrapped):
306 """Wrapper for non-list/dict object to support map operation
306 """Wrapper for non-list/dict object to support map operation
307
307
308 This class allows us to handle both:
308 This class allows us to handle both:
309 - "{manifest}"
309 - "{manifest}"
310 - "{manifest % '{rev}:{node}'}"
310 - "{manifest % '{rev}:{node}'}"
311 - "{manifest.rev}"
311 - "{manifest.rev}"
312 """
312 """
313
313
314 def __init__(self, gen, key, value, makemap):
314 def __init__(self, gen, key, value, makemap):
315 self._gen = gen # generator or function returning generator
315 self._gen = gen # generator or function returning generator
316 self._key = key
316 self._key = key
317 self._value = value # may be generator of strings
317 self._value = value # may be generator of strings
318 self._makemap = makemap
318 self._makemap = makemap
319
319
320 def tomap(self, context):
320 def tomap(self, context):
321 return self._makemap(self._key)
321 return self._makemap(self._key)
322
322
323 def contains(self, context, mapping, item):
323 def contains(self, context, mapping, item):
324 w = makewrapped(context, mapping, self._value)
324 w = makewrapped(context, mapping, self._value)
325 return w.contains(context, mapping, item)
325 return w.contains(context, mapping, item)
326
326
327 def getmember(self, context, mapping, key):
327 def getmember(self, context, mapping, key):
328 w = makewrapped(context, mapping, self._value)
328 w = makewrapped(context, mapping, self._value)
329 return w.getmember(context, mapping, key)
329 return w.getmember(context, mapping, key)
330
330
331 def getmin(self, context, mapping):
331 def getmin(self, context, mapping):
332 w = makewrapped(context, mapping, self._value)
332 w = makewrapped(context, mapping, self._value)
333 return w.getmin(context, mapping)
333 return w.getmin(context, mapping)
334
334
335 def getmax(self, context, mapping):
335 def getmax(self, context, mapping):
336 w = makewrapped(context, mapping, self._value)
336 w = makewrapped(context, mapping, self._value)
337 return w.getmax(context, mapping)
337 return w.getmax(context, mapping)
338
338
339 def join(self, context, mapping, sep):
339 def join(self, context, mapping, sep):
340 w = makewrapped(context, mapping, self._value)
340 w = makewrapped(context, mapping, self._value)
341 return w.join(context, mapping, sep)
341 return w.join(context, mapping, sep)
342
342
343 def show(self, context, mapping):
343 def show(self, context, mapping):
344 # TODO: switch gen to (context, mapping) API?
344 # TODO: switch gen to (context, mapping) API?
345 gen = self._gen
345 gen = self._gen
346 if gen is None:
346 if gen is None:
347 return pycompat.bytestr(self._value)
347 return pycompat.bytestr(self._value)
348 if callable(gen):
348 if callable(gen):
349 return gen()
349 return gen()
350 return gen
350 return gen
351
351
352 def tobool(self, context, mapping):
352 def tobool(self, context, mapping):
353 return bool(self.tovalue(context, mapping))
353 w = makewrapped(context, mapping, self._value)
354 return w.tobool(context, mapping)
354
355
355 def tovalue(self, context, mapping):
356 def tovalue(self, context, mapping):
356 return _unthunk(context, mapping, self._value)
357 return _unthunk(context, mapping, self._value)
357
358
358 class _mappingsequence(wrapped):
359 class _mappingsequence(wrapped):
359 """Wrapper for sequence of template mappings
360 """Wrapper for sequence of template mappings
360
361
361 This represents an inner template structure (i.e. a list of dicts),
362 This represents an inner template structure (i.e. a list of dicts),
362 which can also be rendered by the specified named/literal template.
363 which can also be rendered by the specified named/literal template.
363
364
364 Template mappings may be nested.
365 Template mappings may be nested.
365 """
366 """
366
367
367 def __init__(self, name=None, tmpl=None, sep=''):
368 def __init__(self, name=None, tmpl=None, sep=''):
368 if name is not None and tmpl is not None:
369 if name is not None and tmpl is not None:
369 raise error.ProgrammingError('name and tmpl are mutually exclusive')
370 raise error.ProgrammingError('name and tmpl are mutually exclusive')
370 self._name = name
371 self._name = name
371 self._tmpl = tmpl
372 self._tmpl = tmpl
372 self._defaultsep = sep
373 self._defaultsep = sep
373
374
374 def contains(self, context, mapping, item):
375 def contains(self, context, mapping, item):
375 raise error.ParseError(_('not comparable'))
376 raise error.ParseError(_('not comparable'))
376
377
377 def getmember(self, context, mapping, key):
378 def getmember(self, context, mapping, key):
378 raise error.ParseError(_('not a dictionary'))
379 raise error.ParseError(_('not a dictionary'))
379
380
380 def getmin(self, context, mapping):
381 def getmin(self, context, mapping):
381 raise error.ParseError(_('not comparable'))
382 raise error.ParseError(_('not comparable'))
382
383
383 def getmax(self, context, mapping):
384 def getmax(self, context, mapping):
384 raise error.ParseError(_('not comparable'))
385 raise error.ParseError(_('not comparable'))
385
386
386 def join(self, context, mapping, sep):
387 def join(self, context, mapping, sep):
387 mapsiter = _iteroverlaymaps(context, mapping, self.itermaps(context))
388 mapsiter = _iteroverlaymaps(context, mapping, self.itermaps(context))
388 if self._name:
389 if self._name:
389 itemiter = (context.process(self._name, m) for m in mapsiter)
390 itemiter = (context.process(self._name, m) for m in mapsiter)
390 elif self._tmpl:
391 elif self._tmpl:
391 itemiter = (context.expand(self._tmpl, m) for m in mapsiter)
392 itemiter = (context.expand(self._tmpl, m) for m in mapsiter)
392 else:
393 else:
393 raise error.ParseError(_('not displayable without template'))
394 raise error.ParseError(_('not displayable without template'))
394 return joinitems(itemiter, sep)
395 return joinitems(itemiter, sep)
395
396
396 def show(self, context, mapping):
397 def show(self, context, mapping):
397 return self.join(context, mapping, self._defaultsep)
398 return self.join(context, mapping, self._defaultsep)
398
399
399 def tovalue(self, context, mapping):
400 def tovalue(self, context, mapping):
400 knownres = context.knownresourcekeys()
401 knownres = context.knownresourcekeys()
401 items = []
402 items = []
402 for nm in self.itermaps(context):
403 for nm in self.itermaps(context):
403 # drop internal resources (recursively) which shouldn't be displayed
404 # drop internal resources (recursively) which shouldn't be displayed
404 lm = context.overlaymap(mapping, nm)
405 lm = context.overlaymap(mapping, nm)
405 items.append({k: unwrapvalue(context, lm, v)
406 items.append({k: unwrapvalue(context, lm, v)
406 for k, v in nm.iteritems() if k not in knownres})
407 for k, v in nm.iteritems() if k not in knownres})
407 return items
408 return items
408
409
409 class mappinggenerator(_mappingsequence):
410 class mappinggenerator(_mappingsequence):
410 """Wrapper for generator of template mappings
411 """Wrapper for generator of template mappings
411
412
412 The function ``make(context, *args)`` should return a generator of
413 The function ``make(context, *args)`` should return a generator of
413 mapping dicts.
414 mapping dicts.
414 """
415 """
415
416
416 def __init__(self, make, args=(), name=None, tmpl=None, sep=''):
417 def __init__(self, make, args=(), name=None, tmpl=None, sep=''):
417 super(mappinggenerator, self).__init__(name, tmpl, sep)
418 super(mappinggenerator, self).__init__(name, tmpl, sep)
418 self._make = make
419 self._make = make
419 self._args = args
420 self._args = args
420
421
421 def itermaps(self, context):
422 def itermaps(self, context):
422 return self._make(context, *self._args)
423 return self._make(context, *self._args)
423
424
424 def tobool(self, context, mapping):
425 def tobool(self, context, mapping):
425 return _nonempty(self.itermaps(context))
426 return _nonempty(self.itermaps(context))
426
427
427 class mappinglist(_mappingsequence):
428 class mappinglist(_mappingsequence):
428 """Wrapper for list of template mappings"""
429 """Wrapper for list of template mappings"""
429
430
430 def __init__(self, mappings, name=None, tmpl=None, sep=''):
431 def __init__(self, mappings, name=None, tmpl=None, sep=''):
431 super(mappinglist, self).__init__(name, tmpl, sep)
432 super(mappinglist, self).__init__(name, tmpl, sep)
432 self._mappings = mappings
433 self._mappings = mappings
433
434
434 def itermaps(self, context):
435 def itermaps(self, context):
435 return iter(self._mappings)
436 return iter(self._mappings)
436
437
437 def tobool(self, context, mapping):
438 def tobool(self, context, mapping):
438 return bool(self._mappings)
439 return bool(self._mappings)
439
440
440 class mappedgenerator(wrapped):
441 class mappedgenerator(wrapped):
441 """Wrapper for generator of strings which acts as a list
442 """Wrapper for generator of strings which acts as a list
442
443
443 The function ``make(context, *args)`` should return a generator of
444 The function ``make(context, *args)`` should return a generator of
444 byte strings, or a generator of (possibly nested) generators of byte
445 byte strings, or a generator of (possibly nested) generators of byte
445 strings (i.e. a generator for a list of byte strings.)
446 strings (i.e. a generator for a list of byte strings.)
446 """
447 """
447
448
448 def __init__(self, make, args=()):
449 def __init__(self, make, args=()):
449 self._make = make
450 self._make = make
450 self._args = args
451 self._args = args
451
452
452 def contains(self, context, mapping, item):
453 def contains(self, context, mapping, item):
453 item = stringify(context, mapping, item)
454 item = stringify(context, mapping, item)
454 return item in self.tovalue(context, mapping)
455 return item in self.tovalue(context, mapping)
455
456
456 def _gen(self, context):
457 def _gen(self, context):
457 return self._make(context, *self._args)
458 return self._make(context, *self._args)
458
459
459 def getmember(self, context, mapping, key):
460 def getmember(self, context, mapping, key):
460 raise error.ParseError(_('not a dictionary'))
461 raise error.ParseError(_('not a dictionary'))
461
462
462 def getmin(self, context, mapping):
463 def getmin(self, context, mapping):
463 return self._getby(context, mapping, min)
464 return self._getby(context, mapping, min)
464
465
465 def getmax(self, context, mapping):
466 def getmax(self, context, mapping):
466 return self._getby(context, mapping, max)
467 return self._getby(context, mapping, max)
467
468
468 def _getby(self, context, mapping, func):
469 def _getby(self, context, mapping, func):
469 xs = self.tovalue(context, mapping)
470 xs = self.tovalue(context, mapping)
470 if not xs:
471 if not xs:
471 raise error.ParseError(_('empty sequence'))
472 raise error.ParseError(_('empty sequence'))
472 return func(xs)
473 return func(xs)
473
474
474 def itermaps(self, context):
475 def itermaps(self, context):
475 raise error.ParseError(_('list of strings is not mappable'))
476 raise error.ParseError(_('list of strings is not mappable'))
476
477
477 def join(self, context, mapping, sep):
478 def join(self, context, mapping, sep):
478 return joinitems(self._gen(context), sep)
479 return joinitems(self._gen(context), sep)
479
480
480 def show(self, context, mapping):
481 def show(self, context, mapping):
481 return self.join(context, mapping, '')
482 return self.join(context, mapping, '')
482
483
483 def tobool(self, context, mapping):
484 def tobool(self, context, mapping):
484 return _nonempty(self._gen(context))
485 return _nonempty(self._gen(context))
485
486
486 def tovalue(self, context, mapping):
487 def tovalue(self, context, mapping):
487 return [stringify(context, mapping, x) for x in self._gen(context)]
488 return [stringify(context, mapping, x) for x in self._gen(context)]
488
489
489 def hybriddict(data, key='key', value='value', fmt=None, gen=None):
490 def hybriddict(data, key='key', value='value', fmt=None, gen=None):
490 """Wrap data to support both dict-like and string-like operations"""
491 """Wrap data to support both dict-like and string-like operations"""
491 prefmt = pycompat.identity
492 prefmt = pycompat.identity
492 if fmt is None:
493 if fmt is None:
493 fmt = '%s=%s'
494 fmt = '%s=%s'
494 prefmt = pycompat.bytestr
495 prefmt = pycompat.bytestr
495 return hybrid(gen, data, lambda k: {key: k, value: data[k]},
496 return hybrid(gen, data, lambda k: {key: k, value: data[k]},
496 lambda k: fmt % (prefmt(k), prefmt(data[k])))
497 lambda k: fmt % (prefmt(k), prefmt(data[k])))
497
498
498 def hybridlist(data, name, fmt=None, gen=None):
499 def hybridlist(data, name, fmt=None, gen=None):
499 """Wrap data to support both list-like and string-like operations"""
500 """Wrap data to support both list-like and string-like operations"""
500 prefmt = pycompat.identity
501 prefmt = pycompat.identity
501 if fmt is None:
502 if fmt is None:
502 fmt = '%s'
503 fmt = '%s'
503 prefmt = pycompat.bytestr
504 prefmt = pycompat.bytestr
504 return hybrid(gen, data, lambda x: {name: x}, lambda x: fmt % prefmt(x))
505 return hybrid(gen, data, lambda x: {name: x}, lambda x: fmt % prefmt(x))
505
506
506 def compatdict(context, mapping, name, data, key='key', value='value',
507 def compatdict(context, mapping, name, data, key='key', value='value',
507 fmt=None, plural=None, separator=' '):
508 fmt=None, plural=None, separator=' '):
508 """Wrap data like hybriddict(), but also supports old-style list template
509 """Wrap data like hybriddict(), but also supports old-style list template
509
510
510 This exists for backward compatibility with the old-style template. Use
511 This exists for backward compatibility with the old-style template. Use
511 hybriddict() for new template keywords.
512 hybriddict() for new template keywords.
512 """
513 """
513 c = [{key: k, value: v} for k, v in data.iteritems()]
514 c = [{key: k, value: v} for k, v in data.iteritems()]
514 f = _showcompatlist(context, mapping, name, c, plural, separator)
515 f = _showcompatlist(context, mapping, name, c, plural, separator)
515 return hybriddict(data, key=key, value=value, fmt=fmt, gen=f)
516 return hybriddict(data, key=key, value=value, fmt=fmt, gen=f)
516
517
517 def compatlist(context, mapping, name, data, element=None, fmt=None,
518 def compatlist(context, mapping, name, data, element=None, fmt=None,
518 plural=None, separator=' '):
519 plural=None, separator=' '):
519 """Wrap data like hybridlist(), but also supports old-style list template
520 """Wrap data like hybridlist(), but also supports old-style list template
520
521
521 This exists for backward compatibility with the old-style template. Use
522 This exists for backward compatibility with the old-style template. Use
522 hybridlist() for new template keywords.
523 hybridlist() for new template keywords.
523 """
524 """
524 f = _showcompatlist(context, mapping, name, data, plural, separator)
525 f = _showcompatlist(context, mapping, name, data, plural, separator)
525 return hybridlist(data, name=element or name, fmt=fmt, gen=f)
526 return hybridlist(data, name=element or name, fmt=fmt, gen=f)
526
527
527 def _showcompatlist(context, mapping, name, values, plural=None, separator=' '):
528 def _showcompatlist(context, mapping, name, values, plural=None, separator=' '):
528 """Return a generator that renders old-style list template
529 """Return a generator that renders old-style list template
529
530
530 name is name of key in template map.
531 name is name of key in template map.
531 values is list of strings or dicts.
532 values is list of strings or dicts.
532 plural is plural of name, if not simply name + 's'.
533 plural is plural of name, if not simply name + 's'.
533 separator is used to join values as a string
534 separator is used to join values as a string
534
535
535 expansion works like this, given name 'foo'.
536 expansion works like this, given name 'foo'.
536
537
537 if values is empty, expand 'no_foos'.
538 if values is empty, expand 'no_foos'.
538
539
539 if 'foo' not in template map, return values as a string,
540 if 'foo' not in template map, return values as a string,
540 joined by 'separator'.
541 joined by 'separator'.
541
542
542 expand 'start_foos'.
543 expand 'start_foos'.
543
544
544 for each value, expand 'foo'. if 'last_foo' in template
545 for each value, expand 'foo'. if 'last_foo' in template
545 map, expand it instead of 'foo' for last key.
546 map, expand it instead of 'foo' for last key.
546
547
547 expand 'end_foos'.
548 expand 'end_foos'.
548 """
549 """
549 if not plural:
550 if not plural:
550 plural = name + 's'
551 plural = name + 's'
551 if not values:
552 if not values:
552 noname = 'no_' + plural
553 noname = 'no_' + plural
553 if context.preload(noname):
554 if context.preload(noname):
554 yield context.process(noname, mapping)
555 yield context.process(noname, mapping)
555 return
556 return
556 if not context.preload(name):
557 if not context.preload(name):
557 if isinstance(values[0], bytes):
558 if isinstance(values[0], bytes):
558 yield separator.join(values)
559 yield separator.join(values)
559 else:
560 else:
560 for v in values:
561 for v in values:
561 r = dict(v)
562 r = dict(v)
562 r.update(mapping)
563 r.update(mapping)
563 yield r
564 yield r
564 return
565 return
565 startname = 'start_' + plural
566 startname = 'start_' + plural
566 if context.preload(startname):
567 if context.preload(startname):
567 yield context.process(startname, mapping)
568 yield context.process(startname, mapping)
568 def one(v, tag=name):
569 def one(v, tag=name):
569 vmapping = {}
570 vmapping = {}
570 try:
571 try:
571 vmapping.update(v)
572 vmapping.update(v)
572 # Python 2 raises ValueError if the type of v is wrong. Python
573 # Python 2 raises ValueError if the type of v is wrong. Python
573 # 3 raises TypeError.
574 # 3 raises TypeError.
574 except (AttributeError, TypeError, ValueError):
575 except (AttributeError, TypeError, ValueError):
575 try:
576 try:
576 # Python 2 raises ValueError trying to destructure an e.g.
577 # Python 2 raises ValueError trying to destructure an e.g.
577 # bytes. Python 3 raises TypeError.
578 # bytes. Python 3 raises TypeError.
578 for a, b in v:
579 for a, b in v:
579 vmapping[a] = b
580 vmapping[a] = b
580 except (TypeError, ValueError):
581 except (TypeError, ValueError):
581 vmapping[name] = v
582 vmapping[name] = v
582 vmapping = context.overlaymap(mapping, vmapping)
583 vmapping = context.overlaymap(mapping, vmapping)
583 return context.process(tag, vmapping)
584 return context.process(tag, vmapping)
584 lastname = 'last_' + name
585 lastname = 'last_' + name
585 if context.preload(lastname):
586 if context.preload(lastname):
586 last = values.pop()
587 last = values.pop()
587 else:
588 else:
588 last = None
589 last = None
589 for v in values:
590 for v in values:
590 yield one(v)
591 yield one(v)
591 if last is not None:
592 if last is not None:
592 yield one(last, tag=lastname)
593 yield one(last, tag=lastname)
593 endname = 'end_' + plural
594 endname = 'end_' + plural
594 if context.preload(endname):
595 if context.preload(endname):
595 yield context.process(endname, mapping)
596 yield context.process(endname, mapping)
596
597
597 def flatten(context, mapping, thing):
598 def flatten(context, mapping, thing):
598 """Yield a single stream from a possibly nested set of iterators"""
599 """Yield a single stream from a possibly nested set of iterators"""
599 if isinstance(thing, wrapped):
600 if isinstance(thing, wrapped):
600 thing = thing.show(context, mapping)
601 thing = thing.show(context, mapping)
601 if isinstance(thing, bytes):
602 if isinstance(thing, bytes):
602 yield thing
603 yield thing
603 elif isinstance(thing, str):
604 elif isinstance(thing, str):
604 # We can only hit this on Python 3, and it's here to guard
605 # We can only hit this on Python 3, and it's here to guard
605 # against infinite recursion.
606 # against infinite recursion.
606 raise error.ProgrammingError('Mercurial IO including templates is done'
607 raise error.ProgrammingError('Mercurial IO including templates is done'
607 ' with bytes, not strings, got %r' % thing)
608 ' with bytes, not strings, got %r' % thing)
608 elif thing is None:
609 elif thing is None:
609 pass
610 pass
610 elif not util.safehasattr(thing, '__iter__'):
611 elif not util.safehasattr(thing, '__iter__'):
611 yield pycompat.bytestr(thing)
612 yield pycompat.bytestr(thing)
612 else:
613 else:
613 for i in thing:
614 for i in thing:
614 if isinstance(i, wrapped):
615 if isinstance(i, wrapped):
615 i = i.show(context, mapping)
616 i = i.show(context, mapping)
616 if isinstance(i, bytes):
617 if isinstance(i, bytes):
617 yield i
618 yield i
618 elif i is None:
619 elif i is None:
619 pass
620 pass
620 elif not util.safehasattr(i, '__iter__'):
621 elif not util.safehasattr(i, '__iter__'):
621 yield pycompat.bytestr(i)
622 yield pycompat.bytestr(i)
622 else:
623 else:
623 for j in flatten(context, mapping, i):
624 for j in flatten(context, mapping, i):
624 yield j
625 yield j
625
626
626 def stringify(context, mapping, thing):
627 def stringify(context, mapping, thing):
627 """Turn values into bytes by converting into text and concatenating them"""
628 """Turn values into bytes by converting into text and concatenating them"""
628 if isinstance(thing, bytes):
629 if isinstance(thing, bytes):
629 return thing # retain localstr to be round-tripped
630 return thing # retain localstr to be round-tripped
630 return b''.join(flatten(context, mapping, thing))
631 return b''.join(flatten(context, mapping, thing))
631
632
632 def findsymbolicname(arg):
633 def findsymbolicname(arg):
633 """Find symbolic name for the given compiled expression; returns None
634 """Find symbolic name for the given compiled expression; returns None
634 if nothing found reliably"""
635 if nothing found reliably"""
635 while True:
636 while True:
636 func, data = arg
637 func, data = arg
637 if func is runsymbol:
638 if func is runsymbol:
638 return data
639 return data
639 elif func is runfilter:
640 elif func is runfilter:
640 arg = data[0]
641 arg = data[0]
641 else:
642 else:
642 return None
643 return None
643
644
644 def _nonempty(xiter):
645 def _nonempty(xiter):
645 try:
646 try:
646 next(xiter)
647 next(xiter)
647 return True
648 return True
648 except StopIteration:
649 except StopIteration:
649 return False
650 return False
650
651
651 def _unthunk(context, mapping, thing):
652 def _unthunk(context, mapping, thing):
652 """Evaluate a lazy byte string into value"""
653 """Evaluate a lazy byte string into value"""
653 if not isinstance(thing, types.GeneratorType):
654 if not isinstance(thing, types.GeneratorType):
654 return thing
655 return thing
655 return stringify(context, mapping, thing)
656 return stringify(context, mapping, thing)
656
657
657 def evalrawexp(context, mapping, arg):
658 def evalrawexp(context, mapping, arg):
658 """Evaluate given argument as a bare template object which may require
659 """Evaluate given argument as a bare template object which may require
659 further processing (such as folding generator of strings)"""
660 further processing (such as folding generator of strings)"""
660 func, data = arg
661 func, data = arg
661 return func(context, mapping, data)
662 return func(context, mapping, data)
662
663
663 def evalwrapped(context, mapping, arg):
664 def evalwrapped(context, mapping, arg):
664 """Evaluate given argument to wrapped object"""
665 """Evaluate given argument to wrapped object"""
665 thing = evalrawexp(context, mapping, arg)
666 thing = evalrawexp(context, mapping, arg)
666 return makewrapped(context, mapping, thing)
667 return makewrapped(context, mapping, thing)
667
668
668 def makewrapped(context, mapping, thing):
669 def makewrapped(context, mapping, thing):
669 """Lift object to a wrapped type"""
670 """Lift object to a wrapped type"""
670 if isinstance(thing, wrapped):
671 if isinstance(thing, wrapped):
671 return thing
672 return thing
672 thing = _unthunk(context, mapping, thing)
673 thing = _unthunk(context, mapping, thing)
673 if isinstance(thing, bytes):
674 if isinstance(thing, bytes):
674 return wrappedbytes(thing)
675 return wrappedbytes(thing)
675 return wrappedvalue(thing)
676 return wrappedvalue(thing)
676
677
677 def evalfuncarg(context, mapping, arg):
678 def evalfuncarg(context, mapping, arg):
678 """Evaluate given argument as value type"""
679 """Evaluate given argument as value type"""
679 return unwrapvalue(context, mapping, evalrawexp(context, mapping, arg))
680 return unwrapvalue(context, mapping, evalrawexp(context, mapping, arg))
680
681
681 def unwrapvalue(context, mapping, thing):
682 def unwrapvalue(context, mapping, thing):
682 """Move the inner value object out of the wrapper"""
683 """Move the inner value object out of the wrapper"""
683 if isinstance(thing, wrapped):
684 if isinstance(thing, wrapped):
684 return thing.tovalue(context, mapping)
685 return thing.tovalue(context, mapping)
685 # evalrawexp() may return string, generator of strings or arbitrary object
686 # evalrawexp() may return string, generator of strings or arbitrary object
686 # such as date tuple, but filter does not want generator.
687 # such as date tuple, but filter does not want generator.
687 return _unthunk(context, mapping, thing)
688 return _unthunk(context, mapping, thing)
688
689
689 def evalboolean(context, mapping, arg):
690 def evalboolean(context, mapping, arg):
690 """Evaluate given argument as boolean, but also takes boolean literals"""
691 """Evaluate given argument as boolean, but also takes boolean literals"""
691 func, data = arg
692 func, data = arg
692 if func is runsymbol:
693 if func is runsymbol:
693 thing = func(context, mapping, data, default=None)
694 thing = func(context, mapping, data, default=None)
694 if thing is None:
695 if thing is None:
695 # not a template keyword, takes as a boolean literal
696 # not a template keyword, takes as a boolean literal
696 thing = stringutil.parsebool(data)
697 thing = stringutil.parsebool(data)
697 else:
698 else:
698 thing = func(context, mapping, data)
699 thing = func(context, mapping, data)
699 return makewrapped(context, mapping, thing).tobool(context, mapping)
700 return makewrapped(context, mapping, thing).tobool(context, mapping)
700
701
701 def evaldate(context, mapping, arg, err=None):
702 def evaldate(context, mapping, arg, err=None):
702 """Evaluate given argument as a date tuple or a date string; returns
703 """Evaluate given argument as a date tuple or a date string; returns
703 a (unixtime, offset) tuple"""
704 a (unixtime, offset) tuple"""
704 thing = evalrawexp(context, mapping, arg)
705 thing = evalrawexp(context, mapping, arg)
705 return unwrapdate(context, mapping, thing, err)
706 return unwrapdate(context, mapping, thing, err)
706
707
707 def unwrapdate(context, mapping, thing, err=None):
708 def unwrapdate(context, mapping, thing, err=None):
708 if isinstance(thing, date):
709 if isinstance(thing, date):
709 return thing.tovalue(context, mapping)
710 return thing.tovalue(context, mapping)
710 # TODO: update hgweb to not return bare tuple; then just stringify 'thing'
711 # TODO: update hgweb to not return bare tuple; then just stringify 'thing'
711 thing = unwrapvalue(context, mapping, thing)
712 thing = unwrapvalue(context, mapping, thing)
712 try:
713 try:
713 return dateutil.parsedate(thing)
714 return dateutil.parsedate(thing)
714 except AttributeError:
715 except AttributeError:
715 raise error.ParseError(err or _('not a date tuple nor a string'))
716 raise error.ParseError(err or _('not a date tuple nor a string'))
716 except error.ParseError:
717 except error.ParseError:
717 if not err:
718 if not err:
718 raise
719 raise
719 raise error.ParseError(err)
720 raise error.ParseError(err)
720
721
721 def evalinteger(context, mapping, arg, err=None):
722 def evalinteger(context, mapping, arg, err=None):
722 thing = evalrawexp(context, mapping, arg)
723 thing = evalrawexp(context, mapping, arg)
723 return unwrapinteger(context, mapping, thing, err)
724 return unwrapinteger(context, mapping, thing, err)
724
725
725 def unwrapinteger(context, mapping, thing, err=None):
726 def unwrapinteger(context, mapping, thing, err=None):
726 thing = unwrapvalue(context, mapping, thing)
727 thing = unwrapvalue(context, mapping, thing)
727 try:
728 try:
728 return int(thing)
729 return int(thing)
729 except (TypeError, ValueError):
730 except (TypeError, ValueError):
730 raise error.ParseError(err or _('not an integer'))
731 raise error.ParseError(err or _('not an integer'))
731
732
732 def evalstring(context, mapping, arg):
733 def evalstring(context, mapping, arg):
733 return stringify(context, mapping, evalrawexp(context, mapping, arg))
734 return stringify(context, mapping, evalrawexp(context, mapping, arg))
734
735
735 def evalstringliteral(context, mapping, arg):
736 def evalstringliteral(context, mapping, arg):
736 """Evaluate given argument as string template, but returns symbol name
737 """Evaluate given argument as string template, but returns symbol name
737 if it is unknown"""
738 if it is unknown"""
738 func, data = arg
739 func, data = arg
739 if func is runsymbol:
740 if func is runsymbol:
740 thing = func(context, mapping, data, default=data)
741 thing = func(context, mapping, data, default=data)
741 else:
742 else:
742 thing = func(context, mapping, data)
743 thing = func(context, mapping, data)
743 return stringify(context, mapping, thing)
744 return stringify(context, mapping, thing)
744
745
745 _unwrapfuncbytype = {
746 _unwrapfuncbytype = {
746 None: unwrapvalue,
747 None: unwrapvalue,
747 bytes: stringify,
748 bytes: stringify,
748 date: unwrapdate,
749 date: unwrapdate,
749 int: unwrapinteger,
750 int: unwrapinteger,
750 }
751 }
751
752
752 def unwrapastype(context, mapping, thing, typ):
753 def unwrapastype(context, mapping, thing, typ):
753 """Move the inner value object out of the wrapper and coerce its type"""
754 """Move the inner value object out of the wrapper and coerce its type"""
754 try:
755 try:
755 f = _unwrapfuncbytype[typ]
756 f = _unwrapfuncbytype[typ]
756 except KeyError:
757 except KeyError:
757 raise error.ProgrammingError('invalid type specified: %r' % typ)
758 raise error.ProgrammingError('invalid type specified: %r' % typ)
758 return f(context, mapping, thing)
759 return f(context, mapping, thing)
759
760
760 def runinteger(context, mapping, data):
761 def runinteger(context, mapping, data):
761 return int(data)
762 return int(data)
762
763
763 def runstring(context, mapping, data):
764 def runstring(context, mapping, data):
764 return data
765 return data
765
766
766 def _recursivesymbolblocker(key):
767 def _recursivesymbolblocker(key):
767 def showrecursion(**args):
768 def showrecursion(**args):
768 raise error.Abort(_("recursive reference '%s' in template") % key)
769 raise error.Abort(_("recursive reference '%s' in template") % key)
769 return showrecursion
770 return showrecursion
770
771
771 def runsymbol(context, mapping, key, default=''):
772 def runsymbol(context, mapping, key, default=''):
772 v = context.symbol(mapping, key)
773 v = context.symbol(mapping, key)
773 if v is None:
774 if v is None:
774 # put poison to cut recursion. we can't move this to parsing phase
775 # put poison to cut recursion. we can't move this to parsing phase
775 # because "x = {x}" is allowed if "x" is a keyword. (issue4758)
776 # because "x = {x}" is allowed if "x" is a keyword. (issue4758)
776 safemapping = mapping.copy()
777 safemapping = mapping.copy()
777 safemapping[key] = _recursivesymbolblocker(key)
778 safemapping[key] = _recursivesymbolblocker(key)
778 try:
779 try:
779 v = context.process(key, safemapping)
780 v = context.process(key, safemapping)
780 except TemplateNotFound:
781 except TemplateNotFound:
781 v = default
782 v = default
782 if callable(v) and getattr(v, '_requires', None) is None:
783 if callable(v) and getattr(v, '_requires', None) is None:
783 # old templatekw: expand all keywords and resources
784 # old templatekw: expand all keywords and resources
784 # (TODO: deprecate this after porting web template keywords to new API)
785 # (TODO: deprecate this after porting web template keywords to new API)
785 props = {k: context._resources.lookup(context, mapping, k)
786 props = {k: context._resources.lookup(context, mapping, k)
786 for k in context._resources.knownkeys()}
787 for k in context._resources.knownkeys()}
787 # pass context to _showcompatlist() through templatekw._showlist()
788 # pass context to _showcompatlist() through templatekw._showlist()
788 props['templ'] = context
789 props['templ'] = context
789 props.update(mapping)
790 props.update(mapping)
790 return v(**pycompat.strkwargs(props))
791 return v(**pycompat.strkwargs(props))
791 if callable(v):
792 if callable(v):
792 # new templatekw
793 # new templatekw
793 try:
794 try:
794 return v(context, mapping)
795 return v(context, mapping)
795 except ResourceUnavailable:
796 except ResourceUnavailable:
796 # unsupported keyword is mapped to empty just like unknown keyword
797 # unsupported keyword is mapped to empty just like unknown keyword
797 return None
798 return None
798 return v
799 return v
799
800
800 def runtemplate(context, mapping, template):
801 def runtemplate(context, mapping, template):
801 for arg in template:
802 for arg in template:
802 yield evalrawexp(context, mapping, arg)
803 yield evalrawexp(context, mapping, arg)
803
804
804 def runfilter(context, mapping, data):
805 def runfilter(context, mapping, data):
805 arg, filt = data
806 arg, filt = data
806 thing = evalrawexp(context, mapping, arg)
807 thing = evalrawexp(context, mapping, arg)
807 intype = getattr(filt, '_intype', None)
808 intype = getattr(filt, '_intype', None)
808 try:
809 try:
809 thing = unwrapastype(context, mapping, thing, intype)
810 thing = unwrapastype(context, mapping, thing, intype)
810 return filt(thing)
811 return filt(thing)
811 except error.ParseError as e:
812 except error.ParseError as e:
812 raise error.ParseError(bytes(e), hint=_formatfiltererror(arg, filt))
813 raise error.ParseError(bytes(e), hint=_formatfiltererror(arg, filt))
813
814
814 def _formatfiltererror(arg, filt):
815 def _formatfiltererror(arg, filt):
815 fn = pycompat.sysbytes(filt.__name__)
816 fn = pycompat.sysbytes(filt.__name__)
816 sym = findsymbolicname(arg)
817 sym = findsymbolicname(arg)
817 if not sym:
818 if not sym:
818 return _("incompatible use of template filter '%s'") % fn
819 return _("incompatible use of template filter '%s'") % fn
819 return (_("template filter '%s' is not compatible with keyword '%s'")
820 return (_("template filter '%s' is not compatible with keyword '%s'")
820 % (fn, sym))
821 % (fn, sym))
821
822
822 def _iteroverlaymaps(context, origmapping, newmappings):
823 def _iteroverlaymaps(context, origmapping, newmappings):
823 """Generate combined mappings from the original mapping and an iterable
824 """Generate combined mappings from the original mapping and an iterable
824 of partial mappings to override the original"""
825 of partial mappings to override the original"""
825 for i, nm in enumerate(newmappings):
826 for i, nm in enumerate(newmappings):
826 lm = context.overlaymap(origmapping, nm)
827 lm = context.overlaymap(origmapping, nm)
827 lm['index'] = i
828 lm['index'] = i
828 yield lm
829 yield lm
829
830
830 def _applymap(context, mapping, d, darg, targ):
831 def _applymap(context, mapping, d, darg, targ):
831 try:
832 try:
832 diter = d.itermaps(context)
833 diter = d.itermaps(context)
833 except error.ParseError as err:
834 except error.ParseError as err:
834 sym = findsymbolicname(darg)
835 sym = findsymbolicname(darg)
835 if not sym:
836 if not sym:
836 raise
837 raise
837 hint = _("keyword '%s' does not support map operation") % sym
838 hint = _("keyword '%s' does not support map operation") % sym
838 raise error.ParseError(bytes(err), hint=hint)
839 raise error.ParseError(bytes(err), hint=hint)
839 for lm in _iteroverlaymaps(context, mapping, diter):
840 for lm in _iteroverlaymaps(context, mapping, diter):
840 yield evalrawexp(context, lm, targ)
841 yield evalrawexp(context, lm, targ)
841
842
842 def runmap(context, mapping, data):
843 def runmap(context, mapping, data):
843 darg, targ = data
844 darg, targ = data
844 d = evalwrapped(context, mapping, darg)
845 d = evalwrapped(context, mapping, darg)
845 return mappedgenerator(_applymap, args=(mapping, d, darg, targ))
846 return mappedgenerator(_applymap, args=(mapping, d, darg, targ))
846
847
847 def runmember(context, mapping, data):
848 def runmember(context, mapping, data):
848 darg, memb = data
849 darg, memb = data
849 d = evalwrapped(context, mapping, darg)
850 d = evalwrapped(context, mapping, darg)
850 if isinstance(d, mappable):
851 if isinstance(d, mappable):
851 lm = context.overlaymap(mapping, d.tomap(context))
852 lm = context.overlaymap(mapping, d.tomap(context))
852 return runsymbol(context, lm, memb)
853 return runsymbol(context, lm, memb)
853 try:
854 try:
854 return d.getmember(context, mapping, memb)
855 return d.getmember(context, mapping, memb)
855 except error.ParseError as err:
856 except error.ParseError as err:
856 sym = findsymbolicname(darg)
857 sym = findsymbolicname(darg)
857 if not sym:
858 if not sym:
858 raise
859 raise
859 hint = _("keyword '%s' does not support member operation") % sym
860 hint = _("keyword '%s' does not support member operation") % sym
860 raise error.ParseError(bytes(err), hint=hint)
861 raise error.ParseError(bytes(err), hint=hint)
861
862
862 def runnegate(context, mapping, data):
863 def runnegate(context, mapping, data):
863 data = evalinteger(context, mapping, data,
864 data = evalinteger(context, mapping, data,
864 _('negation needs an integer argument'))
865 _('negation needs an integer argument'))
865 return -data
866 return -data
866
867
867 def runarithmetic(context, mapping, data):
868 def runarithmetic(context, mapping, data):
868 func, left, right = data
869 func, left, right = data
869 left = evalinteger(context, mapping, left,
870 left = evalinteger(context, mapping, left,
870 _('arithmetic only defined on integers'))
871 _('arithmetic only defined on integers'))
871 right = evalinteger(context, mapping, right,
872 right = evalinteger(context, mapping, right,
872 _('arithmetic only defined on integers'))
873 _('arithmetic only defined on integers'))
873 try:
874 try:
874 return func(left, right)
875 return func(left, right)
875 except ZeroDivisionError:
876 except ZeroDivisionError:
876 raise error.Abort(_('division by zero is not defined'))
877 raise error.Abort(_('division by zero is not defined'))
877
878
878 def joinitems(itemiter, sep):
879 def joinitems(itemiter, sep):
879 """Join items with the separator; Returns generator of bytes"""
880 """Join items with the separator; Returns generator of bytes"""
880 first = True
881 first = True
881 for x in itemiter:
882 for x in itemiter:
882 if first:
883 if first:
883 first = False
884 first = False
884 elif sep:
885 elif sep:
885 yield sep
886 yield sep
886 yield x
887 yield x
@@ -1,1376 +1,1378 b''
1 Test template filters and functions
1 Test template filters and functions
2 ===================================
2 ===================================
3
3
4 $ hg init a
4 $ hg init a
5 $ cd a
5 $ cd a
6 $ echo a > a
6 $ echo a > a
7 $ hg add a
7 $ hg add a
8 $ echo line 1 > b
8 $ echo line 1 > b
9 $ echo line 2 >> b
9 $ echo line 2 >> b
10 $ hg commit -l b -d '1000000 0' -u 'User Name <user@hostname>'
10 $ hg commit -l b -d '1000000 0' -u 'User Name <user@hostname>'
11
11
12 $ hg add b
12 $ hg add b
13 $ echo other 1 > c
13 $ echo other 1 > c
14 $ echo other 2 >> c
14 $ echo other 2 >> c
15 $ echo >> c
15 $ echo >> c
16 $ echo other 3 >> c
16 $ echo other 3 >> c
17 $ hg commit -l c -d '1100000 0' -u 'A. N. Other <other@place>'
17 $ hg commit -l c -d '1100000 0' -u 'A. N. Other <other@place>'
18
18
19 $ hg add c
19 $ hg add c
20 $ hg commit -m 'no person' -d '1200000 0' -u 'other@place'
20 $ hg commit -m 'no person' -d '1200000 0' -u 'other@place'
21 $ echo c >> c
21 $ echo c >> c
22 $ hg commit -m 'no user, no domain' -d '1300000 0' -u 'person'
22 $ hg commit -m 'no user, no domain' -d '1300000 0' -u 'person'
23
23
24 $ echo foo > .hg/branch
24 $ echo foo > .hg/branch
25 $ hg commit -m 'new branch' -d '1400000 0' -u 'person'
25 $ hg commit -m 'new branch' -d '1400000 0' -u 'person'
26
26
27 $ hg co -q 3
27 $ hg co -q 3
28 $ echo other 4 >> d
28 $ echo other 4 >> d
29 $ hg add d
29 $ hg add d
30 $ hg commit -m 'new head' -d '1500000 0' -u 'person'
30 $ hg commit -m 'new head' -d '1500000 0' -u 'person'
31
31
32 $ hg merge -q foo
32 $ hg merge -q foo
33 $ hg commit -m 'merge' -d '1500001 0' -u 'person'
33 $ hg commit -m 'merge' -d '1500001 0' -u 'person'
34
34
35 Second branch starting at nullrev:
35 Second branch starting at nullrev:
36
36
37 $ hg update null
37 $ hg update null
38 0 files updated, 0 files merged, 4 files removed, 0 files unresolved
38 0 files updated, 0 files merged, 4 files removed, 0 files unresolved
39 $ echo second > second
39 $ echo second > second
40 $ hg add second
40 $ hg add second
41 $ hg commit -m second -d '1000000 0' -u 'User Name <user@hostname>'
41 $ hg commit -m second -d '1000000 0' -u 'User Name <user@hostname>'
42 created new head
42 created new head
43
43
44 $ echo third > third
44 $ echo third > third
45 $ hg add third
45 $ hg add third
46 $ hg mv second fourth
46 $ hg mv second fourth
47 $ hg commit -m third -d "2020-01-01 10:01"
47 $ hg commit -m third -d "2020-01-01 10:01"
48
48
49 $ hg phase -r 5 --public
49 $ hg phase -r 5 --public
50 $ hg phase -r 7 --secret --force
50 $ hg phase -r 7 --secret --force
51
51
52 Filters work:
52 Filters work:
53
53
54 $ hg log --template '{author|domain}\n'
54 $ hg log --template '{author|domain}\n'
55
55
56 hostname
56 hostname
57
57
58
58
59
59
60
60
61 place
61 place
62 place
62 place
63 hostname
63 hostname
64
64
65 $ hg log --template '{author|person}\n'
65 $ hg log --template '{author|person}\n'
66 test
66 test
67 User Name
67 User Name
68 person
68 person
69 person
69 person
70 person
70 person
71 person
71 person
72 other
72 other
73 A. N. Other
73 A. N. Other
74 User Name
74 User Name
75
75
76 $ hg log --template '{author|user}\n'
76 $ hg log --template '{author|user}\n'
77 test
77 test
78 user
78 user
79 person
79 person
80 person
80 person
81 person
81 person
82 person
82 person
83 other
83 other
84 other
84 other
85 user
85 user
86
86
87 $ hg log --template '{date|date}\n'
87 $ hg log --template '{date|date}\n'
88 Wed Jan 01 10:01:00 2020 +0000
88 Wed Jan 01 10:01:00 2020 +0000
89 Mon Jan 12 13:46:40 1970 +0000
89 Mon Jan 12 13:46:40 1970 +0000
90 Sun Jan 18 08:40:01 1970 +0000
90 Sun Jan 18 08:40:01 1970 +0000
91 Sun Jan 18 08:40:00 1970 +0000
91 Sun Jan 18 08:40:00 1970 +0000
92 Sat Jan 17 04:53:20 1970 +0000
92 Sat Jan 17 04:53:20 1970 +0000
93 Fri Jan 16 01:06:40 1970 +0000
93 Fri Jan 16 01:06:40 1970 +0000
94 Wed Jan 14 21:20:00 1970 +0000
94 Wed Jan 14 21:20:00 1970 +0000
95 Tue Jan 13 17:33:20 1970 +0000
95 Tue Jan 13 17:33:20 1970 +0000
96 Mon Jan 12 13:46:40 1970 +0000
96 Mon Jan 12 13:46:40 1970 +0000
97
97
98 $ hg log --template '{date|isodate}\n'
98 $ hg log --template '{date|isodate}\n'
99 2020-01-01 10:01 +0000
99 2020-01-01 10:01 +0000
100 1970-01-12 13:46 +0000
100 1970-01-12 13:46 +0000
101 1970-01-18 08:40 +0000
101 1970-01-18 08:40 +0000
102 1970-01-18 08:40 +0000
102 1970-01-18 08:40 +0000
103 1970-01-17 04:53 +0000
103 1970-01-17 04:53 +0000
104 1970-01-16 01:06 +0000
104 1970-01-16 01:06 +0000
105 1970-01-14 21:20 +0000
105 1970-01-14 21:20 +0000
106 1970-01-13 17:33 +0000
106 1970-01-13 17:33 +0000
107 1970-01-12 13:46 +0000
107 1970-01-12 13:46 +0000
108
108
109 $ hg log --template '{date|isodatesec}\n'
109 $ hg log --template '{date|isodatesec}\n'
110 2020-01-01 10:01:00 +0000
110 2020-01-01 10:01:00 +0000
111 1970-01-12 13:46:40 +0000
111 1970-01-12 13:46:40 +0000
112 1970-01-18 08:40:01 +0000
112 1970-01-18 08:40:01 +0000
113 1970-01-18 08:40:00 +0000
113 1970-01-18 08:40:00 +0000
114 1970-01-17 04:53:20 +0000
114 1970-01-17 04:53:20 +0000
115 1970-01-16 01:06:40 +0000
115 1970-01-16 01:06:40 +0000
116 1970-01-14 21:20:00 +0000
116 1970-01-14 21:20:00 +0000
117 1970-01-13 17:33:20 +0000
117 1970-01-13 17:33:20 +0000
118 1970-01-12 13:46:40 +0000
118 1970-01-12 13:46:40 +0000
119
119
120 $ hg log --template '{date|rfc822date}\n'
120 $ hg log --template '{date|rfc822date}\n'
121 Wed, 01 Jan 2020 10:01:00 +0000
121 Wed, 01 Jan 2020 10:01:00 +0000
122 Mon, 12 Jan 1970 13:46:40 +0000
122 Mon, 12 Jan 1970 13:46:40 +0000
123 Sun, 18 Jan 1970 08:40:01 +0000
123 Sun, 18 Jan 1970 08:40:01 +0000
124 Sun, 18 Jan 1970 08:40:00 +0000
124 Sun, 18 Jan 1970 08:40:00 +0000
125 Sat, 17 Jan 1970 04:53:20 +0000
125 Sat, 17 Jan 1970 04:53:20 +0000
126 Fri, 16 Jan 1970 01:06:40 +0000
126 Fri, 16 Jan 1970 01:06:40 +0000
127 Wed, 14 Jan 1970 21:20:00 +0000
127 Wed, 14 Jan 1970 21:20:00 +0000
128 Tue, 13 Jan 1970 17:33:20 +0000
128 Tue, 13 Jan 1970 17:33:20 +0000
129 Mon, 12 Jan 1970 13:46:40 +0000
129 Mon, 12 Jan 1970 13:46:40 +0000
130
130
131 $ hg log --template '{desc|firstline}\n'
131 $ hg log --template '{desc|firstline}\n'
132 third
132 third
133 second
133 second
134 merge
134 merge
135 new head
135 new head
136 new branch
136 new branch
137 no user, no domain
137 no user, no domain
138 no person
138 no person
139 other 1
139 other 1
140 line 1
140 line 1
141
141
142 $ hg log --template '{node|short}\n'
142 $ hg log --template '{node|short}\n'
143 95c24699272e
143 95c24699272e
144 29114dbae42b
144 29114dbae42b
145 d41e714fe50d
145 d41e714fe50d
146 13207e5a10d9
146 13207e5a10d9
147 bbe44766e73d
147 bbe44766e73d
148 10e46f2dcbf4
148 10e46f2dcbf4
149 97054abb4ab8
149 97054abb4ab8
150 b608e9d1a3f0
150 b608e9d1a3f0
151 1e4e1b8f71e0
151 1e4e1b8f71e0
152
152
153 $ hg log --template '<changeset author="{author|xmlescape}"/>\n'
153 $ hg log --template '<changeset author="{author|xmlescape}"/>\n'
154 <changeset author="test"/>
154 <changeset author="test"/>
155 <changeset author="User Name &lt;user@hostname&gt;"/>
155 <changeset author="User Name &lt;user@hostname&gt;"/>
156 <changeset author="person"/>
156 <changeset author="person"/>
157 <changeset author="person"/>
157 <changeset author="person"/>
158 <changeset author="person"/>
158 <changeset author="person"/>
159 <changeset author="person"/>
159 <changeset author="person"/>
160 <changeset author="other@place"/>
160 <changeset author="other@place"/>
161 <changeset author="A. N. Other &lt;other@place&gt;"/>
161 <changeset author="A. N. Other &lt;other@place&gt;"/>
162 <changeset author="User Name &lt;user@hostname&gt;"/>
162 <changeset author="User Name &lt;user@hostname&gt;"/>
163
163
164 $ hg log --template '{rev}: {children}\n'
164 $ hg log --template '{rev}: {children}\n'
165 8:
165 8:
166 7: 8:95c24699272e
166 7: 8:95c24699272e
167 6:
167 6:
168 5: 6:d41e714fe50d
168 5: 6:d41e714fe50d
169 4: 6:d41e714fe50d
169 4: 6:d41e714fe50d
170 3: 4:bbe44766e73d 5:13207e5a10d9
170 3: 4:bbe44766e73d 5:13207e5a10d9
171 2: 3:10e46f2dcbf4
171 2: 3:10e46f2dcbf4
172 1: 2:97054abb4ab8
172 1: 2:97054abb4ab8
173 0: 1:b608e9d1a3f0
173 0: 1:b608e9d1a3f0
174
174
175 Formatnode filter works:
175 Formatnode filter works:
176
176
177 $ hg -q log -r 0 --template '{node|formatnode}\n'
177 $ hg -q log -r 0 --template '{node|formatnode}\n'
178 1e4e1b8f71e0
178 1e4e1b8f71e0
179
179
180 $ hg log -r 0 --template '{node|formatnode}\n'
180 $ hg log -r 0 --template '{node|formatnode}\n'
181 1e4e1b8f71e0
181 1e4e1b8f71e0
182
182
183 $ hg -v log -r 0 --template '{node|formatnode}\n'
183 $ hg -v log -r 0 --template '{node|formatnode}\n'
184 1e4e1b8f71e0
184 1e4e1b8f71e0
185
185
186 $ hg --debug log -r 0 --template '{node|formatnode}\n'
186 $ hg --debug log -r 0 --template '{node|formatnode}\n'
187 1e4e1b8f71e05681d422154f5421e385fec3454f
187 1e4e1b8f71e05681d422154f5421e385fec3454f
188
188
189 Age filter:
189 Age filter:
190
190
191 $ hg init unstable-hash
191 $ hg init unstable-hash
192 $ cd unstable-hash
192 $ cd unstable-hash
193 $ hg log --template '{date|age}\n' > /dev/null || exit 1
193 $ hg log --template '{date|age}\n' > /dev/null || exit 1
194
194
195 >>> from __future__ import absolute_import
195 >>> from __future__ import absolute_import
196 >>> import datetime
196 >>> import datetime
197 >>> fp = open('a', 'wb')
197 >>> fp = open('a', 'wb')
198 >>> n = datetime.datetime.now() + datetime.timedelta(366 * 7)
198 >>> n = datetime.datetime.now() + datetime.timedelta(366 * 7)
199 >>> fp.write(b'%d-%d-%d 00:00' % (n.year, n.month, n.day)) and None
199 >>> fp.write(b'%d-%d-%d 00:00' % (n.year, n.month, n.day)) and None
200 >>> fp.close()
200 >>> fp.close()
201 $ hg add a
201 $ hg add a
202 $ hg commit -m future -d "`cat a`"
202 $ hg commit -m future -d "`cat a`"
203
203
204 $ hg log -l1 --template '{date|age}\n'
204 $ hg log -l1 --template '{date|age}\n'
205 7 years from now
205 7 years from now
206
206
207 $ cd ..
207 $ cd ..
208 $ rm -rf unstable-hash
208 $ rm -rf unstable-hash
209
209
210 Filename filters:
210 Filename filters:
211
211
212 $ hg debugtemplate '{"foo/bar"|basename}|{"foo/"|basename}|{"foo"|basename}|\n'
212 $ hg debugtemplate '{"foo/bar"|basename}|{"foo/"|basename}|{"foo"|basename}|\n'
213 bar||foo|
213 bar||foo|
214 $ hg debugtemplate '{"foo/bar"|dirname}|{"foo/"|dirname}|{"foo"|dirname}|\n'
214 $ hg debugtemplate '{"foo/bar"|dirname}|{"foo/"|dirname}|{"foo"|dirname}|\n'
215 foo|foo||
215 foo|foo||
216 $ hg debugtemplate '{"foo/bar"|stripdir}|{"foo/"|stripdir}|{"foo"|stripdir}|\n'
216 $ hg debugtemplate '{"foo/bar"|stripdir}|{"foo/"|stripdir}|{"foo"|stripdir}|\n'
217 foo|foo|foo|
217 foo|foo|foo|
218
218
219 commondir() filter:
219 commondir() filter:
220
220
221 $ hg debugtemplate '{""|splitlines|commondir}\n'
221 $ hg debugtemplate '{""|splitlines|commondir}\n'
222
222
223 $ hg debugtemplate '{"foo/bar\nfoo/baz\nfoo/foobar\n"|splitlines|commondir}\n'
223 $ hg debugtemplate '{"foo/bar\nfoo/baz\nfoo/foobar\n"|splitlines|commondir}\n'
224 foo
224 foo
225 $ hg debugtemplate '{"foo/bar\nfoo/bar\n"|splitlines|commondir}\n'
225 $ hg debugtemplate '{"foo/bar\nfoo/bar\n"|splitlines|commondir}\n'
226 foo
226 foo
227 $ hg debugtemplate '{"/foo/bar\n/foo/bar\n"|splitlines|commondir}\n'
227 $ hg debugtemplate '{"/foo/bar\n/foo/bar\n"|splitlines|commondir}\n'
228 foo
228 foo
229 $ hg debugtemplate '{"/foo\n/foo\n"|splitlines|commondir}\n'
229 $ hg debugtemplate '{"/foo\n/foo\n"|splitlines|commondir}\n'
230
230
231 $ hg debugtemplate '{"foo/bar\nbar/baz"|splitlines|commondir}\n'
231 $ hg debugtemplate '{"foo/bar\nbar/baz"|splitlines|commondir}\n'
232
232
233 $ hg debugtemplate '{"foo/bar\nbar/baz\nbar/foo\n"|splitlines|commondir}\n'
233 $ hg debugtemplate '{"foo/bar\nbar/baz\nbar/foo\n"|splitlines|commondir}\n'
234
234
235 $ hg debugtemplate '{"foo/../bar\nfoo/bar"|splitlines|commondir}\n'
235 $ hg debugtemplate '{"foo/../bar\nfoo/bar"|splitlines|commondir}\n'
236 foo
236 foo
237 $ hg debugtemplate '{"foo\n/foo"|splitlines|commondir}\n'
237 $ hg debugtemplate '{"foo\n/foo"|splitlines|commondir}\n'
238
238
239
239
240 $ hg log -r null -T '{rev|commondir}'
240 $ hg log -r null -T '{rev|commondir}'
241 hg: parse error: argument is not a list of text
241 hg: parse error: argument is not a list of text
242 (template filter 'commondir' is not compatible with keyword 'rev')
242 (template filter 'commondir' is not compatible with keyword 'rev')
243 [255]
243 [255]
244
244
245 Add a dummy commit to make up for the instability of the above:
245 Add a dummy commit to make up for the instability of the above:
246
246
247 $ echo a > a
247 $ echo a > a
248 $ hg add a
248 $ hg add a
249 $ hg ci -m future
249 $ hg ci -m future
250
250
251 Count filter:
251 Count filter:
252
252
253 $ hg log -l1 --template '{node|count} {node|short|count}\n'
253 $ hg log -l1 --template '{node|count} {node|short|count}\n'
254 40 12
254 40 12
255
255
256 $ hg log -l1 --template '{revset("null^")|count} {revset(".")|count} {revset("0::3")|count}\n'
256 $ hg log -l1 --template '{revset("null^")|count} {revset(".")|count} {revset("0::3")|count}\n'
257 0 1 4
257 0 1 4
258
258
259 $ hg log -G --template '{rev}: children: {children|count}, \
259 $ hg log -G --template '{rev}: children: {children|count}, \
260 > tags: {tags|count}, file_adds: {file_adds|count}, \
260 > tags: {tags|count}, file_adds: {file_adds|count}, \
261 > ancestors: {revset("ancestors(%s)", rev)|count}'
261 > ancestors: {revset("ancestors(%s)", rev)|count}'
262 @ 9: children: 0, tags: 1, file_adds: 1, ancestors: 3
262 @ 9: children: 0, tags: 1, file_adds: 1, ancestors: 3
263 |
263 |
264 o 8: children: 1, tags: 0, file_adds: 2, ancestors: 2
264 o 8: children: 1, tags: 0, file_adds: 2, ancestors: 2
265 |
265 |
266 o 7: children: 1, tags: 0, file_adds: 1, ancestors: 1
266 o 7: children: 1, tags: 0, file_adds: 1, ancestors: 1
267
267
268 o 6: children: 0, tags: 0, file_adds: 0, ancestors: 7
268 o 6: children: 0, tags: 0, file_adds: 0, ancestors: 7
269 |\
269 |\
270 | o 5: children: 1, tags: 0, file_adds: 1, ancestors: 5
270 | o 5: children: 1, tags: 0, file_adds: 1, ancestors: 5
271 | |
271 | |
272 o | 4: children: 1, tags: 0, file_adds: 0, ancestors: 5
272 o | 4: children: 1, tags: 0, file_adds: 0, ancestors: 5
273 |/
273 |/
274 o 3: children: 2, tags: 0, file_adds: 0, ancestors: 4
274 o 3: children: 2, tags: 0, file_adds: 0, ancestors: 4
275 |
275 |
276 o 2: children: 1, tags: 0, file_adds: 1, ancestors: 3
276 o 2: children: 1, tags: 0, file_adds: 1, ancestors: 3
277 |
277 |
278 o 1: children: 1, tags: 0, file_adds: 1, ancestors: 2
278 o 1: children: 1, tags: 0, file_adds: 1, ancestors: 2
279 |
279 |
280 o 0: children: 1, tags: 0, file_adds: 1, ancestors: 1
280 o 0: children: 1, tags: 0, file_adds: 1, ancestors: 1
281
281
282
282
283 $ hg log -l1 -T '{termwidth|count}\n'
283 $ hg log -l1 -T '{termwidth|count}\n'
284 hg: parse error: not countable
284 hg: parse error: not countable
285 (template filter 'count' is not compatible with keyword 'termwidth')
285 (template filter 'count' is not compatible with keyword 'termwidth')
286 [255]
286 [255]
287
287
288 Upper/lower filters:
288 Upper/lower filters:
289
289
290 $ hg log -r0 --template '{branch|upper}\n'
290 $ hg log -r0 --template '{branch|upper}\n'
291 DEFAULT
291 DEFAULT
292 $ hg log -r0 --template '{author|lower}\n'
292 $ hg log -r0 --template '{author|lower}\n'
293 user name <user@hostname>
293 user name <user@hostname>
294 $ hg log -r0 --template '{date|upper}\n'
294 $ hg log -r0 --template '{date|upper}\n'
295 1000000.00
295 1000000.00
296
296
297 Add a commit that does all possible modifications at once
297 Add a commit that does all possible modifications at once
298
298
299 $ echo modify >> third
299 $ echo modify >> third
300 $ touch b
300 $ touch b
301 $ hg add b
301 $ hg add b
302 $ hg mv fourth fifth
302 $ hg mv fourth fifth
303 $ hg rm a
303 $ hg rm a
304 $ hg ci -m "Modify, add, remove, rename"
304 $ hg ci -m "Modify, add, remove, rename"
305
305
306 Pass generator object created by template function to filter
306 Pass generator object created by template function to filter
307
307
308 $ hg log -l 1 --template '{if(author, author)|user}\n'
308 $ hg log -l 1 --template '{if(author, author)|user}\n'
309 test
309 test
310
310
311 Test diff function:
311 Test diff function:
312
312
313 $ hg diff -c 8
313 $ hg diff -c 8
314 diff -r 29114dbae42b -r 95c24699272e fourth
314 diff -r 29114dbae42b -r 95c24699272e fourth
315 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
315 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
316 +++ b/fourth Wed Jan 01 10:01:00 2020 +0000
316 +++ b/fourth Wed Jan 01 10:01:00 2020 +0000
317 @@ -0,0 +1,1 @@
317 @@ -0,0 +1,1 @@
318 +second
318 +second
319 diff -r 29114dbae42b -r 95c24699272e second
319 diff -r 29114dbae42b -r 95c24699272e second
320 --- a/second Mon Jan 12 13:46:40 1970 +0000
320 --- a/second Mon Jan 12 13:46:40 1970 +0000
321 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000
321 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000
322 @@ -1,1 +0,0 @@
322 @@ -1,1 +0,0 @@
323 -second
323 -second
324 diff -r 29114dbae42b -r 95c24699272e third
324 diff -r 29114dbae42b -r 95c24699272e third
325 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
325 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
326 +++ b/third Wed Jan 01 10:01:00 2020 +0000
326 +++ b/third Wed Jan 01 10:01:00 2020 +0000
327 @@ -0,0 +1,1 @@
327 @@ -0,0 +1,1 @@
328 +third
328 +third
329
329
330 $ hg log -r 8 -T "{diff()}"
330 $ hg log -r 8 -T "{diff()}"
331 diff -r 29114dbae42b -r 95c24699272e fourth
331 diff -r 29114dbae42b -r 95c24699272e fourth
332 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
332 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
333 +++ b/fourth Wed Jan 01 10:01:00 2020 +0000
333 +++ b/fourth Wed Jan 01 10:01:00 2020 +0000
334 @@ -0,0 +1,1 @@
334 @@ -0,0 +1,1 @@
335 +second
335 +second
336 diff -r 29114dbae42b -r 95c24699272e second
336 diff -r 29114dbae42b -r 95c24699272e second
337 --- a/second Mon Jan 12 13:46:40 1970 +0000
337 --- a/second Mon Jan 12 13:46:40 1970 +0000
338 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000
338 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000
339 @@ -1,1 +0,0 @@
339 @@ -1,1 +0,0 @@
340 -second
340 -second
341 diff -r 29114dbae42b -r 95c24699272e third
341 diff -r 29114dbae42b -r 95c24699272e third
342 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
342 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
343 +++ b/third Wed Jan 01 10:01:00 2020 +0000
343 +++ b/third Wed Jan 01 10:01:00 2020 +0000
344 @@ -0,0 +1,1 @@
344 @@ -0,0 +1,1 @@
345 +third
345 +third
346
346
347 $ hg log -r 8 -T "{diff('glob:f*')}"
347 $ hg log -r 8 -T "{diff('glob:f*')}"
348 diff -r 29114dbae42b -r 95c24699272e fourth
348 diff -r 29114dbae42b -r 95c24699272e fourth
349 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
349 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
350 +++ b/fourth Wed Jan 01 10:01:00 2020 +0000
350 +++ b/fourth Wed Jan 01 10:01:00 2020 +0000
351 @@ -0,0 +1,1 @@
351 @@ -0,0 +1,1 @@
352 +second
352 +second
353
353
354 $ hg log -r 8 -T "{diff('', 'glob:f*')}"
354 $ hg log -r 8 -T "{diff('', 'glob:f*')}"
355 diff -r 29114dbae42b -r 95c24699272e second
355 diff -r 29114dbae42b -r 95c24699272e second
356 --- a/second Mon Jan 12 13:46:40 1970 +0000
356 --- a/second Mon Jan 12 13:46:40 1970 +0000
357 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000
357 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000
358 @@ -1,1 +0,0 @@
358 @@ -1,1 +0,0 @@
359 -second
359 -second
360 diff -r 29114dbae42b -r 95c24699272e third
360 diff -r 29114dbae42b -r 95c24699272e third
361 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
361 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
362 +++ b/third Wed Jan 01 10:01:00 2020 +0000
362 +++ b/third Wed Jan 01 10:01:00 2020 +0000
363 @@ -0,0 +1,1 @@
363 @@ -0,0 +1,1 @@
364 +third
364 +third
365
365
366 $ hg log -r 8 -T "{diff('FOURTH'|lower)}"
366 $ hg log -r 8 -T "{diff('FOURTH'|lower)}"
367 diff -r 29114dbae42b -r 95c24699272e fourth
367 diff -r 29114dbae42b -r 95c24699272e fourth
368 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
368 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
369 +++ b/fourth Wed Jan 01 10:01:00 2020 +0000
369 +++ b/fourth Wed Jan 01 10:01:00 2020 +0000
370 @@ -0,0 +1,1 @@
370 @@ -0,0 +1,1 @@
371 +second
371 +second
372
372
373 $ cd ..
373 $ cd ..
374
374
375 latesttag() function:
375 latesttag() function:
376
376
377 $ hg init latesttag
377 $ hg init latesttag
378 $ cd latesttag
378 $ cd latesttag
379
379
380 $ echo a > file
380 $ echo a > file
381 $ hg ci -Am a -d '0 0'
381 $ hg ci -Am a -d '0 0'
382 adding file
382 adding file
383
383
384 $ echo b >> file
384 $ echo b >> file
385 $ hg ci -m b -d '1 0'
385 $ hg ci -m b -d '1 0'
386
386
387 $ echo c >> head1
387 $ echo c >> head1
388 $ hg ci -Am h1c -d '2 0'
388 $ hg ci -Am h1c -d '2 0'
389 adding head1
389 adding head1
390
390
391 $ hg update -q 1
391 $ hg update -q 1
392 $ echo d >> head2
392 $ echo d >> head2
393 $ hg ci -Am h2d -d '3 0'
393 $ hg ci -Am h2d -d '3 0'
394 adding head2
394 adding head2
395 created new head
395 created new head
396
396
397 $ echo e >> head2
397 $ echo e >> head2
398 $ hg ci -m h2e -d '4 0'
398 $ hg ci -m h2e -d '4 0'
399
399
400 $ hg merge -q
400 $ hg merge -q
401 $ hg ci -m merge -d '5 -3600'
401 $ hg ci -m merge -d '5 -3600'
402
402
403 $ hg tag -r 1 -m t1 -d '6 0' t1
403 $ hg tag -r 1 -m t1 -d '6 0' t1
404 $ hg tag -r 2 -m t2 -d '7 0' t2
404 $ hg tag -r 2 -m t2 -d '7 0' t2
405 $ hg tag -r 3 -m t3 -d '8 0' t3
405 $ hg tag -r 3 -m t3 -d '8 0' t3
406 $ hg tag -r 4 -m t4 -d '4 0' t4 # older than t2, but should not matter
406 $ hg tag -r 4 -m t4 -d '4 0' t4 # older than t2, but should not matter
407 $ hg tag -r 5 -m t5 -d '9 0' t5
407 $ hg tag -r 5 -m t5 -d '9 0' t5
408 $ hg tag -r 3 -m at3 -d '10 0' at3
408 $ hg tag -r 3 -m at3 -d '10 0' at3
409
409
410 $ hg log -G --template "{rev}: {latesttag('re:^t[13]$') % '{tag}, C: {changes}, D: {distance}'}\n"
410 $ hg log -G --template "{rev}: {latesttag('re:^t[13]$') % '{tag}, C: {changes}, D: {distance}'}\n"
411 @ 11: t3, C: 9, D: 8
411 @ 11: t3, C: 9, D: 8
412 |
412 |
413 o 10: t3, C: 8, D: 7
413 o 10: t3, C: 8, D: 7
414 |
414 |
415 o 9: t3, C: 7, D: 6
415 o 9: t3, C: 7, D: 6
416 |
416 |
417 o 8: t3, C: 6, D: 5
417 o 8: t3, C: 6, D: 5
418 |
418 |
419 o 7: t3, C: 5, D: 4
419 o 7: t3, C: 5, D: 4
420 |
420 |
421 o 6: t3, C: 4, D: 3
421 o 6: t3, C: 4, D: 3
422 |
422 |
423 o 5: t3, C: 3, D: 2
423 o 5: t3, C: 3, D: 2
424 |\
424 |\
425 | o 4: t3, C: 1, D: 1
425 | o 4: t3, C: 1, D: 1
426 | |
426 | |
427 | o 3: t3, C: 0, D: 0
427 | o 3: t3, C: 0, D: 0
428 | |
428 | |
429 o | 2: t1, C: 1, D: 1
429 o | 2: t1, C: 1, D: 1
430 |/
430 |/
431 o 1: t1, C: 0, D: 0
431 o 1: t1, C: 0, D: 0
432 |
432 |
433 o 0: null, C: 1, D: 1
433 o 0: null, C: 1, D: 1
434
434
435
435
436 $ cd ..
436 $ cd ..
437
437
438 Test manifest/get() can be join()-ed as string, though it's silly:
438 Test manifest/get() can be join()-ed as string, though it's silly:
439
439
440 $ hg log -R latesttag -r tip -T '{join(manifest, ".")}\n'
440 $ hg log -R latesttag -r tip -T '{join(manifest, ".")}\n'
441 1.1.:.2.b.c.6.e.9.0.0.6.c.e.2
441 1.1.:.2.b.c.6.e.9.0.0.6.c.e.2
442 $ hg log -R latesttag -r tip -T '{join(get(extras, "branch"), ".")}\n'
442 $ hg log -R latesttag -r tip -T '{join(get(extras, "branch"), ".")}\n'
443 d.e.f.a.u.l.t
443 d.e.f.a.u.l.t
444
444
445 Test join() over string
445 Test join() over string
446
446
447 $ hg log -R latesttag -r tip -T '{join(rev|stringify, ".")}\n'
447 $ hg log -R latesttag -r tip -T '{join(rev|stringify, ".")}\n'
448 1.1
448 1.1
449
449
450 Test join() over uniterable
450 Test join() over uniterable
451
451
452 $ hg log -R latesttag -r tip -T '{join(rev, "")}\n'
452 $ hg log -R latesttag -r tip -T '{join(rev, "")}\n'
453 hg: parse error: 11 is not iterable
453 hg: parse error: 11 is not iterable
454 [255]
454 [255]
455
455
456 Test min/max of integers
456 Test min/max of integers
457
457
458 $ hg log -R latesttag -l1 -T '{min(revset("9:10"))}\n'
458 $ hg log -R latesttag -l1 -T '{min(revset("9:10"))}\n'
459 9
459 9
460 $ hg log -R latesttag -l1 -T '{max(revset("9:10"))}\n'
460 $ hg log -R latesttag -l1 -T '{max(revset("9:10"))}\n'
461 10
461 10
462
462
463 Test min/max over map operation:
463 Test min/max over map operation:
464
464
465 $ hg log -R latesttag -r3 -T '{min(tags % "{tag}")}\n'
465 $ hg log -R latesttag -r3 -T '{min(tags % "{tag}")}\n'
466 at3
466 at3
467 $ hg log -R latesttag -r3 -T '{max(tags % "{tag}")}\n'
467 $ hg log -R latesttag -r3 -T '{max(tags % "{tag}")}\n'
468 t3
468 t3
469
469
470 Test min/max of strings:
470 Test min/max of strings:
471
471
472 $ hg log -R latesttag -l1 -T '{min(desc)}\n'
472 $ hg log -R latesttag -l1 -T '{min(desc)}\n'
473 3
473 3
474 $ hg log -R latesttag -l1 -T '{max(desc)}\n'
474 $ hg log -R latesttag -l1 -T '{max(desc)}\n'
475 t
475 t
476
476
477 Test min/max of non-iterable:
477 Test min/max of non-iterable:
478
478
479 $ hg debugtemplate '{min(1)}'
479 $ hg debugtemplate '{min(1)}'
480 hg: parse error: 1 is not iterable
480 hg: parse error: 1 is not iterable
481 (min first argument should be an iterable)
481 (min first argument should be an iterable)
482 [255]
482 [255]
483 $ hg debugtemplate '{max(2)}'
483 $ hg debugtemplate '{max(2)}'
484 hg: parse error: 2 is not iterable
484 hg: parse error: 2 is not iterable
485 (max first argument should be an iterable)
485 (max first argument should be an iterable)
486 [255]
486 [255]
487
487
488 $ hg log -R latesttag -l1 -T '{min(date)}'
488 $ hg log -R latesttag -l1 -T '{min(date)}'
489 hg: parse error: date is not iterable
489 hg: parse error: date is not iterable
490 (min first argument should be an iterable)
490 (min first argument should be an iterable)
491 [255]
491 [255]
492 $ hg log -R latesttag -l1 -T '{max(date)}'
492 $ hg log -R latesttag -l1 -T '{max(date)}'
493 hg: parse error: date is not iterable
493 hg: parse error: date is not iterable
494 (max first argument should be an iterable)
494 (max first argument should be an iterable)
495 [255]
495 [255]
496
496
497 Test min/max of empty sequence:
497 Test min/max of empty sequence:
498
498
499 $ hg debugtemplate '{min("")}'
499 $ hg debugtemplate '{min("")}'
500 hg: parse error: empty string
500 hg: parse error: empty string
501 (min first argument should be an iterable)
501 (min first argument should be an iterable)
502 [255]
502 [255]
503 $ hg debugtemplate '{max("")}'
503 $ hg debugtemplate '{max("")}'
504 hg: parse error: empty string
504 hg: parse error: empty string
505 (max first argument should be an iterable)
505 (max first argument should be an iterable)
506 [255]
506 [255]
507 $ hg debugtemplate '{min(dict())}'
507 $ hg debugtemplate '{min(dict())}'
508 hg: parse error: empty sequence
508 hg: parse error: empty sequence
509 (min first argument should be an iterable)
509 (min first argument should be an iterable)
510 [255]
510 [255]
511 $ hg debugtemplate '{max(dict())}'
511 $ hg debugtemplate '{max(dict())}'
512 hg: parse error: empty sequence
512 hg: parse error: empty sequence
513 (max first argument should be an iterable)
513 (max first argument should be an iterable)
514 [255]
514 [255]
515 $ hg debugtemplate '{min(dict() % "")}'
515 $ hg debugtemplate '{min(dict() % "")}'
516 hg: parse error: empty sequence
516 hg: parse error: empty sequence
517 (min first argument should be an iterable)
517 (min first argument should be an iterable)
518 [255]
518 [255]
519 $ hg debugtemplate '{max(dict() % "")}'
519 $ hg debugtemplate '{max(dict() % "")}'
520 hg: parse error: empty sequence
520 hg: parse error: empty sequence
521 (max first argument should be an iterable)
521 (max first argument should be an iterable)
522 [255]
522 [255]
523
523
524 Test min/max of if() result
524 Test min/max of if() result
525
525
526 $ cd latesttag
526 $ cd latesttag
527 $ hg log -l1 -T '{min(if(true, revset("9:10"), ""))}\n'
527 $ hg log -l1 -T '{min(if(true, revset("9:10"), ""))}\n'
528 9
528 9
529 $ hg log -l1 -T '{max(if(false, "", revset("9:10")))}\n'
529 $ hg log -l1 -T '{max(if(false, "", revset("9:10")))}\n'
530 10
530 10
531 $ hg log -l1 -T '{min(ifcontains("a", "aa", revset("9:10"), ""))}\n'
531 $ hg log -l1 -T '{min(ifcontains("a", "aa", revset("9:10"), ""))}\n'
532 9
532 9
533 $ hg log -l1 -T '{max(ifcontains("a", "bb", "", revset("9:10")))}\n'
533 $ hg log -l1 -T '{max(ifcontains("a", "bb", "", revset("9:10")))}\n'
534 10
534 10
535 $ hg log -l1 -T '{min(ifeq(0, 0, revset("9:10"), ""))}\n'
535 $ hg log -l1 -T '{min(ifeq(0, 0, revset("9:10"), ""))}\n'
536 9
536 9
537 $ hg log -l1 -T '{max(ifeq(0, 1, "", revset("9:10")))}\n'
537 $ hg log -l1 -T '{max(ifeq(0, 1, "", revset("9:10")))}\n'
538 10
538 10
539 $ cd ..
539 $ cd ..
540
540
541 Test laziness of if() then/else clause
541 Test laziness of if() then/else clause
542
542
543 $ hg debugtemplate '{count(0)}'
543 $ hg debugtemplate '{count(0)}'
544 hg: parse error: not countable
544 hg: parse error: not countable
545 (incompatible use of template filter 'count')
545 (incompatible use of template filter 'count')
546 [255]
546 [255]
547 $ hg debugtemplate '{if(true, "", count(0))}'
547 $ hg debugtemplate '{if(true, "", count(0))}'
548 $ hg debugtemplate '{if(false, count(0), "")}'
548 $ hg debugtemplate '{if(false, count(0), "")}'
549 $ hg debugtemplate '{ifcontains("a", "aa", "", count(0))}'
549 $ hg debugtemplate '{ifcontains("a", "aa", "", count(0))}'
550 $ hg debugtemplate '{ifcontains("a", "bb", count(0), "")}'
550 $ hg debugtemplate '{ifcontains("a", "bb", count(0), "")}'
551 $ hg debugtemplate '{ifeq(0, 0, "", count(0))}'
551 $ hg debugtemplate '{ifeq(0, 0, "", count(0))}'
552 $ hg debugtemplate '{ifeq(0, 1, count(0), "")}'
552 $ hg debugtemplate '{ifeq(0, 1, count(0), "")}'
553
553
554 Test the sub function of templating for expansion:
554 Test the sub function of templating for expansion:
555
555
556 $ hg log -R latesttag -r 10 --template '{sub("[0-9]", "x", "{rev}")}\n'
556 $ hg log -R latesttag -r 10 --template '{sub("[0-9]", "x", "{rev}")}\n'
557 xx
557 xx
558
558
559 $ hg log -R latesttag -r 10 -T '{sub("[", "x", rev)}\n'
559 $ hg log -R latesttag -r 10 -T '{sub("[", "x", rev)}\n'
560 hg: parse error: sub got an invalid pattern: [
560 hg: parse error: sub got an invalid pattern: [
561 [255]
561 [255]
562 $ hg log -R latesttag -r 10 -T '{sub("[0-9]", r"\1", rev)}\n'
562 $ hg log -R latesttag -r 10 -T '{sub("[0-9]", r"\1", rev)}\n'
563 hg: parse error: sub got an invalid replacement: \1
563 hg: parse error: sub got an invalid replacement: \1
564 [255]
564 [255]
565
565
566 Test the strip function with chars specified:
566 Test the strip function with chars specified:
567
567
568 $ hg log -R latesttag --template '{desc}\n'
568 $ hg log -R latesttag --template '{desc}\n'
569 at3
569 at3
570 t5
570 t5
571 t4
571 t4
572 t3
572 t3
573 t2
573 t2
574 t1
574 t1
575 merge
575 merge
576 h2e
576 h2e
577 h2d
577 h2d
578 h1c
578 h1c
579 b
579 b
580 a
580 a
581
581
582 $ hg log -R latesttag --template '{strip(desc, "te")}\n'
582 $ hg log -R latesttag --template '{strip(desc, "te")}\n'
583 at3
583 at3
584 5
584 5
585 4
585 4
586 3
586 3
587 2
587 2
588 1
588 1
589 merg
589 merg
590 h2
590 h2
591 h2d
591 h2d
592 h1c
592 h1c
593 b
593 b
594 a
594 a
595
595
596 Test date format:
596 Test date format:
597
597
598 $ hg log -R latesttag --template 'date: {date(date, "%y %m %d %S %z")}\n'
598 $ hg log -R latesttag --template 'date: {date(date, "%y %m %d %S %z")}\n'
599 date: 70 01 01 10 +0000
599 date: 70 01 01 10 +0000
600 date: 70 01 01 09 +0000
600 date: 70 01 01 09 +0000
601 date: 70 01 01 04 +0000
601 date: 70 01 01 04 +0000
602 date: 70 01 01 08 +0000
602 date: 70 01 01 08 +0000
603 date: 70 01 01 07 +0000
603 date: 70 01 01 07 +0000
604 date: 70 01 01 06 +0000
604 date: 70 01 01 06 +0000
605 date: 70 01 01 05 +0100
605 date: 70 01 01 05 +0100
606 date: 70 01 01 04 +0000
606 date: 70 01 01 04 +0000
607 date: 70 01 01 03 +0000
607 date: 70 01 01 03 +0000
608 date: 70 01 01 02 +0000
608 date: 70 01 01 02 +0000
609 date: 70 01 01 01 +0000
609 date: 70 01 01 01 +0000
610 date: 70 01 01 00 +0000
610 date: 70 01 01 00 +0000
611
611
612 Test invalid date:
612 Test invalid date:
613
613
614 $ hg log -R latesttag -T '{date(rev)}\n'
614 $ hg log -R latesttag -T '{date(rev)}\n'
615 hg: parse error: date expects a date information
615 hg: parse error: date expects a date information
616 [255]
616 [255]
617
617
618 Set up repository containing template fragments in commit metadata:
618 Set up repository containing template fragments in commit metadata:
619
619
620 $ hg init r
620 $ hg init r
621 $ cd r
621 $ cd r
622 $ echo a > a
622 $ echo a > a
623 $ hg ci -Am '{rev}'
623 $ hg ci -Am '{rev}'
624 adding a
624 adding a
625
625
626 $ hg branch -q 'text.{rev}'
626 $ hg branch -q 'text.{rev}'
627 $ echo aa >> aa
627 $ echo aa >> aa
628 $ hg ci -u '{node|short}' -m 'desc to be wrapped desc to be wrapped'
628 $ hg ci -u '{node|short}' -m 'desc to be wrapped desc to be wrapped'
629
629
630 color effect can be specified without quoting:
630 color effect can be specified without quoting:
631
631
632 $ hg log --color=always -l 1 --template '{label(red, "text\n")}'
632 $ hg log --color=always -l 1 --template '{label(red, "text\n")}'
633 \x1b[0;31mtext\x1b[0m (esc)
633 \x1b[0;31mtext\x1b[0m (esc)
634
634
635 color effects can be nested (issue5413)
635 color effects can be nested (issue5413)
636
636
637 $ hg debugtemplate --color=always \
637 $ hg debugtemplate --color=always \
638 > '{label(red, "red{label(magenta, "ma{label(cyan, "cyan")}{label(yellow, "yellow")}genta")}")}\n'
638 > '{label(red, "red{label(magenta, "ma{label(cyan, "cyan")}{label(yellow, "yellow")}genta")}")}\n'
639 \x1b[0;31mred\x1b[0;35mma\x1b[0;36mcyan\x1b[0m\x1b[0;31m\x1b[0;35m\x1b[0;33myellow\x1b[0m\x1b[0;31m\x1b[0;35mgenta\x1b[0m (esc)
639 \x1b[0;31mred\x1b[0;35mma\x1b[0;36mcyan\x1b[0m\x1b[0;31m\x1b[0;35m\x1b[0;33myellow\x1b[0m\x1b[0;31m\x1b[0;35mgenta\x1b[0m (esc)
640
640
641 pad() should interact well with color codes (issue5416)
641 pad() should interact well with color codes (issue5416)
642
642
643 $ hg debugtemplate --color=always \
643 $ hg debugtemplate --color=always \
644 > '{pad(label(red, "red"), 5, label(cyan, "-"))}\n'
644 > '{pad(label(red, "red"), 5, label(cyan, "-"))}\n'
645 \x1b[0;31mred\x1b[0m\x1b[0;36m-\x1b[0m\x1b[0;36m-\x1b[0m (esc)
645 \x1b[0;31mred\x1b[0m\x1b[0;36m-\x1b[0m\x1b[0;36m-\x1b[0m (esc)
646
646
647 label should be no-op if color is disabled:
647 label should be no-op if color is disabled:
648
648
649 $ hg log --color=never -l 1 --template '{label(red, "text\n")}'
649 $ hg log --color=never -l 1 --template '{label(red, "text\n")}'
650 text
650 text
651 $ hg log --config extensions.color=! -l 1 --template '{label(red, "text\n")}'
651 $ hg log --config extensions.color=! -l 1 --template '{label(red, "text\n")}'
652 text
652 text
653
653
654 Test branches inside if statement:
654 Test branches inside if statement:
655
655
656 $ hg log -r 0 --template '{if(branches, "yes", "no")}\n'
656 $ hg log -r 0 --template '{if(branches, "yes", "no")}\n'
657 no
657 no
658
658
659 Test dict constructor:
659 Test dict constructor:
660
660
661 $ hg log -r 0 -T '{dict(y=node|short, x=rev)}\n'
661 $ hg log -r 0 -T '{dict(y=node|short, x=rev)}\n'
662 y=f7769ec2ab97 x=0
662 y=f7769ec2ab97 x=0
663 $ hg log -r 0 -T '{dict(x=rev, y=node|short) % "{key}={value}\n"}'
663 $ hg log -r 0 -T '{dict(x=rev, y=node|short) % "{key}={value}\n"}'
664 x=0
664 x=0
665 y=f7769ec2ab97
665 y=f7769ec2ab97
666 $ hg log -r 0 -T '{dict(x=rev, y=node|short)|json}\n'
666 $ hg log -r 0 -T '{dict(x=rev, y=node|short)|json}\n'
667 {"x": 0, "y": "f7769ec2ab97"}
667 {"x": 0, "y": "f7769ec2ab97"}
668 $ hg log -r 0 -T '{dict()|json}\n'
668 $ hg log -r 0 -T '{dict()|json}\n'
669 {}
669 {}
670
670
671 $ hg log -r 0 -T '{dict(rev, node=node|short)}\n'
671 $ hg log -r 0 -T '{dict(rev, node=node|short)}\n'
672 rev=0 node=f7769ec2ab97
672 rev=0 node=f7769ec2ab97
673 $ hg log -r 0 -T '{dict(rev, node|short)}\n'
673 $ hg log -r 0 -T '{dict(rev, node|short)}\n'
674 rev=0 node=f7769ec2ab97
674 rev=0 node=f7769ec2ab97
675
675
676 $ hg log -r 0 -T '{dict(rev, rev=rev)}\n'
676 $ hg log -r 0 -T '{dict(rev, rev=rev)}\n'
677 hg: parse error: duplicated dict key 'rev' inferred
677 hg: parse error: duplicated dict key 'rev' inferred
678 [255]
678 [255]
679 $ hg log -r 0 -T '{dict(node, node|short)}\n'
679 $ hg log -r 0 -T '{dict(node, node|short)}\n'
680 hg: parse error: duplicated dict key 'node' inferred
680 hg: parse error: duplicated dict key 'node' inferred
681 [255]
681 [255]
682 $ hg log -r 0 -T '{dict(1 + 2)}'
682 $ hg log -r 0 -T '{dict(1 + 2)}'
683 hg: parse error: dict key cannot be inferred
683 hg: parse error: dict key cannot be inferred
684 [255]
684 [255]
685
685
686 $ hg log -r 0 -T '{dict(x=rev, x=node)}'
686 $ hg log -r 0 -T '{dict(x=rev, x=node)}'
687 hg: parse error: dict got multiple values for keyword argument 'x'
687 hg: parse error: dict got multiple values for keyword argument 'x'
688 [255]
688 [255]
689
689
690 Test get function:
690 Test get function:
691
691
692 $ hg log -r 0 --template '{get(extras, "branch")}\n'
692 $ hg log -r 0 --template '{get(extras, "branch")}\n'
693 default
693 default
694 $ hg log -r 0 --template '{get(extras, "br{"anch"}")}\n'
694 $ hg log -r 0 --template '{get(extras, "br{"anch"}")}\n'
695 default
695 default
696 $ hg log -r 0 --template '{get(files, "should_fail")}\n'
696 $ hg log -r 0 --template '{get(files, "should_fail")}\n'
697 hg: parse error: not a dictionary
697 hg: parse error: not a dictionary
698 (get() expects a dict as first argument)
698 (get() expects a dict as first argument)
699 [255]
699 [255]
700
700
701 Test json filter applied to wrapped object:
701 Test json filter applied to wrapped object:
702
702
703 $ hg log -r0 -T '{files|json}\n'
703 $ hg log -r0 -T '{files|json}\n'
704 ["a"]
704 ["a"]
705 $ hg log -r0 -T '{extras|json}\n'
705 $ hg log -r0 -T '{extras|json}\n'
706 {"branch": "default"}
706 {"branch": "default"}
707 $ hg log -r0 -T '{date|json}\n'
707 $ hg log -r0 -T '{date|json}\n'
708 [0, 0]
708 [0, 0]
709
709
710 Test json filter applied to map result:
710 Test json filter applied to map result:
711
711
712 $ hg log -r0 -T '{json(extras % "{key}")}\n'
712 $ hg log -r0 -T '{json(extras % "{key}")}\n'
713 ["branch"]
713 ["branch"]
714
714
715 Test localdate(date, tz) function:
715 Test localdate(date, tz) function:
716
716
717 $ TZ=JST-09 hg log -r0 -T '{date|localdate|isodate}\n'
717 $ TZ=JST-09 hg log -r0 -T '{date|localdate|isodate}\n'
718 1970-01-01 09:00 +0900
718 1970-01-01 09:00 +0900
719 $ TZ=JST-09 hg log -r0 -T '{localdate(date, "UTC")|isodate}\n'
719 $ TZ=JST-09 hg log -r0 -T '{localdate(date, "UTC")|isodate}\n'
720 1970-01-01 00:00 +0000
720 1970-01-01 00:00 +0000
721 $ TZ=JST-09 hg log -r0 -T '{localdate(date, "blahUTC")|isodate}\n'
721 $ TZ=JST-09 hg log -r0 -T '{localdate(date, "blahUTC")|isodate}\n'
722 hg: parse error: localdate expects a timezone
722 hg: parse error: localdate expects a timezone
723 [255]
723 [255]
724 $ TZ=JST-09 hg log -r0 -T '{localdate(date, "+0200")|isodate}\n'
724 $ TZ=JST-09 hg log -r0 -T '{localdate(date, "+0200")|isodate}\n'
725 1970-01-01 02:00 +0200
725 1970-01-01 02:00 +0200
726 $ TZ=JST-09 hg log -r0 -T '{localdate(date, "0")|isodate}\n'
726 $ TZ=JST-09 hg log -r0 -T '{localdate(date, "0")|isodate}\n'
727 1970-01-01 00:00 +0000
727 1970-01-01 00:00 +0000
728 $ TZ=JST-09 hg log -r0 -T '{localdate(date, 0)|isodate}\n'
728 $ TZ=JST-09 hg log -r0 -T '{localdate(date, 0)|isodate}\n'
729 1970-01-01 00:00 +0000
729 1970-01-01 00:00 +0000
730 $ hg log -r0 -T '{localdate(date, "invalid")|isodate}\n'
730 $ hg log -r0 -T '{localdate(date, "invalid")|isodate}\n'
731 hg: parse error: localdate expects a timezone
731 hg: parse error: localdate expects a timezone
732 [255]
732 [255]
733 $ hg log -r0 -T '{localdate(date, date)|isodate}\n'
733 $ hg log -r0 -T '{localdate(date, date)|isodate}\n'
734 hg: parse error: localdate expects a timezone
734 hg: parse error: localdate expects a timezone
735 [255]
735 [255]
736
736
737 Test shortest(node) function:
737 Test shortest(node) function:
738
738
739 $ echo b > b
739 $ echo b > b
740 $ hg ci -qAm b
740 $ hg ci -qAm b
741 $ hg log --template '{shortest(node)}\n'
741 $ hg log --template '{shortest(node)}\n'
742 e777
742 e777
743 bcc7
743 bcc7
744 f776
744 f776
745 $ hg log --template '{shortest(node, 10)}\n'
745 $ hg log --template '{shortest(node, 10)}\n'
746 e777603221
746 e777603221
747 bcc7ff960b
747 bcc7ff960b
748 f7769ec2ab
748 f7769ec2ab
749 $ hg log --template '{node|shortest}\n' -l1
749 $ hg log --template '{node|shortest}\n' -l1
750 e777
750 e777
751
751
752 $ hg log -r 0 -T '{shortest(node, "1{"0"}")}\n'
752 $ hg log -r 0 -T '{shortest(node, "1{"0"}")}\n'
753 f7769ec2ab
753 f7769ec2ab
754 $ hg log -r 0 -T '{shortest(node, "not an int")}\n'
754 $ hg log -r 0 -T '{shortest(node, "not an int")}\n'
755 hg: parse error: shortest() expects an integer minlength
755 hg: parse error: shortest() expects an integer minlength
756 [255]
756 [255]
757
757
758 $ hg log -r 'wdir()' -T '{node|shortest}\n'
758 $ hg log -r 'wdir()' -T '{node|shortest}\n'
759 ffff
759 ffff
760
760
761 $ hg log --template '{shortest("f")}\n' -l1
761 $ hg log --template '{shortest("f")}\n' -l1
762 f
762 f
763
763
764 $ hg log --template '{shortest("0123456789012345678901234567890123456789")}\n' -l1
764 $ hg log --template '{shortest("0123456789012345678901234567890123456789")}\n' -l1
765 0123456789012345678901234567890123456789
765 0123456789012345678901234567890123456789
766
766
767 $ hg log --template '{shortest("01234567890123456789012345678901234567890123456789")}\n' -l1
767 $ hg log --template '{shortest("01234567890123456789012345678901234567890123456789")}\n' -l1
768 01234567890123456789012345678901234567890123456789
768 01234567890123456789012345678901234567890123456789
769
769
770 $ hg log --template '{shortest("not a hex string")}\n' -l1
770 $ hg log --template '{shortest("not a hex string")}\n' -l1
771 not a hex string
771 not a hex string
772
772
773 $ hg log --template '{shortest("not a hex string, but it'\''s 40 bytes long")}\n' -l1
773 $ hg log --template '{shortest("not a hex string, but it'\''s 40 bytes long")}\n' -l1
774 not a hex string, but it's 40 bytes long
774 not a hex string, but it's 40 bytes long
775
775
776 $ hg log --template '{shortest("ffffffffffffffffffffffffffffffffffffffff")}\n' -l1
776 $ hg log --template '{shortest("ffffffffffffffffffffffffffffffffffffffff")}\n' -l1
777 ffff
777 ffff
778
778
779 $ hg log --template '{shortest("fffffff")}\n' -l1
779 $ hg log --template '{shortest("fffffff")}\n' -l1
780 ffff
780 ffff
781
781
782 $ hg log --template '{shortest("ff")}\n' -l1
782 $ hg log --template '{shortest("ff")}\n' -l1
783 ffff
783 ffff
784
784
785 $ cd ..
785 $ cd ..
786
786
787 Test shortest(node) with the repo having short hash collision:
787 Test shortest(node) with the repo having short hash collision:
788
788
789 $ hg init hashcollision
789 $ hg init hashcollision
790 $ cd hashcollision
790 $ cd hashcollision
791 $ cat <<EOF >> .hg/hgrc
791 $ cat <<EOF >> .hg/hgrc
792 > [experimental]
792 > [experimental]
793 > evolution.createmarkers=True
793 > evolution.createmarkers=True
794 > EOF
794 > EOF
795 $ echo 0 > a
795 $ echo 0 > a
796 $ hg ci -qAm 0
796 $ hg ci -qAm 0
797 $ for i in 17 129 248 242 480 580 617 1057 2857 4025; do
797 $ for i in 17 129 248 242 480 580 617 1057 2857 4025; do
798 > hg up -q 0
798 > hg up -q 0
799 > echo $i > a
799 > echo $i > a
800 > hg ci -qm $i
800 > hg ci -qm $i
801 > done
801 > done
802 $ hg up -q null
802 $ hg up -q null
803 $ hg log -r0: -T '{rev}:{node}\n'
803 $ hg log -r0: -T '{rev}:{node}\n'
804 0:b4e73ffab476aa0ee32ed81ca51e07169844bc6a
804 0:b4e73ffab476aa0ee32ed81ca51e07169844bc6a
805 1:11424df6dc1dd4ea255eae2b58eaca7831973bbc
805 1:11424df6dc1dd4ea255eae2b58eaca7831973bbc
806 2:11407b3f1b9c3e76a79c1ec5373924df096f0499
806 2:11407b3f1b9c3e76a79c1ec5373924df096f0499
807 3:11dd92fe0f39dfdaacdaa5f3997edc533875cfc4
807 3:11dd92fe0f39dfdaacdaa5f3997edc533875cfc4
808 4:10776689e627b465361ad5c296a20a487e153ca4
808 4:10776689e627b465361ad5c296a20a487e153ca4
809 5:a00be79088084cb3aff086ab799f8790e01a976b
809 5:a00be79088084cb3aff086ab799f8790e01a976b
810 6:a0b0acd79b4498d0052993d35a6a748dd51d13e6
810 6:a0b0acd79b4498d0052993d35a6a748dd51d13e6
811 7:a0457b3450b8e1b778f1163b31a435802987fe5d
811 7:a0457b3450b8e1b778f1163b31a435802987fe5d
812 8:c56256a09cd28e5764f32e8e2810d0f01e2e357a
812 8:c56256a09cd28e5764f32e8e2810d0f01e2e357a
813 9:c5623987d205cd6d9d8389bfc40fff9dbb670b48
813 9:c5623987d205cd6d9d8389bfc40fff9dbb670b48
814 10:c562ddd9c94164376c20b86b0b4991636a3bf84f
814 10:c562ddd9c94164376c20b86b0b4991636a3bf84f
815 $ hg debugobsolete a00be79088084cb3aff086ab799f8790e01a976b
815 $ hg debugobsolete a00be79088084cb3aff086ab799f8790e01a976b
816 obsoleted 1 changesets
816 obsoleted 1 changesets
817 $ hg debugobsolete c5623987d205cd6d9d8389bfc40fff9dbb670b48
817 $ hg debugobsolete c5623987d205cd6d9d8389bfc40fff9dbb670b48
818 obsoleted 1 changesets
818 obsoleted 1 changesets
819 $ hg debugobsolete c562ddd9c94164376c20b86b0b4991636a3bf84f
819 $ hg debugobsolete c562ddd9c94164376c20b86b0b4991636a3bf84f
820 obsoleted 1 changesets
820 obsoleted 1 changesets
821
821
822 nodes starting with '11' (we don't have the revision number '11' though)
822 nodes starting with '11' (we don't have the revision number '11' though)
823
823
824 $ hg log -r 1:3 -T '{rev}:{shortest(node, 0)}\n'
824 $ hg log -r 1:3 -T '{rev}:{shortest(node, 0)}\n'
825 1:1142
825 1:1142
826 2:1140
826 2:1140
827 3:11d
827 3:11d
828
828
829 '5:a00' is hidden, but still we have two nodes starting with 'a0'
829 '5:a00' is hidden, but still we have two nodes starting with 'a0'
830
830
831 $ hg log -r 6:7 -T '{rev}:{shortest(node, 0)}\n'
831 $ hg log -r 6:7 -T '{rev}:{shortest(node, 0)}\n'
832 6:a0b
832 6:a0b
833 7:a04
833 7:a04
834
834
835 node '10' conflicts with the revision number '10' even if it is hidden
835 node '10' conflicts with the revision number '10' even if it is hidden
836 (we could exclude hidden revision numbers, but currently we don't)
836 (we could exclude hidden revision numbers, but currently we don't)
837
837
838 $ hg log -r 4 -T '{rev}:{shortest(node, 0)}\n'
838 $ hg log -r 4 -T '{rev}:{shortest(node, 0)}\n'
839 4:107
839 4:107
840 $ hg log -r 4 -T '{rev}:{shortest(node, 0)}\n' --hidden
840 $ hg log -r 4 -T '{rev}:{shortest(node, 0)}\n' --hidden
841 4:107
841 4:107
842
842
843 node 'c562' should be unique if the other 'c562' nodes are hidden
843 node 'c562' should be unique if the other 'c562' nodes are hidden
844 (but we don't try the slow path to filter out hidden nodes for now)
844 (but we don't try the slow path to filter out hidden nodes for now)
845
845
846 $ hg log -r 8 -T '{rev}:{node|shortest}\n'
846 $ hg log -r 8 -T '{rev}:{node|shortest}\n'
847 8:c5625
847 8:c5625
848 $ hg log -r 8:10 -T '{rev}:{node|shortest}\n' --hidden
848 $ hg log -r 8:10 -T '{rev}:{node|shortest}\n' --hidden
849 8:c5625
849 8:c5625
850 9:c5623
850 9:c5623
851 10:c562d
851 10:c562d
852
852
853 $ cd ..
853 $ cd ..
854
854
855 Test pad function
855 Test pad function
856
856
857 $ cd r
857 $ cd r
858
858
859 $ hg log --template '{pad(rev, 20)} {author|user}\n'
859 $ hg log --template '{pad(rev, 20)} {author|user}\n'
860 2 test
860 2 test
861 1 {node|short}
861 1 {node|short}
862 0 test
862 0 test
863
863
864 $ hg log --template '{pad(rev, 20, " ", True)} {author|user}\n'
864 $ hg log --template '{pad(rev, 20, " ", True)} {author|user}\n'
865 2 test
865 2 test
866 1 {node|short}
866 1 {node|short}
867 0 test
867 0 test
868
868
869 $ hg log --template '{pad(rev, 20, "-", False)} {author|user}\n'
869 $ hg log --template '{pad(rev, 20, "-", False)} {author|user}\n'
870 2------------------- test
870 2------------------- test
871 1------------------- {node|short}
871 1------------------- {node|short}
872 0------------------- test
872 0------------------- test
873
873
874 Test template string in pad function
874 Test template string in pad function
875
875
876 $ hg log -r 0 -T '{pad("\{{rev}}", 10)} {author|user}\n'
876 $ hg log -r 0 -T '{pad("\{{rev}}", 10)} {author|user}\n'
877 {0} test
877 {0} test
878
878
879 $ hg log -r 0 -T '{pad(r"\{rev}", 10)} {author|user}\n'
879 $ hg log -r 0 -T '{pad(r"\{rev}", 10)} {author|user}\n'
880 \{rev} test
880 \{rev} test
881
881
882 Test width argument passed to pad function
882 Test width argument passed to pad function
883
883
884 $ hg log -r 0 -T '{pad(rev, "1{"0"}")} {author|user}\n'
884 $ hg log -r 0 -T '{pad(rev, "1{"0"}")} {author|user}\n'
885 0 test
885 0 test
886 $ hg log -r 0 -T '{pad(rev, "not an int")}\n'
886 $ hg log -r 0 -T '{pad(rev, "not an int")}\n'
887 hg: parse error: pad() expects an integer width
887 hg: parse error: pad() expects an integer width
888 [255]
888 [255]
889
889
890 Test invalid fillchar passed to pad function
890 Test invalid fillchar passed to pad function
891
891
892 $ hg log -r 0 -T '{pad(rev, 10, "")}\n'
892 $ hg log -r 0 -T '{pad(rev, 10, "")}\n'
893 hg: parse error: pad() expects a single fill character
893 hg: parse error: pad() expects a single fill character
894 [255]
894 [255]
895 $ hg log -r 0 -T '{pad(rev, 10, "--")}\n'
895 $ hg log -r 0 -T '{pad(rev, 10, "--")}\n'
896 hg: parse error: pad() expects a single fill character
896 hg: parse error: pad() expects a single fill character
897 [255]
897 [255]
898
898
899 Test boolean argument passed to pad function
899 Test boolean argument passed to pad function
900
900
901 no crash
901 no crash
902
902
903 $ hg log -r 0 -T '{pad(rev, 10, "-", "f{"oo"}")}\n'
903 $ hg log -r 0 -T '{pad(rev, 10, "-", "f{"oo"}")}\n'
904 ---------0
904 ---------0
905
905
906 string/literal
906 string/literal
907
907
908 $ hg log -r 0 -T '{pad(rev, 10, "-", "false")}\n'
908 $ hg log -r 0 -T '{pad(rev, 10, "-", "false")}\n'
909 ---------0
909 ---------0
910 $ hg log -r 0 -T '{pad(rev, 10, "-", false)}\n'
910 $ hg log -r 0 -T '{pad(rev, 10, "-", false)}\n'
911 0---------
911 0---------
912 $ hg log -r 0 -T '{pad(rev, 10, "-", "")}\n'
912 $ hg log -r 0 -T '{pad(rev, 10, "-", "")}\n'
913 0---------
913 0---------
914
914
915 unknown keyword is evaluated to ''
915 unknown keyword is evaluated to ''
916
916
917 $ hg log -r 0 -T '{pad(rev, 10, "-", unknownkeyword)}\n'
917 $ hg log -r 0 -T '{pad(rev, 10, "-", unknownkeyword)}\n'
918 0---------
918 0---------
919
919
920 Test separate function
920 Test separate function
921
921
922 $ hg log -r 0 -T '{separate("-", "", "a", "b", "", "", "c", "")}\n'
922 $ hg log -r 0 -T '{separate("-", "", "a", "b", "", "", "c", "")}\n'
923 a-b-c
923 a-b-c
924 $ hg log -r 0 -T '{separate(" ", "{rev}:{node|short}", author|user, branch)}\n'
924 $ hg log -r 0 -T '{separate(" ", "{rev}:{node|short}", author|user, branch)}\n'
925 0:f7769ec2ab97 test default
925 0:f7769ec2ab97 test default
926 $ hg log -r 0 --color=always -T '{separate(" ", "a", label(red, "b"), "c", label(red, ""), "d")}\n'
926 $ hg log -r 0 --color=always -T '{separate(" ", "a", label(red, "b"), "c", label(red, ""), "d")}\n'
927 a \x1b[0;31mb\x1b[0m c d (esc)
927 a \x1b[0;31mb\x1b[0m c d (esc)
928
928
929 Test boolean expression/literal passed to if function
929 Test boolean expression/literal passed to if function
930
930
931 $ hg log -r 0 -T '{if(rev, "rev 0 is True")}\n'
931 $ hg log -r 0 -T '{if(rev, "rev 0 is True")}\n'
932 rev 0 is True
932 rev 0 is True
933 $ hg log -r 0 -T '{if(0, "literal 0 is True as well")}\n'
933 $ hg log -r 0 -T '{if(0, "literal 0 is True as well")}\n'
934 literal 0 is True as well
934 literal 0 is True as well
935 $ hg log -r 0 -T '{if(min(revset(r"0")), "0 of hybriditem is also True")}\n'
936 0 of hybriditem is also True
935 $ hg log -r 0 -T '{if("", "", "empty string is False")}\n'
937 $ hg log -r 0 -T '{if("", "", "empty string is False")}\n'
936 empty string is False
938 empty string is False
937 $ hg log -r 0 -T '{if(revset(r"0 - 0"), "", "empty list is False")}\n'
939 $ hg log -r 0 -T '{if(revset(r"0 - 0"), "", "empty list is False")}\n'
938 empty list is False
940 empty list is False
939 $ hg log -r 0 -T '{if(revset(r"0"), "non-empty list is True")}\n'
941 $ hg log -r 0 -T '{if(revset(r"0"), "non-empty list is True")}\n'
940 non-empty list is True
942 non-empty list is True
941 $ hg log -r 0 -T '{if(revset(r"0") % "", "list of empty strings is True")}\n'
943 $ hg log -r 0 -T '{if(revset(r"0") % "", "list of empty strings is True")}\n'
942 list of empty strings is True
944 list of empty strings is True
943 $ hg log -r 0 -T '{if(true, "true is True")}\n'
945 $ hg log -r 0 -T '{if(true, "true is True")}\n'
944 true is True
946 true is True
945 $ hg log -r 0 -T '{if(false, "", "false is False")}\n'
947 $ hg log -r 0 -T '{if(false, "", "false is False")}\n'
946 false is False
948 false is False
947 $ hg log -r 0 -T '{if("false", "non-empty string is True")}\n'
949 $ hg log -r 0 -T '{if("false", "non-empty string is True")}\n'
948 non-empty string is True
950 non-empty string is True
949
951
950 Test ifcontains function
952 Test ifcontains function
951
953
952 $ hg log --template '{rev} {ifcontains(rev, "2 two 0", "is in the string", "is not")}\n'
954 $ hg log --template '{rev} {ifcontains(rev, "2 two 0", "is in the string", "is not")}\n'
953 2 is in the string
955 2 is in the string
954 1 is not
956 1 is not
955 0 is in the string
957 0 is in the string
956
958
957 $ hg log -T '{rev} {ifcontains(rev, "2 two{" 0"}", "is in the string", "is not")}\n'
959 $ hg log -T '{rev} {ifcontains(rev, "2 two{" 0"}", "is in the string", "is not")}\n'
958 2 is in the string
960 2 is in the string
959 1 is not
961 1 is not
960 0 is in the string
962 0 is in the string
961
963
962 $ hg log --template '{rev} {ifcontains("a", file_adds, "added a", "did not add a")}\n'
964 $ hg log --template '{rev} {ifcontains("a", file_adds, "added a", "did not add a")}\n'
963 2 did not add a
965 2 did not add a
964 1 did not add a
966 1 did not add a
965 0 added a
967 0 added a
966
968
967 $ hg log --debug -T '{rev}{ifcontains(1, parents, " is parent of 1")}\n'
969 $ hg log --debug -T '{rev}{ifcontains(1, parents, " is parent of 1")}\n'
968 2 is parent of 1
970 2 is parent of 1
969 1
971 1
970 0
972 0
971
973
972 $ hg log -l1 -T '{ifcontains("branch", extras, "t", "f")}\n'
974 $ hg log -l1 -T '{ifcontains("branch", extras, "t", "f")}\n'
973 t
975 t
974 $ hg log -l1 -T '{ifcontains("branch", extras % "{key}", "t", "f")}\n'
976 $ hg log -l1 -T '{ifcontains("branch", extras % "{key}", "t", "f")}\n'
975 t
977 t
976 $ hg log -l1 -T '{ifcontains("branc", extras % "{key}", "t", "f")}\n'
978 $ hg log -l1 -T '{ifcontains("branc", extras % "{key}", "t", "f")}\n'
977 f
979 f
978 $ hg log -l1 -T '{ifcontains("branc", stringify(extras % "{key}"), "t", "f")}\n'
980 $ hg log -l1 -T '{ifcontains("branc", stringify(extras % "{key}"), "t", "f")}\n'
979 t
981 t
980
982
981 Test revset function
983 Test revset function
982
984
983 $ hg log --template '{rev} {ifcontains(rev, revset("."), "current rev", "not current rev")}\n'
985 $ hg log --template '{rev} {ifcontains(rev, revset("."), "current rev", "not current rev")}\n'
984 2 current rev
986 2 current rev
985 1 not current rev
987 1 not current rev
986 0 not current rev
988 0 not current rev
987
989
988 $ hg log --template '{rev} {ifcontains(rev, revset(". + .^"), "match rev", "not match rev")}\n'
990 $ hg log --template '{rev} {ifcontains(rev, revset(". + .^"), "match rev", "not match rev")}\n'
989 2 match rev
991 2 match rev
990 1 match rev
992 1 match rev
991 0 not match rev
993 0 not match rev
992
994
993 $ hg log -T '{ifcontains(desc, revset(":"), "", "type not match")}\n' -l1
995 $ hg log -T '{ifcontains(desc, revset(":"), "", "type not match")}\n' -l1
994 type not match
996 type not match
995
997
996 $ hg log --template '{rev} Parents: {revset("parents(%s)", rev)}\n'
998 $ hg log --template '{rev} Parents: {revset("parents(%s)", rev)}\n'
997 2 Parents: 1
999 2 Parents: 1
998 1 Parents: 0
1000 1 Parents: 0
999 0 Parents:
1001 0 Parents:
1000
1002
1001 $ cat >> .hg/hgrc <<EOF
1003 $ cat >> .hg/hgrc <<EOF
1002 > [revsetalias]
1004 > [revsetalias]
1003 > myparents(\$1) = parents(\$1)
1005 > myparents(\$1) = parents(\$1)
1004 > EOF
1006 > EOF
1005 $ hg log --template '{rev} Parents: {revset("myparents(%s)", rev)}\n'
1007 $ hg log --template '{rev} Parents: {revset("myparents(%s)", rev)}\n'
1006 2 Parents: 1
1008 2 Parents: 1
1007 1 Parents: 0
1009 1 Parents: 0
1008 0 Parents:
1010 0 Parents:
1009
1011
1010 $ hg log --template 'Rev: {rev}\n{revset("::%s", rev) % "Ancestor: {revision}\n"}\n'
1012 $ hg log --template 'Rev: {rev}\n{revset("::%s", rev) % "Ancestor: {revision}\n"}\n'
1011 Rev: 2
1013 Rev: 2
1012 Ancestor: 0
1014 Ancestor: 0
1013 Ancestor: 1
1015 Ancestor: 1
1014 Ancestor: 2
1016 Ancestor: 2
1015
1017
1016 Rev: 1
1018 Rev: 1
1017 Ancestor: 0
1019 Ancestor: 0
1018 Ancestor: 1
1020 Ancestor: 1
1019
1021
1020 Rev: 0
1022 Rev: 0
1021 Ancestor: 0
1023 Ancestor: 0
1022
1024
1023 $ hg log --template '{revset("TIP"|lower)}\n' -l1
1025 $ hg log --template '{revset("TIP"|lower)}\n' -l1
1024 2
1026 2
1025
1027
1026 $ hg log -T '{revset("%s", "t{"ip"}")}\n' -l1
1028 $ hg log -T '{revset("%s", "t{"ip"}")}\n' -l1
1027 2
1029 2
1028
1030
1029 a list template is evaluated for each item of revset/parents
1031 a list template is evaluated for each item of revset/parents
1030
1032
1031 $ hg log -T '{rev} p: {revset("p1(%s)", rev) % "{rev}:{node|short}"}\n'
1033 $ hg log -T '{rev} p: {revset("p1(%s)", rev) % "{rev}:{node|short}"}\n'
1032 2 p: 1:bcc7ff960b8e
1034 2 p: 1:bcc7ff960b8e
1033 1 p: 0:f7769ec2ab97
1035 1 p: 0:f7769ec2ab97
1034 0 p:
1036 0 p:
1035
1037
1036 $ hg log --debug -T '{rev} p:{parents % " {rev}:{node|short}"}\n'
1038 $ hg log --debug -T '{rev} p:{parents % " {rev}:{node|short}"}\n'
1037 2 p: 1:bcc7ff960b8e -1:000000000000
1039 2 p: 1:bcc7ff960b8e -1:000000000000
1038 1 p: 0:f7769ec2ab97 -1:000000000000
1040 1 p: 0:f7769ec2ab97 -1:000000000000
1039 0 p: -1:000000000000 -1:000000000000
1041 0 p: -1:000000000000 -1:000000000000
1040
1042
1041 therefore, 'revcache' should be recreated for each rev
1043 therefore, 'revcache' should be recreated for each rev
1042
1044
1043 $ hg log -T '{rev} {file_adds}\np {revset("p1(%s)", rev) % "{file_adds}"}\n'
1045 $ hg log -T '{rev} {file_adds}\np {revset("p1(%s)", rev) % "{file_adds}"}\n'
1044 2 aa b
1046 2 aa b
1045 p
1047 p
1046 1
1048 1
1047 p a
1049 p a
1048 0 a
1050 0 a
1049 p
1051 p
1050
1052
1051 $ hg log --debug -T '{rev} {file_adds}\np {parents % "{file_adds}"}\n'
1053 $ hg log --debug -T '{rev} {file_adds}\np {parents % "{file_adds}"}\n'
1052 2 aa b
1054 2 aa b
1053 p
1055 p
1054 1
1056 1
1055 p a
1057 p a
1056 0 a
1058 0 a
1057 p
1059 p
1058
1060
1059 a revset item must be evaluated as an integer revision, not an offset from tip
1061 a revset item must be evaluated as an integer revision, not an offset from tip
1060
1062
1061 $ hg log -l 1 -T '{revset("null") % "{rev}:{node|short}"}\n'
1063 $ hg log -l 1 -T '{revset("null") % "{rev}:{node|short}"}\n'
1062 -1:000000000000
1064 -1:000000000000
1063 $ hg log -l 1 -T '{revset("%s", "null") % "{rev}:{node|short}"}\n'
1065 $ hg log -l 1 -T '{revset("%s", "null") % "{rev}:{node|short}"}\n'
1064 -1:000000000000
1066 -1:000000000000
1065
1067
1066 join() should pick '{rev}' from revset items:
1068 join() should pick '{rev}' from revset items:
1067
1069
1068 $ hg log -R ../a -T '{join(revset("parents(%d)", rev), ", ")}\n' -r6
1070 $ hg log -R ../a -T '{join(revset("parents(%d)", rev), ", ")}\n' -r6
1069 4, 5
1071 4, 5
1070
1072
1071 on the other hand, parents are formatted as '{rev}:{node|formatnode}' by
1073 on the other hand, parents are formatted as '{rev}:{node|formatnode}' by
1072 default. join() should agree with the default formatting:
1074 default. join() should agree with the default formatting:
1073
1075
1074 $ hg log -R ../a -T '{join(parents, ", ")}\n' -r6
1076 $ hg log -R ../a -T '{join(parents, ", ")}\n' -r6
1075 5:13207e5a10d9, 4:bbe44766e73d
1077 5:13207e5a10d9, 4:bbe44766e73d
1076
1078
1077 $ hg log -R ../a -T '{join(parents, ",\n")}\n' -r6 --debug
1079 $ hg log -R ../a -T '{join(parents, ",\n")}\n' -r6 --debug
1078 5:13207e5a10d9fd28ec424934298e176197f2c67f,
1080 5:13207e5a10d9fd28ec424934298e176197f2c67f,
1079 4:bbe44766e73d5f11ed2177f1838de10c53ef3e74
1081 4:bbe44766e73d5f11ed2177f1838de10c53ef3e74
1080
1082
1081 Invalid arguments passed to revset()
1083 Invalid arguments passed to revset()
1082
1084
1083 $ hg log -T '{revset("%whatever", 0)}\n'
1085 $ hg log -T '{revset("%whatever", 0)}\n'
1084 hg: parse error: unexpected revspec format character w
1086 hg: parse error: unexpected revspec format character w
1085 [255]
1087 [255]
1086 $ hg log -T '{revset("%lwhatever", files)}\n'
1088 $ hg log -T '{revset("%lwhatever", files)}\n'
1087 hg: parse error: unexpected revspec format character w
1089 hg: parse error: unexpected revspec format character w
1088 [255]
1090 [255]
1089 $ hg log -T '{revset("%s %s", 0)}\n'
1091 $ hg log -T '{revset("%s %s", 0)}\n'
1090 hg: parse error: missing argument for revspec
1092 hg: parse error: missing argument for revspec
1091 [255]
1093 [255]
1092 $ hg log -T '{revset("", 0)}\n'
1094 $ hg log -T '{revset("", 0)}\n'
1093 hg: parse error: too many revspec arguments specified
1095 hg: parse error: too many revspec arguments specified
1094 [255]
1096 [255]
1095 $ hg log -T '{revset("%s", 0, 1)}\n'
1097 $ hg log -T '{revset("%s", 0, 1)}\n'
1096 hg: parse error: too many revspec arguments specified
1098 hg: parse error: too many revspec arguments specified
1097 [255]
1099 [255]
1098 $ hg log -T '{revset("%", 0)}\n'
1100 $ hg log -T '{revset("%", 0)}\n'
1099 hg: parse error: incomplete revspec format character
1101 hg: parse error: incomplete revspec format character
1100 [255]
1102 [255]
1101 $ hg log -T '{revset("%l", 0)}\n'
1103 $ hg log -T '{revset("%l", 0)}\n'
1102 hg: parse error: incomplete revspec format character
1104 hg: parse error: incomplete revspec format character
1103 [255]
1105 [255]
1104 $ hg log -T '{revset("%d", 'foo')}\n'
1106 $ hg log -T '{revset("%d", 'foo')}\n'
1105 hg: parse error: invalid argument for revspec
1107 hg: parse error: invalid argument for revspec
1106 [255]
1108 [255]
1107 $ hg log -T '{revset("%ld", files)}\n'
1109 $ hg log -T '{revset("%ld", files)}\n'
1108 hg: parse error: invalid argument for revspec
1110 hg: parse error: invalid argument for revspec
1109 [255]
1111 [255]
1110 $ hg log -T '{revset("%ls", 0)}\n'
1112 $ hg log -T '{revset("%ls", 0)}\n'
1111 hg: parse error: invalid argument for revspec
1113 hg: parse error: invalid argument for revspec
1112 [255]
1114 [255]
1113 $ hg log -T '{revset("%b", 'foo')}\n'
1115 $ hg log -T '{revset("%b", 'foo')}\n'
1114 hg: parse error: invalid argument for revspec
1116 hg: parse error: invalid argument for revspec
1115 [255]
1117 [255]
1116 $ hg log -T '{revset("%lb", files)}\n'
1118 $ hg log -T '{revset("%lb", files)}\n'
1117 hg: parse error: invalid argument for revspec
1119 hg: parse error: invalid argument for revspec
1118 [255]
1120 [255]
1119 $ hg log -T '{revset("%r", 0)}\n'
1121 $ hg log -T '{revset("%r", 0)}\n'
1120 hg: parse error: invalid argument for revspec
1122 hg: parse error: invalid argument for revspec
1121 [255]
1123 [255]
1122
1124
1123 Test files function
1125 Test files function
1124
1126
1125 $ hg log -T "{rev}\n{join(files('*'), '\n')}\n"
1127 $ hg log -T "{rev}\n{join(files('*'), '\n')}\n"
1126 2
1128 2
1127 a
1129 a
1128 aa
1130 aa
1129 b
1131 b
1130 1
1132 1
1131 a
1133 a
1132 0
1134 0
1133 a
1135 a
1134
1136
1135 $ hg log -T "{rev}\n{join(files('aa'), '\n')}\n"
1137 $ hg log -T "{rev}\n{join(files('aa'), '\n')}\n"
1136 2
1138 2
1137 aa
1139 aa
1138 1
1140 1
1139
1141
1140 0
1142 0
1141
1143
1142 $ hg rm a
1144 $ hg rm a
1143 $ hg log -r "wdir()" -T "{rev}\n{join(files('*'), '\n')}\n"
1145 $ hg log -r "wdir()" -T "{rev}\n{join(files('*'), '\n')}\n"
1144 2147483647
1146 2147483647
1145 aa
1147 aa
1146 b
1148 b
1147 $ hg revert a
1149 $ hg revert a
1148
1150
1149 Test relpath function
1151 Test relpath function
1150
1152
1151 $ hg log -r0 -T '{files % "{file|relpath}\n"}'
1153 $ hg log -r0 -T '{files % "{file|relpath}\n"}'
1152 a
1154 a
1153 $ cd ..
1155 $ cd ..
1154 $ hg log -R r -r0 -T '{files % "{file|relpath}\n"}'
1156 $ hg log -R r -r0 -T '{files % "{file|relpath}\n"}'
1155 r/a
1157 r/a
1156
1158
1157 Test stringify on sub expressions
1159 Test stringify on sub expressions
1158
1160
1159 $ hg log -R a -r 8 --template '{join(files, if("1", if("1", ", ")))}\n'
1161 $ hg log -R a -r 8 --template '{join(files, if("1", if("1", ", ")))}\n'
1160 fourth, second, third
1162 fourth, second, third
1161 $ hg log -R a -r 8 --template '{strip(if("1", if("1", "-abc-")), if("1", if("1", "-")))}\n'
1163 $ hg log -R a -r 8 --template '{strip(if("1", if("1", "-abc-")), if("1", if("1", "-")))}\n'
1162 abc
1164 abc
1163
1165
1164 Test splitlines
1166 Test splitlines
1165
1167
1166 $ hg log -Gv -R a --template "{splitlines(desc) % 'foo {line}\n'}"
1168 $ hg log -Gv -R a --template "{splitlines(desc) % 'foo {line}\n'}"
1167 @ foo Modify, add, remove, rename
1169 @ foo Modify, add, remove, rename
1168 |
1170 |
1169 o foo future
1171 o foo future
1170 |
1172 |
1171 o foo third
1173 o foo third
1172 |
1174 |
1173 o foo second
1175 o foo second
1174
1176
1175 o foo merge
1177 o foo merge
1176 |\
1178 |\
1177 | o foo new head
1179 | o foo new head
1178 | |
1180 | |
1179 o | foo new branch
1181 o | foo new branch
1180 |/
1182 |/
1181 o foo no user, no domain
1183 o foo no user, no domain
1182 |
1184 |
1183 o foo no person
1185 o foo no person
1184 |
1186 |
1185 o foo other 1
1187 o foo other 1
1186 | foo other 2
1188 | foo other 2
1187 | foo
1189 | foo
1188 | foo other 3
1190 | foo other 3
1189 o foo line 1
1191 o foo line 1
1190 foo line 2
1192 foo line 2
1191
1193
1192 $ hg log -R a -r0 -T '{desc|splitlines}\n'
1194 $ hg log -R a -r0 -T '{desc|splitlines}\n'
1193 line 1 line 2
1195 line 1 line 2
1194 $ hg log -R a -r0 -T '{join(desc|splitlines, "|")}\n'
1196 $ hg log -R a -r0 -T '{join(desc|splitlines, "|")}\n'
1195 line 1|line 2
1197 line 1|line 2
1196
1198
1197 Test startswith
1199 Test startswith
1198 $ hg log -Gv -R a --template "{startswith(desc)}"
1200 $ hg log -Gv -R a --template "{startswith(desc)}"
1199 hg: parse error: startswith expects two arguments
1201 hg: parse error: startswith expects two arguments
1200 [255]
1202 [255]
1201
1203
1202 $ hg log -Gv -R a --template "{startswith('line', desc)}"
1204 $ hg log -Gv -R a --template "{startswith('line', desc)}"
1203 @
1205 @
1204 |
1206 |
1205 o
1207 o
1206 |
1208 |
1207 o
1209 o
1208 |
1210 |
1209 o
1211 o
1210
1212
1211 o
1213 o
1212 |\
1214 |\
1213 | o
1215 | o
1214 | |
1216 | |
1215 o |
1217 o |
1216 |/
1218 |/
1217 o
1219 o
1218 |
1220 |
1219 o
1221 o
1220 |
1222 |
1221 o
1223 o
1222 |
1224 |
1223 o line 1
1225 o line 1
1224 line 2
1226 line 2
1225
1227
1226 Test word function (including index out of bounds graceful failure)
1228 Test word function (including index out of bounds graceful failure)
1227
1229
1228 $ hg log -Gv -R a --template "{word('1', desc)}"
1230 $ hg log -Gv -R a --template "{word('1', desc)}"
1229 @ add,
1231 @ add,
1230 |
1232 |
1231 o
1233 o
1232 |
1234 |
1233 o
1235 o
1234 |
1236 |
1235 o
1237 o
1236
1238
1237 o
1239 o
1238 |\
1240 |\
1239 | o head
1241 | o head
1240 | |
1242 | |
1241 o | branch
1243 o | branch
1242 |/
1244 |/
1243 o user,
1245 o user,
1244 |
1246 |
1245 o person
1247 o person
1246 |
1248 |
1247 o 1
1249 o 1
1248 |
1250 |
1249 o 1
1251 o 1
1250
1252
1251
1253
1252 Test word third parameter used as splitter
1254 Test word third parameter used as splitter
1253
1255
1254 $ hg log -Gv -R a --template "{word('0', desc, 'o')}"
1256 $ hg log -Gv -R a --template "{word('0', desc, 'o')}"
1255 @ M
1257 @ M
1256 |
1258 |
1257 o future
1259 o future
1258 |
1260 |
1259 o third
1261 o third
1260 |
1262 |
1261 o sec
1263 o sec
1262
1264
1263 o merge
1265 o merge
1264 |\
1266 |\
1265 | o new head
1267 | o new head
1266 | |
1268 | |
1267 o | new branch
1269 o | new branch
1268 |/
1270 |/
1269 o n
1271 o n
1270 |
1272 |
1271 o n
1273 o n
1272 |
1274 |
1273 o
1275 o
1274 |
1276 |
1275 o line 1
1277 o line 1
1276 line 2
1278 line 2
1277
1279
1278 Test word error messages for not enough and too many arguments
1280 Test word error messages for not enough and too many arguments
1279
1281
1280 $ hg log -Gv -R a --template "{word('0')}"
1282 $ hg log -Gv -R a --template "{word('0')}"
1281 hg: parse error: word expects two or three arguments, got 1
1283 hg: parse error: word expects two or three arguments, got 1
1282 [255]
1284 [255]
1283
1285
1284 $ hg log -Gv -R a --template "{word('0', desc, 'o', 'h', 'b', 'o', 'y')}"
1286 $ hg log -Gv -R a --template "{word('0', desc, 'o', 'h', 'b', 'o', 'y')}"
1285 hg: parse error: word expects two or three arguments, got 7
1287 hg: parse error: word expects two or three arguments, got 7
1286 [255]
1288 [255]
1287
1289
1288 Test word for integer literal
1290 Test word for integer literal
1289
1291
1290 $ hg log -R a --template "{word(2, desc)}\n" -r0
1292 $ hg log -R a --template "{word(2, desc)}\n" -r0
1291 line
1293 line
1292
1294
1293 Test word for invalid numbers
1295 Test word for invalid numbers
1294
1296
1295 $ hg log -Gv -R a --template "{word('a', desc)}"
1297 $ hg log -Gv -R a --template "{word('a', desc)}"
1296 hg: parse error: word expects an integer index
1298 hg: parse error: word expects an integer index
1297 [255]
1299 [255]
1298
1300
1299 Test word for out of range
1301 Test word for out of range
1300
1302
1301 $ hg log -R a --template "{word(10000, desc)}"
1303 $ hg log -R a --template "{word(10000, desc)}"
1302 $ hg log -R a --template "{word(-10000, desc)}"
1304 $ hg log -R a --template "{word(-10000, desc)}"
1303
1305
1304 Test indent and not adding to empty lines
1306 Test indent and not adding to empty lines
1305
1307
1306 $ hg log -T "-----\n{indent(desc, '>> ', ' > ')}\n" -r 0:1 -R a
1308 $ hg log -T "-----\n{indent(desc, '>> ', ' > ')}\n" -r 0:1 -R a
1307 -----
1309 -----
1308 > line 1
1310 > line 1
1309 >> line 2
1311 >> line 2
1310 -----
1312 -----
1311 > other 1
1313 > other 1
1312 >> other 2
1314 >> other 2
1313
1315
1314 >> other 3
1316 >> other 3
1315
1317
1316 Test with non-strings like dates
1318 Test with non-strings like dates
1317
1319
1318 $ hg log -T "{indent(date, ' ')}\n" -r 2:3 -R a
1320 $ hg log -T "{indent(date, ' ')}\n" -r 2:3 -R a
1319 1200000.00
1321 1200000.00
1320 1300000.00
1322 1300000.00
1321
1323
1322 json filter should escape HTML tags so that the output can be embedded in hgweb:
1324 json filter should escape HTML tags so that the output can be embedded in hgweb:
1323
1325
1324 $ hg log -T "{'<foo@example.org>'|json}\n" -R a -l1
1326 $ hg log -T "{'<foo@example.org>'|json}\n" -R a -l1
1325 "\u003cfoo@example.org\u003e"
1327 "\u003cfoo@example.org\u003e"
1326
1328
1327 Set up repository for non-ascii encoding tests:
1329 Set up repository for non-ascii encoding tests:
1328
1330
1329 $ hg init nonascii
1331 $ hg init nonascii
1330 $ cd nonascii
1332 $ cd nonascii
1331 $ $PYTHON <<EOF
1333 $ $PYTHON <<EOF
1332 > open('latin1', 'wb').write(b'\xe9')
1334 > open('latin1', 'wb').write(b'\xe9')
1333 > open('utf-8', 'wb').write(b'\xc3\xa9')
1335 > open('utf-8', 'wb').write(b'\xc3\xa9')
1334 > EOF
1336 > EOF
1335 $ HGENCODING=utf-8 hg branch -q `cat utf-8`
1337 $ HGENCODING=utf-8 hg branch -q `cat utf-8`
1336 $ HGENCODING=utf-8 hg ci -qAm "non-ascii branch: `cat utf-8`" utf-8
1338 $ HGENCODING=utf-8 hg ci -qAm "non-ascii branch: `cat utf-8`" utf-8
1337
1339
1338 json filter should try round-trip conversion to utf-8:
1340 json filter should try round-trip conversion to utf-8:
1339
1341
1340 $ HGENCODING=ascii hg log -T "{branch|json}\n" -r0
1342 $ HGENCODING=ascii hg log -T "{branch|json}\n" -r0
1341 "\u00e9"
1343 "\u00e9"
1342 $ HGENCODING=ascii hg log -T "{desc|json}\n" -r0
1344 $ HGENCODING=ascii hg log -T "{desc|json}\n" -r0
1343 "non-ascii branch: \u00e9"
1345 "non-ascii branch: \u00e9"
1344
1346
1345 json filter should take input as utf-8 if it was converted from utf-8:
1347 json filter should take input as utf-8 if it was converted from utf-8:
1346
1348
1347 $ HGENCODING=latin-1 hg log -T "{branch|json}\n" -r0
1349 $ HGENCODING=latin-1 hg log -T "{branch|json}\n" -r0
1348 "\u00e9"
1350 "\u00e9"
1349 $ HGENCODING=latin-1 hg log -T "{desc|json}\n" -r0
1351 $ HGENCODING=latin-1 hg log -T "{desc|json}\n" -r0
1350 "non-ascii branch: \u00e9"
1352 "non-ascii branch: \u00e9"
1351
1353
1352 json filter takes input as utf-8b:
1354 json filter takes input as utf-8b:
1353
1355
1354 $ HGENCODING=ascii hg log -T "{'`cat utf-8`'|json}\n" -l1
1356 $ HGENCODING=ascii hg log -T "{'`cat utf-8`'|json}\n" -l1
1355 "\u00e9"
1357 "\u00e9"
1356 $ HGENCODING=ascii hg log -T "{'`cat latin1`'|json}\n" -l1
1358 $ HGENCODING=ascii hg log -T "{'`cat latin1`'|json}\n" -l1
1357 "\udce9"
1359 "\udce9"
1358
1360
1359 utf8 filter:
1361 utf8 filter:
1360
1362
1361 $ HGENCODING=ascii hg log -T "round-trip: {branch|utf8|hex}\n" -r0
1363 $ HGENCODING=ascii hg log -T "round-trip: {branch|utf8|hex}\n" -r0
1362 round-trip: c3a9
1364 round-trip: c3a9
1363 $ HGENCODING=latin1 hg log -T "decoded: {'`cat latin1`'|utf8|hex}\n" -l1
1365 $ HGENCODING=latin1 hg log -T "decoded: {'`cat latin1`'|utf8|hex}\n" -l1
1364 decoded: c3a9
1366 decoded: c3a9
1365 $ HGENCODING=ascii hg log -T "replaced: {'`cat latin1`'|utf8|hex}\n" -l1
1367 $ HGENCODING=ascii hg log -T "replaced: {'`cat latin1`'|utf8|hex}\n" -l1
1366 abort: decoding near * (glob)
1368 abort: decoding near * (glob)
1367 [255]
1369 [255]
1368 $ hg log -T "coerced to string: {rev|utf8}\n" -r0
1370 $ hg log -T "coerced to string: {rev|utf8}\n" -r0
1369 coerced to string: 0
1371 coerced to string: 0
1370
1372
1371 pad width:
1373 pad width:
1372
1374
1373 $ HGENCODING=utf-8 hg debugtemplate "{pad('`cat utf-8`', 2, '-')}\n"
1375 $ HGENCODING=utf-8 hg debugtemplate "{pad('`cat utf-8`', 2, '-')}\n"
1374 \xc3\xa9- (esc)
1376 \xc3\xa9- (esc)
1375
1377
1376 $ cd ..
1378 $ cd ..
General Comments 0
You need to be logged in to leave comments. Login now