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