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