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