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