##// END OF EJS Templates
formatter: remove unused private attributes from baseformatter...
Yuya Nishihara -
r37614:8c121a98 default
parent child Browse files
Show More
@@ -1,610 +1,608 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 collections
110 import collections
111 import contextlib
111 import contextlib
112 import itertools
112 import itertools
113 import os
113 import os
114
114
115 from .i18n import _
115 from .i18n import _
116 from .node import (
116 from .node import (
117 hex,
117 hex,
118 short,
118 short,
119 )
119 )
120
120
121 from . import (
121 from . import (
122 error,
122 error,
123 pycompat,
123 pycompat,
124 templatefilters,
124 templatefilters,
125 templatekw,
125 templatekw,
126 templater,
126 templater,
127 templateutil,
127 templateutil,
128 util,
128 util,
129 )
129 )
130 from .utils import dateutil
130 from .utils import dateutil
131
131
132 pickle = util.pickle
132 pickle = util.pickle
133
133
134 class _nullconverter(object):
134 class _nullconverter(object):
135 '''convert non-primitive data types to be processed by formatter'''
135 '''convert non-primitive data types to be processed by formatter'''
136
136
137 # set to True if context object should be stored as item
137 # set to True if context object should be stored as item
138 storecontext = False
138 storecontext = False
139
139
140 @staticmethod
140 @staticmethod
141 def wrapnested(data, tmpl, sep):
141 def wrapnested(data, tmpl, sep):
142 '''wrap nested data by appropriate type'''
142 '''wrap nested data by appropriate type'''
143 return data
143 return data
144 @staticmethod
144 @staticmethod
145 def formatdate(date, fmt):
145 def formatdate(date, fmt):
146 '''convert date tuple to appropriate format'''
146 '''convert date tuple to appropriate format'''
147 return date
147 return date
148 @staticmethod
148 @staticmethod
149 def formatdict(data, key, value, fmt, sep):
149 def formatdict(data, key, value, fmt, sep):
150 '''convert dict or key-value pairs to appropriate dict format'''
150 '''convert dict or key-value pairs to appropriate dict format'''
151 # use plain dict instead of util.sortdict so that data can be
151 # use plain dict instead of util.sortdict so that data can be
152 # serialized as a builtin dict in pickle output
152 # serialized as a builtin dict in pickle output
153 return dict(data)
153 return dict(data)
154 @staticmethod
154 @staticmethod
155 def formatlist(data, name, fmt, sep):
155 def formatlist(data, name, fmt, sep):
156 '''convert iterable to appropriate list format'''
156 '''convert iterable to appropriate list format'''
157 return list(data)
157 return list(data)
158
158
159 class baseformatter(object):
159 class baseformatter(object):
160 def __init__(self, ui, topic, opts, converter):
160 def __init__(self, ui, topic, opts, converter):
161 self._ui = ui
161 self._ui = ui
162 self._topic = topic
162 self._topic = topic
163 self._style = opts.get("style")
164 self._template = opts.get("template")
165 self._converter = converter
163 self._converter = converter
166 self._item = None
164 self._item = None
167 # function to convert node to string suitable for this output
165 # function to convert node to string suitable for this output
168 self.hexfunc = hex
166 self.hexfunc = hex
169 def __enter__(self):
167 def __enter__(self):
170 return self
168 return self
171 def __exit__(self, exctype, excvalue, traceback):
169 def __exit__(self, exctype, excvalue, traceback):
172 if exctype is None:
170 if exctype is None:
173 self.end()
171 self.end()
174 def _showitem(self):
172 def _showitem(self):
175 '''show a formatted item once all data is collected'''
173 '''show a formatted item once all data is collected'''
176 def startitem(self):
174 def startitem(self):
177 '''begin an item in the format list'''
175 '''begin an item in the format list'''
178 if self._item is not None:
176 if self._item is not None:
179 self._showitem()
177 self._showitem()
180 self._item = {}
178 self._item = {}
181 def formatdate(self, date, fmt='%a %b %d %H:%M:%S %Y %1%2'):
179 def formatdate(self, date, fmt='%a %b %d %H:%M:%S %Y %1%2'):
182 '''convert date tuple to appropriate format'''
180 '''convert date tuple to appropriate format'''
183 return self._converter.formatdate(date, fmt)
181 return self._converter.formatdate(date, fmt)
184 def formatdict(self, data, key='key', value='value', fmt=None, sep=' '):
182 def formatdict(self, data, key='key', value='value', fmt=None, sep=' '):
185 '''convert dict or key-value pairs to appropriate dict format'''
183 '''convert dict or key-value pairs to appropriate dict format'''
186 return self._converter.formatdict(data, key, value, fmt, sep)
184 return self._converter.formatdict(data, key, value, fmt, sep)
187 def formatlist(self, data, name, fmt=None, sep=' '):
185 def formatlist(self, data, name, fmt=None, sep=' '):
188 '''convert iterable to appropriate list format'''
186 '''convert iterable to appropriate list format'''
189 # name is mandatory argument for now, but it could be optional if
187 # name is mandatory argument for now, but it could be optional if
190 # we have default template keyword, e.g. {item}
188 # we have default template keyword, e.g. {item}
191 return self._converter.formatlist(data, name, fmt, sep)
189 return self._converter.formatlist(data, name, fmt, sep)
192 def context(self, **ctxs):
190 def context(self, **ctxs):
193 '''insert context objects to be used to render template keywords'''
191 '''insert context objects to be used to render template keywords'''
194 ctxs = pycompat.byteskwargs(ctxs)
192 ctxs = pycompat.byteskwargs(ctxs)
195 assert all(k in {'ctx', 'fctx'} for k in ctxs)
193 assert all(k in {'ctx', 'fctx'} for k in ctxs)
196 if self._converter.storecontext:
194 if self._converter.storecontext:
197 self._item.update(ctxs)
195 self._item.update(ctxs)
198 def data(self, **data):
196 def data(self, **data):
199 '''insert data into item that's not shown in default output'''
197 '''insert data into item that's not shown in default output'''
200 data = pycompat.byteskwargs(data)
198 data = pycompat.byteskwargs(data)
201 self._item.update(data)
199 self._item.update(data)
202 def write(self, fields, deftext, *fielddata, **opts):
200 def write(self, fields, deftext, *fielddata, **opts):
203 '''do default text output while assigning data to item'''
201 '''do default text output while assigning data to item'''
204 fieldkeys = fields.split()
202 fieldkeys = fields.split()
205 assert len(fieldkeys) == len(fielddata)
203 assert len(fieldkeys) == len(fielddata)
206 self._item.update(zip(fieldkeys, fielddata))
204 self._item.update(zip(fieldkeys, fielddata))
207 def condwrite(self, cond, fields, deftext, *fielddata, **opts):
205 def condwrite(self, cond, fields, deftext, *fielddata, **opts):
208 '''do conditional write (primarily for plain formatter)'''
206 '''do conditional write (primarily for plain formatter)'''
209 fieldkeys = fields.split()
207 fieldkeys = fields.split()
210 assert len(fieldkeys) == len(fielddata)
208 assert len(fieldkeys) == len(fielddata)
211 self._item.update(zip(fieldkeys, fielddata))
209 self._item.update(zip(fieldkeys, fielddata))
212 def plain(self, text, **opts):
210 def plain(self, text, **opts):
213 '''show raw text for non-templated mode'''
211 '''show raw text for non-templated mode'''
214 def isplain(self):
212 def isplain(self):
215 '''check for plain formatter usage'''
213 '''check for plain formatter usage'''
216 return False
214 return False
217 def nested(self, field, tmpl=None, sep=''):
215 def nested(self, field, tmpl=None, sep=''):
218 '''sub formatter to store nested data in the specified field'''
216 '''sub formatter to store nested data in the specified field'''
219 data = []
217 data = []
220 self._item[field] = self._converter.wrapnested(data, tmpl, sep)
218 self._item[field] = self._converter.wrapnested(data, tmpl, sep)
221 return _nestedformatter(self._ui, self._converter, data)
219 return _nestedformatter(self._ui, self._converter, data)
222 def end(self):
220 def end(self):
223 '''end output for the formatter'''
221 '''end output for the formatter'''
224 if self._item is not None:
222 if self._item is not None:
225 self._showitem()
223 self._showitem()
226
224
227 def nullformatter(ui, topic):
225 def nullformatter(ui, topic):
228 '''formatter that prints nothing'''
226 '''formatter that prints nothing'''
229 return baseformatter(ui, topic, opts={}, converter=_nullconverter)
227 return baseformatter(ui, topic, opts={}, converter=_nullconverter)
230
228
231 class _nestedformatter(baseformatter):
229 class _nestedformatter(baseformatter):
232 '''build sub items and store them in the parent formatter'''
230 '''build sub items and store them in the parent formatter'''
233 def __init__(self, ui, converter, data):
231 def __init__(self, ui, converter, data):
234 baseformatter.__init__(self, ui, topic='', opts={}, converter=converter)
232 baseformatter.__init__(self, ui, topic='', opts={}, converter=converter)
235 self._data = data
233 self._data = data
236 def _showitem(self):
234 def _showitem(self):
237 self._data.append(self._item)
235 self._data.append(self._item)
238
236
239 def _iteritems(data):
237 def _iteritems(data):
240 '''iterate key-value pairs in stable order'''
238 '''iterate key-value pairs in stable order'''
241 if isinstance(data, dict):
239 if isinstance(data, dict):
242 return sorted(data.iteritems())
240 return sorted(data.iteritems())
243 return data
241 return data
244
242
245 class _plainconverter(object):
243 class _plainconverter(object):
246 '''convert non-primitive data types to text'''
244 '''convert non-primitive data types to text'''
247
245
248 storecontext = False
246 storecontext = False
249
247
250 @staticmethod
248 @staticmethod
251 def wrapnested(data, tmpl, sep):
249 def wrapnested(data, tmpl, sep):
252 raise error.ProgrammingError('plainformatter should never be nested')
250 raise error.ProgrammingError('plainformatter should never be nested')
253 @staticmethod
251 @staticmethod
254 def formatdate(date, fmt):
252 def formatdate(date, fmt):
255 '''stringify date tuple in the given format'''
253 '''stringify date tuple in the given format'''
256 return dateutil.datestr(date, fmt)
254 return dateutil.datestr(date, fmt)
257 @staticmethod
255 @staticmethod
258 def formatdict(data, key, value, fmt, sep):
256 def formatdict(data, key, value, fmt, sep):
259 '''stringify key-value pairs separated by sep'''
257 '''stringify key-value pairs separated by sep'''
260 prefmt = pycompat.identity
258 prefmt = pycompat.identity
261 if fmt is None:
259 if fmt is None:
262 fmt = '%s=%s'
260 fmt = '%s=%s'
263 prefmt = pycompat.bytestr
261 prefmt = pycompat.bytestr
264 return sep.join(fmt % (prefmt(k), prefmt(v))
262 return sep.join(fmt % (prefmt(k), prefmt(v))
265 for k, v in _iteritems(data))
263 for k, v in _iteritems(data))
266 @staticmethod
264 @staticmethod
267 def formatlist(data, name, fmt, sep):
265 def formatlist(data, name, fmt, sep):
268 '''stringify iterable separated by sep'''
266 '''stringify iterable separated by sep'''
269 prefmt = pycompat.identity
267 prefmt = pycompat.identity
270 if fmt is None:
268 if fmt is None:
271 fmt = '%s'
269 fmt = '%s'
272 prefmt = pycompat.bytestr
270 prefmt = pycompat.bytestr
273 return sep.join(fmt % prefmt(e) for e in data)
271 return sep.join(fmt % prefmt(e) for e in data)
274
272
275 class plainformatter(baseformatter):
273 class plainformatter(baseformatter):
276 '''the default text output scheme'''
274 '''the default text output scheme'''
277 def __init__(self, ui, out, topic, opts):
275 def __init__(self, ui, out, topic, opts):
278 baseformatter.__init__(self, ui, topic, opts, _plainconverter)
276 baseformatter.__init__(self, ui, topic, opts, _plainconverter)
279 if ui.debugflag:
277 if ui.debugflag:
280 self.hexfunc = hex
278 self.hexfunc = hex
281 else:
279 else:
282 self.hexfunc = short
280 self.hexfunc = short
283 if ui is out:
281 if ui is out:
284 self._write = ui.write
282 self._write = ui.write
285 else:
283 else:
286 self._write = lambda s, **opts: out.write(s)
284 self._write = lambda s, **opts: out.write(s)
287 def startitem(self):
285 def startitem(self):
288 pass
286 pass
289 def data(self, **data):
287 def data(self, **data):
290 pass
288 pass
291 def write(self, fields, deftext, *fielddata, **opts):
289 def write(self, fields, deftext, *fielddata, **opts):
292 self._write(deftext % fielddata, **opts)
290 self._write(deftext % fielddata, **opts)
293 def condwrite(self, cond, fields, deftext, *fielddata, **opts):
291 def condwrite(self, cond, fields, deftext, *fielddata, **opts):
294 '''do conditional write'''
292 '''do conditional write'''
295 if cond:
293 if cond:
296 self._write(deftext % fielddata, **opts)
294 self._write(deftext % fielddata, **opts)
297 def plain(self, text, **opts):
295 def plain(self, text, **opts):
298 self._write(text, **opts)
296 self._write(text, **opts)
299 def isplain(self):
297 def isplain(self):
300 return True
298 return True
301 def nested(self, field, tmpl=None, sep=''):
299 def nested(self, field, tmpl=None, sep=''):
302 # nested data will be directly written to ui
300 # nested data will be directly written to ui
303 return self
301 return self
304 def end(self):
302 def end(self):
305 pass
303 pass
306
304
307 class debugformatter(baseformatter):
305 class debugformatter(baseformatter):
308 def __init__(self, ui, out, topic, opts):
306 def __init__(self, ui, out, topic, opts):
309 baseformatter.__init__(self, ui, topic, opts, _nullconverter)
307 baseformatter.__init__(self, ui, topic, opts, _nullconverter)
310 self._out = out
308 self._out = out
311 self._out.write("%s = [\n" % self._topic)
309 self._out.write("%s = [\n" % self._topic)
312 def _showitem(self):
310 def _showitem(self):
313 self._out.write(' %s,\n' % pycompat.byterepr(self._item))
311 self._out.write(' %s,\n' % pycompat.byterepr(self._item))
314 def end(self):
312 def end(self):
315 baseformatter.end(self)
313 baseformatter.end(self)
316 self._out.write("]\n")
314 self._out.write("]\n")
317
315
318 class pickleformatter(baseformatter):
316 class pickleformatter(baseformatter):
319 def __init__(self, ui, out, topic, opts):
317 def __init__(self, ui, out, topic, opts):
320 baseformatter.__init__(self, ui, topic, opts, _nullconverter)
318 baseformatter.__init__(self, ui, topic, opts, _nullconverter)
321 self._out = out
319 self._out = out
322 self._data = []
320 self._data = []
323 def _showitem(self):
321 def _showitem(self):
324 self._data.append(self._item)
322 self._data.append(self._item)
325 def end(self):
323 def end(self):
326 baseformatter.end(self)
324 baseformatter.end(self)
327 self._out.write(pickle.dumps(self._data))
325 self._out.write(pickle.dumps(self._data))
328
326
329 class jsonformatter(baseformatter):
327 class jsonformatter(baseformatter):
330 def __init__(self, ui, out, topic, opts):
328 def __init__(self, ui, out, topic, opts):
331 baseformatter.__init__(self, ui, topic, opts, _nullconverter)
329 baseformatter.__init__(self, ui, topic, opts, _nullconverter)
332 self._out = out
330 self._out = out
333 self._out.write("[")
331 self._out.write("[")
334 self._first = True
332 self._first = True
335 def _showitem(self):
333 def _showitem(self):
336 if self._first:
334 if self._first:
337 self._first = False
335 self._first = False
338 else:
336 else:
339 self._out.write(",")
337 self._out.write(",")
340
338
341 self._out.write("\n {\n")
339 self._out.write("\n {\n")
342 first = True
340 first = True
343 for k, v in sorted(self._item.items()):
341 for k, v in sorted(self._item.items()):
344 if first:
342 if first:
345 first = False
343 first = False
346 else:
344 else:
347 self._out.write(",\n")
345 self._out.write(",\n")
348 u = templatefilters.json(v, paranoid=False)
346 u = templatefilters.json(v, paranoid=False)
349 self._out.write(' "%s": %s' % (k, u))
347 self._out.write(' "%s": %s' % (k, u))
350 self._out.write("\n }")
348 self._out.write("\n }")
351 def end(self):
349 def end(self):
352 baseformatter.end(self)
350 baseformatter.end(self)
353 self._out.write("\n]\n")
351 self._out.write("\n]\n")
354
352
355 class _templateconverter(object):
353 class _templateconverter(object):
356 '''convert non-primitive data types to be processed by templater'''
354 '''convert non-primitive data types to be processed by templater'''
357
355
358 storecontext = True
356 storecontext = True
359
357
360 @staticmethod
358 @staticmethod
361 def wrapnested(data, tmpl, sep):
359 def wrapnested(data, tmpl, sep):
362 '''wrap nested data by templatable type'''
360 '''wrap nested data by templatable type'''
363 return templateutil.mappinglist(data, tmpl=tmpl, sep=sep)
361 return templateutil.mappinglist(data, tmpl=tmpl, sep=sep)
364 @staticmethod
362 @staticmethod
365 def formatdate(date, fmt):
363 def formatdate(date, fmt):
366 '''return date tuple'''
364 '''return date tuple'''
367 return date
365 return date
368 @staticmethod
366 @staticmethod
369 def formatdict(data, key, value, fmt, sep):
367 def formatdict(data, key, value, fmt, sep):
370 '''build object that can be evaluated as either plain string or dict'''
368 '''build object that can be evaluated as either plain string or dict'''
371 data = util.sortdict(_iteritems(data))
369 data = util.sortdict(_iteritems(data))
372 def f():
370 def f():
373 yield _plainconverter.formatdict(data, key, value, fmt, sep)
371 yield _plainconverter.formatdict(data, key, value, fmt, sep)
374 return templateutil.hybriddict(data, key=key, value=value, fmt=fmt,
372 return templateutil.hybriddict(data, key=key, value=value, fmt=fmt,
375 gen=f)
373 gen=f)
376 @staticmethod
374 @staticmethod
377 def formatlist(data, name, fmt, sep):
375 def formatlist(data, name, fmt, sep):
378 '''build object that can be evaluated as either plain string or list'''
376 '''build object that can be evaluated as either plain string or list'''
379 data = list(data)
377 data = list(data)
380 def f():
378 def f():
381 yield _plainconverter.formatlist(data, name, fmt, sep)
379 yield _plainconverter.formatlist(data, name, fmt, sep)
382 return templateutil.hybridlist(data, name=name, fmt=fmt, gen=f)
380 return templateutil.hybridlist(data, name=name, fmt=fmt, gen=f)
383
381
384 class templateformatter(baseformatter):
382 class templateformatter(baseformatter):
385 def __init__(self, ui, out, topic, opts):
383 def __init__(self, ui, out, topic, opts):
386 baseformatter.__init__(self, ui, topic, opts, _templateconverter)
384 baseformatter.__init__(self, ui, topic, opts, _templateconverter)
387 self._out = out
385 self._out = out
388 spec = lookuptemplate(ui, topic, opts.get('template', ''))
386 spec = lookuptemplate(ui, topic, opts.get('template', ''))
389 self._tref = spec.ref
387 self._tref = spec.ref
390 self._t = loadtemplater(ui, spec, defaults=templatekw.keywords,
388 self._t = loadtemplater(ui, spec, defaults=templatekw.keywords,
391 resources=templateresources(ui),
389 resources=templateresources(ui),
392 cache=templatekw.defaulttempl)
390 cache=templatekw.defaulttempl)
393 self._parts = templatepartsmap(spec, self._t,
391 self._parts = templatepartsmap(spec, self._t,
394 ['docheader', 'docfooter', 'separator'])
392 ['docheader', 'docfooter', 'separator'])
395 self._counter = itertools.count()
393 self._counter = itertools.count()
396 self._renderitem('docheader', {})
394 self._renderitem('docheader', {})
397
395
398 def _showitem(self):
396 def _showitem(self):
399 item = self._item.copy()
397 item = self._item.copy()
400 item['index'] = index = next(self._counter)
398 item['index'] = index = next(self._counter)
401 if index > 0:
399 if index > 0:
402 self._renderitem('separator', {})
400 self._renderitem('separator', {})
403 self._renderitem(self._tref, item)
401 self._renderitem(self._tref, item)
404
402
405 def _renderitem(self, part, item):
403 def _renderitem(self, part, item):
406 if part not in self._parts:
404 if part not in self._parts:
407 return
405 return
408 ref = self._parts[part]
406 ref = self._parts[part]
409 self._out.write(self._t.render(ref, item))
407 self._out.write(self._t.render(ref, item))
410
408
411 def end(self):
409 def end(self):
412 baseformatter.end(self)
410 baseformatter.end(self)
413 self._renderitem('docfooter', {})
411 self._renderitem('docfooter', {})
414
412
415 templatespec = collections.namedtuple(r'templatespec',
413 templatespec = collections.namedtuple(r'templatespec',
416 r'ref tmpl mapfile')
414 r'ref tmpl mapfile')
417
415
418 def lookuptemplate(ui, topic, tmpl):
416 def lookuptemplate(ui, topic, tmpl):
419 """Find the template matching the given -T/--template spec 'tmpl'
417 """Find the template matching the given -T/--template spec 'tmpl'
420
418
421 'tmpl' can be any of the following:
419 'tmpl' can be any of the following:
422
420
423 - a literal template (e.g. '{rev}')
421 - a literal template (e.g. '{rev}')
424 - a map-file name or path (e.g. 'changelog')
422 - a map-file name or path (e.g. 'changelog')
425 - a reference to [templates] in config file
423 - a reference to [templates] in config file
426 - a path to raw template file
424 - a path to raw template file
427
425
428 A map file defines a stand-alone template environment. If a map file
426 A map file defines a stand-alone template environment. If a map file
429 selected, all templates defined in the file will be loaded, and the
427 selected, all templates defined in the file will be loaded, and the
430 template matching the given topic will be rendered. Aliases won't be
428 template matching the given topic will be rendered. Aliases won't be
431 loaded from user config, but from the map file.
429 loaded from user config, but from the map file.
432
430
433 If no map file selected, all templates in [templates] section will be
431 If no map file selected, all templates in [templates] section will be
434 available as well as aliases in [templatealias].
432 available as well as aliases in [templatealias].
435 """
433 """
436
434
437 # looks like a literal template?
435 # looks like a literal template?
438 if '{' in tmpl:
436 if '{' in tmpl:
439 return templatespec('', tmpl, None)
437 return templatespec('', tmpl, None)
440
438
441 # perhaps a stock style?
439 # perhaps a stock style?
442 if not os.path.split(tmpl)[0]:
440 if not os.path.split(tmpl)[0]:
443 mapname = (templater.templatepath('map-cmdline.' + tmpl)
441 mapname = (templater.templatepath('map-cmdline.' + tmpl)
444 or templater.templatepath(tmpl))
442 or templater.templatepath(tmpl))
445 if mapname and os.path.isfile(mapname):
443 if mapname and os.path.isfile(mapname):
446 return templatespec(topic, None, mapname)
444 return templatespec(topic, None, mapname)
447
445
448 # perhaps it's a reference to [templates]
446 # perhaps it's a reference to [templates]
449 if ui.config('templates', tmpl):
447 if ui.config('templates', tmpl):
450 return templatespec(tmpl, None, None)
448 return templatespec(tmpl, None, None)
451
449
452 if tmpl == 'list':
450 if tmpl == 'list':
453 ui.write(_("available styles: %s\n") % templater.stylelist())
451 ui.write(_("available styles: %s\n") % templater.stylelist())
454 raise error.Abort(_("specify a template"))
452 raise error.Abort(_("specify a template"))
455
453
456 # perhaps it's a path to a map or a template
454 # perhaps it's a path to a map or a template
457 if ('/' in tmpl or '\\' in tmpl) and os.path.isfile(tmpl):
455 if ('/' in tmpl or '\\' in tmpl) and os.path.isfile(tmpl):
458 # is it a mapfile for a style?
456 # is it a mapfile for a style?
459 if os.path.basename(tmpl).startswith("map-"):
457 if os.path.basename(tmpl).startswith("map-"):
460 return templatespec(topic, None, os.path.realpath(tmpl))
458 return templatespec(topic, None, os.path.realpath(tmpl))
461 with util.posixfile(tmpl, 'rb') as f:
459 with util.posixfile(tmpl, 'rb') as f:
462 tmpl = f.read()
460 tmpl = f.read()
463 return templatespec('', tmpl, None)
461 return templatespec('', tmpl, None)
464
462
465 # constant string?
463 # constant string?
466 return templatespec('', tmpl, None)
464 return templatespec('', tmpl, None)
467
465
468 def templatepartsmap(spec, t, partnames):
466 def templatepartsmap(spec, t, partnames):
469 """Create a mapping of {part: ref}"""
467 """Create a mapping of {part: ref}"""
470 partsmap = {spec.ref: spec.ref} # initial ref must exist in t
468 partsmap = {spec.ref: spec.ref} # initial ref must exist in t
471 if spec.mapfile:
469 if spec.mapfile:
472 partsmap.update((p, p) for p in partnames if p in t)
470 partsmap.update((p, p) for p in partnames if p in t)
473 elif spec.ref:
471 elif spec.ref:
474 for part in partnames:
472 for part in partnames:
475 ref = '%s:%s' % (spec.ref, part) # select config sub-section
473 ref = '%s:%s' % (spec.ref, part) # select config sub-section
476 if ref in t:
474 if ref in t:
477 partsmap[part] = ref
475 partsmap[part] = ref
478 return partsmap
476 return partsmap
479
477
480 def loadtemplater(ui, spec, defaults=None, resources=None, cache=None):
478 def loadtemplater(ui, spec, defaults=None, resources=None, cache=None):
481 """Create a templater from either a literal template or loading from
479 """Create a templater from either a literal template or loading from
482 a map file"""
480 a map file"""
483 assert not (spec.tmpl and spec.mapfile)
481 assert not (spec.tmpl and spec.mapfile)
484 if spec.mapfile:
482 if spec.mapfile:
485 frommapfile = templater.templater.frommapfile
483 frommapfile = templater.templater.frommapfile
486 return frommapfile(spec.mapfile, defaults=defaults, resources=resources,
484 return frommapfile(spec.mapfile, defaults=defaults, resources=resources,
487 cache=cache)
485 cache=cache)
488 return maketemplater(ui, spec.tmpl, defaults=defaults, resources=resources,
486 return maketemplater(ui, spec.tmpl, defaults=defaults, resources=resources,
489 cache=cache)
487 cache=cache)
490
488
491 def maketemplater(ui, tmpl, defaults=None, resources=None, cache=None):
489 def maketemplater(ui, tmpl, defaults=None, resources=None, cache=None):
492 """Create a templater from a string template 'tmpl'"""
490 """Create a templater from a string template 'tmpl'"""
493 aliases = ui.configitems('templatealias')
491 aliases = ui.configitems('templatealias')
494 t = templater.templater(defaults=defaults, resources=resources,
492 t = templater.templater(defaults=defaults, resources=resources,
495 cache=cache, aliases=aliases)
493 cache=cache, aliases=aliases)
496 t.cache.update((k, templater.unquotestring(v))
494 t.cache.update((k, templater.unquotestring(v))
497 for k, v in ui.configitems('templates'))
495 for k, v in ui.configitems('templates'))
498 if tmpl:
496 if tmpl:
499 t.cache[''] = tmpl
497 t.cache[''] = tmpl
500 return t
498 return t
501
499
502 class templateresources(templater.resourcemapper):
500 class templateresources(templater.resourcemapper):
503 """Resource mapper designed for the default templatekw and function"""
501 """Resource mapper designed for the default templatekw and function"""
504
502
505 def __init__(self, ui, repo=None):
503 def __init__(self, ui, repo=None):
506 self._resmap = {
504 self._resmap = {
507 'cache': {}, # for templatekw/funcs to store reusable data
505 'cache': {}, # for templatekw/funcs to store reusable data
508 'repo': repo,
506 'repo': repo,
509 'ui': ui,
507 'ui': ui,
510 }
508 }
511
509
512 def availablekeys(self, context, mapping):
510 def availablekeys(self, context, mapping):
513 return {k for k, g in self._gettermap.iteritems()
511 return {k for k, g in self._gettermap.iteritems()
514 if g(self, context, mapping, k) is not None}
512 if g(self, context, mapping, k) is not None}
515
513
516 def knownkeys(self):
514 def knownkeys(self):
517 return self._knownkeys
515 return self._knownkeys
518
516
519 def lookup(self, context, mapping, key):
517 def lookup(self, context, mapping, key):
520 get = self._gettermap.get(key)
518 get = self._gettermap.get(key)
521 if not get:
519 if not get:
522 return None
520 return None
523 return get(self, context, mapping, key)
521 return get(self, context, mapping, key)
524
522
525 def populatemap(self, context, origmapping, newmapping):
523 def populatemap(self, context, origmapping, newmapping):
526 mapping = {}
524 mapping = {}
527 if self._hasctx(newmapping):
525 if self._hasctx(newmapping):
528 mapping['revcache'] = {} # per-ctx cache
526 mapping['revcache'] = {} # per-ctx cache
529 if (('node' in origmapping or self._hasctx(origmapping))
527 if (('node' in origmapping or self._hasctx(origmapping))
530 and ('node' in newmapping or self._hasctx(newmapping))):
528 and ('node' in newmapping or self._hasctx(newmapping))):
531 orignode = templateutil.runsymbol(context, origmapping, 'node')
529 orignode = templateutil.runsymbol(context, origmapping, 'node')
532 mapping['originalnode'] = orignode
530 mapping['originalnode'] = orignode
533 return mapping
531 return mapping
534
532
535 def _getsome(self, context, mapping, key):
533 def _getsome(self, context, mapping, key):
536 v = mapping.get(key)
534 v = mapping.get(key)
537 if v is not None:
535 if v is not None:
538 return v
536 return v
539 return self._resmap.get(key)
537 return self._resmap.get(key)
540
538
541 def _hasctx(self, mapping):
539 def _hasctx(self, mapping):
542 return 'ctx' in mapping or 'fctx' in mapping
540 return 'ctx' in mapping or 'fctx' in mapping
543
541
544 def _getctx(self, context, mapping, key):
542 def _getctx(self, context, mapping, key):
545 ctx = mapping.get('ctx')
543 ctx = mapping.get('ctx')
546 if ctx is not None:
544 if ctx is not None:
547 return ctx
545 return ctx
548 fctx = mapping.get('fctx')
546 fctx = mapping.get('fctx')
549 if fctx is not None:
547 if fctx is not None:
550 return fctx.changectx()
548 return fctx.changectx()
551
549
552 def _getrepo(self, context, mapping, key):
550 def _getrepo(self, context, mapping, key):
553 ctx = self._getctx(context, mapping, 'ctx')
551 ctx = self._getctx(context, mapping, 'ctx')
554 if ctx is not None:
552 if ctx is not None:
555 return ctx.repo()
553 return ctx.repo()
556 return self._getsome(context, mapping, key)
554 return self._getsome(context, mapping, key)
557
555
558 _gettermap = {
556 _gettermap = {
559 'cache': _getsome,
557 'cache': _getsome,
560 'ctx': _getctx,
558 'ctx': _getctx,
561 'fctx': _getsome,
559 'fctx': _getsome,
562 'repo': _getrepo,
560 'repo': _getrepo,
563 'revcache': _getsome,
561 'revcache': _getsome,
564 'ui': _getsome,
562 'ui': _getsome,
565 }
563 }
566 _knownkeys = set(_gettermap.keys())
564 _knownkeys = set(_gettermap.keys())
567
565
568 def formatter(ui, out, topic, opts):
566 def formatter(ui, out, topic, opts):
569 template = opts.get("template", "")
567 template = opts.get("template", "")
570 if template == "json":
568 if template == "json":
571 return jsonformatter(ui, out, topic, opts)
569 return jsonformatter(ui, out, topic, opts)
572 elif template == "pickle":
570 elif template == "pickle":
573 return pickleformatter(ui, out, topic, opts)
571 return pickleformatter(ui, out, topic, opts)
574 elif template == "debug":
572 elif template == "debug":
575 return debugformatter(ui, out, topic, opts)
573 return debugformatter(ui, out, topic, opts)
576 elif template != "":
574 elif template != "":
577 return templateformatter(ui, out, topic, opts)
575 return templateformatter(ui, out, topic, opts)
578 # developer config: ui.formatdebug
576 # developer config: ui.formatdebug
579 elif ui.configbool('ui', 'formatdebug'):
577 elif ui.configbool('ui', 'formatdebug'):
580 return debugformatter(ui, out, topic, opts)
578 return debugformatter(ui, out, topic, opts)
581 # deprecated config: ui.formatjson
579 # deprecated config: ui.formatjson
582 elif ui.configbool('ui', 'formatjson'):
580 elif ui.configbool('ui', 'formatjson'):
583 return jsonformatter(ui, out, topic, opts)
581 return jsonformatter(ui, out, topic, opts)
584 return plainformatter(ui, out, topic, opts)
582 return plainformatter(ui, out, topic, opts)
585
583
586 @contextlib.contextmanager
584 @contextlib.contextmanager
587 def openformatter(ui, filename, topic, opts):
585 def openformatter(ui, filename, topic, opts):
588 """Create a formatter that writes outputs to the specified file
586 """Create a formatter that writes outputs to the specified file
589
587
590 Must be invoked using the 'with' statement.
588 Must be invoked using the 'with' statement.
591 """
589 """
592 with util.posixfile(filename, 'wb') as out:
590 with util.posixfile(filename, 'wb') as out:
593 with formatter(ui, out, topic, opts) as fm:
591 with formatter(ui, out, topic, opts) as fm:
594 yield fm
592 yield fm
595
593
596 @contextlib.contextmanager
594 @contextlib.contextmanager
597 def _neverending(fm):
595 def _neverending(fm):
598 yield fm
596 yield fm
599
597
600 def maybereopen(fm, filename, opts):
598 def maybereopen(fm, filename, opts):
601 """Create a formatter backed by file if filename specified, else return
599 """Create a formatter backed by file if filename specified, else return
602 the given formatter
600 the given formatter
603
601
604 Must be invoked using the 'with' statement. This will never call fm.end()
602 Must be invoked using the 'with' statement. This will never call fm.end()
605 of the given formatter.
603 of the given formatter.
606 """
604 """
607 if filename:
605 if filename:
608 return openformatter(fm._ui, filename, fm._topic, opts)
606 return openformatter(fm._ui, filename, fm._topic, opts)
609 else:
607 else:
610 return _neverending(fm)
608 return _neverending(fm)
General Comments 0
You need to be logged in to leave comments. Login now