##// END OF EJS Templates
formatter: set _first on formatter, not ui...
Martin von Zweigbergk -
r31298:59d09565 default
parent child Browse files
Show More
@@ -1,443 +1,443
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 os
106 import os
107
107
108 from .i18n import _
108 from .i18n import _
109 from .node import (
109 from .node import (
110 hex,
110 hex,
111 short,
111 short,
112 )
112 )
113
113
114 from . import (
114 from . import (
115 encoding,
115 encoding,
116 error,
116 error,
117 templatekw,
117 templatekw,
118 templater,
118 templater,
119 util,
119 util,
120 )
120 )
121
121
122 pickle = util.pickle
122 pickle = util.pickle
123
123
124 class _nullconverter(object):
124 class _nullconverter(object):
125 '''convert non-primitive data types to be processed by formatter'''
125 '''convert non-primitive data types to be processed by formatter'''
126 @staticmethod
126 @staticmethod
127 def formatdate(date, fmt):
127 def formatdate(date, fmt):
128 '''convert date tuple to appropriate format'''
128 '''convert date tuple to appropriate format'''
129 return date
129 return date
130 @staticmethod
130 @staticmethod
131 def formatdict(data, key, value, fmt, sep):
131 def formatdict(data, key, value, fmt, sep):
132 '''convert dict or key-value pairs to appropriate dict format'''
132 '''convert dict or key-value pairs to appropriate dict format'''
133 # use plain dict instead of util.sortdict so that data can be
133 # use plain dict instead of util.sortdict so that data can be
134 # serialized as a builtin dict in pickle output
134 # serialized as a builtin dict in pickle output
135 return dict(data)
135 return dict(data)
136 @staticmethod
136 @staticmethod
137 def formatlist(data, name, fmt, sep):
137 def formatlist(data, name, fmt, sep):
138 '''convert iterable to appropriate list format'''
138 '''convert iterable to appropriate list format'''
139 return list(data)
139 return list(data)
140
140
141 class baseformatter(object):
141 class baseformatter(object):
142 def __init__(self, ui, topic, opts, converter):
142 def __init__(self, ui, topic, opts, converter):
143 self._ui = ui
143 self._ui = ui
144 self._topic = topic
144 self._topic = topic
145 self._style = opts.get("style")
145 self._style = opts.get("style")
146 self._template = opts.get("template")
146 self._template = opts.get("template")
147 self._converter = converter
147 self._converter = converter
148 self._item = None
148 self._item = None
149 # function to convert node to string suitable for this output
149 # function to convert node to string suitable for this output
150 self.hexfunc = hex
150 self.hexfunc = hex
151 def __enter__(self):
151 def __enter__(self):
152 return self
152 return self
153 def __exit__(self, exctype, excvalue, traceback):
153 def __exit__(self, exctype, excvalue, traceback):
154 if exctype is None:
154 if exctype is None:
155 self.end()
155 self.end()
156 def _showitem(self):
156 def _showitem(self):
157 '''show a formatted item once all data is collected'''
157 '''show a formatted item once all data is collected'''
158 pass
158 pass
159 def startitem(self):
159 def startitem(self):
160 '''begin an item in the format list'''
160 '''begin an item in the format list'''
161 if self._item is not None:
161 if self._item is not None:
162 self._showitem()
162 self._showitem()
163 self._item = {}
163 self._item = {}
164 def formatdate(self, date, fmt='%a %b %d %H:%M:%S %Y %1%2'):
164 def formatdate(self, date, fmt='%a %b %d %H:%M:%S %Y %1%2'):
165 '''convert date tuple to appropriate format'''
165 '''convert date tuple to appropriate format'''
166 return self._converter.formatdate(date, fmt)
166 return self._converter.formatdate(date, fmt)
167 def formatdict(self, data, key='key', value='value', fmt='%s=%s', sep=' '):
167 def formatdict(self, data, key='key', value='value', fmt='%s=%s', sep=' '):
168 '''convert dict or key-value pairs to appropriate dict format'''
168 '''convert dict or key-value pairs to appropriate dict format'''
169 return self._converter.formatdict(data, key, value, fmt, sep)
169 return self._converter.formatdict(data, key, value, fmt, sep)
170 def formatlist(self, data, name, fmt='%s', sep=' '):
170 def formatlist(self, data, name, fmt='%s', sep=' '):
171 '''convert iterable to appropriate list format'''
171 '''convert iterable to appropriate list format'''
172 # name is mandatory argument for now, but it could be optional if
172 # name is mandatory argument for now, but it could be optional if
173 # we have default template keyword, e.g. {item}
173 # we have default template keyword, e.g. {item}
174 return self._converter.formatlist(data, name, fmt, sep)
174 return self._converter.formatlist(data, name, fmt, sep)
175 def context(self, **ctxs):
175 def context(self, **ctxs):
176 '''insert context objects to be used to render template keywords'''
176 '''insert context objects to be used to render template keywords'''
177 pass
177 pass
178 def data(self, **data):
178 def data(self, **data):
179 '''insert data into item that's not shown in default output'''
179 '''insert data into item that's not shown in default output'''
180 self._item.update(data)
180 self._item.update(data)
181 def write(self, fields, deftext, *fielddata, **opts):
181 def write(self, fields, deftext, *fielddata, **opts):
182 '''do default text output while assigning data to item'''
182 '''do default text output while assigning data to item'''
183 fieldkeys = fields.split()
183 fieldkeys = fields.split()
184 assert len(fieldkeys) == len(fielddata)
184 assert len(fieldkeys) == len(fielddata)
185 self._item.update(zip(fieldkeys, fielddata))
185 self._item.update(zip(fieldkeys, fielddata))
186 def condwrite(self, cond, fields, deftext, *fielddata, **opts):
186 def condwrite(self, cond, fields, deftext, *fielddata, **opts):
187 '''do conditional write (primarily for plain formatter)'''
187 '''do conditional write (primarily for plain formatter)'''
188 fieldkeys = fields.split()
188 fieldkeys = fields.split()
189 assert len(fieldkeys) == len(fielddata)
189 assert len(fieldkeys) == len(fielddata)
190 self._item.update(zip(fieldkeys, fielddata))
190 self._item.update(zip(fieldkeys, fielddata))
191 def plain(self, text, **opts):
191 def plain(self, text, **opts):
192 '''show raw text for non-templated mode'''
192 '''show raw text for non-templated mode'''
193 pass
193 pass
194 def isplain(self):
194 def isplain(self):
195 '''check for plain formatter usage'''
195 '''check for plain formatter usage'''
196 return False
196 return False
197 def nested(self, field):
197 def nested(self, field):
198 '''sub formatter to store nested data in the specified field'''
198 '''sub formatter to store nested data in the specified field'''
199 self._item[field] = data = []
199 self._item[field] = data = []
200 return _nestedformatter(self._ui, self._converter, data)
200 return _nestedformatter(self._ui, self._converter, data)
201 def end(self):
201 def end(self):
202 '''end output for the formatter'''
202 '''end output for the formatter'''
203 if self._item is not None:
203 if self._item is not None:
204 self._showitem()
204 self._showitem()
205
205
206 class _nestedformatter(baseformatter):
206 class _nestedformatter(baseformatter):
207 '''build sub items and store them in the parent formatter'''
207 '''build sub items and store them in the parent formatter'''
208 def __init__(self, ui, converter, data):
208 def __init__(self, ui, converter, data):
209 baseformatter.__init__(self, ui, topic='', opts={}, converter=converter)
209 baseformatter.__init__(self, ui, topic='', opts={}, converter=converter)
210 self._data = data
210 self._data = data
211 def _showitem(self):
211 def _showitem(self):
212 self._data.append(self._item)
212 self._data.append(self._item)
213
213
214 def _iteritems(data):
214 def _iteritems(data):
215 '''iterate key-value pairs in stable order'''
215 '''iterate key-value pairs in stable order'''
216 if isinstance(data, dict):
216 if isinstance(data, dict):
217 return sorted(data.iteritems())
217 return sorted(data.iteritems())
218 return data
218 return data
219
219
220 class _plainconverter(object):
220 class _plainconverter(object):
221 '''convert non-primitive data types to text'''
221 '''convert non-primitive data types to text'''
222 @staticmethod
222 @staticmethod
223 def formatdate(date, fmt):
223 def formatdate(date, fmt):
224 '''stringify date tuple in the given format'''
224 '''stringify date tuple in the given format'''
225 return util.datestr(date, fmt)
225 return util.datestr(date, fmt)
226 @staticmethod
226 @staticmethod
227 def formatdict(data, key, value, fmt, sep):
227 def formatdict(data, key, value, fmt, sep):
228 '''stringify key-value pairs separated by sep'''
228 '''stringify key-value pairs separated by sep'''
229 return sep.join(fmt % (k, v) for k, v in _iteritems(data))
229 return sep.join(fmt % (k, v) for k, v in _iteritems(data))
230 @staticmethod
230 @staticmethod
231 def formatlist(data, name, fmt, sep):
231 def formatlist(data, name, fmt, sep):
232 '''stringify iterable separated by sep'''
232 '''stringify iterable separated by sep'''
233 return sep.join(fmt % e for e in data)
233 return sep.join(fmt % e for e in data)
234
234
235 class plainformatter(baseformatter):
235 class plainformatter(baseformatter):
236 '''the default text output scheme'''
236 '''the default text output scheme'''
237 def __init__(self, ui, topic, opts):
237 def __init__(self, ui, topic, opts):
238 baseformatter.__init__(self, ui, topic, opts, _plainconverter)
238 baseformatter.__init__(self, ui, topic, opts, _plainconverter)
239 if ui.debugflag:
239 if ui.debugflag:
240 self.hexfunc = hex
240 self.hexfunc = hex
241 else:
241 else:
242 self.hexfunc = short
242 self.hexfunc = short
243 def startitem(self):
243 def startitem(self):
244 pass
244 pass
245 def data(self, **data):
245 def data(self, **data):
246 pass
246 pass
247 def write(self, fields, deftext, *fielddata, **opts):
247 def write(self, fields, deftext, *fielddata, **opts):
248 self._ui.write(deftext % fielddata, **opts)
248 self._ui.write(deftext % fielddata, **opts)
249 def condwrite(self, cond, fields, deftext, *fielddata, **opts):
249 def condwrite(self, cond, fields, deftext, *fielddata, **opts):
250 '''do conditional write'''
250 '''do conditional write'''
251 if cond:
251 if cond:
252 self._ui.write(deftext % fielddata, **opts)
252 self._ui.write(deftext % fielddata, **opts)
253 def plain(self, text, **opts):
253 def plain(self, text, **opts):
254 self._ui.write(text, **opts)
254 self._ui.write(text, **opts)
255 def isplain(self):
255 def isplain(self):
256 return True
256 return True
257 def nested(self, field):
257 def nested(self, field):
258 # nested data will be directly written to ui
258 # nested data will be directly written to ui
259 return self
259 return self
260 def end(self):
260 def end(self):
261 pass
261 pass
262
262
263 class debugformatter(baseformatter):
263 class debugformatter(baseformatter):
264 def __init__(self, ui, out, topic, opts):
264 def __init__(self, ui, out, topic, opts):
265 baseformatter.__init__(self, ui, topic, opts, _nullconverter)
265 baseformatter.__init__(self, ui, topic, opts, _nullconverter)
266 self._out = out
266 self._out = out
267 self._out.write("%s = [\n" % self._topic)
267 self._out.write("%s = [\n" % self._topic)
268 def _showitem(self):
268 def _showitem(self):
269 self._out.write(" " + repr(self._item) + ",\n")
269 self._out.write(" " + repr(self._item) + ",\n")
270 def end(self):
270 def end(self):
271 baseformatter.end(self)
271 baseformatter.end(self)
272 self._out.write("]\n")
272 self._out.write("]\n")
273
273
274 class pickleformatter(baseformatter):
274 class pickleformatter(baseformatter):
275 def __init__(self, ui, out, topic, opts):
275 def __init__(self, ui, out, topic, opts):
276 baseformatter.__init__(self, ui, topic, opts, _nullconverter)
276 baseformatter.__init__(self, ui, topic, opts, _nullconverter)
277 self._out = out
277 self._out = out
278 self._data = []
278 self._data = []
279 def _showitem(self):
279 def _showitem(self):
280 self._data.append(self._item)
280 self._data.append(self._item)
281 def end(self):
281 def end(self):
282 baseformatter.end(self)
282 baseformatter.end(self)
283 self._out.write(pickle.dumps(self._data))
283 self._out.write(pickle.dumps(self._data))
284
284
285 def _jsonifyobj(v):
285 def _jsonifyobj(v):
286 if isinstance(v, dict):
286 if isinstance(v, dict):
287 xs = ['"%s": %s' % (encoding.jsonescape(k), _jsonifyobj(u))
287 xs = ['"%s": %s' % (encoding.jsonescape(k), _jsonifyobj(u))
288 for k, u in sorted(v.iteritems())]
288 for k, u in sorted(v.iteritems())]
289 return '{' + ', '.join(xs) + '}'
289 return '{' + ', '.join(xs) + '}'
290 elif isinstance(v, (list, tuple)):
290 elif isinstance(v, (list, tuple)):
291 return '[' + ', '.join(_jsonifyobj(e) for e in v) + ']'
291 return '[' + ', '.join(_jsonifyobj(e) for e in v) + ']'
292 elif v is None:
292 elif v is None:
293 return 'null'
293 return 'null'
294 elif v is True:
294 elif v is True:
295 return 'true'
295 return 'true'
296 elif v is False:
296 elif v is False:
297 return 'false'
297 return 'false'
298 elif isinstance(v, (int, float)):
298 elif isinstance(v, (int, float)):
299 return str(v)
299 return str(v)
300 else:
300 else:
301 return '"%s"' % encoding.jsonescape(v)
301 return '"%s"' % encoding.jsonescape(v)
302
302
303 class jsonformatter(baseformatter):
303 class jsonformatter(baseformatter):
304 def __init__(self, ui, out, topic, opts):
304 def __init__(self, ui, out, topic, opts):
305 baseformatter.__init__(self, ui, topic, opts, _nullconverter)
305 baseformatter.__init__(self, ui, topic, opts, _nullconverter)
306 self._out = out
306 self._out = out
307 self._out.write("[")
307 self._out.write("[")
308 self._ui._first = True
308 self._first = True
309 def _showitem(self):
309 def _showitem(self):
310 if self._ui._first:
310 if self._first:
311 self._ui._first = False
311 self._first = False
312 else:
312 else:
313 self._out.write(",")
313 self._out.write(",")
314
314
315 self._out.write("\n {\n")
315 self._out.write("\n {\n")
316 first = True
316 first = True
317 for k, v in sorted(self._item.items()):
317 for k, v in sorted(self._item.items()):
318 if first:
318 if first:
319 first = False
319 first = False
320 else:
320 else:
321 self._out.write(",\n")
321 self._out.write(",\n")
322 self._out.write(' "%s": %s' % (k, _jsonifyobj(v)))
322 self._out.write(' "%s": %s' % (k, _jsonifyobj(v)))
323 self._out.write("\n }")
323 self._out.write("\n }")
324 def end(self):
324 def end(self):
325 baseformatter.end(self)
325 baseformatter.end(self)
326 self._out.write("\n]\n")
326 self._out.write("\n]\n")
327
327
328 class _templateconverter(object):
328 class _templateconverter(object):
329 '''convert non-primitive data types to be processed by templater'''
329 '''convert non-primitive data types to be processed by templater'''
330 @staticmethod
330 @staticmethod
331 def formatdate(date, fmt):
331 def formatdate(date, fmt):
332 '''return date tuple'''
332 '''return date tuple'''
333 return date
333 return date
334 @staticmethod
334 @staticmethod
335 def formatdict(data, key, value, fmt, sep):
335 def formatdict(data, key, value, fmt, sep):
336 '''build object that can be evaluated as either plain string or dict'''
336 '''build object that can be evaluated as either plain string or dict'''
337 data = util.sortdict(_iteritems(data))
337 data = util.sortdict(_iteritems(data))
338 def f():
338 def f():
339 yield _plainconverter.formatdict(data, key, value, fmt, sep)
339 yield _plainconverter.formatdict(data, key, value, fmt, sep)
340 return templatekw._hybrid(f(), data, lambda k: {key: k, value: data[k]},
340 return templatekw._hybrid(f(), data, lambda k: {key: k, value: data[k]},
341 lambda d: fmt % (d[key], d[value]))
341 lambda d: fmt % (d[key], d[value]))
342 @staticmethod
342 @staticmethod
343 def formatlist(data, name, fmt, sep):
343 def formatlist(data, name, fmt, sep):
344 '''build object that can be evaluated as either plain string or list'''
344 '''build object that can be evaluated as either plain string or list'''
345 data = list(data)
345 data = list(data)
346 def f():
346 def f():
347 yield _plainconverter.formatlist(data, name, fmt, sep)
347 yield _plainconverter.formatlist(data, name, fmt, sep)
348 return templatekw._hybrid(f(), data, lambda x: {name: x},
348 return templatekw._hybrid(f(), data, lambda x: {name: x},
349 lambda d: fmt % d[name])
349 lambda d: fmt % d[name])
350
350
351 class templateformatter(baseformatter):
351 class templateformatter(baseformatter):
352 def __init__(self, ui, out, topic, opts):
352 def __init__(self, ui, out, topic, opts):
353 baseformatter.__init__(self, ui, topic, opts, _templateconverter)
353 baseformatter.__init__(self, ui, topic, opts, _templateconverter)
354 self._out = out
354 self._out = out
355 self._topic = topic
355 self._topic = topic
356 self._t = gettemplater(ui, topic, opts.get('template', ''),
356 self._t = gettemplater(ui, topic, opts.get('template', ''),
357 cache=templatekw.defaulttempl)
357 cache=templatekw.defaulttempl)
358 self._cache = {} # for templatekw/funcs to store reusable data
358 self._cache = {} # for templatekw/funcs to store reusable data
359 def context(self, **ctxs):
359 def context(self, **ctxs):
360 '''insert context objects to be used to render template keywords'''
360 '''insert context objects to be used to render template keywords'''
361 assert all(k == 'ctx' for k in ctxs)
361 assert all(k == 'ctx' for k in ctxs)
362 self._item.update(ctxs)
362 self._item.update(ctxs)
363 def _showitem(self):
363 def _showitem(self):
364 # TODO: add support for filectx. probably each template keyword or
364 # TODO: add support for filectx. probably each template keyword or
365 # function will have to declare dependent resources. e.g.
365 # function will have to declare dependent resources. e.g.
366 # @templatekeyword(..., requires=('ctx',))
366 # @templatekeyword(..., requires=('ctx',))
367 if 'ctx' in self._item:
367 if 'ctx' in self._item:
368 props = templatekw.keywords.copy()
368 props = templatekw.keywords.copy()
369 # explicitly-defined fields precede templatekw
369 # explicitly-defined fields precede templatekw
370 props.update(self._item)
370 props.update(self._item)
371 # but template resources must be always available
371 # but template resources must be always available
372 props['templ'] = self._t
372 props['templ'] = self._t
373 props['repo'] = props['ctx'].repo()
373 props['repo'] = props['ctx'].repo()
374 props['revcache'] = {}
374 props['revcache'] = {}
375 else:
375 else:
376 props = self._item
376 props = self._item
377 g = self._t(self._topic, ui=self._ui, cache=self._cache, **props)
377 g = self._t(self._topic, ui=self._ui, cache=self._cache, **props)
378 self._out.write(templater.stringify(g))
378 self._out.write(templater.stringify(g))
379
379
380 def lookuptemplate(ui, topic, tmpl):
380 def lookuptemplate(ui, topic, tmpl):
381 # looks like a literal template?
381 # looks like a literal template?
382 if '{' in tmpl:
382 if '{' in tmpl:
383 return tmpl, None
383 return tmpl, None
384
384
385 # perhaps a stock style?
385 # perhaps a stock style?
386 if not os.path.split(tmpl)[0]:
386 if not os.path.split(tmpl)[0]:
387 mapname = (templater.templatepath('map-cmdline.' + tmpl)
387 mapname = (templater.templatepath('map-cmdline.' + tmpl)
388 or templater.templatepath(tmpl))
388 or templater.templatepath(tmpl))
389 if mapname and os.path.isfile(mapname):
389 if mapname and os.path.isfile(mapname):
390 return None, mapname
390 return None, mapname
391
391
392 # perhaps it's a reference to [templates]
392 # perhaps it's a reference to [templates]
393 t = ui.config('templates', tmpl)
393 t = ui.config('templates', tmpl)
394 if t:
394 if t:
395 return templater.unquotestring(t), None
395 return templater.unquotestring(t), None
396
396
397 if tmpl == 'list':
397 if tmpl == 'list':
398 ui.write(_("available styles: %s\n") % templater.stylelist())
398 ui.write(_("available styles: %s\n") % templater.stylelist())
399 raise error.Abort(_("specify a template"))
399 raise error.Abort(_("specify a template"))
400
400
401 # perhaps it's a path to a map or a template
401 # perhaps it's a path to a map or a template
402 if ('/' in tmpl or '\\' in tmpl) and os.path.isfile(tmpl):
402 if ('/' in tmpl or '\\' in tmpl) and os.path.isfile(tmpl):
403 # is it a mapfile for a style?
403 # is it a mapfile for a style?
404 if os.path.basename(tmpl).startswith("map-"):
404 if os.path.basename(tmpl).startswith("map-"):
405 return None, os.path.realpath(tmpl)
405 return None, os.path.realpath(tmpl)
406 tmpl = open(tmpl).read()
406 tmpl = open(tmpl).read()
407 return tmpl, None
407 return tmpl, None
408
408
409 # constant string?
409 # constant string?
410 return tmpl, None
410 return tmpl, None
411
411
412 def gettemplater(ui, topic, spec, cache=None):
412 def gettemplater(ui, topic, spec, cache=None):
413 tmpl, mapfile = lookuptemplate(ui, topic, spec)
413 tmpl, mapfile = lookuptemplate(ui, topic, spec)
414 assert not (tmpl and mapfile)
414 assert not (tmpl and mapfile)
415 if mapfile:
415 if mapfile:
416 return templater.templater.frommapfile(mapfile, cache=cache)
416 return templater.templater.frommapfile(mapfile, cache=cache)
417 return maketemplater(ui, topic, tmpl, cache=cache)
417 return maketemplater(ui, topic, tmpl, cache=cache)
418
418
419 def maketemplater(ui, topic, tmpl, cache=None):
419 def maketemplater(ui, topic, tmpl, cache=None):
420 """Create a templater from a string template 'tmpl'"""
420 """Create a templater from a string template 'tmpl'"""
421 aliases = ui.configitems('templatealias')
421 aliases = ui.configitems('templatealias')
422 t = templater.templater(cache=cache, aliases=aliases)
422 t = templater.templater(cache=cache, aliases=aliases)
423 if tmpl:
423 if tmpl:
424 t.cache[topic] = tmpl
424 t.cache[topic] = tmpl
425 return t
425 return t
426
426
427 def formatter(ui, topic, opts):
427 def formatter(ui, topic, opts):
428 template = opts.get("template", "")
428 template = opts.get("template", "")
429 if template == "json":
429 if template == "json":
430 return jsonformatter(ui, ui, topic, opts)
430 return jsonformatter(ui, ui, topic, opts)
431 elif template == "pickle":
431 elif template == "pickle":
432 return pickleformatter(ui, ui, topic, opts)
432 return pickleformatter(ui, ui, topic, opts)
433 elif template == "debug":
433 elif template == "debug":
434 return debugformatter(ui, ui, topic, opts)
434 return debugformatter(ui, ui, topic, opts)
435 elif template != "":
435 elif template != "":
436 return templateformatter(ui, ui, topic, opts)
436 return templateformatter(ui, ui, topic, opts)
437 # developer config: ui.formatdebug
437 # developer config: ui.formatdebug
438 elif ui.configbool('ui', 'formatdebug'):
438 elif ui.configbool('ui', 'formatdebug'):
439 return debugformatter(ui, ui, topic, opts)
439 return debugformatter(ui, ui, topic, opts)
440 # deprecated config: ui.formatjson
440 # deprecated config: ui.formatjson
441 elif ui.configbool('ui', 'formatjson'):
441 elif ui.configbool('ui', 'formatjson'):
442 return jsonformatter(ui, ui, topic, opts)
442 return jsonformatter(ui, ui, topic, opts)
443 return plainformatter(ui, topic, opts)
443 return plainformatter(ui, topic, opts)
General Comments 0
You need to be logged in to leave comments. Login now