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