##// END OF EJS Templates
templatekw: add public function to wrap a dict by _hybrid object
Yuya Nishihara -
r31925:5b2241e8 default
parent child Browse files
Show More
@@ -1,428 +1,428 b''
1 # formatter.py - generic output formatting for mercurial
1 # formatter.py - generic output formatting for mercurial
2 #
2 #
3 # Copyright 2012 Matt Mackall <mpm@selenic.com>
3 # Copyright 2012 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 """Generic output formatting for Mercurial
8 """Generic output formatting for Mercurial
9
9
10 The formatter provides API to show data in various ways. The following
10 The formatter provides API to show data in various ways. The following
11 functions should be used in place of ui.write():
11 functions should be used in place of ui.write():
12
12
13 - fm.write() for unconditional output
13 - fm.write() for unconditional output
14 - fm.condwrite() to show some extra data conditionally in plain output
14 - fm.condwrite() to show some extra data conditionally in plain output
15 - fm.context() to provide changectx to template output
15 - fm.context() to provide changectx to template output
16 - fm.data() to provide extra data to JSON or template output
16 - fm.data() to provide extra data to JSON or template output
17 - fm.plain() to show raw text that isn't provided to JSON or template output
17 - fm.plain() to show raw text that isn't provided to JSON or template output
18
18
19 To show structured data (e.g. date tuples, dicts, lists), apply fm.format*()
19 To show structured data (e.g. date tuples, dicts, lists), apply fm.format*()
20 beforehand so the data is converted to the appropriate data type. Use
20 beforehand so the data is converted to the appropriate data type. Use
21 fm.isplain() if you need to convert or format data conditionally which isn't
21 fm.isplain() if you need to convert or format data conditionally which isn't
22 supported by the formatter API.
22 supported by the formatter API.
23
23
24 To build nested structure (i.e. a list of dicts), use fm.nested().
24 To build nested structure (i.e. a list of dicts), use fm.nested().
25
25
26 See also https://www.mercurial-scm.org/wiki/GenericTemplatingPlan
26 See also https://www.mercurial-scm.org/wiki/GenericTemplatingPlan
27
27
28 fm.condwrite() vs 'if cond:':
28 fm.condwrite() vs 'if cond:':
29
29
30 In most cases, use fm.condwrite() so users can selectively show the data
30 In most cases, use fm.condwrite() so users can selectively show the data
31 in template output. If it's costly to build data, use plain 'if cond:' with
31 in template output. If it's costly to build data, use plain 'if cond:' with
32 fm.write().
32 fm.write().
33
33
34 fm.nested() vs fm.formatdict() (or fm.formatlist()):
34 fm.nested() vs fm.formatdict() (or fm.formatlist()):
35
35
36 fm.nested() should be used to form a tree structure (a list of dicts of
36 fm.nested() should be used to form a tree structure (a list of dicts of
37 lists of dicts...) which can be accessed through template keywords, e.g.
37 lists of dicts...) which can be accessed through template keywords, e.g.
38 "{foo % "{bar % {...}} {baz % {...}}"}". On the other hand, fm.formatdict()
38 "{foo % "{bar % {...}} {baz % {...}}"}". On the other hand, fm.formatdict()
39 exports a dict-type object to template, which can be accessed by e.g.
39 exports a dict-type object to template, which can be accessed by e.g.
40 "{get(foo, key)}" function.
40 "{get(foo, key)}" function.
41
41
42 Doctest helper:
42 Doctest helper:
43
43
44 >>> def show(fn, verbose=False, **opts):
44 >>> def show(fn, verbose=False, **opts):
45 ... import sys
45 ... import sys
46 ... from . import ui as uimod
46 ... from . import ui as uimod
47 ... ui = uimod.ui()
47 ... ui = uimod.ui()
48 ... ui.fout = sys.stdout # redirect to doctest
48 ... ui.fout = sys.stdout # redirect to doctest
49 ... ui.verbose = verbose
49 ... ui.verbose = verbose
50 ... return fn(ui, ui.formatter(fn.__name__, opts))
50 ... return fn(ui, ui.formatter(fn.__name__, opts))
51
51
52 Basic example:
52 Basic example:
53
53
54 >>> def files(ui, fm):
54 >>> def files(ui, fm):
55 ... files = [('foo', 123, (0, 0)), ('bar', 456, (1, 0))]
55 ... files = [('foo', 123, (0, 0)), ('bar', 456, (1, 0))]
56 ... for f in files:
56 ... for f in files:
57 ... fm.startitem()
57 ... fm.startitem()
58 ... fm.write('path', '%s', f[0])
58 ... fm.write('path', '%s', f[0])
59 ... fm.condwrite(ui.verbose, 'date', ' %s',
59 ... fm.condwrite(ui.verbose, 'date', ' %s',
60 ... fm.formatdate(f[2], '%Y-%m-%d %H:%M:%S'))
60 ... fm.formatdate(f[2], '%Y-%m-%d %H:%M:%S'))
61 ... fm.data(size=f[1])
61 ... fm.data(size=f[1])
62 ... fm.plain('\\n')
62 ... fm.plain('\\n')
63 ... fm.end()
63 ... fm.end()
64 >>> show(files)
64 >>> show(files)
65 foo
65 foo
66 bar
66 bar
67 >>> show(files, verbose=True)
67 >>> show(files, verbose=True)
68 foo 1970-01-01 00:00:00
68 foo 1970-01-01 00:00:00
69 bar 1970-01-01 00:00:01
69 bar 1970-01-01 00:00:01
70 >>> show(files, template='json')
70 >>> show(files, template='json')
71 [
71 [
72 {
72 {
73 "date": [0, 0],
73 "date": [0, 0],
74 "path": "foo",
74 "path": "foo",
75 "size": 123
75 "size": 123
76 },
76 },
77 {
77 {
78 "date": [1, 0],
78 "date": [1, 0],
79 "path": "bar",
79 "path": "bar",
80 "size": 456
80 "size": 456
81 }
81 }
82 ]
82 ]
83 >>> show(files, template='path: {path}\\ndate: {date|rfc3339date}\\n')
83 >>> show(files, template='path: {path}\\ndate: {date|rfc3339date}\\n')
84 path: foo
84 path: foo
85 date: 1970-01-01T00:00:00+00:00
85 date: 1970-01-01T00:00:00+00:00
86 path: bar
86 path: bar
87 date: 1970-01-01T00:00:01+00:00
87 date: 1970-01-01T00:00:01+00:00
88
88
89 Nested example:
89 Nested example:
90
90
91 >>> def subrepos(ui, fm):
91 >>> def subrepos(ui, fm):
92 ... fm.startitem()
92 ... fm.startitem()
93 ... fm.write('repo', '[%s]\\n', 'baz')
93 ... fm.write('repo', '[%s]\\n', 'baz')
94 ... files(ui, fm.nested('files'))
94 ... files(ui, fm.nested('files'))
95 ... fm.end()
95 ... fm.end()
96 >>> show(subrepos)
96 >>> show(subrepos)
97 [baz]
97 [baz]
98 foo
98 foo
99 bar
99 bar
100 >>> show(subrepos, template='{repo}: {join(files % "{path}", ", ")}\\n')
100 >>> show(subrepos, template='{repo}: {join(files % "{path}", ", ")}\\n')
101 baz: foo, bar
101 baz: foo, bar
102 """
102 """
103
103
104 from __future__ import absolute_import
104 from __future__ import absolute_import
105
105
106 import itertools
106 import itertools
107 import os
107 import os
108
108
109 from .i18n import _
109 from .i18n import _
110 from .node import (
110 from .node import (
111 hex,
111 hex,
112 short,
112 short,
113 )
113 )
114
114
115 from . import (
115 from . import (
116 error,
116 error,
117 templatefilters,
117 templatefilters,
118 templatekw,
118 templatekw,
119 templater,
119 templater,
120 util,
120 util,
121 )
121 )
122
122
123 pickle = util.pickle
123 pickle = util.pickle
124
124
125 class _nullconverter(object):
125 class _nullconverter(object):
126 '''convert non-primitive data types to be processed by formatter'''
126 '''convert non-primitive data types to be processed by formatter'''
127 @staticmethod
127 @staticmethod
128 def formatdate(date, fmt):
128 def formatdate(date, fmt):
129 '''convert date tuple to appropriate format'''
129 '''convert date tuple to appropriate format'''
130 return date
130 return date
131 @staticmethod
131 @staticmethod
132 def formatdict(data, key, value, fmt, sep):
132 def formatdict(data, key, value, fmt, sep):
133 '''convert dict or key-value pairs to appropriate dict format'''
133 '''convert dict or key-value pairs to appropriate dict format'''
134 # use plain dict instead of util.sortdict so that data can be
134 # use plain dict instead of util.sortdict so that data can be
135 # serialized as a builtin dict in pickle output
135 # serialized as a builtin dict in pickle output
136 return dict(data)
136 return dict(data)
137 @staticmethod
137 @staticmethod
138 def formatlist(data, name, fmt, sep):
138 def formatlist(data, name, fmt, sep):
139 '''convert iterable to appropriate list format'''
139 '''convert iterable to appropriate list format'''
140 return list(data)
140 return list(data)
141
141
142 class baseformatter(object):
142 class baseformatter(object):
143 def __init__(self, ui, topic, opts, converter):
143 def __init__(self, ui, topic, opts, converter):
144 self._ui = ui
144 self._ui = ui
145 self._topic = topic
145 self._topic = topic
146 self._style = opts.get("style")
146 self._style = opts.get("style")
147 self._template = opts.get("template")
147 self._template = opts.get("template")
148 self._converter = converter
148 self._converter = converter
149 self._item = None
149 self._item = None
150 # function to convert node to string suitable for this output
150 # function to convert node to string suitable for this output
151 self.hexfunc = hex
151 self.hexfunc = hex
152 def __enter__(self):
152 def __enter__(self):
153 return self
153 return self
154 def __exit__(self, exctype, excvalue, traceback):
154 def __exit__(self, exctype, excvalue, traceback):
155 if exctype is None:
155 if exctype is None:
156 self.end()
156 self.end()
157 def _showitem(self):
157 def _showitem(self):
158 '''show a formatted item once all data is collected'''
158 '''show a formatted item once all data is collected'''
159 pass
159 pass
160 def startitem(self):
160 def startitem(self):
161 '''begin an item in the format list'''
161 '''begin an item in the format list'''
162 if self._item is not None:
162 if self._item is not None:
163 self._showitem()
163 self._showitem()
164 self._item = {}
164 self._item = {}
165 def formatdate(self, date, fmt='%a %b %d %H:%M:%S %Y %1%2'):
165 def formatdate(self, date, fmt='%a %b %d %H:%M:%S %Y %1%2'):
166 '''convert date tuple to appropriate format'''
166 '''convert date tuple to appropriate format'''
167 return self._converter.formatdate(date, fmt)
167 return self._converter.formatdate(date, fmt)
168 def formatdict(self, data, key='key', value='value', fmt='%s=%s', sep=' '):
168 def formatdict(self, data, key='key', value='value', fmt='%s=%s', sep=' '):
169 '''convert dict or key-value pairs to appropriate dict format'''
169 '''convert dict or key-value pairs to appropriate dict format'''
170 return self._converter.formatdict(data, key, value, fmt, sep)
170 return self._converter.formatdict(data, key, value, fmt, sep)
171 def formatlist(self, data, name, fmt='%s', sep=' '):
171 def formatlist(self, data, name, fmt='%s', sep=' '):
172 '''convert iterable to appropriate list format'''
172 '''convert iterable to appropriate list format'''
173 # name is mandatory argument for now, but it could be optional if
173 # name is mandatory argument for now, but it could be optional if
174 # we have default template keyword, e.g. {item}
174 # we have default template keyword, e.g. {item}
175 return self._converter.formatlist(data, name, fmt, sep)
175 return self._converter.formatlist(data, name, fmt, sep)
176 def context(self, **ctxs):
176 def context(self, **ctxs):
177 '''insert context objects to be used to render template keywords'''
177 '''insert context objects to be used to render template keywords'''
178 pass
178 pass
179 def data(self, **data):
179 def data(self, **data):
180 '''insert data into item that's not shown in default output'''
180 '''insert data into item that's not shown in default output'''
181 self._item.update(data)
181 self._item.update(data)
182 def write(self, fields, deftext, *fielddata, **opts):
182 def write(self, fields, deftext, *fielddata, **opts):
183 '''do default text output while assigning data to item'''
183 '''do default text output while assigning data to item'''
184 fieldkeys = fields.split()
184 fieldkeys = fields.split()
185 assert len(fieldkeys) == len(fielddata)
185 assert len(fieldkeys) == len(fielddata)
186 self._item.update(zip(fieldkeys, fielddata))
186 self._item.update(zip(fieldkeys, fielddata))
187 def condwrite(self, cond, fields, deftext, *fielddata, **opts):
187 def condwrite(self, cond, fields, deftext, *fielddata, **opts):
188 '''do conditional write (primarily for plain formatter)'''
188 '''do conditional write (primarily for plain formatter)'''
189 fieldkeys = fields.split()
189 fieldkeys = fields.split()
190 assert len(fieldkeys) == len(fielddata)
190 assert len(fieldkeys) == len(fielddata)
191 self._item.update(zip(fieldkeys, fielddata))
191 self._item.update(zip(fieldkeys, fielddata))
192 def plain(self, text, **opts):
192 def plain(self, text, **opts):
193 '''show raw text for non-templated mode'''
193 '''show raw text for non-templated mode'''
194 pass
194 pass
195 def isplain(self):
195 def isplain(self):
196 '''check for plain formatter usage'''
196 '''check for plain formatter usage'''
197 return False
197 return False
198 def nested(self, field):
198 def nested(self, field):
199 '''sub formatter to store nested data in the specified field'''
199 '''sub formatter to store nested data in the specified field'''
200 self._item[field] = data = []
200 self._item[field] = data = []
201 return _nestedformatter(self._ui, self._converter, data)
201 return _nestedformatter(self._ui, self._converter, data)
202 def end(self):
202 def end(self):
203 '''end output for the formatter'''
203 '''end output for the formatter'''
204 if self._item is not None:
204 if self._item is not None:
205 self._showitem()
205 self._showitem()
206
206
207 class _nestedformatter(baseformatter):
207 class _nestedformatter(baseformatter):
208 '''build sub items and store them in the parent formatter'''
208 '''build sub items and store them in the parent formatter'''
209 def __init__(self, ui, converter, data):
209 def __init__(self, ui, converter, data):
210 baseformatter.__init__(self, ui, topic='', opts={}, converter=converter)
210 baseformatter.__init__(self, ui, topic='', opts={}, converter=converter)
211 self._data = data
211 self._data = data
212 def _showitem(self):
212 def _showitem(self):
213 self._data.append(self._item)
213 self._data.append(self._item)
214
214
215 def _iteritems(data):
215 def _iteritems(data):
216 '''iterate key-value pairs in stable order'''
216 '''iterate key-value pairs in stable order'''
217 if isinstance(data, dict):
217 if isinstance(data, dict):
218 return sorted(data.iteritems())
218 return sorted(data.iteritems())
219 return data
219 return data
220
220
221 class _plainconverter(object):
221 class _plainconverter(object):
222 '''convert non-primitive data types to text'''
222 '''convert non-primitive data types to text'''
223 @staticmethod
223 @staticmethod
224 def formatdate(date, fmt):
224 def formatdate(date, fmt):
225 '''stringify date tuple in the given format'''
225 '''stringify date tuple in the given format'''
226 return util.datestr(date, fmt)
226 return util.datestr(date, fmt)
227 @staticmethod
227 @staticmethod
228 def formatdict(data, key, value, fmt, sep):
228 def formatdict(data, key, value, fmt, sep):
229 '''stringify key-value pairs separated by sep'''
229 '''stringify key-value pairs separated by sep'''
230 return sep.join(fmt % (k, v) for k, v in _iteritems(data))
230 return sep.join(fmt % (k, v) for k, v in _iteritems(data))
231 @staticmethod
231 @staticmethod
232 def formatlist(data, name, fmt, sep):
232 def formatlist(data, name, fmt, sep):
233 '''stringify iterable separated by sep'''
233 '''stringify iterable separated by sep'''
234 return sep.join(fmt % e for e in data)
234 return sep.join(fmt % e for e in data)
235
235
236 class plainformatter(baseformatter):
236 class plainformatter(baseformatter):
237 '''the default text output scheme'''
237 '''the default text output scheme'''
238 def __init__(self, ui, topic, opts):
238 def __init__(self, ui, topic, opts):
239 baseformatter.__init__(self, ui, topic, opts, _plainconverter)
239 baseformatter.__init__(self, ui, topic, opts, _plainconverter)
240 if ui.debugflag:
240 if ui.debugflag:
241 self.hexfunc = hex
241 self.hexfunc = hex
242 else:
242 else:
243 self.hexfunc = short
243 self.hexfunc = short
244 def startitem(self):
244 def startitem(self):
245 pass
245 pass
246 def data(self, **data):
246 def data(self, **data):
247 pass
247 pass
248 def write(self, fields, deftext, *fielddata, **opts):
248 def write(self, fields, deftext, *fielddata, **opts):
249 self._ui.write(deftext % fielddata, **opts)
249 self._ui.write(deftext % fielddata, **opts)
250 def condwrite(self, cond, fields, deftext, *fielddata, **opts):
250 def condwrite(self, cond, fields, deftext, *fielddata, **opts):
251 '''do conditional write'''
251 '''do conditional write'''
252 if cond:
252 if cond:
253 self._ui.write(deftext % fielddata, **opts)
253 self._ui.write(deftext % fielddata, **opts)
254 def plain(self, text, **opts):
254 def plain(self, text, **opts):
255 self._ui.write(text, **opts)
255 self._ui.write(text, **opts)
256 def isplain(self):
256 def isplain(self):
257 return True
257 return True
258 def nested(self, field):
258 def nested(self, field):
259 # nested data will be directly written to ui
259 # nested data will be directly written to ui
260 return self
260 return self
261 def end(self):
261 def end(self):
262 pass
262 pass
263
263
264 class debugformatter(baseformatter):
264 class debugformatter(baseformatter):
265 def __init__(self, ui, out, topic, opts):
265 def __init__(self, ui, out, topic, opts):
266 baseformatter.__init__(self, ui, topic, opts, _nullconverter)
266 baseformatter.__init__(self, ui, topic, opts, _nullconverter)
267 self._out = out
267 self._out = out
268 self._out.write("%s = [\n" % self._topic)
268 self._out.write("%s = [\n" % self._topic)
269 def _showitem(self):
269 def _showitem(self):
270 self._out.write(" " + repr(self._item) + ",\n")
270 self._out.write(" " + repr(self._item) + ",\n")
271 def end(self):
271 def end(self):
272 baseformatter.end(self)
272 baseformatter.end(self)
273 self._out.write("]\n")
273 self._out.write("]\n")
274
274
275 class pickleformatter(baseformatter):
275 class pickleformatter(baseformatter):
276 def __init__(self, ui, out, topic, opts):
276 def __init__(self, ui, out, topic, opts):
277 baseformatter.__init__(self, ui, topic, opts, _nullconverter)
277 baseformatter.__init__(self, ui, topic, opts, _nullconverter)
278 self._out = out
278 self._out = out
279 self._data = []
279 self._data = []
280 def _showitem(self):
280 def _showitem(self):
281 self._data.append(self._item)
281 self._data.append(self._item)
282 def end(self):
282 def end(self):
283 baseformatter.end(self)
283 baseformatter.end(self)
284 self._out.write(pickle.dumps(self._data))
284 self._out.write(pickle.dumps(self._data))
285
285
286 class jsonformatter(baseformatter):
286 class jsonformatter(baseformatter):
287 def __init__(self, ui, out, topic, opts):
287 def __init__(self, ui, out, topic, opts):
288 baseformatter.__init__(self, ui, topic, opts, _nullconverter)
288 baseformatter.__init__(self, ui, topic, opts, _nullconverter)
289 self._out = out
289 self._out = out
290 self._out.write("[")
290 self._out.write("[")
291 self._first = True
291 self._first = True
292 def _showitem(self):
292 def _showitem(self):
293 if self._first:
293 if self._first:
294 self._first = False
294 self._first = False
295 else:
295 else:
296 self._out.write(",")
296 self._out.write(",")
297
297
298 self._out.write("\n {\n")
298 self._out.write("\n {\n")
299 first = True
299 first = True
300 for k, v in sorted(self._item.items()):
300 for k, v in sorted(self._item.items()):
301 if first:
301 if first:
302 first = False
302 first = False
303 else:
303 else:
304 self._out.write(",\n")
304 self._out.write(",\n")
305 u = templatefilters.json(v, paranoid=False)
305 u = templatefilters.json(v, paranoid=False)
306 self._out.write(' "%s": %s' % (k, u))
306 self._out.write(' "%s": %s' % (k, u))
307 self._out.write("\n }")
307 self._out.write("\n }")
308 def end(self):
308 def end(self):
309 baseformatter.end(self)
309 baseformatter.end(self)
310 self._out.write("\n]\n")
310 self._out.write("\n]\n")
311
311
312 class _templateconverter(object):
312 class _templateconverter(object):
313 '''convert non-primitive data types to be processed by templater'''
313 '''convert non-primitive data types to be processed by templater'''
314 @staticmethod
314 @staticmethod
315 def formatdate(date, fmt):
315 def formatdate(date, fmt):
316 '''return date tuple'''
316 '''return date tuple'''
317 return date
317 return date
318 @staticmethod
318 @staticmethod
319 def formatdict(data, key, value, fmt, sep):
319 def formatdict(data, key, value, fmt, sep):
320 '''build object that can be evaluated as either plain string or dict'''
320 '''build object that can be evaluated as either plain string or dict'''
321 data = util.sortdict(_iteritems(data))
321 data = util.sortdict(_iteritems(data))
322 def f():
322 def f():
323 yield _plainconverter.formatdict(data, key, value, fmt, sep)
323 yield _plainconverter.formatdict(data, key, value, fmt, sep)
324 return templatekw._hybrid(f(), data, lambda k: {key: k, value: data[k]},
324 return templatekw.hybriddict(data, key=key, value=value, fmt=fmt,
325 lambda d: fmt % (d[key], d[value]))
325 gen=f())
326 @staticmethod
326 @staticmethod
327 def formatlist(data, name, fmt, sep):
327 def formatlist(data, name, fmt, sep):
328 '''build object that can be evaluated as either plain string or list'''
328 '''build object that can be evaluated as either plain string or list'''
329 data = list(data)
329 data = list(data)
330 def f():
330 def f():
331 yield _plainconverter.formatlist(data, name, fmt, sep)
331 yield _plainconverter.formatlist(data, name, fmt, sep)
332 return templatekw.hybridlist(data, name=name, fmt=fmt, gen=f())
332 return templatekw.hybridlist(data, name=name, fmt=fmt, gen=f())
333
333
334 class templateformatter(baseformatter):
334 class templateformatter(baseformatter):
335 def __init__(self, ui, out, topic, opts):
335 def __init__(self, ui, out, topic, opts):
336 baseformatter.__init__(self, ui, topic, opts, _templateconverter)
336 baseformatter.__init__(self, ui, topic, opts, _templateconverter)
337 self._out = out
337 self._out = out
338 self._topic = topic
338 self._topic = topic
339 self._t = gettemplater(ui, topic, opts.get('template', ''),
339 self._t = gettemplater(ui, topic, opts.get('template', ''),
340 cache=templatekw.defaulttempl)
340 cache=templatekw.defaulttempl)
341 self._counter = itertools.count()
341 self._counter = itertools.count()
342 self._cache = {} # for templatekw/funcs to store reusable data
342 self._cache = {} # for templatekw/funcs to store reusable data
343 def context(self, **ctxs):
343 def context(self, **ctxs):
344 '''insert context objects to be used to render template keywords'''
344 '''insert context objects to be used to render template keywords'''
345 assert all(k == 'ctx' for k in ctxs)
345 assert all(k == 'ctx' for k in ctxs)
346 self._item.update(ctxs)
346 self._item.update(ctxs)
347 def _showitem(self):
347 def _showitem(self):
348 # TODO: add support for filectx. probably each template keyword or
348 # TODO: add support for filectx. probably each template keyword or
349 # function will have to declare dependent resources. e.g.
349 # function will have to declare dependent resources. e.g.
350 # @templatekeyword(..., requires=('ctx',))
350 # @templatekeyword(..., requires=('ctx',))
351 props = {}
351 props = {}
352 if 'ctx' in self._item:
352 if 'ctx' in self._item:
353 props.update(templatekw.keywords)
353 props.update(templatekw.keywords)
354 props['index'] = next(self._counter)
354 props['index'] = next(self._counter)
355 # explicitly-defined fields precede templatekw
355 # explicitly-defined fields precede templatekw
356 props.update(self._item)
356 props.update(self._item)
357 if 'ctx' in self._item:
357 if 'ctx' in self._item:
358 # but template resources must be always available
358 # but template resources must be always available
359 props['templ'] = self._t
359 props['templ'] = self._t
360 props['repo'] = props['ctx'].repo()
360 props['repo'] = props['ctx'].repo()
361 props['revcache'] = {}
361 props['revcache'] = {}
362 g = self._t(self._topic, ui=self._ui, cache=self._cache, **props)
362 g = self._t(self._topic, ui=self._ui, cache=self._cache, **props)
363 self._out.write(templater.stringify(g))
363 self._out.write(templater.stringify(g))
364
364
365 def lookuptemplate(ui, topic, tmpl):
365 def lookuptemplate(ui, topic, tmpl):
366 # looks like a literal template?
366 # looks like a literal template?
367 if '{' in tmpl:
367 if '{' in tmpl:
368 return tmpl, None
368 return tmpl, None
369
369
370 # perhaps a stock style?
370 # perhaps a stock style?
371 if not os.path.split(tmpl)[0]:
371 if not os.path.split(tmpl)[0]:
372 mapname = (templater.templatepath('map-cmdline.' + tmpl)
372 mapname = (templater.templatepath('map-cmdline.' + tmpl)
373 or templater.templatepath(tmpl))
373 or templater.templatepath(tmpl))
374 if mapname and os.path.isfile(mapname):
374 if mapname and os.path.isfile(mapname):
375 return None, mapname
375 return None, mapname
376
376
377 # perhaps it's a reference to [templates]
377 # perhaps it's a reference to [templates]
378 t = ui.config('templates', tmpl)
378 t = ui.config('templates', tmpl)
379 if t:
379 if t:
380 return templater.unquotestring(t), None
380 return templater.unquotestring(t), None
381
381
382 if tmpl == 'list':
382 if tmpl == 'list':
383 ui.write(_("available styles: %s\n") % templater.stylelist())
383 ui.write(_("available styles: %s\n") % templater.stylelist())
384 raise error.Abort(_("specify a template"))
384 raise error.Abort(_("specify a template"))
385
385
386 # perhaps it's a path to a map or a template
386 # perhaps it's a path to a map or a template
387 if ('/' in tmpl or '\\' in tmpl) and os.path.isfile(tmpl):
387 if ('/' in tmpl or '\\' in tmpl) and os.path.isfile(tmpl):
388 # is it a mapfile for a style?
388 # is it a mapfile for a style?
389 if os.path.basename(tmpl).startswith("map-"):
389 if os.path.basename(tmpl).startswith("map-"):
390 return None, os.path.realpath(tmpl)
390 return None, os.path.realpath(tmpl)
391 tmpl = open(tmpl).read()
391 tmpl = open(tmpl).read()
392 return tmpl, None
392 return tmpl, None
393
393
394 # constant string?
394 # constant string?
395 return tmpl, None
395 return tmpl, None
396
396
397 def gettemplater(ui, topic, spec, cache=None):
397 def gettemplater(ui, topic, spec, cache=None):
398 tmpl, mapfile = lookuptemplate(ui, topic, spec)
398 tmpl, mapfile = lookuptemplate(ui, topic, spec)
399 assert not (tmpl and mapfile)
399 assert not (tmpl and mapfile)
400 if mapfile:
400 if mapfile:
401 return templater.templater.frommapfile(mapfile, cache=cache)
401 return templater.templater.frommapfile(mapfile, cache=cache)
402 return maketemplater(ui, topic, tmpl, cache=cache)
402 return maketemplater(ui, topic, tmpl, cache=cache)
403
403
404 def maketemplater(ui, topic, tmpl, cache=None):
404 def maketemplater(ui, topic, tmpl, cache=None):
405 """Create a templater from a string template 'tmpl'"""
405 """Create a templater from a string template 'tmpl'"""
406 aliases = ui.configitems('templatealias')
406 aliases = ui.configitems('templatealias')
407 t = templater.templater(cache=cache, aliases=aliases)
407 t = templater.templater(cache=cache, aliases=aliases)
408 if tmpl:
408 if tmpl:
409 t.cache[topic] = tmpl
409 t.cache[topic] = tmpl
410 return t
410 return t
411
411
412 def formatter(ui, topic, opts):
412 def formatter(ui, topic, opts):
413 template = opts.get("template", "")
413 template = opts.get("template", "")
414 if template == "json":
414 if template == "json":
415 return jsonformatter(ui, ui, topic, opts)
415 return jsonformatter(ui, ui, topic, opts)
416 elif template == "pickle":
416 elif template == "pickle":
417 return pickleformatter(ui, ui, topic, opts)
417 return pickleformatter(ui, ui, topic, opts)
418 elif template == "debug":
418 elif template == "debug":
419 return debugformatter(ui, ui, topic, opts)
419 return debugformatter(ui, ui, topic, opts)
420 elif template != "":
420 elif template != "":
421 return templateformatter(ui, ui, topic, opts)
421 return templateformatter(ui, ui, topic, opts)
422 # developer config: ui.formatdebug
422 # developer config: ui.formatdebug
423 elif ui.configbool('ui', 'formatdebug'):
423 elif ui.configbool('ui', 'formatdebug'):
424 return debugformatter(ui, ui, topic, opts)
424 return debugformatter(ui, ui, topic, opts)
425 # deprecated config: ui.formatjson
425 # deprecated config: ui.formatjson
426 elif ui.configbool('ui', 'formatjson'):
426 elif ui.configbool('ui', 'formatjson'):
427 return jsonformatter(ui, ui, topic, opts)
427 return jsonformatter(ui, ui, topic, opts)
428 return plainformatter(ui, topic, opts)
428 return plainformatter(ui, topic, opts)
@@ -1,672 +1,677 b''
1 # templatekw.py - common changeset template keywords
1 # templatekw.py - common changeset template keywords
2 #
2 #
3 # Copyright 2005-2009 Matt Mackall <mpm@selenic.com>
3 # Copyright 2005-2009 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 from .i18n import _
10 from .i18n import _
11 from .node import hex, nullid
11 from .node import hex, nullid
12 from . import (
12 from . import (
13 encoding,
13 encoding,
14 error,
14 error,
15 hbisect,
15 hbisect,
16 patch,
16 patch,
17 registrar,
17 registrar,
18 scmutil,
18 scmutil,
19 util,
19 util,
20 )
20 )
21
21
22 class _hybrid(object):
22 class _hybrid(object):
23 """Wrapper for list or dict to support legacy template
23 """Wrapper for list or dict to support legacy template
24
24
25 This class allows us to handle both:
25 This class allows us to handle both:
26 - "{files}" (legacy command-line-specific list hack) and
26 - "{files}" (legacy command-line-specific list hack) and
27 - "{files % '{file}\n'}" (hgweb-style with inlining and function support)
27 - "{files % '{file}\n'}" (hgweb-style with inlining and function support)
28 and to access raw values:
28 and to access raw values:
29 - "{ifcontains(file, files, ...)}", "{ifcontains(key, extras, ...)}"
29 - "{ifcontains(file, files, ...)}", "{ifcontains(key, extras, ...)}"
30 - "{get(extras, key)}"
30 - "{get(extras, key)}"
31 - "{files|json}"
31 - "{files|json}"
32 """
32 """
33
33
34 def __init__(self, gen, values, makemap, joinfmt):
34 def __init__(self, gen, values, makemap, joinfmt):
35 if gen is not None:
35 if gen is not None:
36 self.gen = gen
36 self.gen = gen
37 self._values = values
37 self._values = values
38 self._makemap = makemap
38 self._makemap = makemap
39 self.joinfmt = joinfmt
39 self.joinfmt = joinfmt
40 @util.propertycache
40 @util.propertycache
41 def gen(self):
41 def gen(self):
42 return self._defaultgen()
42 return self._defaultgen()
43 def _defaultgen(self):
43 def _defaultgen(self):
44 """Generator to stringify this as {join(self, ' ')}"""
44 """Generator to stringify this as {join(self, ' ')}"""
45 for i, d in enumerate(self.itermaps()):
45 for i, d in enumerate(self.itermaps()):
46 if i > 0:
46 if i > 0:
47 yield ' '
47 yield ' '
48 yield self.joinfmt(d)
48 yield self.joinfmt(d)
49 def itermaps(self):
49 def itermaps(self):
50 makemap = self._makemap
50 makemap = self._makemap
51 for x in self._values:
51 for x in self._values:
52 yield makemap(x)
52 yield makemap(x)
53 def __contains__(self, x):
53 def __contains__(self, x):
54 return x in self._values
54 return x in self._values
55 def __len__(self):
55 def __len__(self):
56 return len(self._values)
56 return len(self._values)
57 def __iter__(self):
57 def __iter__(self):
58 return iter(self._values)
58 return iter(self._values)
59 def __getattr__(self, name):
59 def __getattr__(self, name):
60 if name not in ('get', 'items', 'iteritems', 'iterkeys', 'itervalues',
60 if name not in ('get', 'items', 'iteritems', 'iterkeys', 'itervalues',
61 'keys', 'values'):
61 'keys', 'values'):
62 raise AttributeError(name)
62 raise AttributeError(name)
63 return getattr(self._values, name)
63 return getattr(self._values, name)
64
64
65 def hybriddict(data, key='key', value='value', fmt='%s=%s', gen=None):
66 """Wrap data to support both dict-like and string-like operations"""
67 return _hybrid(gen, data, lambda k: {key: k, value: data[k]},
68 lambda d: fmt % (d[key], d[value]))
69
65 def hybridlist(data, name, fmt='%s', gen=None):
70 def hybridlist(data, name, fmt='%s', gen=None):
66 """Wrap data to support both list-like and string-like operations"""
71 """Wrap data to support both list-like and string-like operations"""
67 return _hybrid(gen, data, lambda x: {name: x}, lambda d: fmt % d[name])
72 return _hybrid(gen, data, lambda x: {name: x}, lambda d: fmt % d[name])
68
73
69 def unwraphybrid(thing):
74 def unwraphybrid(thing):
70 """Return an object which can be stringified possibly by using a legacy
75 """Return an object which can be stringified possibly by using a legacy
71 template"""
76 template"""
72 if not util.safehasattr(thing, 'gen'):
77 if not util.safehasattr(thing, 'gen'):
73 return thing
78 return thing
74 return thing.gen
79 return thing.gen
75
80
76 def showlist(name, values, plural=None, element=None, separator=' ', **args):
81 def showlist(name, values, plural=None, element=None, separator=' ', **args):
77 if not element:
82 if not element:
78 element = name
83 element = name
79 f = _showlist(name, values, plural, separator, **args)
84 f = _showlist(name, values, plural, separator, **args)
80 return hybridlist(values, name=element, gen=f)
85 return hybridlist(values, name=element, gen=f)
81
86
82 def _showlist(name, values, plural=None, separator=' ', **args):
87 def _showlist(name, values, plural=None, separator=' ', **args):
83 '''expand set of values.
88 '''expand set of values.
84 name is name of key in template map.
89 name is name of key in template map.
85 values is list of strings or dicts.
90 values is list of strings or dicts.
86 plural is plural of name, if not simply name + 's'.
91 plural is plural of name, if not simply name + 's'.
87 separator is used to join values as a string
92 separator is used to join values as a string
88
93
89 expansion works like this, given name 'foo'.
94 expansion works like this, given name 'foo'.
90
95
91 if values is empty, expand 'no_foos'.
96 if values is empty, expand 'no_foos'.
92
97
93 if 'foo' not in template map, return values as a string,
98 if 'foo' not in template map, return values as a string,
94 joined by 'separator'.
99 joined by 'separator'.
95
100
96 expand 'start_foos'.
101 expand 'start_foos'.
97
102
98 for each value, expand 'foo'. if 'last_foo' in template
103 for each value, expand 'foo'. if 'last_foo' in template
99 map, expand it instead of 'foo' for last key.
104 map, expand it instead of 'foo' for last key.
100
105
101 expand 'end_foos'.
106 expand 'end_foos'.
102 '''
107 '''
103 templ = args['templ']
108 templ = args['templ']
104 if plural:
109 if plural:
105 names = plural
110 names = plural
106 else: names = name + 's'
111 else: names = name + 's'
107 if not values:
112 if not values:
108 noname = 'no_' + names
113 noname = 'no_' + names
109 if noname in templ:
114 if noname in templ:
110 yield templ(noname, **args)
115 yield templ(noname, **args)
111 return
116 return
112 if name not in templ:
117 if name not in templ:
113 if isinstance(values[0], str):
118 if isinstance(values[0], str):
114 yield separator.join(values)
119 yield separator.join(values)
115 else:
120 else:
116 for v in values:
121 for v in values:
117 yield dict(v, **args)
122 yield dict(v, **args)
118 return
123 return
119 startname = 'start_' + names
124 startname = 'start_' + names
120 if startname in templ:
125 if startname in templ:
121 yield templ(startname, **args)
126 yield templ(startname, **args)
122 vargs = args.copy()
127 vargs = args.copy()
123 def one(v, tag=name):
128 def one(v, tag=name):
124 try:
129 try:
125 vargs.update(v)
130 vargs.update(v)
126 except (AttributeError, ValueError):
131 except (AttributeError, ValueError):
127 try:
132 try:
128 for a, b in v:
133 for a, b in v:
129 vargs[a] = b
134 vargs[a] = b
130 except ValueError:
135 except ValueError:
131 vargs[name] = v
136 vargs[name] = v
132 return templ(tag, **vargs)
137 return templ(tag, **vargs)
133 lastname = 'last_' + name
138 lastname = 'last_' + name
134 if lastname in templ:
139 if lastname in templ:
135 last = values.pop()
140 last = values.pop()
136 else:
141 else:
137 last = None
142 last = None
138 for v in values:
143 for v in values:
139 yield one(v)
144 yield one(v)
140 if last is not None:
145 if last is not None:
141 yield one(last, tag=lastname)
146 yield one(last, tag=lastname)
142 endname = 'end_' + names
147 endname = 'end_' + names
143 if endname in templ:
148 if endname in templ:
144 yield templ(endname, **args)
149 yield templ(endname, **args)
145
150
146 def _formatrevnode(ctx):
151 def _formatrevnode(ctx):
147 """Format changeset as '{rev}:{node|formatnode}', which is the default
152 """Format changeset as '{rev}:{node|formatnode}', which is the default
148 template provided by cmdutil.changeset_templater"""
153 template provided by cmdutil.changeset_templater"""
149 repo = ctx.repo()
154 repo = ctx.repo()
150 if repo.ui.debugflag:
155 if repo.ui.debugflag:
151 hexnode = ctx.hex()
156 hexnode = ctx.hex()
152 else:
157 else:
153 hexnode = ctx.hex()[:12]
158 hexnode = ctx.hex()[:12]
154 return '%d:%s' % (scmutil.intrev(ctx.rev()), hexnode)
159 return '%d:%s' % (scmutil.intrev(ctx.rev()), hexnode)
155
160
156 def getfiles(repo, ctx, revcache):
161 def getfiles(repo, ctx, revcache):
157 if 'files' not in revcache:
162 if 'files' not in revcache:
158 revcache['files'] = repo.status(ctx.p1(), ctx)[:3]
163 revcache['files'] = repo.status(ctx.p1(), ctx)[:3]
159 return revcache['files']
164 return revcache['files']
160
165
161 def getlatesttags(repo, ctx, cache, pattern=None):
166 def getlatesttags(repo, ctx, cache, pattern=None):
162 '''return date, distance and name for the latest tag of rev'''
167 '''return date, distance and name for the latest tag of rev'''
163
168
164 cachename = 'latesttags'
169 cachename = 'latesttags'
165 if pattern is not None:
170 if pattern is not None:
166 cachename += '-' + pattern
171 cachename += '-' + pattern
167 match = util.stringmatcher(pattern)[2]
172 match = util.stringmatcher(pattern)[2]
168 else:
173 else:
169 match = util.always
174 match = util.always
170
175
171 if cachename not in cache:
176 if cachename not in cache:
172 # Cache mapping from rev to a tuple with tag date, tag
177 # Cache mapping from rev to a tuple with tag date, tag
173 # distance and tag name
178 # distance and tag name
174 cache[cachename] = {-1: (0, 0, ['null'])}
179 cache[cachename] = {-1: (0, 0, ['null'])}
175 latesttags = cache[cachename]
180 latesttags = cache[cachename]
176
181
177 rev = ctx.rev()
182 rev = ctx.rev()
178 todo = [rev]
183 todo = [rev]
179 while todo:
184 while todo:
180 rev = todo.pop()
185 rev = todo.pop()
181 if rev in latesttags:
186 if rev in latesttags:
182 continue
187 continue
183 ctx = repo[rev]
188 ctx = repo[rev]
184 tags = [t for t in ctx.tags()
189 tags = [t for t in ctx.tags()
185 if (repo.tagtype(t) and repo.tagtype(t) != 'local'
190 if (repo.tagtype(t) and repo.tagtype(t) != 'local'
186 and match(t))]
191 and match(t))]
187 if tags:
192 if tags:
188 latesttags[rev] = ctx.date()[0], 0, [t for t in sorted(tags)]
193 latesttags[rev] = ctx.date()[0], 0, [t for t in sorted(tags)]
189 continue
194 continue
190 try:
195 try:
191 # The tuples are laid out so the right one can be found by
196 # The tuples are laid out so the right one can be found by
192 # comparison.
197 # comparison.
193 pdate, pdist, ptag = max(
198 pdate, pdist, ptag = max(
194 latesttags[p.rev()] for p in ctx.parents())
199 latesttags[p.rev()] for p in ctx.parents())
195 except KeyError:
200 except KeyError:
196 # Cache miss - recurse
201 # Cache miss - recurse
197 todo.append(rev)
202 todo.append(rev)
198 todo.extend(p.rev() for p in ctx.parents())
203 todo.extend(p.rev() for p in ctx.parents())
199 continue
204 continue
200 latesttags[rev] = pdate, pdist + 1, ptag
205 latesttags[rev] = pdate, pdist + 1, ptag
201 return latesttags[rev]
206 return latesttags[rev]
202
207
203 def getrenamedfn(repo, endrev=None):
208 def getrenamedfn(repo, endrev=None):
204 rcache = {}
209 rcache = {}
205 if endrev is None:
210 if endrev is None:
206 endrev = len(repo)
211 endrev = len(repo)
207
212
208 def getrenamed(fn, rev):
213 def getrenamed(fn, rev):
209 '''looks up all renames for a file (up to endrev) the first
214 '''looks up all renames for a file (up to endrev) the first
210 time the file is given. It indexes on the changerev and only
215 time the file is given. It indexes on the changerev and only
211 parses the manifest if linkrev != changerev.
216 parses the manifest if linkrev != changerev.
212 Returns rename info for fn at changerev rev.'''
217 Returns rename info for fn at changerev rev.'''
213 if fn not in rcache:
218 if fn not in rcache:
214 rcache[fn] = {}
219 rcache[fn] = {}
215 fl = repo.file(fn)
220 fl = repo.file(fn)
216 for i in fl:
221 for i in fl:
217 lr = fl.linkrev(i)
222 lr = fl.linkrev(i)
218 renamed = fl.renamed(fl.node(i))
223 renamed = fl.renamed(fl.node(i))
219 rcache[fn][lr] = renamed
224 rcache[fn][lr] = renamed
220 if lr >= endrev:
225 if lr >= endrev:
221 break
226 break
222 if rev in rcache[fn]:
227 if rev in rcache[fn]:
223 return rcache[fn][rev]
228 return rcache[fn][rev]
224
229
225 # If linkrev != rev (i.e. rev not found in rcache) fallback to
230 # If linkrev != rev (i.e. rev not found in rcache) fallback to
226 # filectx logic.
231 # filectx logic.
227 try:
232 try:
228 return repo[rev][fn].renamed()
233 return repo[rev][fn].renamed()
229 except error.LookupError:
234 except error.LookupError:
230 return None
235 return None
231
236
232 return getrenamed
237 return getrenamed
233
238
234 # default templates internally used for rendering of lists
239 # default templates internally used for rendering of lists
235 defaulttempl = {
240 defaulttempl = {
236 'parent': '{rev}:{node|formatnode} ',
241 'parent': '{rev}:{node|formatnode} ',
237 'manifest': '{rev}:{node|formatnode}',
242 'manifest': '{rev}:{node|formatnode}',
238 'file_copy': '{name} ({source})',
243 'file_copy': '{name} ({source})',
239 'envvar': '{key}={value}',
244 'envvar': '{key}={value}',
240 'extra': '{key}={value|stringescape}'
245 'extra': '{key}={value|stringescape}'
241 }
246 }
242 # filecopy is preserved for compatibility reasons
247 # filecopy is preserved for compatibility reasons
243 defaulttempl['filecopy'] = defaulttempl['file_copy']
248 defaulttempl['filecopy'] = defaulttempl['file_copy']
244
249
245 # keywords are callables like:
250 # keywords are callables like:
246 # fn(repo, ctx, templ, cache, revcache, **args)
251 # fn(repo, ctx, templ, cache, revcache, **args)
247 # with:
252 # with:
248 # repo - current repository instance
253 # repo - current repository instance
249 # ctx - the changectx being displayed
254 # ctx - the changectx being displayed
250 # templ - the templater instance
255 # templ - the templater instance
251 # cache - a cache dictionary for the whole templater run
256 # cache - a cache dictionary for the whole templater run
252 # revcache - a cache dictionary for the current revision
257 # revcache - a cache dictionary for the current revision
253 keywords = {}
258 keywords = {}
254
259
255 templatekeyword = registrar.templatekeyword(keywords)
260 templatekeyword = registrar.templatekeyword(keywords)
256
261
257 @templatekeyword('author')
262 @templatekeyword('author')
258 def showauthor(repo, ctx, templ, **args):
263 def showauthor(repo, ctx, templ, **args):
259 """String. The unmodified author of the changeset."""
264 """String. The unmodified author of the changeset."""
260 return ctx.user()
265 return ctx.user()
261
266
262 @templatekeyword('bisect')
267 @templatekeyword('bisect')
263 def showbisect(repo, ctx, templ, **args):
268 def showbisect(repo, ctx, templ, **args):
264 """String. The changeset bisection status."""
269 """String. The changeset bisection status."""
265 return hbisect.label(repo, ctx.node())
270 return hbisect.label(repo, ctx.node())
266
271
267 @templatekeyword('branch')
272 @templatekeyword('branch')
268 def showbranch(**args):
273 def showbranch(**args):
269 """String. The name of the branch on which the changeset was
274 """String. The name of the branch on which the changeset was
270 committed.
275 committed.
271 """
276 """
272 return args['ctx'].branch()
277 return args['ctx'].branch()
273
278
274 @templatekeyword('branches')
279 @templatekeyword('branches')
275 def showbranches(**args):
280 def showbranches(**args):
276 """List of strings. The name of the branch on which the
281 """List of strings. The name of the branch on which the
277 changeset was committed. Will be empty if the branch name was
282 changeset was committed. Will be empty if the branch name was
278 default. (DEPRECATED)
283 default. (DEPRECATED)
279 """
284 """
280 branch = args['ctx'].branch()
285 branch = args['ctx'].branch()
281 if branch != 'default':
286 if branch != 'default':
282 return showlist('branch', [branch], plural='branches', **args)
287 return showlist('branch', [branch], plural='branches', **args)
283 return showlist('branch', [], plural='branches', **args)
288 return showlist('branch', [], plural='branches', **args)
284
289
285 @templatekeyword('bookmarks')
290 @templatekeyword('bookmarks')
286 def showbookmarks(**args):
291 def showbookmarks(**args):
287 """List of strings. Any bookmarks associated with the
292 """List of strings. Any bookmarks associated with the
288 changeset. Also sets 'active', the name of the active bookmark.
293 changeset. Also sets 'active', the name of the active bookmark.
289 """
294 """
290 repo = args['ctx']._repo
295 repo = args['ctx']._repo
291 bookmarks = args['ctx'].bookmarks()
296 bookmarks = args['ctx'].bookmarks()
292 active = repo._activebookmark
297 active = repo._activebookmark
293 makemap = lambda v: {'bookmark': v, 'active': active, 'current': active}
298 makemap = lambda v: {'bookmark': v, 'active': active, 'current': active}
294 f = _showlist('bookmark', bookmarks, **args)
299 f = _showlist('bookmark', bookmarks, **args)
295 return _hybrid(f, bookmarks, makemap, lambda x: x['bookmark'])
300 return _hybrid(f, bookmarks, makemap, lambda x: x['bookmark'])
296
301
297 @templatekeyword('children')
302 @templatekeyword('children')
298 def showchildren(**args):
303 def showchildren(**args):
299 """List of strings. The children of the changeset."""
304 """List of strings. The children of the changeset."""
300 ctx = args['ctx']
305 ctx = args['ctx']
301 childrevs = ['%d:%s' % (cctx, cctx) for cctx in ctx.children()]
306 childrevs = ['%d:%s' % (cctx, cctx) for cctx in ctx.children()]
302 return showlist('children', childrevs, element='child', **args)
307 return showlist('children', childrevs, element='child', **args)
303
308
304 # Deprecated, but kept alive for help generation a purpose.
309 # Deprecated, but kept alive for help generation a purpose.
305 @templatekeyword('currentbookmark')
310 @templatekeyword('currentbookmark')
306 def showcurrentbookmark(**args):
311 def showcurrentbookmark(**args):
307 """String. The active bookmark, if it is
312 """String. The active bookmark, if it is
308 associated with the changeset (DEPRECATED)"""
313 associated with the changeset (DEPRECATED)"""
309 return showactivebookmark(**args)
314 return showactivebookmark(**args)
310
315
311 @templatekeyword('activebookmark')
316 @templatekeyword('activebookmark')
312 def showactivebookmark(**args):
317 def showactivebookmark(**args):
313 """String. The active bookmark, if it is
318 """String. The active bookmark, if it is
314 associated with the changeset"""
319 associated with the changeset"""
315 active = args['repo']._activebookmark
320 active = args['repo']._activebookmark
316 if active and active in args['ctx'].bookmarks():
321 if active and active in args['ctx'].bookmarks():
317 return active
322 return active
318 return ''
323 return ''
319
324
320 @templatekeyword('date')
325 @templatekeyword('date')
321 def showdate(repo, ctx, templ, **args):
326 def showdate(repo, ctx, templ, **args):
322 """Date information. The date when the changeset was committed."""
327 """Date information. The date when the changeset was committed."""
323 return ctx.date()
328 return ctx.date()
324
329
325 @templatekeyword('desc')
330 @templatekeyword('desc')
326 def showdescription(repo, ctx, templ, **args):
331 def showdescription(repo, ctx, templ, **args):
327 """String. The text of the changeset description."""
332 """String. The text of the changeset description."""
328 s = ctx.description()
333 s = ctx.description()
329 if isinstance(s, encoding.localstr):
334 if isinstance(s, encoding.localstr):
330 # try hard to preserve utf-8 bytes
335 # try hard to preserve utf-8 bytes
331 return encoding.tolocal(encoding.fromlocal(s).strip())
336 return encoding.tolocal(encoding.fromlocal(s).strip())
332 else:
337 else:
333 return s.strip()
338 return s.strip()
334
339
335 @templatekeyword('diffstat')
340 @templatekeyword('diffstat')
336 def showdiffstat(repo, ctx, templ, **args):
341 def showdiffstat(repo, ctx, templ, **args):
337 """String. Statistics of changes with the following format:
342 """String. Statistics of changes with the following format:
338 "modified files: +added/-removed lines"
343 "modified files: +added/-removed lines"
339 """
344 """
340 stats = patch.diffstatdata(util.iterlines(ctx.diff(noprefix=False)))
345 stats = patch.diffstatdata(util.iterlines(ctx.diff(noprefix=False)))
341 maxname, maxtotal, adds, removes, binary = patch.diffstatsum(stats)
346 maxname, maxtotal, adds, removes, binary = patch.diffstatsum(stats)
342 return '%s: +%s/-%s' % (len(stats), adds, removes)
347 return '%s: +%s/-%s' % (len(stats), adds, removes)
343
348
344 @templatekeyword('envvars')
349 @templatekeyword('envvars')
345 def showenvvars(repo, **args):
350 def showenvvars(repo, **args):
346 """A dictionary of environment variables. (EXPERIMENTAL)"""
351 """A dictionary of environment variables. (EXPERIMENTAL)"""
347
352
348 env = repo.ui.exportableenviron()
353 env = repo.ui.exportableenviron()
349 env = util.sortdict((k, env[k]) for k in sorted(env))
354 env = util.sortdict((k, env[k]) for k in sorted(env))
350 makemap = lambda k: {'key': k, 'value': env[k]}
355 makemap = lambda k: {'key': k, 'value': env[k]}
351 c = [makemap(k) for k in env]
356 c = [makemap(k) for k in env]
352 f = _showlist('envvar', c, plural='envvars', **args)
357 f = _showlist('envvar', c, plural='envvars', **args)
353 return _hybrid(f, env, makemap,
358 return _hybrid(f, env, makemap,
354 lambda x: '%s=%s' % (x['key'], x['value']))
359 lambda x: '%s=%s' % (x['key'], x['value']))
355
360
356 @templatekeyword('extras')
361 @templatekeyword('extras')
357 def showextras(**args):
362 def showextras(**args):
358 """List of dicts with key, value entries of the 'extras'
363 """List of dicts with key, value entries of the 'extras'
359 field of this changeset."""
364 field of this changeset."""
360 extras = args['ctx'].extra()
365 extras = args['ctx'].extra()
361 extras = util.sortdict((k, extras[k]) for k in sorted(extras))
366 extras = util.sortdict((k, extras[k]) for k in sorted(extras))
362 makemap = lambda k: {'key': k, 'value': extras[k]}
367 makemap = lambda k: {'key': k, 'value': extras[k]}
363 c = [makemap(k) for k in extras]
368 c = [makemap(k) for k in extras]
364 f = _showlist('extra', c, plural='extras', **args)
369 f = _showlist('extra', c, plural='extras', **args)
365 return _hybrid(f, extras, makemap,
370 return _hybrid(f, extras, makemap,
366 lambda x: '%s=%s' % (x['key'], util.escapestr(x['value'])))
371 lambda x: '%s=%s' % (x['key'], util.escapestr(x['value'])))
367
372
368 @templatekeyword('file_adds')
373 @templatekeyword('file_adds')
369 def showfileadds(**args):
374 def showfileadds(**args):
370 """List of strings. Files added by this changeset."""
375 """List of strings. Files added by this changeset."""
371 repo, ctx, revcache = args['repo'], args['ctx'], args['revcache']
376 repo, ctx, revcache = args['repo'], args['ctx'], args['revcache']
372 return showlist('file_add', getfiles(repo, ctx, revcache)[1],
377 return showlist('file_add', getfiles(repo, ctx, revcache)[1],
373 element='file', **args)
378 element='file', **args)
374
379
375 @templatekeyword('file_copies')
380 @templatekeyword('file_copies')
376 def showfilecopies(**args):
381 def showfilecopies(**args):
377 """List of strings. Files copied in this changeset with
382 """List of strings. Files copied in this changeset with
378 their sources.
383 their sources.
379 """
384 """
380 cache, ctx = args['cache'], args['ctx']
385 cache, ctx = args['cache'], args['ctx']
381 copies = args['revcache'].get('copies')
386 copies = args['revcache'].get('copies')
382 if copies is None:
387 if copies is None:
383 if 'getrenamed' not in cache:
388 if 'getrenamed' not in cache:
384 cache['getrenamed'] = getrenamedfn(args['repo'])
389 cache['getrenamed'] = getrenamedfn(args['repo'])
385 copies = []
390 copies = []
386 getrenamed = cache['getrenamed']
391 getrenamed = cache['getrenamed']
387 for fn in ctx.files():
392 for fn in ctx.files():
388 rename = getrenamed(fn, ctx.rev())
393 rename = getrenamed(fn, ctx.rev())
389 if rename:
394 if rename:
390 copies.append((fn, rename[0]))
395 copies.append((fn, rename[0]))
391
396
392 copies = util.sortdict(copies)
397 copies = util.sortdict(copies)
393 makemap = lambda k: {'name': k, 'source': copies[k]}
398 makemap = lambda k: {'name': k, 'source': copies[k]}
394 c = [makemap(k) for k in copies]
399 c = [makemap(k) for k in copies]
395 f = _showlist('file_copy', c, plural='file_copies', **args)
400 f = _showlist('file_copy', c, plural='file_copies', **args)
396 return _hybrid(f, copies, makemap,
401 return _hybrid(f, copies, makemap,
397 lambda x: '%s (%s)' % (x['name'], x['source']))
402 lambda x: '%s (%s)' % (x['name'], x['source']))
398
403
399 # showfilecopiesswitch() displays file copies only if copy records are
404 # showfilecopiesswitch() displays file copies only if copy records are
400 # provided before calling the templater, usually with a --copies
405 # provided before calling the templater, usually with a --copies
401 # command line switch.
406 # command line switch.
402 @templatekeyword('file_copies_switch')
407 @templatekeyword('file_copies_switch')
403 def showfilecopiesswitch(**args):
408 def showfilecopiesswitch(**args):
404 """List of strings. Like "file_copies" but displayed
409 """List of strings. Like "file_copies" but displayed
405 only if the --copied switch is set.
410 only if the --copied switch is set.
406 """
411 """
407 copies = args['revcache'].get('copies') or []
412 copies = args['revcache'].get('copies') or []
408 copies = util.sortdict(copies)
413 copies = util.sortdict(copies)
409 makemap = lambda k: {'name': k, 'source': copies[k]}
414 makemap = lambda k: {'name': k, 'source': copies[k]}
410 c = [makemap(k) for k in copies]
415 c = [makemap(k) for k in copies]
411 f = _showlist('file_copy', c, plural='file_copies', **args)
416 f = _showlist('file_copy', c, plural='file_copies', **args)
412 return _hybrid(f, copies, makemap,
417 return _hybrid(f, copies, makemap,
413 lambda x: '%s (%s)' % (x['name'], x['source']))
418 lambda x: '%s (%s)' % (x['name'], x['source']))
414
419
415 @templatekeyword('file_dels')
420 @templatekeyword('file_dels')
416 def showfiledels(**args):
421 def showfiledels(**args):
417 """List of strings. Files removed by this changeset."""
422 """List of strings. Files removed by this changeset."""
418 repo, ctx, revcache = args['repo'], args['ctx'], args['revcache']
423 repo, ctx, revcache = args['repo'], args['ctx'], args['revcache']
419 return showlist('file_del', getfiles(repo, ctx, revcache)[2],
424 return showlist('file_del', getfiles(repo, ctx, revcache)[2],
420 element='file', **args)
425 element='file', **args)
421
426
422 @templatekeyword('file_mods')
427 @templatekeyword('file_mods')
423 def showfilemods(**args):
428 def showfilemods(**args):
424 """List of strings. Files modified by this changeset."""
429 """List of strings. Files modified by this changeset."""
425 repo, ctx, revcache = args['repo'], args['ctx'], args['revcache']
430 repo, ctx, revcache = args['repo'], args['ctx'], args['revcache']
426 return showlist('file_mod', getfiles(repo, ctx, revcache)[0],
431 return showlist('file_mod', getfiles(repo, ctx, revcache)[0],
427 element='file', **args)
432 element='file', **args)
428
433
429 @templatekeyword('files')
434 @templatekeyword('files')
430 def showfiles(**args):
435 def showfiles(**args):
431 """List of strings. All files modified, added, or removed by this
436 """List of strings. All files modified, added, or removed by this
432 changeset.
437 changeset.
433 """
438 """
434 return showlist('file', args['ctx'].files(), **args)
439 return showlist('file', args['ctx'].files(), **args)
435
440
436 @templatekeyword('graphnode')
441 @templatekeyword('graphnode')
437 def showgraphnode(repo, ctx, **args):
442 def showgraphnode(repo, ctx, **args):
438 """String. The character representing the changeset node in
443 """String. The character representing the changeset node in
439 an ASCII revision graph"""
444 an ASCII revision graph"""
440 wpnodes = repo.dirstate.parents()
445 wpnodes = repo.dirstate.parents()
441 if wpnodes[1] == nullid:
446 if wpnodes[1] == nullid:
442 wpnodes = wpnodes[:1]
447 wpnodes = wpnodes[:1]
443 if ctx.node() in wpnodes:
448 if ctx.node() in wpnodes:
444 return '@'
449 return '@'
445 elif ctx.obsolete():
450 elif ctx.obsolete():
446 return 'x'
451 return 'x'
447 elif ctx.closesbranch():
452 elif ctx.closesbranch():
448 return '_'
453 return '_'
449 else:
454 else:
450 return 'o'
455 return 'o'
451
456
452 @templatekeyword('index')
457 @templatekeyword('index')
453 def showindex(**args):
458 def showindex(**args):
454 """Integer. The current iteration of the loop. (0 indexed)"""
459 """Integer. The current iteration of the loop. (0 indexed)"""
455 # just hosts documentation; should be overridden by template mapping
460 # just hosts documentation; should be overridden by template mapping
456 raise error.Abort(_("can't use index in this context"))
461 raise error.Abort(_("can't use index in this context"))
457
462
458 @templatekeyword('latesttag')
463 @templatekeyword('latesttag')
459 def showlatesttag(**args):
464 def showlatesttag(**args):
460 """List of strings. The global tags on the most recent globally
465 """List of strings. The global tags on the most recent globally
461 tagged ancestor of this changeset. If no such tags exist, the list
466 tagged ancestor of this changeset. If no such tags exist, the list
462 consists of the single string "null".
467 consists of the single string "null".
463 """
468 """
464 return showlatesttags(None, **args)
469 return showlatesttags(None, **args)
465
470
466 def showlatesttags(pattern, **args):
471 def showlatesttags(pattern, **args):
467 """helper method for the latesttag keyword and function"""
472 """helper method for the latesttag keyword and function"""
468 repo, ctx = args['repo'], args['ctx']
473 repo, ctx = args['repo'], args['ctx']
469 cache = args['cache']
474 cache = args['cache']
470 latesttags = getlatesttags(repo, ctx, cache, pattern)
475 latesttags = getlatesttags(repo, ctx, cache, pattern)
471
476
472 # latesttag[0] is an implementation detail for sorting csets on different
477 # latesttag[0] is an implementation detail for sorting csets on different
473 # branches in a stable manner- it is the date the tagged cset was created,
478 # branches in a stable manner- it is the date the tagged cset was created,
474 # not the date the tag was created. Therefore it isn't made visible here.
479 # not the date the tag was created. Therefore it isn't made visible here.
475 makemap = lambda v: {
480 makemap = lambda v: {
476 'changes': _showchangessincetag,
481 'changes': _showchangessincetag,
477 'distance': latesttags[1],
482 'distance': latesttags[1],
478 'latesttag': v, # BC with {latesttag % '{latesttag}'}
483 'latesttag': v, # BC with {latesttag % '{latesttag}'}
479 'tag': v
484 'tag': v
480 }
485 }
481
486
482 tags = latesttags[2]
487 tags = latesttags[2]
483 f = _showlist('latesttag', tags, separator=':', **args)
488 f = _showlist('latesttag', tags, separator=':', **args)
484 return _hybrid(f, tags, makemap, lambda x: x['latesttag'])
489 return _hybrid(f, tags, makemap, lambda x: x['latesttag'])
485
490
486 @templatekeyword('latesttagdistance')
491 @templatekeyword('latesttagdistance')
487 def showlatesttagdistance(repo, ctx, templ, cache, **args):
492 def showlatesttagdistance(repo, ctx, templ, cache, **args):
488 """Integer. Longest path to the latest tag."""
493 """Integer. Longest path to the latest tag."""
489 return getlatesttags(repo, ctx, cache)[1]
494 return getlatesttags(repo, ctx, cache)[1]
490
495
491 @templatekeyword('changessincelatesttag')
496 @templatekeyword('changessincelatesttag')
492 def showchangessincelatesttag(repo, ctx, templ, cache, **args):
497 def showchangessincelatesttag(repo, ctx, templ, cache, **args):
493 """Integer. All ancestors not in the latest tag."""
498 """Integer. All ancestors not in the latest tag."""
494 latesttag = getlatesttags(repo, ctx, cache)[2][0]
499 latesttag = getlatesttags(repo, ctx, cache)[2][0]
495
500
496 return _showchangessincetag(repo, ctx, tag=latesttag, **args)
501 return _showchangessincetag(repo, ctx, tag=latesttag, **args)
497
502
498 def _showchangessincetag(repo, ctx, **args):
503 def _showchangessincetag(repo, ctx, **args):
499 offset = 0
504 offset = 0
500 revs = [ctx.rev()]
505 revs = [ctx.rev()]
501 tag = args['tag']
506 tag = args['tag']
502
507
503 # The only() revset doesn't currently support wdir()
508 # The only() revset doesn't currently support wdir()
504 if ctx.rev() is None:
509 if ctx.rev() is None:
505 offset = 1
510 offset = 1
506 revs = [p.rev() for p in ctx.parents()]
511 revs = [p.rev() for p in ctx.parents()]
507
512
508 return len(repo.revs('only(%ld, %s)', revs, tag)) + offset
513 return len(repo.revs('only(%ld, %s)', revs, tag)) + offset
509
514
510 @templatekeyword('manifest')
515 @templatekeyword('manifest')
511 def showmanifest(**args):
516 def showmanifest(**args):
512 repo, ctx, templ = args['repo'], args['ctx'], args['templ']
517 repo, ctx, templ = args['repo'], args['ctx'], args['templ']
513 mnode = ctx.manifestnode()
518 mnode = ctx.manifestnode()
514 if mnode is None:
519 if mnode is None:
515 # just avoid crash, we might want to use the 'ff...' hash in future
520 # just avoid crash, we might want to use the 'ff...' hash in future
516 return
521 return
517 args = args.copy()
522 args = args.copy()
518 args.update({'rev': repo.manifestlog._revlog.rev(mnode),
523 args.update({'rev': repo.manifestlog._revlog.rev(mnode),
519 'node': hex(mnode)})
524 'node': hex(mnode)})
520 return templ('manifest', **args)
525 return templ('manifest', **args)
521
526
522 def shownames(namespace, **args):
527 def shownames(namespace, **args):
523 """helper method to generate a template keyword for a namespace"""
528 """helper method to generate a template keyword for a namespace"""
524 ctx = args['ctx']
529 ctx = args['ctx']
525 repo = ctx.repo()
530 repo = ctx.repo()
526 ns = repo.names[namespace]
531 ns = repo.names[namespace]
527 names = ns.names(repo, ctx.node())
532 names = ns.names(repo, ctx.node())
528 return showlist(ns.templatename, names, plural=namespace, **args)
533 return showlist(ns.templatename, names, plural=namespace, **args)
529
534
530 @templatekeyword('namespaces')
535 @templatekeyword('namespaces')
531 def shownamespaces(**args):
536 def shownamespaces(**args):
532 """Dict of lists. Names attached to this changeset per
537 """Dict of lists. Names attached to this changeset per
533 namespace."""
538 namespace."""
534 ctx = args['ctx']
539 ctx = args['ctx']
535 repo = ctx.repo()
540 repo = ctx.repo()
536 namespaces = util.sortdict((k, showlist('name', ns.names(repo, ctx.node()),
541 namespaces = util.sortdict((k, showlist('name', ns.names(repo, ctx.node()),
537 **args))
542 **args))
538 for k, ns in repo.names.iteritems())
543 for k, ns in repo.names.iteritems())
539 f = _showlist('namespace', list(namespaces), **args)
544 f = _showlist('namespace', list(namespaces), **args)
540 return _hybrid(f, namespaces,
545 return _hybrid(f, namespaces,
541 lambda k: {'namespace': k, 'names': namespaces[k]},
546 lambda k: {'namespace': k, 'names': namespaces[k]},
542 lambda x: x['namespace'])
547 lambda x: x['namespace'])
543
548
544 @templatekeyword('node')
549 @templatekeyword('node')
545 def shownode(repo, ctx, templ, **args):
550 def shownode(repo, ctx, templ, **args):
546 """String. The changeset identification hash, as a 40 hexadecimal
551 """String. The changeset identification hash, as a 40 hexadecimal
547 digit string.
552 digit string.
548 """
553 """
549 return ctx.hex()
554 return ctx.hex()
550
555
551 @templatekeyword('obsolete')
556 @templatekeyword('obsolete')
552 def showobsolete(repo, ctx, templ, **args):
557 def showobsolete(repo, ctx, templ, **args):
553 """String. Whether the changeset is obsolete.
558 """String. Whether the changeset is obsolete.
554 """
559 """
555 if ctx.obsolete():
560 if ctx.obsolete():
556 return 'obsolete'
561 return 'obsolete'
557 return ''
562 return ''
558
563
559 @templatekeyword('p1rev')
564 @templatekeyword('p1rev')
560 def showp1rev(repo, ctx, templ, **args):
565 def showp1rev(repo, ctx, templ, **args):
561 """Integer. The repository-local revision number of the changeset's
566 """Integer. The repository-local revision number of the changeset's
562 first parent, or -1 if the changeset has no parents."""
567 first parent, or -1 if the changeset has no parents."""
563 return ctx.p1().rev()
568 return ctx.p1().rev()
564
569
565 @templatekeyword('p2rev')
570 @templatekeyword('p2rev')
566 def showp2rev(repo, ctx, templ, **args):
571 def showp2rev(repo, ctx, templ, **args):
567 """Integer. The repository-local revision number of the changeset's
572 """Integer. The repository-local revision number of the changeset's
568 second parent, or -1 if the changeset has no second parent."""
573 second parent, or -1 if the changeset has no second parent."""
569 return ctx.p2().rev()
574 return ctx.p2().rev()
570
575
571 @templatekeyword('p1node')
576 @templatekeyword('p1node')
572 def showp1node(repo, ctx, templ, **args):
577 def showp1node(repo, ctx, templ, **args):
573 """String. The identification hash of the changeset's first parent,
578 """String. The identification hash of the changeset's first parent,
574 as a 40 digit hexadecimal string. If the changeset has no parents, all
579 as a 40 digit hexadecimal string. If the changeset has no parents, all
575 digits are 0."""
580 digits are 0."""
576 return ctx.p1().hex()
581 return ctx.p1().hex()
577
582
578 @templatekeyword('p2node')
583 @templatekeyword('p2node')
579 def showp2node(repo, ctx, templ, **args):
584 def showp2node(repo, ctx, templ, **args):
580 """String. The identification hash of the changeset's second
585 """String. The identification hash of the changeset's second
581 parent, as a 40 digit hexadecimal string. If the changeset has no second
586 parent, as a 40 digit hexadecimal string. If the changeset has no second
582 parent, all digits are 0."""
587 parent, all digits are 0."""
583 return ctx.p2().hex()
588 return ctx.p2().hex()
584
589
585 @templatekeyword('parents')
590 @templatekeyword('parents')
586 def showparents(**args):
591 def showparents(**args):
587 """List of strings. The parents of the changeset in "rev:node"
592 """List of strings. The parents of the changeset in "rev:node"
588 format. If the changeset has only one "natural" parent (the predecessor
593 format. If the changeset has only one "natural" parent (the predecessor
589 revision) nothing is shown."""
594 revision) nothing is shown."""
590 repo = args['repo']
595 repo = args['repo']
591 ctx = args['ctx']
596 ctx = args['ctx']
592 pctxs = scmutil.meaningfulparents(repo, ctx)
597 pctxs = scmutil.meaningfulparents(repo, ctx)
593 prevs = [str(p.rev()) for p in pctxs] # ifcontains() needs a list of str
598 prevs = [str(p.rev()) for p in pctxs] # ifcontains() needs a list of str
594 parents = [[('rev', p.rev()),
599 parents = [[('rev', p.rev()),
595 ('node', p.hex()),
600 ('node', p.hex()),
596 ('phase', p.phasestr())]
601 ('phase', p.phasestr())]
597 for p in pctxs]
602 for p in pctxs]
598 f = _showlist('parent', parents, **args)
603 f = _showlist('parent', parents, **args)
599 return _hybrid(f, prevs, lambda x: {'ctx': repo[int(x)], 'revcache': {}},
604 return _hybrid(f, prevs, lambda x: {'ctx': repo[int(x)], 'revcache': {}},
600 lambda d: _formatrevnode(d['ctx']))
605 lambda d: _formatrevnode(d['ctx']))
601
606
602 @templatekeyword('phase')
607 @templatekeyword('phase')
603 def showphase(repo, ctx, templ, **args):
608 def showphase(repo, ctx, templ, **args):
604 """String. The changeset phase name."""
609 """String. The changeset phase name."""
605 return ctx.phasestr()
610 return ctx.phasestr()
606
611
607 @templatekeyword('phaseidx')
612 @templatekeyword('phaseidx')
608 def showphaseidx(repo, ctx, templ, **args):
613 def showphaseidx(repo, ctx, templ, **args):
609 """Integer. The changeset phase index."""
614 """Integer. The changeset phase index."""
610 return ctx.phase()
615 return ctx.phase()
611
616
612 @templatekeyword('rev')
617 @templatekeyword('rev')
613 def showrev(repo, ctx, templ, **args):
618 def showrev(repo, ctx, templ, **args):
614 """Integer. The repository-local changeset revision number."""
619 """Integer. The repository-local changeset revision number."""
615 return scmutil.intrev(ctx.rev())
620 return scmutil.intrev(ctx.rev())
616
621
617 def showrevslist(name, revs, **args):
622 def showrevslist(name, revs, **args):
618 """helper to generate a list of revisions in which a mapped template will
623 """helper to generate a list of revisions in which a mapped template will
619 be evaluated"""
624 be evaluated"""
620 repo = args['ctx'].repo()
625 repo = args['ctx'].repo()
621 revs = [str(r) for r in revs] # ifcontains() needs a list of str
626 revs = [str(r) for r in revs] # ifcontains() needs a list of str
622 f = _showlist(name, revs, **args)
627 f = _showlist(name, revs, **args)
623 return _hybrid(f, revs,
628 return _hybrid(f, revs,
624 lambda x: {name: x, 'ctx': repo[int(x)], 'revcache': {}},
629 lambda x: {name: x, 'ctx': repo[int(x)], 'revcache': {}},
625 lambda d: d[name])
630 lambda d: d[name])
626
631
627 @templatekeyword('subrepos')
632 @templatekeyword('subrepos')
628 def showsubrepos(**args):
633 def showsubrepos(**args):
629 """List of strings. Updated subrepositories in the changeset."""
634 """List of strings. Updated subrepositories in the changeset."""
630 ctx = args['ctx']
635 ctx = args['ctx']
631 substate = ctx.substate
636 substate = ctx.substate
632 if not substate:
637 if not substate:
633 return showlist('subrepo', [], **args)
638 return showlist('subrepo', [], **args)
634 psubstate = ctx.parents()[0].substate or {}
639 psubstate = ctx.parents()[0].substate or {}
635 subrepos = []
640 subrepos = []
636 for sub in substate:
641 for sub in substate:
637 if sub not in psubstate or substate[sub] != psubstate[sub]:
642 if sub not in psubstate or substate[sub] != psubstate[sub]:
638 subrepos.append(sub) # modified or newly added in ctx
643 subrepos.append(sub) # modified or newly added in ctx
639 for sub in psubstate:
644 for sub in psubstate:
640 if sub not in substate:
645 if sub not in substate:
641 subrepos.append(sub) # removed in ctx
646 subrepos.append(sub) # removed in ctx
642 return showlist('subrepo', sorted(subrepos), **args)
647 return showlist('subrepo', sorted(subrepos), **args)
643
648
644 # don't remove "showtags" definition, even though namespaces will put
649 # don't remove "showtags" definition, even though namespaces will put
645 # a helper function for "tags" keyword into "keywords" map automatically,
650 # a helper function for "tags" keyword into "keywords" map automatically,
646 # because online help text is built without namespaces initialization
651 # because online help text is built without namespaces initialization
647 @templatekeyword('tags')
652 @templatekeyword('tags')
648 def showtags(**args):
653 def showtags(**args):
649 """List of strings. Any tags associated with the changeset."""
654 """List of strings. Any tags associated with the changeset."""
650 return shownames('tags', **args)
655 return shownames('tags', **args)
651
656
652 def loadkeyword(ui, extname, registrarobj):
657 def loadkeyword(ui, extname, registrarobj):
653 """Load template keyword from specified registrarobj
658 """Load template keyword from specified registrarobj
654 """
659 """
655 for name, func in registrarobj._table.iteritems():
660 for name, func in registrarobj._table.iteritems():
656 keywords[name] = func
661 keywords[name] = func
657
662
658 @templatekeyword('termwidth')
663 @templatekeyword('termwidth')
659 def termwidth(repo, ctx, templ, **args):
664 def termwidth(repo, ctx, templ, **args):
660 """Integer. The width of the current terminal."""
665 """Integer. The width of the current terminal."""
661 return repo.ui.termwidth()
666 return repo.ui.termwidth()
662
667
663 @templatekeyword('troubles')
668 @templatekeyword('troubles')
664 def showtroubles(**args):
669 def showtroubles(**args):
665 """List of strings. Evolution troubles affecting the changeset.
670 """List of strings. Evolution troubles affecting the changeset.
666
671
667 (EXPERIMENTAL)
672 (EXPERIMENTAL)
668 """
673 """
669 return showlist('trouble', args['ctx'].troubles(), **args)
674 return showlist('trouble', args['ctx'].troubles(), **args)
670
675
671 # tell hggettext to extract docstrings from these functions:
676 # tell hggettext to extract docstrings from these functions:
672 i18nfunctions = keywords.values()
677 i18nfunctions = keywords.values()
General Comments 0
You need to be logged in to leave comments. Login now