##// END OF EJS Templates
formatter: add nullformatter...
Yuya Nishihara -
r32575:e9bf3e13 default
parent child Browse files
Show More
@@ -1,461 +1,465
1 # formatter.py - generic output formatting for mercurial
1 # formatter.py - generic output formatting for mercurial
2 #
2 #
3 # Copyright 2012 Matt Mackall <mpm@selenic.com>
3 # Copyright 2012 Matt Mackall <mpm@selenic.com>
4 #
4 #
5 # This software may be used and distributed according to the terms of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version.
6 # GNU General Public License version 2 or any later version.
7
7
8 """Generic output formatting for Mercurial
8 """Generic output formatting for Mercurial
9
9
10 The formatter provides API to show data in various ways. The following
10 The formatter provides API to show data in various ways. The following
11 functions should be used in place of ui.write():
11 functions should be used in place of ui.write():
12
12
13 - fm.write() for unconditional output
13 - fm.write() for unconditional output
14 - fm.condwrite() to show some extra data conditionally in plain output
14 - fm.condwrite() to show some extra data conditionally in plain output
15 - fm.context() to provide changectx to template output
15 - fm.context() to provide changectx to template output
16 - fm.data() to provide extra data to JSON or template output
16 - fm.data() to provide extra data to JSON or template output
17 - fm.plain() to show raw text that isn't provided to JSON or template output
17 - fm.plain() to show raw text that isn't provided to JSON or template output
18
18
19 To show structured data (e.g. date tuples, dicts, lists), apply fm.format*()
19 To show structured data (e.g. date tuples, dicts, lists), apply fm.format*()
20 beforehand so the data is converted to the appropriate data type. Use
20 beforehand so the data is converted to the appropriate data type. Use
21 fm.isplain() if you need to convert or format data conditionally which isn't
21 fm.isplain() if you need to convert or format data conditionally which isn't
22 supported by the formatter API.
22 supported by the formatter API.
23
23
24 To build nested structure (i.e. a list of dicts), use fm.nested().
24 To build nested structure (i.e. a list of dicts), use fm.nested().
25
25
26 See also https://www.mercurial-scm.org/wiki/GenericTemplatingPlan
26 See also https://www.mercurial-scm.org/wiki/GenericTemplatingPlan
27
27
28 fm.condwrite() vs 'if cond:':
28 fm.condwrite() vs 'if cond:':
29
29
30 In most cases, use fm.condwrite() so users can selectively show the data
30 In most cases, use fm.condwrite() so users can selectively show the data
31 in template output. If it's costly to build data, use plain 'if cond:' with
31 in template output. If it's costly to build data, use plain 'if cond:' with
32 fm.write().
32 fm.write().
33
33
34 fm.nested() vs fm.formatdict() (or fm.formatlist()):
34 fm.nested() vs fm.formatdict() (or fm.formatlist()):
35
35
36 fm.nested() should be used to form a tree structure (a list of dicts of
36 fm.nested() should be used to form a tree structure (a list of dicts of
37 lists of dicts...) which can be accessed through template keywords, e.g.
37 lists of dicts...) which can be accessed through template keywords, e.g.
38 "{foo % "{bar % {...}} {baz % {...}}"}". On the other hand, fm.formatdict()
38 "{foo % "{bar % {...}} {baz % {...}}"}". On the other hand, fm.formatdict()
39 exports a dict-type object to template, which can be accessed by e.g.
39 exports a dict-type object to template, which can be accessed by e.g.
40 "{get(foo, key)}" function.
40 "{get(foo, key)}" function.
41
41
42 Doctest helper:
42 Doctest helper:
43
43
44 >>> def show(fn, verbose=False, **opts):
44 >>> def show(fn, verbose=False, **opts):
45 ... import sys
45 ... import sys
46 ... from . import ui as uimod
46 ... from . import ui as uimod
47 ... ui = uimod.ui()
47 ... ui = uimod.ui()
48 ... ui.fout = sys.stdout # redirect to doctest
48 ... ui.fout = sys.stdout # redirect to doctest
49 ... ui.verbose = verbose
49 ... ui.verbose = verbose
50 ... return fn(ui, ui.formatter(fn.__name__, opts))
50 ... return fn(ui, ui.formatter(fn.__name__, opts))
51
51
52 Basic example:
52 Basic example:
53
53
54 >>> def files(ui, fm):
54 >>> def files(ui, fm):
55 ... files = [('foo', 123, (0, 0)), ('bar', 456, (1, 0))]
55 ... files = [('foo', 123, (0, 0)), ('bar', 456, (1, 0))]
56 ... for f in files:
56 ... for f in files:
57 ... fm.startitem()
57 ... fm.startitem()
58 ... fm.write('path', '%s', f[0])
58 ... fm.write('path', '%s', f[0])
59 ... fm.condwrite(ui.verbose, 'date', ' %s',
59 ... fm.condwrite(ui.verbose, 'date', ' %s',
60 ... fm.formatdate(f[2], '%Y-%m-%d %H:%M:%S'))
60 ... fm.formatdate(f[2], '%Y-%m-%d %H:%M:%S'))
61 ... fm.data(size=f[1])
61 ... fm.data(size=f[1])
62 ... fm.plain('\\n')
62 ... fm.plain('\\n')
63 ... fm.end()
63 ... fm.end()
64 >>> show(files)
64 >>> show(files)
65 foo
65 foo
66 bar
66 bar
67 >>> show(files, verbose=True)
67 >>> show(files, verbose=True)
68 foo 1970-01-01 00:00:00
68 foo 1970-01-01 00:00:00
69 bar 1970-01-01 00:00:01
69 bar 1970-01-01 00:00:01
70 >>> show(files, template='json')
70 >>> show(files, template='json')
71 [
71 [
72 {
72 {
73 "date": [0, 0],
73 "date": [0, 0],
74 "path": "foo",
74 "path": "foo",
75 "size": 123
75 "size": 123
76 },
76 },
77 {
77 {
78 "date": [1, 0],
78 "date": [1, 0],
79 "path": "bar",
79 "path": "bar",
80 "size": 456
80 "size": 456
81 }
81 }
82 ]
82 ]
83 >>> show(files, template='path: {path}\\ndate: {date|rfc3339date}\\n')
83 >>> show(files, template='path: {path}\\ndate: {date|rfc3339date}\\n')
84 path: foo
84 path: foo
85 date: 1970-01-01T00:00:00+00:00
85 date: 1970-01-01T00:00:00+00:00
86 path: bar
86 path: bar
87 date: 1970-01-01T00:00:01+00:00
87 date: 1970-01-01T00:00:01+00:00
88
88
89 Nested example:
89 Nested example:
90
90
91 >>> def subrepos(ui, fm):
91 >>> def subrepos(ui, fm):
92 ... fm.startitem()
92 ... fm.startitem()
93 ... fm.write('repo', '[%s]\\n', 'baz')
93 ... fm.write('repo', '[%s]\\n', 'baz')
94 ... files(ui, fm.nested('files'))
94 ... files(ui, fm.nested('files'))
95 ... fm.end()
95 ... fm.end()
96 >>> show(subrepos)
96 >>> show(subrepos)
97 [baz]
97 [baz]
98 foo
98 foo
99 bar
99 bar
100 >>> show(subrepos, template='{repo}: {join(files % "{path}", ", ")}\\n')
100 >>> show(subrepos, template='{repo}: {join(files % "{path}", ", ")}\\n')
101 baz: foo, bar
101 baz: foo, bar
102 """
102 """
103
103
104 from __future__ import absolute_import
104 from __future__ import absolute_import
105
105
106 import contextlib
106 import contextlib
107 import itertools
107 import itertools
108 import os
108 import os
109
109
110 from .i18n import _
110 from .i18n import _
111 from .node import (
111 from .node import (
112 hex,
112 hex,
113 short,
113 short,
114 )
114 )
115
115
116 from . import (
116 from . import (
117 error,
117 error,
118 pycompat,
118 pycompat,
119 templatefilters,
119 templatefilters,
120 templatekw,
120 templatekw,
121 templater,
121 templater,
122 util,
122 util,
123 )
123 )
124
124
125 pickle = util.pickle
125 pickle = util.pickle
126
126
127 class _nullconverter(object):
127 class _nullconverter(object):
128 '''convert non-primitive data types to be processed by formatter'''
128 '''convert non-primitive data types to be processed by formatter'''
129 @staticmethod
129 @staticmethod
130 def formatdate(date, fmt):
130 def formatdate(date, fmt):
131 '''convert date tuple to appropriate format'''
131 '''convert date tuple to appropriate format'''
132 return date
132 return date
133 @staticmethod
133 @staticmethod
134 def formatdict(data, key, value, fmt, sep):
134 def formatdict(data, key, value, fmt, sep):
135 '''convert dict or key-value pairs to appropriate dict format'''
135 '''convert dict or key-value pairs to appropriate dict format'''
136 # use plain dict instead of util.sortdict so that data can be
136 # use plain dict instead of util.sortdict so that data can be
137 # serialized as a builtin dict in pickle output
137 # serialized as a builtin dict in pickle output
138 return dict(data)
138 return dict(data)
139 @staticmethod
139 @staticmethod
140 def formatlist(data, name, fmt, sep):
140 def formatlist(data, name, fmt, sep):
141 '''convert iterable to appropriate list format'''
141 '''convert iterable to appropriate list format'''
142 return list(data)
142 return list(data)
143
143
144 class baseformatter(object):
144 class baseformatter(object):
145 def __init__(self, ui, topic, opts, converter):
145 def __init__(self, ui, topic, opts, converter):
146 self._ui = ui
146 self._ui = ui
147 self._topic = topic
147 self._topic = topic
148 self._style = opts.get("style")
148 self._style = opts.get("style")
149 self._template = opts.get("template")
149 self._template = opts.get("template")
150 self._converter = converter
150 self._converter = converter
151 self._item = None
151 self._item = None
152 # function to convert node to string suitable for this output
152 # function to convert node to string suitable for this output
153 self.hexfunc = hex
153 self.hexfunc = hex
154 def __enter__(self):
154 def __enter__(self):
155 return self
155 return self
156 def __exit__(self, exctype, excvalue, traceback):
156 def __exit__(self, exctype, excvalue, traceback):
157 if exctype is None:
157 if exctype is None:
158 self.end()
158 self.end()
159 def _showitem(self):
159 def _showitem(self):
160 '''show a formatted item once all data is collected'''
160 '''show a formatted item once all data is collected'''
161 pass
161 pass
162 def startitem(self):
162 def startitem(self):
163 '''begin an item in the format list'''
163 '''begin an item in the format list'''
164 if self._item is not None:
164 if self._item is not None:
165 self._showitem()
165 self._showitem()
166 self._item = {}
166 self._item = {}
167 def formatdate(self, date, fmt='%a %b %d %H:%M:%S %Y %1%2'):
167 def formatdate(self, date, fmt='%a %b %d %H:%M:%S %Y %1%2'):
168 '''convert date tuple to appropriate format'''
168 '''convert date tuple to appropriate format'''
169 return self._converter.formatdate(date, fmt)
169 return self._converter.formatdate(date, fmt)
170 def formatdict(self, data, key='key', value='value', fmt='%s=%s', sep=' '):
170 def formatdict(self, data, key='key', value='value', fmt='%s=%s', sep=' '):
171 '''convert dict or key-value pairs to appropriate dict format'''
171 '''convert dict or key-value pairs to appropriate dict format'''
172 return self._converter.formatdict(data, key, value, fmt, sep)
172 return self._converter.formatdict(data, key, value, fmt, sep)
173 def formatlist(self, data, name, fmt='%s', sep=' '):
173 def formatlist(self, data, name, fmt='%s', sep=' '):
174 '''convert iterable to appropriate list format'''
174 '''convert iterable to appropriate list format'''
175 # name is mandatory argument for now, but it could be optional if
175 # name is mandatory argument for now, but it could be optional if
176 # we have default template keyword, e.g. {item}
176 # we have default template keyword, e.g. {item}
177 return self._converter.formatlist(data, name, fmt, sep)
177 return self._converter.formatlist(data, name, fmt, sep)
178 def context(self, **ctxs):
178 def context(self, **ctxs):
179 '''insert context objects to be used to render template keywords'''
179 '''insert context objects to be used to render template keywords'''
180 pass
180 pass
181 def data(self, **data):
181 def data(self, **data):
182 '''insert data into item that's not shown in default output'''
182 '''insert data into item that's not shown in default output'''
183 data = pycompat.byteskwargs(data)
183 data = pycompat.byteskwargs(data)
184 self._item.update(data)
184 self._item.update(data)
185 def write(self, fields, deftext, *fielddata, **opts):
185 def write(self, fields, deftext, *fielddata, **opts):
186 '''do default text output while assigning data to item'''
186 '''do default text output while assigning data to item'''
187 fieldkeys = fields.split()
187 fieldkeys = fields.split()
188 assert len(fieldkeys) == len(fielddata)
188 assert len(fieldkeys) == len(fielddata)
189 self._item.update(zip(fieldkeys, fielddata))
189 self._item.update(zip(fieldkeys, fielddata))
190 def condwrite(self, cond, fields, deftext, *fielddata, **opts):
190 def condwrite(self, cond, fields, deftext, *fielddata, **opts):
191 '''do conditional write (primarily for plain formatter)'''
191 '''do conditional write (primarily for plain formatter)'''
192 fieldkeys = fields.split()
192 fieldkeys = fields.split()
193 assert len(fieldkeys) == len(fielddata)
193 assert len(fieldkeys) == len(fielddata)
194 self._item.update(zip(fieldkeys, fielddata))
194 self._item.update(zip(fieldkeys, fielddata))
195 def plain(self, text, **opts):
195 def plain(self, text, **opts):
196 '''show raw text for non-templated mode'''
196 '''show raw text for non-templated mode'''
197 pass
197 pass
198 def isplain(self):
198 def isplain(self):
199 '''check for plain formatter usage'''
199 '''check for plain formatter usage'''
200 return False
200 return False
201 def nested(self, field):
201 def nested(self, field):
202 '''sub formatter to store nested data in the specified field'''
202 '''sub formatter to store nested data in the specified field'''
203 self._item[field] = data = []
203 self._item[field] = data = []
204 return _nestedformatter(self._ui, self._converter, data)
204 return _nestedformatter(self._ui, self._converter, data)
205 def end(self):
205 def end(self):
206 '''end output for the formatter'''
206 '''end output for the formatter'''
207 if self._item is not None:
207 if self._item is not None:
208 self._showitem()
208 self._showitem()
209
209
210 def nullformatter(ui, topic):
211 '''formatter that prints nothing'''
212 return baseformatter(ui, topic, opts={}, converter=_nullconverter)
213
210 class _nestedformatter(baseformatter):
214 class _nestedformatter(baseformatter):
211 '''build sub items and store them in the parent formatter'''
215 '''build sub items and store them in the parent formatter'''
212 def __init__(self, ui, converter, data):
216 def __init__(self, ui, converter, data):
213 baseformatter.__init__(self, ui, topic='', opts={}, converter=converter)
217 baseformatter.__init__(self, ui, topic='', opts={}, converter=converter)
214 self._data = data
218 self._data = data
215 def _showitem(self):
219 def _showitem(self):
216 self._data.append(self._item)
220 self._data.append(self._item)
217
221
218 def _iteritems(data):
222 def _iteritems(data):
219 '''iterate key-value pairs in stable order'''
223 '''iterate key-value pairs in stable order'''
220 if isinstance(data, dict):
224 if isinstance(data, dict):
221 return sorted(data.iteritems())
225 return sorted(data.iteritems())
222 return data
226 return data
223
227
224 class _plainconverter(object):
228 class _plainconverter(object):
225 '''convert non-primitive data types to text'''
229 '''convert non-primitive data types to text'''
226 @staticmethod
230 @staticmethod
227 def formatdate(date, fmt):
231 def formatdate(date, fmt):
228 '''stringify date tuple in the given format'''
232 '''stringify date tuple in the given format'''
229 return util.datestr(date, fmt)
233 return util.datestr(date, fmt)
230 @staticmethod
234 @staticmethod
231 def formatdict(data, key, value, fmt, sep):
235 def formatdict(data, key, value, fmt, sep):
232 '''stringify key-value pairs separated by sep'''
236 '''stringify key-value pairs separated by sep'''
233 return sep.join(fmt % (k, v) for k, v in _iteritems(data))
237 return sep.join(fmt % (k, v) for k, v in _iteritems(data))
234 @staticmethod
238 @staticmethod
235 def formatlist(data, name, fmt, sep):
239 def formatlist(data, name, fmt, sep):
236 '''stringify iterable separated by sep'''
240 '''stringify iterable separated by sep'''
237 return sep.join(fmt % e for e in data)
241 return sep.join(fmt % e for e in data)
238
242
239 class plainformatter(baseformatter):
243 class plainformatter(baseformatter):
240 '''the default text output scheme'''
244 '''the default text output scheme'''
241 def __init__(self, ui, out, topic, opts):
245 def __init__(self, ui, out, topic, opts):
242 baseformatter.__init__(self, ui, topic, opts, _plainconverter)
246 baseformatter.__init__(self, ui, topic, opts, _plainconverter)
243 if ui.debugflag:
247 if ui.debugflag:
244 self.hexfunc = hex
248 self.hexfunc = hex
245 else:
249 else:
246 self.hexfunc = short
250 self.hexfunc = short
247 if ui is out:
251 if ui is out:
248 self._write = ui.write
252 self._write = ui.write
249 else:
253 else:
250 self._write = lambda s, **opts: out.write(s)
254 self._write = lambda s, **opts: out.write(s)
251 def startitem(self):
255 def startitem(self):
252 pass
256 pass
253 def data(self, **data):
257 def data(self, **data):
254 pass
258 pass
255 def write(self, fields, deftext, *fielddata, **opts):
259 def write(self, fields, deftext, *fielddata, **opts):
256 self._write(deftext % fielddata, **opts)
260 self._write(deftext % fielddata, **opts)
257 def condwrite(self, cond, fields, deftext, *fielddata, **opts):
261 def condwrite(self, cond, fields, deftext, *fielddata, **opts):
258 '''do conditional write'''
262 '''do conditional write'''
259 if cond:
263 if cond:
260 self._write(deftext % fielddata, **opts)
264 self._write(deftext % fielddata, **opts)
261 def plain(self, text, **opts):
265 def plain(self, text, **opts):
262 self._write(text, **opts)
266 self._write(text, **opts)
263 def isplain(self):
267 def isplain(self):
264 return True
268 return True
265 def nested(self, field):
269 def nested(self, field):
266 # nested data will be directly written to ui
270 # nested data will be directly written to ui
267 return self
271 return self
268 def end(self):
272 def end(self):
269 pass
273 pass
270
274
271 class debugformatter(baseformatter):
275 class debugformatter(baseformatter):
272 def __init__(self, ui, out, topic, opts):
276 def __init__(self, ui, out, topic, opts):
273 baseformatter.__init__(self, ui, topic, opts, _nullconverter)
277 baseformatter.__init__(self, ui, topic, opts, _nullconverter)
274 self._out = out
278 self._out = out
275 self._out.write("%s = [\n" % self._topic)
279 self._out.write("%s = [\n" % self._topic)
276 def _showitem(self):
280 def _showitem(self):
277 self._out.write(" " + repr(self._item) + ",\n")
281 self._out.write(" " + repr(self._item) + ",\n")
278 def end(self):
282 def end(self):
279 baseformatter.end(self)
283 baseformatter.end(self)
280 self._out.write("]\n")
284 self._out.write("]\n")
281
285
282 class pickleformatter(baseformatter):
286 class pickleformatter(baseformatter):
283 def __init__(self, ui, out, topic, opts):
287 def __init__(self, ui, out, topic, opts):
284 baseformatter.__init__(self, ui, topic, opts, _nullconverter)
288 baseformatter.__init__(self, ui, topic, opts, _nullconverter)
285 self._out = out
289 self._out = out
286 self._data = []
290 self._data = []
287 def _showitem(self):
291 def _showitem(self):
288 self._data.append(self._item)
292 self._data.append(self._item)
289 def end(self):
293 def end(self):
290 baseformatter.end(self)
294 baseformatter.end(self)
291 self._out.write(pickle.dumps(self._data))
295 self._out.write(pickle.dumps(self._data))
292
296
293 class jsonformatter(baseformatter):
297 class jsonformatter(baseformatter):
294 def __init__(self, ui, out, topic, opts):
298 def __init__(self, ui, out, topic, opts):
295 baseformatter.__init__(self, ui, topic, opts, _nullconverter)
299 baseformatter.__init__(self, ui, topic, opts, _nullconverter)
296 self._out = out
300 self._out = out
297 self._out.write("[")
301 self._out.write("[")
298 self._first = True
302 self._first = True
299 def _showitem(self):
303 def _showitem(self):
300 if self._first:
304 if self._first:
301 self._first = False
305 self._first = False
302 else:
306 else:
303 self._out.write(",")
307 self._out.write(",")
304
308
305 self._out.write("\n {\n")
309 self._out.write("\n {\n")
306 first = True
310 first = True
307 for k, v in sorted(self._item.items()):
311 for k, v in sorted(self._item.items()):
308 if first:
312 if first:
309 first = False
313 first = False
310 else:
314 else:
311 self._out.write(",\n")
315 self._out.write(",\n")
312 u = templatefilters.json(v, paranoid=False)
316 u = templatefilters.json(v, paranoid=False)
313 self._out.write(' "%s": %s' % (k, u))
317 self._out.write(' "%s": %s' % (k, u))
314 self._out.write("\n }")
318 self._out.write("\n }")
315 def end(self):
319 def end(self):
316 baseformatter.end(self)
320 baseformatter.end(self)
317 self._out.write("\n]\n")
321 self._out.write("\n]\n")
318
322
319 class _templateconverter(object):
323 class _templateconverter(object):
320 '''convert non-primitive data types to be processed by templater'''
324 '''convert non-primitive data types to be processed by templater'''
321 @staticmethod
325 @staticmethod
322 def formatdate(date, fmt):
326 def formatdate(date, fmt):
323 '''return date tuple'''
327 '''return date tuple'''
324 return date
328 return date
325 @staticmethod
329 @staticmethod
326 def formatdict(data, key, value, fmt, sep):
330 def formatdict(data, key, value, fmt, sep):
327 '''build object that can be evaluated as either plain string or dict'''
331 '''build object that can be evaluated as either plain string or dict'''
328 data = util.sortdict(_iteritems(data))
332 data = util.sortdict(_iteritems(data))
329 def f():
333 def f():
330 yield _plainconverter.formatdict(data, key, value, fmt, sep)
334 yield _plainconverter.formatdict(data, key, value, fmt, sep)
331 return templatekw.hybriddict(data, key=key, value=value, fmt=fmt,
335 return templatekw.hybriddict(data, key=key, value=value, fmt=fmt,
332 gen=f())
336 gen=f())
333 @staticmethod
337 @staticmethod
334 def formatlist(data, name, fmt, sep):
338 def formatlist(data, name, fmt, sep):
335 '''build object that can be evaluated as either plain string or list'''
339 '''build object that can be evaluated as either plain string or list'''
336 data = list(data)
340 data = list(data)
337 def f():
341 def f():
338 yield _plainconverter.formatlist(data, name, fmt, sep)
342 yield _plainconverter.formatlist(data, name, fmt, sep)
339 return templatekw.hybridlist(data, name=name, fmt=fmt, gen=f())
343 return templatekw.hybridlist(data, name=name, fmt=fmt, gen=f())
340
344
341 class templateformatter(baseformatter):
345 class templateformatter(baseformatter):
342 def __init__(self, ui, out, topic, opts):
346 def __init__(self, ui, out, topic, opts):
343 baseformatter.__init__(self, ui, topic, opts, _templateconverter)
347 baseformatter.__init__(self, ui, topic, opts, _templateconverter)
344 self._out = out
348 self._out = out
345 self._topic = topic
349 self._topic = topic
346 self._t = gettemplater(ui, topic, opts.get('template', ''),
350 self._t = gettemplater(ui, topic, opts.get('template', ''),
347 cache=templatekw.defaulttempl)
351 cache=templatekw.defaulttempl)
348 self._counter = itertools.count()
352 self._counter = itertools.count()
349 self._cache = {} # for templatekw/funcs to store reusable data
353 self._cache = {} # for templatekw/funcs to store reusable data
350 def context(self, **ctxs):
354 def context(self, **ctxs):
351 '''insert context objects to be used to render template keywords'''
355 '''insert context objects to be used to render template keywords'''
352 assert all(k == 'ctx' for k in ctxs)
356 assert all(k == 'ctx' for k in ctxs)
353 self._item.update(ctxs)
357 self._item.update(ctxs)
354 def _showitem(self):
358 def _showitem(self):
355 # TODO: add support for filectx. probably each template keyword or
359 # TODO: add support for filectx. probably each template keyword or
356 # function will have to declare dependent resources. e.g.
360 # function will have to declare dependent resources. e.g.
357 # @templatekeyword(..., requires=('ctx',))
361 # @templatekeyword(..., requires=('ctx',))
358 props = {}
362 props = {}
359 if 'ctx' in self._item:
363 if 'ctx' in self._item:
360 props.update(templatekw.keywords)
364 props.update(templatekw.keywords)
361 props['index'] = next(self._counter)
365 props['index'] = next(self._counter)
362 # explicitly-defined fields precede templatekw
366 # explicitly-defined fields precede templatekw
363 props.update(self._item)
367 props.update(self._item)
364 if 'ctx' in self._item:
368 if 'ctx' in self._item:
365 # but template resources must be always available
369 # but template resources must be always available
366 props['templ'] = self._t
370 props['templ'] = self._t
367 props['repo'] = props['ctx'].repo()
371 props['repo'] = props['ctx'].repo()
368 props['revcache'] = {}
372 props['revcache'] = {}
369 g = self._t(self._topic, ui=self._ui, cache=self._cache, **props)
373 g = self._t(self._topic, ui=self._ui, cache=self._cache, **props)
370 self._out.write(templater.stringify(g))
374 self._out.write(templater.stringify(g))
371
375
372 def lookuptemplate(ui, topic, tmpl):
376 def lookuptemplate(ui, topic, tmpl):
373 # looks like a literal template?
377 # looks like a literal template?
374 if '{' in tmpl:
378 if '{' in tmpl:
375 return tmpl, None
379 return tmpl, None
376
380
377 # perhaps a stock style?
381 # perhaps a stock style?
378 if not os.path.split(tmpl)[0]:
382 if not os.path.split(tmpl)[0]:
379 mapname = (templater.templatepath('map-cmdline.' + tmpl)
383 mapname = (templater.templatepath('map-cmdline.' + tmpl)
380 or templater.templatepath(tmpl))
384 or templater.templatepath(tmpl))
381 if mapname and os.path.isfile(mapname):
385 if mapname and os.path.isfile(mapname):
382 return None, mapname
386 return None, mapname
383
387
384 # perhaps it's a reference to [templates]
388 # perhaps it's a reference to [templates]
385 t = ui.config('templates', tmpl)
389 t = ui.config('templates', tmpl)
386 if t:
390 if t:
387 return templater.unquotestring(t), None
391 return templater.unquotestring(t), None
388
392
389 if tmpl == 'list':
393 if tmpl == 'list':
390 ui.write(_("available styles: %s\n") % templater.stylelist())
394 ui.write(_("available styles: %s\n") % templater.stylelist())
391 raise error.Abort(_("specify a template"))
395 raise error.Abort(_("specify a template"))
392
396
393 # perhaps it's a path to a map or a template
397 # perhaps it's a path to a map or a template
394 if ('/' in tmpl or '\\' in tmpl) and os.path.isfile(tmpl):
398 if ('/' in tmpl or '\\' in tmpl) and os.path.isfile(tmpl):
395 # is it a mapfile for a style?
399 # is it a mapfile for a style?
396 if os.path.basename(tmpl).startswith("map-"):
400 if os.path.basename(tmpl).startswith("map-"):
397 return None, os.path.realpath(tmpl)
401 return None, os.path.realpath(tmpl)
398 tmpl = open(tmpl).read()
402 tmpl = open(tmpl).read()
399 return tmpl, None
403 return tmpl, None
400
404
401 # constant string?
405 # constant string?
402 return tmpl, None
406 return tmpl, None
403
407
404 def gettemplater(ui, topic, spec, cache=None):
408 def gettemplater(ui, topic, spec, cache=None):
405 tmpl, mapfile = lookuptemplate(ui, topic, spec)
409 tmpl, mapfile = lookuptemplate(ui, topic, spec)
406 assert not (tmpl and mapfile)
410 assert not (tmpl and mapfile)
407 if mapfile:
411 if mapfile:
408 return templater.templater.frommapfile(mapfile, cache=cache)
412 return templater.templater.frommapfile(mapfile, cache=cache)
409 return maketemplater(ui, topic, tmpl, cache=cache)
413 return maketemplater(ui, topic, tmpl, cache=cache)
410
414
411 def maketemplater(ui, topic, tmpl, cache=None):
415 def maketemplater(ui, topic, tmpl, cache=None):
412 """Create a templater from a string template 'tmpl'"""
416 """Create a templater from a string template 'tmpl'"""
413 aliases = ui.configitems('templatealias')
417 aliases = ui.configitems('templatealias')
414 t = templater.templater(cache=cache, aliases=aliases)
418 t = templater.templater(cache=cache, aliases=aliases)
415 if tmpl:
419 if tmpl:
416 t.cache[topic] = tmpl
420 t.cache[topic] = tmpl
417 return t
421 return t
418
422
419 def formatter(ui, out, topic, opts):
423 def formatter(ui, out, topic, opts):
420 template = opts.get("template", "")
424 template = opts.get("template", "")
421 if template == "json":
425 if template == "json":
422 return jsonformatter(ui, out, topic, opts)
426 return jsonformatter(ui, out, topic, opts)
423 elif template == "pickle":
427 elif template == "pickle":
424 return pickleformatter(ui, out, topic, opts)
428 return pickleformatter(ui, out, topic, opts)
425 elif template == "debug":
429 elif template == "debug":
426 return debugformatter(ui, out, topic, opts)
430 return debugformatter(ui, out, topic, opts)
427 elif template != "":
431 elif template != "":
428 return templateformatter(ui, out, topic, opts)
432 return templateformatter(ui, out, topic, opts)
429 # developer config: ui.formatdebug
433 # developer config: ui.formatdebug
430 elif ui.configbool('ui', 'formatdebug'):
434 elif ui.configbool('ui', 'formatdebug'):
431 return debugformatter(ui, out, topic, opts)
435 return debugformatter(ui, out, topic, opts)
432 # deprecated config: ui.formatjson
436 # deprecated config: ui.formatjson
433 elif ui.configbool('ui', 'formatjson'):
437 elif ui.configbool('ui', 'formatjson'):
434 return jsonformatter(ui, out, topic, opts)
438 return jsonformatter(ui, out, topic, opts)
435 return plainformatter(ui, out, topic, opts)
439 return plainformatter(ui, out, topic, opts)
436
440
437 @contextlib.contextmanager
441 @contextlib.contextmanager
438 def openformatter(ui, filename, topic, opts):
442 def openformatter(ui, filename, topic, opts):
439 """Create a formatter that writes outputs to the specified file
443 """Create a formatter that writes outputs to the specified file
440
444
441 Must be invoked using the 'with' statement.
445 Must be invoked using the 'with' statement.
442 """
446 """
443 with util.posixfile(filename, 'wb') as out:
447 with util.posixfile(filename, 'wb') as out:
444 with formatter(ui, out, topic, opts) as fm:
448 with formatter(ui, out, topic, opts) as fm:
445 yield fm
449 yield fm
446
450
447 @contextlib.contextmanager
451 @contextlib.contextmanager
448 def _neverending(fm):
452 def _neverending(fm):
449 yield fm
453 yield fm
450
454
451 def maybereopen(fm, filename, opts):
455 def maybereopen(fm, filename, opts):
452 """Create a formatter backed by file if filename specified, else return
456 """Create a formatter backed by file if filename specified, else return
453 the given formatter
457 the given formatter
454
458
455 Must be invoked using the 'with' statement. This will never call fm.end()
459 Must be invoked using the 'with' statement. This will never call fm.end()
456 of the given formatter.
460 of the given formatter.
457 """
461 """
458 if filename:
462 if filename:
459 return openformatter(fm._ui, filename, fm._topic, opts)
463 return openformatter(fm._ui, filename, fm._topic, opts)
460 else:
464 else:
461 return _neverending(fm)
465 return _neverending(fm)
General Comments 0
You need to be logged in to leave comments. Login now