##// END OF EJS Templates
formatter: remove now-unnecessary check for file-ness...
Martin von Zweigbergk -
r45820:653b2a43 default
parent child Browse files
Show More
@@ -1,843 +1,843 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', tmpl=b'{reponame}'))
98 ... files(ui, fm.nested(b'files', tmpl=b'{reponame}'))
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 contextlib
110 import contextlib
111 import itertools
111 import itertools
112 import os
112 import os
113
113
114 from .i18n import _
114 from .i18n import _
115 from .node import (
115 from .node import (
116 hex,
116 hex,
117 short,
117 short,
118 )
118 )
119 from .thirdparty import attr
119 from .thirdparty import attr
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 templateutil,
127 templateutil,
128 util,
128 util,
129 )
129 )
130 from .utils import (
130 from .utils import (
131 cborutil,
131 cborutil,
132 dateutil,
132 dateutil,
133 stringutil,
133 stringutil,
134 )
134 )
135
135
136 pickle = util.pickle
136 pickle = util.pickle
137
137
138
138
139 def isprintable(obj):
139 def isprintable(obj):
140 """Check if the given object can be directly passed in to formatter's
140 """Check if the given object can be directly passed in to formatter's
141 write() and data() functions
141 write() and data() functions
142
142
143 Returns False if the object is unsupported or must be pre-processed by
143 Returns False if the object is unsupported or must be pre-processed by
144 formatdate(), formatdict(), or formatlist().
144 formatdate(), formatdict(), or formatlist().
145 """
145 """
146 return isinstance(obj, (type(None), bool, int, pycompat.long, float, bytes))
146 return isinstance(obj, (type(None), bool, int, pycompat.long, float, bytes))
147
147
148
148
149 class _nullconverter(object):
149 class _nullconverter(object):
150 '''convert non-primitive data types to be processed by formatter'''
150 '''convert non-primitive data types to be processed by formatter'''
151
151
152 # set to True if context object should be stored as item
152 # set to True if context object should be stored as item
153 storecontext = False
153 storecontext = False
154
154
155 @staticmethod
155 @staticmethod
156 def wrapnested(data, tmpl, sep):
156 def wrapnested(data, tmpl, sep):
157 '''wrap nested data by appropriate type'''
157 '''wrap nested data by appropriate type'''
158 return data
158 return data
159
159
160 @staticmethod
160 @staticmethod
161 def formatdate(date, fmt):
161 def formatdate(date, fmt):
162 '''convert date tuple to appropriate format'''
162 '''convert date tuple to appropriate format'''
163 # timestamp can be float, but the canonical form should be int
163 # timestamp can be float, but the canonical form should be int
164 ts, tz = date
164 ts, tz = date
165 return (int(ts), tz)
165 return (int(ts), tz)
166
166
167 @staticmethod
167 @staticmethod
168 def formatdict(data, key, value, fmt, sep):
168 def formatdict(data, key, value, fmt, sep):
169 '''convert dict or key-value pairs to appropriate dict format'''
169 '''convert dict or key-value pairs to appropriate dict format'''
170 # use plain dict instead of util.sortdict so that data can be
170 # use plain dict instead of util.sortdict so that data can be
171 # serialized as a builtin dict in pickle output
171 # serialized as a builtin dict in pickle output
172 return dict(data)
172 return dict(data)
173
173
174 @staticmethod
174 @staticmethod
175 def formatlist(data, name, fmt, sep):
175 def formatlist(data, name, fmt, sep):
176 '''convert iterable to appropriate list format'''
176 '''convert iterable to appropriate list format'''
177 return list(data)
177 return list(data)
178
178
179
179
180 class baseformatter(object):
180 class baseformatter(object):
181 def __init__(self, ui, topic, opts, converter):
181 def __init__(self, ui, topic, opts, converter):
182 self._ui = ui
182 self._ui = ui
183 self._topic = topic
183 self._topic = topic
184 self._opts = opts
184 self._opts = opts
185 self._converter = converter
185 self._converter = converter
186 self._item = None
186 self._item = None
187 # function to convert node to string suitable for this output
187 # function to convert node to string suitable for this output
188 self.hexfunc = hex
188 self.hexfunc = hex
189
189
190 def __enter__(self):
190 def __enter__(self):
191 return self
191 return self
192
192
193 def __exit__(self, exctype, excvalue, traceback):
193 def __exit__(self, exctype, excvalue, traceback):
194 if exctype is None:
194 if exctype is None:
195 self.end()
195 self.end()
196
196
197 def _showitem(self):
197 def _showitem(self):
198 '''show a formatted item once all data is collected'''
198 '''show a formatted item once all data is collected'''
199
199
200 def startitem(self):
200 def startitem(self):
201 '''begin an item in the format list'''
201 '''begin an item in the format list'''
202 if self._item is not None:
202 if self._item is not None:
203 self._showitem()
203 self._showitem()
204 self._item = {}
204 self._item = {}
205
205
206 def formatdate(self, date, fmt=b'%a %b %d %H:%M:%S %Y %1%2'):
206 def formatdate(self, date, fmt=b'%a %b %d %H:%M:%S %Y %1%2'):
207 '''convert date tuple to appropriate format'''
207 '''convert date tuple to appropriate format'''
208 return self._converter.formatdate(date, fmt)
208 return self._converter.formatdate(date, fmt)
209
209
210 def formatdict(self, data, key=b'key', value=b'value', fmt=None, sep=b' '):
210 def formatdict(self, data, key=b'key', value=b'value', fmt=None, sep=b' '):
211 '''convert dict or key-value pairs to appropriate dict format'''
211 '''convert dict or key-value pairs to appropriate dict format'''
212 return self._converter.formatdict(data, key, value, fmt, sep)
212 return self._converter.formatdict(data, key, value, fmt, sep)
213
213
214 def formatlist(self, data, name, fmt=None, sep=b' '):
214 def formatlist(self, data, name, fmt=None, sep=b' '):
215 '''convert iterable to appropriate list format'''
215 '''convert iterable to appropriate list format'''
216 # name is mandatory argument for now, but it could be optional if
216 # name is mandatory argument for now, but it could be optional if
217 # we have default template keyword, e.g. {item}
217 # we have default template keyword, e.g. {item}
218 return self._converter.formatlist(data, name, fmt, sep)
218 return self._converter.formatlist(data, name, fmt, sep)
219
219
220 def context(self, **ctxs):
220 def context(self, **ctxs):
221 '''insert context objects to be used to render template keywords'''
221 '''insert context objects to be used to render template keywords'''
222 ctxs = pycompat.byteskwargs(ctxs)
222 ctxs = pycompat.byteskwargs(ctxs)
223 assert all(k in {b'repo', b'ctx', b'fctx'} for k in ctxs)
223 assert all(k in {b'repo', b'ctx', b'fctx'} for k in ctxs)
224 if self._converter.storecontext:
224 if self._converter.storecontext:
225 # populate missing resources in fctx -> ctx -> repo order
225 # populate missing resources in fctx -> ctx -> repo order
226 if b'fctx' in ctxs and b'ctx' not in ctxs:
226 if b'fctx' in ctxs and b'ctx' not in ctxs:
227 ctxs[b'ctx'] = ctxs[b'fctx'].changectx()
227 ctxs[b'ctx'] = ctxs[b'fctx'].changectx()
228 if b'ctx' in ctxs and b'repo' not in ctxs:
228 if b'ctx' in ctxs and b'repo' not in ctxs:
229 ctxs[b'repo'] = ctxs[b'ctx'].repo()
229 ctxs[b'repo'] = ctxs[b'ctx'].repo()
230 self._item.update(ctxs)
230 self._item.update(ctxs)
231
231
232 def datahint(self):
232 def datahint(self):
233 '''set of field names to be referenced'''
233 '''set of field names to be referenced'''
234 return set()
234 return set()
235
235
236 def data(self, **data):
236 def data(self, **data):
237 '''insert data into item that's not shown in default output'''
237 '''insert data into item that's not shown in default output'''
238 data = pycompat.byteskwargs(data)
238 data = pycompat.byteskwargs(data)
239 self._item.update(data)
239 self._item.update(data)
240
240
241 def write(self, fields, deftext, *fielddata, **opts):
241 def write(self, fields, deftext, *fielddata, **opts):
242 '''do default text output while assigning data to item'''
242 '''do default text output while assigning data to item'''
243 fieldkeys = fields.split()
243 fieldkeys = fields.split()
244 assert len(fieldkeys) == len(fielddata), (fieldkeys, fielddata)
244 assert len(fieldkeys) == len(fielddata), (fieldkeys, fielddata)
245 self._item.update(zip(fieldkeys, fielddata))
245 self._item.update(zip(fieldkeys, fielddata))
246
246
247 def condwrite(self, cond, fields, deftext, *fielddata, **opts):
247 def condwrite(self, cond, fields, deftext, *fielddata, **opts):
248 '''do conditional write (primarily for plain formatter)'''
248 '''do conditional write (primarily for plain formatter)'''
249 fieldkeys = fields.split()
249 fieldkeys = fields.split()
250 assert len(fieldkeys) == len(fielddata)
250 assert len(fieldkeys) == len(fielddata)
251 self._item.update(zip(fieldkeys, fielddata))
251 self._item.update(zip(fieldkeys, fielddata))
252
252
253 def plain(self, text, **opts):
253 def plain(self, text, **opts):
254 '''show raw text for non-templated mode'''
254 '''show raw text for non-templated mode'''
255
255
256 def isplain(self):
256 def isplain(self):
257 '''check for plain formatter usage'''
257 '''check for plain formatter usage'''
258 return False
258 return False
259
259
260 def nested(self, field, tmpl=None, sep=b''):
260 def nested(self, field, tmpl=None, sep=b''):
261 '''sub formatter to store nested data in the specified field'''
261 '''sub formatter to store nested data in the specified field'''
262 data = []
262 data = []
263 self._item[field] = self._converter.wrapnested(data, tmpl, sep)
263 self._item[field] = self._converter.wrapnested(data, tmpl, sep)
264 return _nestedformatter(self._ui, self._converter, data)
264 return _nestedformatter(self._ui, self._converter, data)
265
265
266 def end(self):
266 def end(self):
267 '''end output for the formatter'''
267 '''end output for the formatter'''
268 if self._item is not None:
268 if self._item is not None:
269 self._showitem()
269 self._showitem()
270
270
271
271
272 def nullformatter(ui, topic, opts):
272 def nullformatter(ui, topic, opts):
273 '''formatter that prints nothing'''
273 '''formatter that prints nothing'''
274 return baseformatter(ui, topic, opts, converter=_nullconverter)
274 return baseformatter(ui, topic, opts, converter=_nullconverter)
275
275
276
276
277 class _nestedformatter(baseformatter):
277 class _nestedformatter(baseformatter):
278 '''build sub items and store them in the parent formatter'''
278 '''build sub items and store them in the parent formatter'''
279
279
280 def __init__(self, ui, converter, data):
280 def __init__(self, ui, converter, data):
281 baseformatter.__init__(
281 baseformatter.__init__(
282 self, ui, topic=b'', opts={}, converter=converter
282 self, ui, topic=b'', opts={}, converter=converter
283 )
283 )
284 self._data = data
284 self._data = data
285
285
286 def _showitem(self):
286 def _showitem(self):
287 self._data.append(self._item)
287 self._data.append(self._item)
288
288
289
289
290 def _iteritems(data):
290 def _iteritems(data):
291 '''iterate key-value pairs in stable order'''
291 '''iterate key-value pairs in stable order'''
292 if isinstance(data, dict):
292 if isinstance(data, dict):
293 return sorted(pycompat.iteritems(data))
293 return sorted(pycompat.iteritems(data))
294 return data
294 return data
295
295
296
296
297 class _plainconverter(object):
297 class _plainconverter(object):
298 '''convert non-primitive data types to text'''
298 '''convert non-primitive data types to text'''
299
299
300 storecontext = False
300 storecontext = False
301
301
302 @staticmethod
302 @staticmethod
303 def wrapnested(data, tmpl, sep):
303 def wrapnested(data, tmpl, sep):
304 raise error.ProgrammingError(b'plainformatter should never be nested')
304 raise error.ProgrammingError(b'plainformatter should never be nested')
305
305
306 @staticmethod
306 @staticmethod
307 def formatdate(date, fmt):
307 def formatdate(date, fmt):
308 '''stringify date tuple in the given format'''
308 '''stringify date tuple in the given format'''
309 return dateutil.datestr(date, fmt)
309 return dateutil.datestr(date, fmt)
310
310
311 @staticmethod
311 @staticmethod
312 def formatdict(data, key, value, fmt, sep):
312 def formatdict(data, key, value, fmt, sep):
313 '''stringify key-value pairs separated by sep'''
313 '''stringify key-value pairs separated by sep'''
314 prefmt = pycompat.identity
314 prefmt = pycompat.identity
315 if fmt is None:
315 if fmt is None:
316 fmt = b'%s=%s'
316 fmt = b'%s=%s'
317 prefmt = pycompat.bytestr
317 prefmt = pycompat.bytestr
318 return sep.join(
318 return sep.join(
319 fmt % (prefmt(k), prefmt(v)) for k, v in _iteritems(data)
319 fmt % (prefmt(k), prefmt(v)) for k, v in _iteritems(data)
320 )
320 )
321
321
322 @staticmethod
322 @staticmethod
323 def formatlist(data, name, fmt, sep):
323 def formatlist(data, name, fmt, sep):
324 '''stringify iterable separated by sep'''
324 '''stringify iterable separated by sep'''
325 prefmt = pycompat.identity
325 prefmt = pycompat.identity
326 if fmt is None:
326 if fmt is None:
327 fmt = b'%s'
327 fmt = b'%s'
328 prefmt = pycompat.bytestr
328 prefmt = pycompat.bytestr
329 return sep.join(fmt % prefmt(e) for e in data)
329 return sep.join(fmt % prefmt(e) for e in data)
330
330
331
331
332 class plainformatter(baseformatter):
332 class plainformatter(baseformatter):
333 '''the default text output scheme'''
333 '''the default text output scheme'''
334
334
335 def __init__(self, ui, out, topic, opts):
335 def __init__(self, ui, out, topic, opts):
336 baseformatter.__init__(self, ui, topic, opts, _plainconverter)
336 baseformatter.__init__(self, ui, topic, opts, _plainconverter)
337 if ui.debugflag:
337 if ui.debugflag:
338 self.hexfunc = hex
338 self.hexfunc = hex
339 else:
339 else:
340 self.hexfunc = short
340 self.hexfunc = short
341 if ui is out:
341 if ui is out:
342 self._write = ui.write
342 self._write = ui.write
343 else:
343 else:
344 self._write = lambda s, **opts: out.write(s)
344 self._write = lambda s, **opts: out.write(s)
345
345
346 def startitem(self):
346 def startitem(self):
347 pass
347 pass
348
348
349 def data(self, **data):
349 def data(self, **data):
350 pass
350 pass
351
351
352 def write(self, fields, deftext, *fielddata, **opts):
352 def write(self, fields, deftext, *fielddata, **opts):
353 self._write(deftext % fielddata, **opts)
353 self._write(deftext % fielddata, **opts)
354
354
355 def condwrite(self, cond, fields, deftext, *fielddata, **opts):
355 def condwrite(self, cond, fields, deftext, *fielddata, **opts):
356 '''do conditional write'''
356 '''do conditional write'''
357 if cond:
357 if cond:
358 self._write(deftext % fielddata, **opts)
358 self._write(deftext % fielddata, **opts)
359
359
360 def plain(self, text, **opts):
360 def plain(self, text, **opts):
361 self._write(text, **opts)
361 self._write(text, **opts)
362
362
363 def isplain(self):
363 def isplain(self):
364 return True
364 return True
365
365
366 def nested(self, field, tmpl=None, sep=b''):
366 def nested(self, field, tmpl=None, sep=b''):
367 # nested data will be directly written to ui
367 # nested data will be directly written to ui
368 return self
368 return self
369
369
370 def end(self):
370 def end(self):
371 pass
371 pass
372
372
373
373
374 class debugformatter(baseformatter):
374 class debugformatter(baseformatter):
375 def __init__(self, ui, out, topic, opts):
375 def __init__(self, ui, out, topic, opts):
376 baseformatter.__init__(self, ui, topic, opts, _nullconverter)
376 baseformatter.__init__(self, ui, topic, opts, _nullconverter)
377 self._out = out
377 self._out = out
378 self._out.write(b"%s = [\n" % self._topic)
378 self._out.write(b"%s = [\n" % self._topic)
379
379
380 def _showitem(self):
380 def _showitem(self):
381 self._out.write(
381 self._out.write(
382 b' %s,\n' % stringutil.pprint(self._item, indent=4, level=1)
382 b' %s,\n' % stringutil.pprint(self._item, indent=4, level=1)
383 )
383 )
384
384
385 def end(self):
385 def end(self):
386 baseformatter.end(self)
386 baseformatter.end(self)
387 self._out.write(b"]\n")
387 self._out.write(b"]\n")
388
388
389
389
390 class pickleformatter(baseformatter):
390 class pickleformatter(baseformatter):
391 def __init__(self, ui, out, topic, opts):
391 def __init__(self, ui, out, topic, opts):
392 baseformatter.__init__(self, ui, topic, opts, _nullconverter)
392 baseformatter.__init__(self, ui, topic, opts, _nullconverter)
393 self._out = out
393 self._out = out
394 self._data = []
394 self._data = []
395
395
396 def _showitem(self):
396 def _showitem(self):
397 self._data.append(self._item)
397 self._data.append(self._item)
398
398
399 def end(self):
399 def end(self):
400 baseformatter.end(self)
400 baseformatter.end(self)
401 self._out.write(pickle.dumps(self._data))
401 self._out.write(pickle.dumps(self._data))
402
402
403
403
404 class cborformatter(baseformatter):
404 class cborformatter(baseformatter):
405 '''serialize items as an indefinite-length CBOR array'''
405 '''serialize items as an indefinite-length CBOR array'''
406
406
407 def __init__(self, ui, out, topic, opts):
407 def __init__(self, ui, out, topic, opts):
408 baseformatter.__init__(self, ui, topic, opts, _nullconverter)
408 baseformatter.__init__(self, ui, topic, opts, _nullconverter)
409 self._out = out
409 self._out = out
410 self._out.write(cborutil.BEGIN_INDEFINITE_ARRAY)
410 self._out.write(cborutil.BEGIN_INDEFINITE_ARRAY)
411
411
412 def _showitem(self):
412 def _showitem(self):
413 self._out.write(b''.join(cborutil.streamencode(self._item)))
413 self._out.write(b''.join(cborutil.streamencode(self._item)))
414
414
415 def end(self):
415 def end(self):
416 baseformatter.end(self)
416 baseformatter.end(self)
417 self._out.write(cborutil.BREAK)
417 self._out.write(cborutil.BREAK)
418
418
419
419
420 class jsonformatter(baseformatter):
420 class jsonformatter(baseformatter):
421 def __init__(self, ui, out, topic, opts):
421 def __init__(self, ui, out, topic, opts):
422 baseformatter.__init__(self, ui, topic, opts, _nullconverter)
422 baseformatter.__init__(self, ui, topic, opts, _nullconverter)
423 self._out = out
423 self._out = out
424 self._out.write(b"[")
424 self._out.write(b"[")
425 self._first = True
425 self._first = True
426
426
427 def _showitem(self):
427 def _showitem(self):
428 if self._first:
428 if self._first:
429 self._first = False
429 self._first = False
430 else:
430 else:
431 self._out.write(b",")
431 self._out.write(b",")
432
432
433 self._out.write(b"\n {\n")
433 self._out.write(b"\n {\n")
434 first = True
434 first = True
435 for k, v in sorted(self._item.items()):
435 for k, v in sorted(self._item.items()):
436 if first:
436 if first:
437 first = False
437 first = False
438 else:
438 else:
439 self._out.write(b",\n")
439 self._out.write(b",\n")
440 u = templatefilters.json(v, paranoid=False)
440 u = templatefilters.json(v, paranoid=False)
441 self._out.write(b' "%s": %s' % (k, u))
441 self._out.write(b' "%s": %s' % (k, u))
442 self._out.write(b"\n }")
442 self._out.write(b"\n }")
443
443
444 def end(self):
444 def end(self):
445 baseformatter.end(self)
445 baseformatter.end(self)
446 self._out.write(b"\n]\n")
446 self._out.write(b"\n]\n")
447
447
448
448
449 class _templateconverter(object):
449 class _templateconverter(object):
450 '''convert non-primitive data types to be processed by templater'''
450 '''convert non-primitive data types to be processed by templater'''
451
451
452 storecontext = True
452 storecontext = True
453
453
454 @staticmethod
454 @staticmethod
455 def wrapnested(data, tmpl, sep):
455 def wrapnested(data, tmpl, sep):
456 '''wrap nested data by templatable type'''
456 '''wrap nested data by templatable type'''
457 return templateutil.mappinglist(data, tmpl=tmpl, sep=sep)
457 return templateutil.mappinglist(data, tmpl=tmpl, sep=sep)
458
458
459 @staticmethod
459 @staticmethod
460 def formatdate(date, fmt):
460 def formatdate(date, fmt):
461 '''return date tuple'''
461 '''return date tuple'''
462 return templateutil.date(date)
462 return templateutil.date(date)
463
463
464 @staticmethod
464 @staticmethod
465 def formatdict(data, key, value, fmt, sep):
465 def formatdict(data, key, value, fmt, sep):
466 '''build object that can be evaluated as either plain string or dict'''
466 '''build object that can be evaluated as either plain string or dict'''
467 data = util.sortdict(_iteritems(data))
467 data = util.sortdict(_iteritems(data))
468
468
469 def f():
469 def f():
470 yield _plainconverter.formatdict(data, key, value, fmt, sep)
470 yield _plainconverter.formatdict(data, key, value, fmt, sep)
471
471
472 return templateutil.hybriddict(
472 return templateutil.hybriddict(
473 data, key=key, value=value, fmt=fmt, gen=f
473 data, key=key, value=value, fmt=fmt, gen=f
474 )
474 )
475
475
476 @staticmethod
476 @staticmethod
477 def formatlist(data, name, fmt, sep):
477 def formatlist(data, name, fmt, sep):
478 '''build object that can be evaluated as either plain string or list'''
478 '''build object that can be evaluated as either plain string or list'''
479 data = list(data)
479 data = list(data)
480
480
481 def f():
481 def f():
482 yield _plainconverter.formatlist(data, name, fmt, sep)
482 yield _plainconverter.formatlist(data, name, fmt, sep)
483
483
484 return templateutil.hybridlist(data, name=name, fmt=fmt, gen=f)
484 return templateutil.hybridlist(data, name=name, fmt=fmt, gen=f)
485
485
486
486
487 class templateformatter(baseformatter):
487 class templateformatter(baseformatter):
488 def __init__(self, ui, out, topic, opts, spec, overridetemplates=None):
488 def __init__(self, ui, out, topic, opts, spec, overridetemplates=None):
489 baseformatter.__init__(self, ui, topic, opts, _templateconverter)
489 baseformatter.__init__(self, ui, topic, opts, _templateconverter)
490 self._out = out
490 self._out = out
491 self._tref = spec.ref
491 self._tref = spec.ref
492 self._t = loadtemplater(
492 self._t = loadtemplater(
493 ui,
493 ui,
494 spec,
494 spec,
495 defaults=templatekw.keywords,
495 defaults=templatekw.keywords,
496 resources=templateresources(ui),
496 resources=templateresources(ui),
497 cache=templatekw.defaulttempl,
497 cache=templatekw.defaulttempl,
498 )
498 )
499 if overridetemplates:
499 if overridetemplates:
500 self._t.cache.update(overridetemplates)
500 self._t.cache.update(overridetemplates)
501 self._parts = templatepartsmap(
501 self._parts = templatepartsmap(
502 spec, self._t, [b'docheader', b'docfooter', b'separator']
502 spec, self._t, [b'docheader', b'docfooter', b'separator']
503 )
503 )
504 self._counter = itertools.count()
504 self._counter = itertools.count()
505 self._renderitem(b'docheader', {})
505 self._renderitem(b'docheader', {})
506
506
507 def _showitem(self):
507 def _showitem(self):
508 item = self._item.copy()
508 item = self._item.copy()
509 item[b'index'] = index = next(self._counter)
509 item[b'index'] = index = next(self._counter)
510 if index > 0:
510 if index > 0:
511 self._renderitem(b'separator', {})
511 self._renderitem(b'separator', {})
512 self._renderitem(self._tref, item)
512 self._renderitem(self._tref, item)
513
513
514 def _renderitem(self, part, item):
514 def _renderitem(self, part, item):
515 if part not in self._parts:
515 if part not in self._parts:
516 return
516 return
517 ref = self._parts[part]
517 ref = self._parts[part]
518 # None can't be put in the mapping dict since it means <unset>
518 # None can't be put in the mapping dict since it means <unset>
519 for k, v in item.items():
519 for k, v in item.items():
520 if v is None:
520 if v is None:
521 item[k] = templateutil.wrappedvalue(v)
521 item[k] = templateutil.wrappedvalue(v)
522 self._out.write(self._t.render(ref, item))
522 self._out.write(self._t.render(ref, item))
523
523
524 @util.propertycache
524 @util.propertycache
525 def _symbolsused(self):
525 def _symbolsused(self):
526 return self._t.symbolsused(self._tref)
526 return self._t.symbolsused(self._tref)
527
527
528 def datahint(self):
528 def datahint(self):
529 '''set of field names to be referenced from the template'''
529 '''set of field names to be referenced from the template'''
530 return self._symbolsused[0]
530 return self._symbolsused[0]
531
531
532 def end(self):
532 def end(self):
533 baseformatter.end(self)
533 baseformatter.end(self)
534 self._renderitem(b'docfooter', {})
534 self._renderitem(b'docfooter', {})
535
535
536
536
537 @attr.s(frozen=True)
537 @attr.s(frozen=True)
538 class templatespec(object):
538 class templatespec(object):
539 ref = attr.ib()
539 ref = attr.ib()
540 tmpl = attr.ib()
540 tmpl = attr.ib()
541 mapfile = attr.ib()
541 mapfile = attr.ib()
542 refargs = attr.ib(default=None)
542 refargs = attr.ib(default=None)
543
543
544
544
545 def lookuptemplate(ui, topic, tmpl):
545 def lookuptemplate(ui, topic, tmpl):
546 """Find the template matching the given -T/--template spec 'tmpl'
546 """Find the template matching the given -T/--template spec 'tmpl'
547
547
548 'tmpl' can be any of the following:
548 'tmpl' can be any of the following:
549
549
550 - a literal template (e.g. '{rev}')
550 - a literal template (e.g. '{rev}')
551 - a reference to built-in template (i.e. formatter)
551 - a reference to built-in template (i.e. formatter)
552 - a map-file name or path (e.g. 'changelog')
552 - a map-file name or path (e.g. 'changelog')
553 - a reference to [templates] in config file
553 - a reference to [templates] in config file
554 - a path to raw template file
554 - a path to raw template file
555
555
556 A map file defines a stand-alone template environment. If a map file
556 A map file defines a stand-alone template environment. If a map file
557 selected, all templates defined in the file will be loaded, and the
557 selected, all templates defined in the file will be loaded, and the
558 template matching the given topic will be rendered. Aliases won't be
558 template matching the given topic will be rendered. Aliases won't be
559 loaded from user config, but from the map file.
559 loaded from user config, but from the map file.
560
560
561 If no map file selected, all templates in [templates] section will be
561 If no map file selected, all templates in [templates] section will be
562 available as well as aliases in [templatealias].
562 available as well as aliases in [templatealias].
563 """
563 """
564
564
565 if not tmpl:
565 if not tmpl:
566 return templatespec(None, None, None)
566 return templatespec(None, None, None)
567
567
568 # looks like a literal template?
568 # looks like a literal template?
569 if b'{' in tmpl:
569 if b'{' in tmpl:
570 return templatespec(b'', tmpl, None)
570 return templatespec(b'', tmpl, None)
571
571
572 # a reference to built-in (formatter) template
572 # a reference to built-in (formatter) template
573 if tmpl in {b'cbor', b'json', b'pickle', b'debug'}:
573 if tmpl in {b'cbor', b'json', b'pickle', b'debug'}:
574 return templatespec(tmpl, None, None)
574 return templatespec(tmpl, None, None)
575
575
576 # a function-style reference to built-in template
576 # a function-style reference to built-in template
577 func, fsep, ftail = tmpl.partition(b'(')
577 func, fsep, ftail = tmpl.partition(b'(')
578 if func in {b'cbor', b'json'} and fsep and ftail.endswith(b')'):
578 if func in {b'cbor', b'json'} and fsep and ftail.endswith(b')'):
579 templater.parseexpr(tmpl) # make sure syntax errors are confined
579 templater.parseexpr(tmpl) # make sure syntax errors are confined
580 return templatespec(func, None, None, refargs=ftail[:-1])
580 return templatespec(func, None, None, refargs=ftail[:-1])
581
581
582 # perhaps a stock style?
582 # perhaps a stock style?
583 if not os.path.split(tmpl)[0]:
583 if not os.path.split(tmpl)[0]:
584 mapname = templater.templatepath(
584 mapname = templater.templatepath(
585 b'map-cmdline.' + tmpl
585 b'map-cmdline.' + tmpl
586 ) or templater.templatepath(tmpl)
586 ) or templater.templatepath(tmpl)
587 if mapname and os.path.isfile(mapname):
587 if mapname:
588 return templatespec(topic, None, mapname)
588 return templatespec(topic, None, mapname)
589
589
590 # perhaps it's a reference to [templates]
590 # perhaps it's a reference to [templates]
591 if ui.config(b'templates', tmpl):
591 if ui.config(b'templates', tmpl):
592 return templatespec(tmpl, None, None)
592 return templatespec(tmpl, None, None)
593
593
594 if tmpl == b'list':
594 if tmpl == b'list':
595 ui.write(_(b"available styles: %s\n") % templater.stylelist())
595 ui.write(_(b"available styles: %s\n") % templater.stylelist())
596 raise error.Abort(_(b"specify a template"))
596 raise error.Abort(_(b"specify a template"))
597
597
598 # perhaps it's a path to a map or a template
598 # perhaps it's a path to a map or a template
599 if (b'/' in tmpl or b'\\' in tmpl) and os.path.isfile(tmpl):
599 if (b'/' in tmpl or b'\\' in tmpl) and os.path.isfile(tmpl):
600 # is it a mapfile for a style?
600 # is it a mapfile for a style?
601 if os.path.basename(tmpl).startswith(b"map-"):
601 if os.path.basename(tmpl).startswith(b"map-"):
602 return templatespec(topic, None, os.path.realpath(tmpl))
602 return templatespec(topic, None, os.path.realpath(tmpl))
603 with util.posixfile(tmpl, b'rb') as f:
603 with util.posixfile(tmpl, b'rb') as f:
604 tmpl = f.read()
604 tmpl = f.read()
605 return templatespec(b'', tmpl, None)
605 return templatespec(b'', tmpl, None)
606
606
607 # constant string?
607 # constant string?
608 return templatespec(b'', tmpl, None)
608 return templatespec(b'', tmpl, None)
609
609
610
610
611 def templatepartsmap(spec, t, partnames):
611 def templatepartsmap(spec, t, partnames):
612 """Create a mapping of {part: ref}"""
612 """Create a mapping of {part: ref}"""
613 partsmap = {spec.ref: spec.ref} # initial ref must exist in t
613 partsmap = {spec.ref: spec.ref} # initial ref must exist in t
614 if spec.mapfile:
614 if spec.mapfile:
615 partsmap.update((p, p) for p in partnames if p in t)
615 partsmap.update((p, p) for p in partnames if p in t)
616 elif spec.ref:
616 elif spec.ref:
617 for part in partnames:
617 for part in partnames:
618 ref = b'%s:%s' % (spec.ref, part) # select config sub-section
618 ref = b'%s:%s' % (spec.ref, part) # select config sub-section
619 if ref in t:
619 if ref in t:
620 partsmap[part] = ref
620 partsmap[part] = ref
621 return partsmap
621 return partsmap
622
622
623
623
624 def loadtemplater(ui, spec, defaults=None, resources=None, cache=None):
624 def loadtemplater(ui, spec, defaults=None, resources=None, cache=None):
625 """Create a templater from either a literal template or loading from
625 """Create a templater from either a literal template or loading from
626 a map file"""
626 a map file"""
627 assert not (spec.tmpl and spec.mapfile)
627 assert not (spec.tmpl and spec.mapfile)
628 if spec.mapfile:
628 if spec.mapfile:
629 frommapfile = templater.templater.frommapfile
629 frommapfile = templater.templater.frommapfile
630 return frommapfile(
630 return frommapfile(
631 spec.mapfile, defaults=defaults, resources=resources, cache=cache
631 spec.mapfile, defaults=defaults, resources=resources, cache=cache
632 )
632 )
633 return maketemplater(
633 return maketemplater(
634 ui, spec.tmpl, defaults=defaults, resources=resources, cache=cache
634 ui, spec.tmpl, defaults=defaults, resources=resources, cache=cache
635 )
635 )
636
636
637
637
638 def maketemplater(ui, tmpl, defaults=None, resources=None, cache=None):
638 def maketemplater(ui, tmpl, defaults=None, resources=None, cache=None):
639 """Create a templater from a string template 'tmpl'"""
639 """Create a templater from a string template 'tmpl'"""
640 aliases = ui.configitems(b'templatealias')
640 aliases = ui.configitems(b'templatealias')
641 t = templater.templater(
641 t = templater.templater(
642 defaults=defaults, resources=resources, cache=cache, aliases=aliases
642 defaults=defaults, resources=resources, cache=cache, aliases=aliases
643 )
643 )
644 t.cache.update(
644 t.cache.update(
645 (k, templater.unquotestring(v)) for k, v in ui.configitems(b'templates')
645 (k, templater.unquotestring(v)) for k, v in ui.configitems(b'templates')
646 )
646 )
647 if tmpl:
647 if tmpl:
648 t.cache[b''] = tmpl
648 t.cache[b''] = tmpl
649 return t
649 return t
650
650
651
651
652 # marker to denote a resource to be loaded on demand based on mapping values
652 # marker to denote a resource to be loaded on demand based on mapping values
653 # (e.g. (ctx, path) -> fctx)
653 # (e.g. (ctx, path) -> fctx)
654 _placeholder = object()
654 _placeholder = object()
655
655
656
656
657 class templateresources(templater.resourcemapper):
657 class templateresources(templater.resourcemapper):
658 """Resource mapper designed for the default templatekw and function"""
658 """Resource mapper designed for the default templatekw and function"""
659
659
660 def __init__(self, ui, repo=None):
660 def __init__(self, ui, repo=None):
661 self._resmap = {
661 self._resmap = {
662 b'cache': {}, # for templatekw/funcs to store reusable data
662 b'cache': {}, # for templatekw/funcs to store reusable data
663 b'repo': repo,
663 b'repo': repo,
664 b'ui': ui,
664 b'ui': ui,
665 }
665 }
666
666
667 def availablekeys(self, mapping):
667 def availablekeys(self, mapping):
668 return {
668 return {
669 k for k in self.knownkeys() if self._getsome(mapping, k) is not None
669 k for k in self.knownkeys() if self._getsome(mapping, k) is not None
670 }
670 }
671
671
672 def knownkeys(self):
672 def knownkeys(self):
673 return {b'cache', b'ctx', b'fctx', b'repo', b'revcache', b'ui'}
673 return {b'cache', b'ctx', b'fctx', b'repo', b'revcache', b'ui'}
674
674
675 def lookup(self, mapping, key):
675 def lookup(self, mapping, key):
676 if key not in self.knownkeys():
676 if key not in self.knownkeys():
677 return None
677 return None
678 v = self._getsome(mapping, key)
678 v = self._getsome(mapping, key)
679 if v is _placeholder:
679 if v is _placeholder:
680 v = mapping[key] = self._loadermap[key](self, mapping)
680 v = mapping[key] = self._loadermap[key](self, mapping)
681 return v
681 return v
682
682
683 def populatemap(self, context, origmapping, newmapping):
683 def populatemap(self, context, origmapping, newmapping):
684 mapping = {}
684 mapping = {}
685 if self._hasnodespec(newmapping):
685 if self._hasnodespec(newmapping):
686 mapping[b'revcache'] = {} # per-ctx cache
686 mapping[b'revcache'] = {} # per-ctx cache
687 if self._hasnodespec(origmapping) and self._hasnodespec(newmapping):
687 if self._hasnodespec(origmapping) and self._hasnodespec(newmapping):
688 orignode = templateutil.runsymbol(context, origmapping, b'node')
688 orignode = templateutil.runsymbol(context, origmapping, b'node')
689 mapping[b'originalnode'] = orignode
689 mapping[b'originalnode'] = orignode
690 # put marker to override 'ctx'/'fctx' in mapping if any, and flag
690 # put marker to override 'ctx'/'fctx' in mapping if any, and flag
691 # its existence to be reported by availablekeys()
691 # its existence to be reported by availablekeys()
692 if b'ctx' not in newmapping and self._hasliteral(newmapping, b'node'):
692 if b'ctx' not in newmapping and self._hasliteral(newmapping, b'node'):
693 mapping[b'ctx'] = _placeholder
693 mapping[b'ctx'] = _placeholder
694 if b'fctx' not in newmapping and self._hasliteral(newmapping, b'path'):
694 if b'fctx' not in newmapping and self._hasliteral(newmapping, b'path'):
695 mapping[b'fctx'] = _placeholder
695 mapping[b'fctx'] = _placeholder
696 return mapping
696 return mapping
697
697
698 def _getsome(self, mapping, key):
698 def _getsome(self, mapping, key):
699 v = mapping.get(key)
699 v = mapping.get(key)
700 if v is not None:
700 if v is not None:
701 return v
701 return v
702 return self._resmap.get(key)
702 return self._resmap.get(key)
703
703
704 def _hasliteral(self, mapping, key):
704 def _hasliteral(self, mapping, key):
705 """Test if a literal value is set or unset in the given mapping"""
705 """Test if a literal value is set or unset in the given mapping"""
706 return key in mapping and not callable(mapping[key])
706 return key in mapping and not callable(mapping[key])
707
707
708 def _getliteral(self, mapping, key):
708 def _getliteral(self, mapping, key):
709 """Return value of the given name if it is a literal"""
709 """Return value of the given name if it is a literal"""
710 v = mapping.get(key)
710 v = mapping.get(key)
711 if callable(v):
711 if callable(v):
712 return None
712 return None
713 return v
713 return v
714
714
715 def _hasnodespec(self, mapping):
715 def _hasnodespec(self, mapping):
716 """Test if context revision is set or unset in the given mapping"""
716 """Test if context revision is set or unset in the given mapping"""
717 return b'node' in mapping or b'ctx' in mapping
717 return b'node' in mapping or b'ctx' in mapping
718
718
719 def _loadctx(self, mapping):
719 def _loadctx(self, mapping):
720 repo = self._getsome(mapping, b'repo')
720 repo = self._getsome(mapping, b'repo')
721 node = self._getliteral(mapping, b'node')
721 node = self._getliteral(mapping, b'node')
722 if repo is None or node is None:
722 if repo is None or node is None:
723 return
723 return
724 try:
724 try:
725 return repo[node]
725 return repo[node]
726 except error.RepoLookupError:
726 except error.RepoLookupError:
727 return None # maybe hidden/non-existent node
727 return None # maybe hidden/non-existent node
728
728
729 def _loadfctx(self, mapping):
729 def _loadfctx(self, mapping):
730 ctx = self._getsome(mapping, b'ctx')
730 ctx = self._getsome(mapping, b'ctx')
731 path = self._getliteral(mapping, b'path')
731 path = self._getliteral(mapping, b'path')
732 if ctx is None or path is None:
732 if ctx is None or path is None:
733 return None
733 return None
734 try:
734 try:
735 return ctx[path]
735 return ctx[path]
736 except error.LookupError:
736 except error.LookupError:
737 return None # maybe removed file?
737 return None # maybe removed file?
738
738
739 _loadermap = {
739 _loadermap = {
740 b'ctx': _loadctx,
740 b'ctx': _loadctx,
741 b'fctx': _loadfctx,
741 b'fctx': _loadfctx,
742 }
742 }
743
743
744
744
745 def _internaltemplateformatter(
745 def _internaltemplateformatter(
746 ui,
746 ui,
747 out,
747 out,
748 topic,
748 topic,
749 opts,
749 opts,
750 spec,
750 spec,
751 tmpl,
751 tmpl,
752 docheader=b'',
752 docheader=b'',
753 docfooter=b'',
753 docfooter=b'',
754 separator=b'',
754 separator=b'',
755 ):
755 ):
756 """Build template formatter that handles customizable built-in templates
756 """Build template formatter that handles customizable built-in templates
757 such as -Tjson(...)"""
757 such as -Tjson(...)"""
758 templates = {spec.ref: tmpl}
758 templates = {spec.ref: tmpl}
759 if docheader:
759 if docheader:
760 templates[b'%s:docheader' % spec.ref] = docheader
760 templates[b'%s:docheader' % spec.ref] = docheader
761 if docfooter:
761 if docfooter:
762 templates[b'%s:docfooter' % spec.ref] = docfooter
762 templates[b'%s:docfooter' % spec.ref] = docfooter
763 if separator:
763 if separator:
764 templates[b'%s:separator' % spec.ref] = separator
764 templates[b'%s:separator' % spec.ref] = separator
765 return templateformatter(
765 return templateformatter(
766 ui, out, topic, opts, spec, overridetemplates=templates
766 ui, out, topic, opts, spec, overridetemplates=templates
767 )
767 )
768
768
769
769
770 def formatter(ui, out, topic, opts):
770 def formatter(ui, out, topic, opts):
771 spec = lookuptemplate(ui, topic, opts.get(b'template', b''))
771 spec = lookuptemplate(ui, topic, opts.get(b'template', b''))
772 if spec.ref == b"cbor" and spec.refargs is not None:
772 if spec.ref == b"cbor" and spec.refargs is not None:
773 return _internaltemplateformatter(
773 return _internaltemplateformatter(
774 ui,
774 ui,
775 out,
775 out,
776 topic,
776 topic,
777 opts,
777 opts,
778 spec,
778 spec,
779 tmpl=b'{dict(%s)|cbor}' % spec.refargs,
779 tmpl=b'{dict(%s)|cbor}' % spec.refargs,
780 docheader=cborutil.BEGIN_INDEFINITE_ARRAY,
780 docheader=cborutil.BEGIN_INDEFINITE_ARRAY,
781 docfooter=cborutil.BREAK,
781 docfooter=cborutil.BREAK,
782 )
782 )
783 elif spec.ref == b"cbor":
783 elif spec.ref == b"cbor":
784 return cborformatter(ui, out, topic, opts)
784 return cborformatter(ui, out, topic, opts)
785 elif spec.ref == b"json" and spec.refargs is not None:
785 elif spec.ref == b"json" and spec.refargs is not None:
786 return _internaltemplateformatter(
786 return _internaltemplateformatter(
787 ui,
787 ui,
788 out,
788 out,
789 topic,
789 topic,
790 opts,
790 opts,
791 spec,
791 spec,
792 tmpl=b'{dict(%s)|json}' % spec.refargs,
792 tmpl=b'{dict(%s)|json}' % spec.refargs,
793 docheader=b'[\n ',
793 docheader=b'[\n ',
794 docfooter=b'\n]\n',
794 docfooter=b'\n]\n',
795 separator=b',\n ',
795 separator=b',\n ',
796 )
796 )
797 elif spec.ref == b"json":
797 elif spec.ref == b"json":
798 return jsonformatter(ui, out, topic, opts)
798 return jsonformatter(ui, out, topic, opts)
799 elif spec.ref == b"pickle":
799 elif spec.ref == b"pickle":
800 assert spec.refargs is None, r'function-style not supported'
800 assert spec.refargs is None, r'function-style not supported'
801 return pickleformatter(ui, out, topic, opts)
801 return pickleformatter(ui, out, topic, opts)
802 elif spec.ref == b"debug":
802 elif spec.ref == b"debug":
803 assert spec.refargs is None, r'function-style not supported'
803 assert spec.refargs is None, r'function-style not supported'
804 return debugformatter(ui, out, topic, opts)
804 return debugformatter(ui, out, topic, opts)
805 elif spec.ref or spec.tmpl or spec.mapfile:
805 elif spec.ref or spec.tmpl or spec.mapfile:
806 assert spec.refargs is None, r'function-style not supported'
806 assert spec.refargs is None, r'function-style not supported'
807 return templateformatter(ui, out, topic, opts, spec)
807 return templateformatter(ui, out, topic, opts, spec)
808 # developer config: ui.formatdebug
808 # developer config: ui.formatdebug
809 elif ui.configbool(b'ui', b'formatdebug'):
809 elif ui.configbool(b'ui', b'formatdebug'):
810 return debugformatter(ui, out, topic, opts)
810 return debugformatter(ui, out, topic, opts)
811 # deprecated config: ui.formatjson
811 # deprecated config: ui.formatjson
812 elif ui.configbool(b'ui', b'formatjson'):
812 elif ui.configbool(b'ui', b'formatjson'):
813 return jsonformatter(ui, out, topic, opts)
813 return jsonformatter(ui, out, topic, opts)
814 return plainformatter(ui, out, topic, opts)
814 return plainformatter(ui, out, topic, opts)
815
815
816
816
817 @contextlib.contextmanager
817 @contextlib.contextmanager
818 def openformatter(ui, filename, topic, opts):
818 def openformatter(ui, filename, topic, opts):
819 """Create a formatter that writes outputs to the specified file
819 """Create a formatter that writes outputs to the specified file
820
820
821 Must be invoked using the 'with' statement.
821 Must be invoked using the 'with' statement.
822 """
822 """
823 with util.posixfile(filename, b'wb') as out:
823 with util.posixfile(filename, b'wb') as out:
824 with formatter(ui, out, topic, opts) as fm:
824 with formatter(ui, out, topic, opts) as fm:
825 yield fm
825 yield fm
826
826
827
827
828 @contextlib.contextmanager
828 @contextlib.contextmanager
829 def _neverending(fm):
829 def _neverending(fm):
830 yield fm
830 yield fm
831
831
832
832
833 def maybereopen(fm, filename):
833 def maybereopen(fm, filename):
834 """Create a formatter backed by file if filename specified, else return
834 """Create a formatter backed by file if filename specified, else return
835 the given formatter
835 the given formatter
836
836
837 Must be invoked using the 'with' statement. This will never call fm.end()
837 Must be invoked using the 'with' statement. This will never call fm.end()
838 of the given formatter.
838 of the given formatter.
839 """
839 """
840 if filename:
840 if filename:
841 return openformatter(fm._ui, filename, fm._topic, fm._opts)
841 return openformatter(fm._ui, filename, fm._topic, fm._opts)
842 else:
842 else:
843 return _neverending(fm)
843 return _neverending(fm)
General Comments 0
You need to be logged in to leave comments. Login now