##// END OF EJS Templates
formatter: extract helper function to render template
Yuya Nishihara -
r32948:12a0794f default
parent child Browse files
Show More
@@ -1,492 +1,497 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 collections
106 import collections
107 import contextlib
107 import contextlib
108 import itertools
108 import itertools
109 import os
109 import os
110
110
111 from .i18n import _
111 from .i18n import _
112 from .node import (
112 from .node import (
113 hex,
113 hex,
114 short,
114 short,
115 )
115 )
116
116
117 from . import (
117 from . import (
118 error,
118 error,
119 pycompat,
119 pycompat,
120 templatefilters,
120 templatefilters,
121 templatekw,
121 templatekw,
122 templater,
122 templater,
123 util,
123 util,
124 )
124 )
125
125
126 pickle = util.pickle
126 pickle = util.pickle
127
127
128 class _nullconverter(object):
128 class _nullconverter(object):
129 '''convert non-primitive data types to be processed by formatter'''
129 '''convert non-primitive data types to be processed by formatter'''
130 @staticmethod
130 @staticmethod
131 def formatdate(date, fmt):
131 def formatdate(date, fmt):
132 '''convert date tuple to appropriate format'''
132 '''convert date tuple to appropriate format'''
133 return date
133 return date
134 @staticmethod
134 @staticmethod
135 def formatdict(data, key, value, fmt, sep):
135 def formatdict(data, key, value, fmt, sep):
136 '''convert dict or key-value pairs to appropriate dict format'''
136 '''convert dict or key-value pairs to appropriate dict format'''
137 # use plain dict instead of util.sortdict so that data can be
137 # use plain dict instead of util.sortdict so that data can be
138 # serialized as a builtin dict in pickle output
138 # serialized as a builtin dict in pickle output
139 return dict(data)
139 return dict(data)
140 @staticmethod
140 @staticmethod
141 def formatlist(data, name, fmt, sep):
141 def formatlist(data, name, fmt, sep):
142 '''convert iterable to appropriate list format'''
142 '''convert iterable to appropriate list format'''
143 return list(data)
143 return list(data)
144
144
145 class baseformatter(object):
145 class baseformatter(object):
146 def __init__(self, ui, topic, opts, converter):
146 def __init__(self, ui, topic, opts, converter):
147 self._ui = ui
147 self._ui = ui
148 self._topic = topic
148 self._topic = topic
149 self._style = opts.get("style")
149 self._style = opts.get("style")
150 self._template = opts.get("template")
150 self._template = opts.get("template")
151 self._converter = converter
151 self._converter = converter
152 self._item = None
152 self._item = None
153 # function to convert node to string suitable for this output
153 # function to convert node to string suitable for this output
154 self.hexfunc = hex
154 self.hexfunc = hex
155 def __enter__(self):
155 def __enter__(self):
156 return self
156 return self
157 def __exit__(self, exctype, excvalue, traceback):
157 def __exit__(self, exctype, excvalue, traceback):
158 if exctype is None:
158 if exctype is None:
159 self.end()
159 self.end()
160 def _showitem(self):
160 def _showitem(self):
161 '''show a formatted item once all data is collected'''
161 '''show a formatted item once all data is collected'''
162 pass
162 pass
163 def startitem(self):
163 def startitem(self):
164 '''begin an item in the format list'''
164 '''begin an item in the format list'''
165 if self._item is not None:
165 if self._item is not None:
166 self._showitem()
166 self._showitem()
167 self._item = {}
167 self._item = {}
168 def formatdate(self, date, fmt='%a %b %d %H:%M:%S %Y %1%2'):
168 def formatdate(self, date, fmt='%a %b %d %H:%M:%S %Y %1%2'):
169 '''convert date tuple to appropriate format'''
169 '''convert date tuple to appropriate format'''
170 return self._converter.formatdate(date, fmt)
170 return self._converter.formatdate(date, fmt)
171 def formatdict(self, data, key='key', value='value', fmt='%s=%s', sep=' '):
171 def formatdict(self, data, key='key', value='value', fmt='%s=%s', sep=' '):
172 '''convert dict or key-value pairs to appropriate dict format'''
172 '''convert dict or key-value pairs to appropriate dict format'''
173 return self._converter.formatdict(data, key, value, fmt, sep)
173 return self._converter.formatdict(data, key, value, fmt, sep)
174 def formatlist(self, data, name, fmt='%s', sep=' '):
174 def formatlist(self, data, name, fmt='%s', sep=' '):
175 '''convert iterable to appropriate list format'''
175 '''convert iterable to appropriate list format'''
176 # name is mandatory argument for now, but it could be optional if
176 # name is mandatory argument for now, but it could be optional if
177 # we have default template keyword, e.g. {item}
177 # we have default template keyword, e.g. {item}
178 return self._converter.formatlist(data, name, fmt, sep)
178 return self._converter.formatlist(data, name, fmt, sep)
179 def context(self, **ctxs):
179 def context(self, **ctxs):
180 '''insert context objects to be used to render template keywords'''
180 '''insert context objects to be used to render template keywords'''
181 pass
181 pass
182 def data(self, **data):
182 def data(self, **data):
183 '''insert data into item that's not shown in default output'''
183 '''insert data into item that's not shown in default output'''
184 data = pycompat.byteskwargs(data)
184 data = pycompat.byteskwargs(data)
185 self._item.update(data)
185 self._item.update(data)
186 def write(self, fields, deftext, *fielddata, **opts):
186 def write(self, fields, deftext, *fielddata, **opts):
187 '''do default text output while assigning data to item'''
187 '''do default text output while assigning data to item'''
188 fieldkeys = fields.split()
188 fieldkeys = fields.split()
189 assert len(fieldkeys) == len(fielddata)
189 assert len(fieldkeys) == len(fielddata)
190 self._item.update(zip(fieldkeys, fielddata))
190 self._item.update(zip(fieldkeys, fielddata))
191 def condwrite(self, cond, fields, deftext, *fielddata, **opts):
191 def condwrite(self, cond, fields, deftext, *fielddata, **opts):
192 '''do conditional write (primarily for plain formatter)'''
192 '''do conditional write (primarily for plain formatter)'''
193 fieldkeys = fields.split()
193 fieldkeys = fields.split()
194 assert len(fieldkeys) == len(fielddata)
194 assert len(fieldkeys) == len(fielddata)
195 self._item.update(zip(fieldkeys, fielddata))
195 self._item.update(zip(fieldkeys, fielddata))
196 def plain(self, text, **opts):
196 def plain(self, text, **opts):
197 '''show raw text for non-templated mode'''
197 '''show raw text for non-templated mode'''
198 pass
198 pass
199 def isplain(self):
199 def isplain(self):
200 '''check for plain formatter usage'''
200 '''check for plain formatter usage'''
201 return False
201 return False
202 def nested(self, field):
202 def nested(self, field):
203 '''sub formatter to store nested data in the specified field'''
203 '''sub formatter to store nested data in the specified field'''
204 self._item[field] = data = []
204 self._item[field] = data = []
205 return _nestedformatter(self._ui, self._converter, data)
205 return _nestedformatter(self._ui, self._converter, data)
206 def end(self):
206 def end(self):
207 '''end output for the formatter'''
207 '''end output for the formatter'''
208 if self._item is not None:
208 if self._item is not None:
209 self._showitem()
209 self._showitem()
210
210
211 def nullformatter(ui, topic):
211 def nullformatter(ui, topic):
212 '''formatter that prints nothing'''
212 '''formatter that prints nothing'''
213 return baseformatter(ui, topic, opts={}, converter=_nullconverter)
213 return baseformatter(ui, topic, opts={}, converter=_nullconverter)
214
214
215 class _nestedformatter(baseformatter):
215 class _nestedformatter(baseformatter):
216 '''build sub items and store them in the parent formatter'''
216 '''build sub items and store them in the parent formatter'''
217 def __init__(self, ui, converter, data):
217 def __init__(self, ui, converter, data):
218 baseformatter.__init__(self, ui, topic='', opts={}, converter=converter)
218 baseformatter.__init__(self, ui, topic='', opts={}, converter=converter)
219 self._data = data
219 self._data = data
220 def _showitem(self):
220 def _showitem(self):
221 self._data.append(self._item)
221 self._data.append(self._item)
222
222
223 def _iteritems(data):
223 def _iteritems(data):
224 '''iterate key-value pairs in stable order'''
224 '''iterate key-value pairs in stable order'''
225 if isinstance(data, dict):
225 if isinstance(data, dict):
226 return sorted(data.iteritems())
226 return sorted(data.iteritems())
227 return data
227 return data
228
228
229 class _plainconverter(object):
229 class _plainconverter(object):
230 '''convert non-primitive data types to text'''
230 '''convert non-primitive data types to text'''
231 @staticmethod
231 @staticmethod
232 def formatdate(date, fmt):
232 def formatdate(date, fmt):
233 '''stringify date tuple in the given format'''
233 '''stringify date tuple in the given format'''
234 return util.datestr(date, fmt)
234 return util.datestr(date, fmt)
235 @staticmethod
235 @staticmethod
236 def formatdict(data, key, value, fmt, sep):
236 def formatdict(data, key, value, fmt, sep):
237 '''stringify key-value pairs separated by sep'''
237 '''stringify key-value pairs separated by sep'''
238 return sep.join(fmt % (k, v) for k, v in _iteritems(data))
238 return sep.join(fmt % (k, v) for k, v in _iteritems(data))
239 @staticmethod
239 @staticmethod
240 def formatlist(data, name, fmt, sep):
240 def formatlist(data, name, fmt, sep):
241 '''stringify iterable separated by sep'''
241 '''stringify iterable separated by sep'''
242 return sep.join(fmt % e for e in data)
242 return sep.join(fmt % e for e in data)
243
243
244 class plainformatter(baseformatter):
244 class plainformatter(baseformatter):
245 '''the default text output scheme'''
245 '''the default text output scheme'''
246 def __init__(self, ui, out, topic, opts):
246 def __init__(self, ui, out, topic, opts):
247 baseformatter.__init__(self, ui, topic, opts, _plainconverter)
247 baseformatter.__init__(self, ui, topic, opts, _plainconverter)
248 if ui.debugflag:
248 if ui.debugflag:
249 self.hexfunc = hex
249 self.hexfunc = hex
250 else:
250 else:
251 self.hexfunc = short
251 self.hexfunc = short
252 if ui is out:
252 if ui is out:
253 self._write = ui.write
253 self._write = ui.write
254 else:
254 else:
255 self._write = lambda s, **opts: out.write(s)
255 self._write = lambda s, **opts: out.write(s)
256 def startitem(self):
256 def startitem(self):
257 pass
257 pass
258 def data(self, **data):
258 def data(self, **data):
259 pass
259 pass
260 def write(self, fields, deftext, *fielddata, **opts):
260 def write(self, fields, deftext, *fielddata, **opts):
261 self._write(deftext % fielddata, **opts)
261 self._write(deftext % fielddata, **opts)
262 def condwrite(self, cond, fields, deftext, *fielddata, **opts):
262 def condwrite(self, cond, fields, deftext, *fielddata, **opts):
263 '''do conditional write'''
263 '''do conditional write'''
264 if cond:
264 if cond:
265 self._write(deftext % fielddata, **opts)
265 self._write(deftext % fielddata, **opts)
266 def plain(self, text, **opts):
266 def plain(self, text, **opts):
267 self._write(text, **opts)
267 self._write(text, **opts)
268 def isplain(self):
268 def isplain(self):
269 return True
269 return True
270 def nested(self, field):
270 def nested(self, field):
271 # nested data will be directly written to ui
271 # nested data will be directly written to ui
272 return self
272 return self
273 def end(self):
273 def end(self):
274 pass
274 pass
275
275
276 class debugformatter(baseformatter):
276 class debugformatter(baseformatter):
277 def __init__(self, ui, out, topic, opts):
277 def __init__(self, ui, out, topic, opts):
278 baseformatter.__init__(self, ui, topic, opts, _nullconverter)
278 baseformatter.__init__(self, ui, topic, opts, _nullconverter)
279 self._out = out
279 self._out = out
280 self._out.write("%s = [\n" % self._topic)
280 self._out.write("%s = [\n" % self._topic)
281 def _showitem(self):
281 def _showitem(self):
282 self._out.write(" " + repr(self._item) + ",\n")
282 self._out.write(" " + repr(self._item) + ",\n")
283 def end(self):
283 def end(self):
284 baseformatter.end(self)
284 baseformatter.end(self)
285 self._out.write("]\n")
285 self._out.write("]\n")
286
286
287 class pickleformatter(baseformatter):
287 class pickleformatter(baseformatter):
288 def __init__(self, ui, out, topic, opts):
288 def __init__(self, ui, out, topic, opts):
289 baseformatter.__init__(self, ui, topic, opts, _nullconverter)
289 baseformatter.__init__(self, ui, topic, opts, _nullconverter)
290 self._out = out
290 self._out = out
291 self._data = []
291 self._data = []
292 def _showitem(self):
292 def _showitem(self):
293 self._data.append(self._item)
293 self._data.append(self._item)
294 def end(self):
294 def end(self):
295 baseformatter.end(self)
295 baseformatter.end(self)
296 self._out.write(pickle.dumps(self._data))
296 self._out.write(pickle.dumps(self._data))
297
297
298 class jsonformatter(baseformatter):
298 class jsonformatter(baseformatter):
299 def __init__(self, ui, out, topic, opts):
299 def __init__(self, ui, out, topic, opts):
300 baseformatter.__init__(self, ui, topic, opts, _nullconverter)
300 baseformatter.__init__(self, ui, topic, opts, _nullconverter)
301 self._out = out
301 self._out = out
302 self._out.write("[")
302 self._out.write("[")
303 self._first = True
303 self._first = True
304 def _showitem(self):
304 def _showitem(self):
305 if self._first:
305 if self._first:
306 self._first = False
306 self._first = False
307 else:
307 else:
308 self._out.write(",")
308 self._out.write(",")
309
309
310 self._out.write("\n {\n")
310 self._out.write("\n {\n")
311 first = True
311 first = True
312 for k, v in sorted(self._item.items()):
312 for k, v in sorted(self._item.items()):
313 if first:
313 if first:
314 first = False
314 first = False
315 else:
315 else:
316 self._out.write(",\n")
316 self._out.write(",\n")
317 u = templatefilters.json(v, paranoid=False)
317 u = templatefilters.json(v, paranoid=False)
318 self._out.write(' "%s": %s' % (k, u))
318 self._out.write(' "%s": %s' % (k, u))
319 self._out.write("\n }")
319 self._out.write("\n }")
320 def end(self):
320 def end(self):
321 baseformatter.end(self)
321 baseformatter.end(self)
322 self._out.write("\n]\n")
322 self._out.write("\n]\n")
323
323
324 class _templateconverter(object):
324 class _templateconverter(object):
325 '''convert non-primitive data types to be processed by templater'''
325 '''convert non-primitive data types to be processed by templater'''
326 @staticmethod
326 @staticmethod
327 def formatdate(date, fmt):
327 def formatdate(date, fmt):
328 '''return date tuple'''
328 '''return date tuple'''
329 return date
329 return date
330 @staticmethod
330 @staticmethod
331 def formatdict(data, key, value, fmt, sep):
331 def formatdict(data, key, value, fmt, sep):
332 '''build object that can be evaluated as either plain string or dict'''
332 '''build object that can be evaluated as either plain string or dict'''
333 data = util.sortdict(_iteritems(data))
333 data = util.sortdict(_iteritems(data))
334 def f():
334 def f():
335 yield _plainconverter.formatdict(data, key, value, fmt, sep)
335 yield _plainconverter.formatdict(data, key, value, fmt, sep)
336 return templatekw.hybriddict(data, key=key, value=value, fmt=fmt,
336 return templatekw.hybriddict(data, key=key, value=value, fmt=fmt,
337 gen=f())
337 gen=f())
338 @staticmethod
338 @staticmethod
339 def formatlist(data, name, fmt, sep):
339 def formatlist(data, name, fmt, sep):
340 '''build object that can be evaluated as either plain string or list'''
340 '''build object that can be evaluated as either plain string or list'''
341 data = list(data)
341 data = list(data)
342 def f():
342 def f():
343 yield _plainconverter.formatlist(data, name, fmt, sep)
343 yield _plainconverter.formatlist(data, name, fmt, sep)
344 return templatekw.hybridlist(data, name=name, fmt=fmt, gen=f())
344 return templatekw.hybridlist(data, name=name, fmt=fmt, gen=f())
345
345
346 class templateformatter(baseformatter):
346 class templateformatter(baseformatter):
347 def __init__(self, ui, out, topic, opts):
347 def __init__(self, ui, out, topic, opts):
348 baseformatter.__init__(self, ui, topic, opts, _templateconverter)
348 baseformatter.__init__(self, ui, topic, opts, _templateconverter)
349 self._out = out
349 self._out = out
350 spec = lookuptemplate(ui, topic, opts.get('template', ''))
350 spec = lookuptemplate(ui, topic, opts.get('template', ''))
351 self._tref = spec.ref
351 self._tref = spec.ref
352 self._t = loadtemplater(ui, spec, cache=templatekw.defaulttempl)
352 self._t = loadtemplater(ui, spec, cache=templatekw.defaulttempl)
353 self._counter = itertools.count()
353 self._counter = itertools.count()
354 self._cache = {} # for templatekw/funcs to store reusable data
354 self._cache = {} # for templatekw/funcs to store reusable data
355 def context(self, **ctxs):
355 def context(self, **ctxs):
356 '''insert context objects to be used to render template keywords'''
356 '''insert context objects to be used to render template keywords'''
357 ctxs = pycompat.byteskwargs(ctxs)
357 ctxs = pycompat.byteskwargs(ctxs)
358 assert all(k == 'ctx' for k in ctxs)
358 assert all(k == 'ctx' for k in ctxs)
359 self._item.update(ctxs)
359 self._item.update(ctxs)
360
360 def _showitem(self):
361 def _showitem(self):
362 item = self._item.copy()
363 item['index'] = next(self._counter)
364 self._renderitem(self._tref, item)
365
366 def _renderitem(self, ref, item):
361 # TODO: add support for filectx. probably each template keyword or
367 # TODO: add support for filectx. probably each template keyword or
362 # function will have to declare dependent resources. e.g.
368 # function will have to declare dependent resources. e.g.
363 # @templatekeyword(..., requires=('ctx',))
369 # @templatekeyword(..., requires=('ctx',))
364 props = {}
370 props = {}
365 if 'ctx' in self._item:
371 if 'ctx' in item:
366 props.update(templatekw.keywords)
372 props.update(templatekw.keywords)
367 props['index'] = next(self._counter)
368 # explicitly-defined fields precede templatekw
373 # explicitly-defined fields precede templatekw
369 props.update(self._item)
374 props.update(item)
370 if 'ctx' in self._item:
375 if 'ctx' in item:
371 # but template resources must be always available
376 # but template resources must be always available
372 props['templ'] = self._t
377 props['templ'] = self._t
373 props['repo'] = props['ctx'].repo()
378 props['repo'] = props['ctx'].repo()
374 props['revcache'] = {}
379 props['revcache'] = {}
375 props = pycompat.strkwargs(props)
380 props = pycompat.strkwargs(props)
376 g = self._t(self._tref, ui=self._ui, cache=self._cache, **props)
381 g = self._t(ref, ui=self._ui, cache=self._cache, **props)
377 self._out.write(templater.stringify(g))
382 self._out.write(templater.stringify(g))
378
383
379 templatespec = collections.namedtuple(r'templatespec',
384 templatespec = collections.namedtuple(r'templatespec',
380 r'ref tmpl mapfile')
385 r'ref tmpl mapfile')
381
386
382 def lookuptemplate(ui, topic, tmpl):
387 def lookuptemplate(ui, topic, tmpl):
383 """Find the template matching the given -T/--template spec 'tmpl'
388 """Find the template matching the given -T/--template spec 'tmpl'
384
389
385 'tmpl' can be any of the following:
390 'tmpl' can be any of the following:
386
391
387 - a literal template (e.g. '{rev}')
392 - a literal template (e.g. '{rev}')
388 - a map-file name or path (e.g. 'changelog')
393 - a map-file name or path (e.g. 'changelog')
389 - a reference to [templates] in config file
394 - a reference to [templates] in config file
390 - a path to raw template file
395 - a path to raw template file
391
396
392 A map file defines a stand-alone template environment. If a map file
397 A map file defines a stand-alone template environment. If a map file
393 selected, all templates defined in the file will be loaded, and the
398 selected, all templates defined in the file will be loaded, and the
394 template matching the given topic will be rendered. No aliases will be
399 template matching the given topic will be rendered. No aliases will be
395 loaded from user config.
400 loaded from user config.
396
401
397 If no map file selected, all templates in [templates] section will be
402 If no map file selected, all templates in [templates] section will be
398 available as well as aliases in [templatealias].
403 available as well as aliases in [templatealias].
399 """
404 """
400
405
401 # looks like a literal template?
406 # looks like a literal template?
402 if '{' in tmpl:
407 if '{' in tmpl:
403 return templatespec('', tmpl, None)
408 return templatespec('', tmpl, None)
404
409
405 # perhaps a stock style?
410 # perhaps a stock style?
406 if not os.path.split(tmpl)[0]:
411 if not os.path.split(tmpl)[0]:
407 mapname = (templater.templatepath('map-cmdline.' + tmpl)
412 mapname = (templater.templatepath('map-cmdline.' + tmpl)
408 or templater.templatepath(tmpl))
413 or templater.templatepath(tmpl))
409 if mapname and os.path.isfile(mapname):
414 if mapname and os.path.isfile(mapname):
410 return templatespec(topic, None, mapname)
415 return templatespec(topic, None, mapname)
411
416
412 # perhaps it's a reference to [templates]
417 # perhaps it's a reference to [templates]
413 if ui.config('templates', tmpl):
418 if ui.config('templates', tmpl):
414 return templatespec(tmpl, None, None)
419 return templatespec(tmpl, None, None)
415
420
416 if tmpl == 'list':
421 if tmpl == 'list':
417 ui.write(_("available styles: %s\n") % templater.stylelist())
422 ui.write(_("available styles: %s\n") % templater.stylelist())
418 raise error.Abort(_("specify a template"))
423 raise error.Abort(_("specify a template"))
419
424
420 # perhaps it's a path to a map or a template
425 # perhaps it's a path to a map or a template
421 if ('/' in tmpl or '\\' in tmpl) and os.path.isfile(tmpl):
426 if ('/' in tmpl or '\\' in tmpl) and os.path.isfile(tmpl):
422 # is it a mapfile for a style?
427 # is it a mapfile for a style?
423 if os.path.basename(tmpl).startswith("map-"):
428 if os.path.basename(tmpl).startswith("map-"):
424 return templatespec(topic, None, os.path.realpath(tmpl))
429 return templatespec(topic, None, os.path.realpath(tmpl))
425 with util.posixfile(tmpl, 'rb') as f:
430 with util.posixfile(tmpl, 'rb') as f:
426 tmpl = f.read()
431 tmpl = f.read()
427 return templatespec('', tmpl, None)
432 return templatespec('', tmpl, None)
428
433
429 # constant string?
434 # constant string?
430 return templatespec('', tmpl, None)
435 return templatespec('', tmpl, None)
431
436
432 def loadtemplater(ui, spec, cache=None):
437 def loadtemplater(ui, spec, cache=None):
433 """Create a templater from either a literal template or loading from
438 """Create a templater from either a literal template or loading from
434 a map file"""
439 a map file"""
435 assert not (spec.tmpl and spec.mapfile)
440 assert not (spec.tmpl and spec.mapfile)
436 if spec.mapfile:
441 if spec.mapfile:
437 return templater.templater.frommapfile(spec.mapfile, cache=cache)
442 return templater.templater.frommapfile(spec.mapfile, cache=cache)
438 return maketemplater(ui, spec.tmpl, cache=cache)
443 return maketemplater(ui, spec.tmpl, cache=cache)
439
444
440 def maketemplater(ui, tmpl, cache=None):
445 def maketemplater(ui, tmpl, cache=None):
441 """Create a templater from a string template 'tmpl'"""
446 """Create a templater from a string template 'tmpl'"""
442 aliases = ui.configitems('templatealias')
447 aliases = ui.configitems('templatealias')
443 t = templater.templater(cache=cache, aliases=aliases)
448 t = templater.templater(cache=cache, aliases=aliases)
444 t.cache.update((k, templater.unquotestring(v))
449 t.cache.update((k, templater.unquotestring(v))
445 for k, v in ui.configitems('templates'))
450 for k, v in ui.configitems('templates'))
446 if tmpl:
451 if tmpl:
447 t.cache[''] = tmpl
452 t.cache[''] = tmpl
448 return t
453 return t
449
454
450 def formatter(ui, out, topic, opts):
455 def formatter(ui, out, topic, opts):
451 template = opts.get("template", "")
456 template = opts.get("template", "")
452 if template == "json":
457 if template == "json":
453 return jsonformatter(ui, out, topic, opts)
458 return jsonformatter(ui, out, topic, opts)
454 elif template == "pickle":
459 elif template == "pickle":
455 return pickleformatter(ui, out, topic, opts)
460 return pickleformatter(ui, out, topic, opts)
456 elif template == "debug":
461 elif template == "debug":
457 return debugformatter(ui, out, topic, opts)
462 return debugformatter(ui, out, topic, opts)
458 elif template != "":
463 elif template != "":
459 return templateformatter(ui, out, topic, opts)
464 return templateformatter(ui, out, topic, opts)
460 # developer config: ui.formatdebug
465 # developer config: ui.formatdebug
461 elif ui.configbool('ui', 'formatdebug'):
466 elif ui.configbool('ui', 'formatdebug'):
462 return debugformatter(ui, out, topic, opts)
467 return debugformatter(ui, out, topic, opts)
463 # deprecated config: ui.formatjson
468 # deprecated config: ui.formatjson
464 elif ui.configbool('ui', 'formatjson'):
469 elif ui.configbool('ui', 'formatjson'):
465 return jsonformatter(ui, out, topic, opts)
470 return jsonformatter(ui, out, topic, opts)
466 return plainformatter(ui, out, topic, opts)
471 return plainformatter(ui, out, topic, opts)
467
472
468 @contextlib.contextmanager
473 @contextlib.contextmanager
469 def openformatter(ui, filename, topic, opts):
474 def openformatter(ui, filename, topic, opts):
470 """Create a formatter that writes outputs to the specified file
475 """Create a formatter that writes outputs to the specified file
471
476
472 Must be invoked using the 'with' statement.
477 Must be invoked using the 'with' statement.
473 """
478 """
474 with util.posixfile(filename, 'wb') as out:
479 with util.posixfile(filename, 'wb') as out:
475 with formatter(ui, out, topic, opts) as fm:
480 with formatter(ui, out, topic, opts) as fm:
476 yield fm
481 yield fm
477
482
478 @contextlib.contextmanager
483 @contextlib.contextmanager
479 def _neverending(fm):
484 def _neverending(fm):
480 yield fm
485 yield fm
481
486
482 def maybereopen(fm, filename, opts):
487 def maybereopen(fm, filename, opts):
483 """Create a formatter backed by file if filename specified, else return
488 """Create a formatter backed by file if filename specified, else return
484 the given formatter
489 the given formatter
485
490
486 Must be invoked using the 'with' statement. This will never call fm.end()
491 Must be invoked using the 'with' statement. This will never call fm.end()
487 of the given formatter.
492 of the given formatter.
488 """
493 """
489 if filename:
494 if filename:
490 return openformatter(fm._ui, filename, fm._topic, opts)
495 return openformatter(fm._ui, filename, fm._topic, opts)
491 else:
496 else:
492 return _neverending(fm)
497 return _neverending(fm)
General Comments 0
You need to be logged in to leave comments. Login now