##// END OF EJS Templates
formatter: add fm.nested(field) to either write or build sub items...
Yuya Nishihara -
r29837:5b886289 default
parent child Browse files
Show More
@@ -1,302 +1,317 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 from __future__ import absolute_import
8 from __future__ import absolute_import
9
9
10 import os
10 import os
11
11
12 from .i18n import _
12 from .i18n import _
13 from .node import (
13 from .node import (
14 hex,
14 hex,
15 short,
15 short,
16 )
16 )
17
17
18 from . import (
18 from . import (
19 encoding,
19 encoding,
20 error,
20 error,
21 templatekw,
21 templatekw,
22 templater,
22 templater,
23 util,
23 util,
24 )
24 )
25
25
26 pickle = util.pickle
26 pickle = util.pickle
27
27
28 class _nullconverter(object):
28 class _nullconverter(object):
29 '''convert non-primitive data types to be processed by formatter'''
29 '''convert non-primitive data types to be processed by formatter'''
30 @staticmethod
30 @staticmethod
31 def formatdate(date, fmt):
31 def formatdate(date, fmt):
32 '''convert date tuple to appropriate format'''
32 '''convert date tuple to appropriate format'''
33 return date
33 return date
34 @staticmethod
34 @staticmethod
35 def formatdict(data, key, value, fmt, sep):
35 def formatdict(data, key, value, fmt, sep):
36 '''convert dict or key-value pairs to appropriate dict format'''
36 '''convert dict or key-value pairs to appropriate dict format'''
37 # use plain dict instead of util.sortdict so that data can be
37 # use plain dict instead of util.sortdict so that data can be
38 # serialized as a builtin dict in pickle output
38 # serialized as a builtin dict in pickle output
39 return dict(data)
39 return dict(data)
40 @staticmethod
40 @staticmethod
41 def formatlist(data, name, fmt, sep):
41 def formatlist(data, name, fmt, sep):
42 '''convert iterable to appropriate list format'''
42 '''convert iterable to appropriate list format'''
43 return list(data)
43 return list(data)
44
44
45 class baseformatter(object):
45 class baseformatter(object):
46 def __init__(self, ui, topic, opts, converter):
46 def __init__(self, ui, topic, opts, converter):
47 self._ui = ui
47 self._ui = ui
48 self._topic = topic
48 self._topic = topic
49 self._style = opts.get("style")
49 self._style = opts.get("style")
50 self._template = opts.get("template")
50 self._template = opts.get("template")
51 self._converter = converter
51 self._converter = converter
52 self._item = None
52 self._item = None
53 # function to convert node to string suitable for this output
53 # function to convert node to string suitable for this output
54 self.hexfunc = hex
54 self.hexfunc = hex
55 def __nonzero__(self):
55 def __nonzero__(self):
56 '''return False if we're not doing real templating so we can
56 '''return False if we're not doing real templating so we can
57 skip extra work'''
57 skip extra work'''
58 return True
58 return True
59 def _showitem(self):
59 def _showitem(self):
60 '''show a formatted item once all data is collected'''
60 '''show a formatted item once all data is collected'''
61 pass
61 pass
62 def startitem(self):
62 def startitem(self):
63 '''begin an item in the format list'''
63 '''begin an item in the format list'''
64 if self._item is not None:
64 if self._item is not None:
65 self._showitem()
65 self._showitem()
66 self._item = {}
66 self._item = {}
67 def formatdate(self, date, fmt='%a %b %d %H:%M:%S %Y %1%2'):
67 def formatdate(self, date, fmt='%a %b %d %H:%M:%S %Y %1%2'):
68 '''convert date tuple to appropriate format'''
68 '''convert date tuple to appropriate format'''
69 return self._converter.formatdate(date, fmt)
69 return self._converter.formatdate(date, fmt)
70 def formatdict(self, data, key='key', value='value', fmt='%s=%s', sep=' '):
70 def formatdict(self, data, key='key', value='value', fmt='%s=%s', sep=' '):
71 '''convert dict or key-value pairs to appropriate dict format'''
71 '''convert dict or key-value pairs to appropriate dict format'''
72 return self._converter.formatdict(data, key, value, fmt, sep)
72 return self._converter.formatdict(data, key, value, fmt, sep)
73 def formatlist(self, data, name, fmt='%s', sep=' '):
73 def formatlist(self, data, name, fmt='%s', sep=' '):
74 '''convert iterable to appropriate list format'''
74 '''convert iterable to appropriate list format'''
75 # name is mandatory argument for now, but it could be optional if
75 # name is mandatory argument for now, but it could be optional if
76 # we have default template keyword, e.g. {item}
76 # we have default template keyword, e.g. {item}
77 return self._converter.formatlist(data, name, fmt, sep)
77 return self._converter.formatlist(data, name, fmt, sep)
78 def data(self, **data):
78 def data(self, **data):
79 '''insert data into item that's not shown in default output'''
79 '''insert data into item that's not shown in default output'''
80 self._item.update(data)
80 self._item.update(data)
81 def write(self, fields, deftext, *fielddata, **opts):
81 def write(self, fields, deftext, *fielddata, **opts):
82 '''do default text output while assigning data to item'''
82 '''do default text output while assigning data to item'''
83 fieldkeys = fields.split()
83 fieldkeys = fields.split()
84 assert len(fieldkeys) == len(fielddata)
84 assert len(fieldkeys) == len(fielddata)
85 self._item.update(zip(fieldkeys, fielddata))
85 self._item.update(zip(fieldkeys, fielddata))
86 def condwrite(self, cond, fields, deftext, *fielddata, **opts):
86 def condwrite(self, cond, fields, deftext, *fielddata, **opts):
87 '''do conditional write (primarily for plain formatter)'''
87 '''do conditional write (primarily for plain formatter)'''
88 fieldkeys = fields.split()
88 fieldkeys = fields.split()
89 assert len(fieldkeys) == len(fielddata)
89 assert len(fieldkeys) == len(fielddata)
90 self._item.update(zip(fieldkeys, fielddata))
90 self._item.update(zip(fieldkeys, fielddata))
91 def plain(self, text, **opts):
91 def plain(self, text, **opts):
92 '''show raw text for non-templated mode'''
92 '''show raw text for non-templated mode'''
93 pass
93 pass
94 def nested(self, field):
95 '''sub formatter to store nested data in the specified field'''
96 self._item[field] = data = []
97 return _nestedformatter(self._ui, self._converter, data)
94 def end(self):
98 def end(self):
95 '''end output for the formatter'''
99 '''end output for the formatter'''
96 if self._item is not None:
100 if self._item is not None:
97 self._showitem()
101 self._showitem()
98
102
103 class _nestedformatter(baseformatter):
104 '''build sub items and store them in the parent formatter'''
105 def __init__(self, ui, converter, data):
106 baseformatter.__init__(self, ui, topic='', opts={}, converter=converter)
107 self._data = data
108 def _showitem(self):
109 self._data.append(self._item)
110
99 def _iteritems(data):
111 def _iteritems(data):
100 '''iterate key-value pairs in stable order'''
112 '''iterate key-value pairs in stable order'''
101 if isinstance(data, dict):
113 if isinstance(data, dict):
102 return sorted(data.iteritems())
114 return sorted(data.iteritems())
103 return data
115 return data
104
116
105 class _plainconverter(object):
117 class _plainconverter(object):
106 '''convert non-primitive data types to text'''
118 '''convert non-primitive data types to text'''
107 @staticmethod
119 @staticmethod
108 def formatdate(date, fmt):
120 def formatdate(date, fmt):
109 '''stringify date tuple in the given format'''
121 '''stringify date tuple in the given format'''
110 return util.datestr(date, fmt)
122 return util.datestr(date, fmt)
111 @staticmethod
123 @staticmethod
112 def formatdict(data, key, value, fmt, sep):
124 def formatdict(data, key, value, fmt, sep):
113 '''stringify key-value pairs separated by sep'''
125 '''stringify key-value pairs separated by sep'''
114 return sep.join(fmt % (k, v) for k, v in _iteritems(data))
126 return sep.join(fmt % (k, v) for k, v in _iteritems(data))
115 @staticmethod
127 @staticmethod
116 def formatlist(data, name, fmt, sep):
128 def formatlist(data, name, fmt, sep):
117 '''stringify iterable separated by sep'''
129 '''stringify iterable separated by sep'''
118 return sep.join(fmt % e for e in data)
130 return sep.join(fmt % e for e in data)
119
131
120 class plainformatter(baseformatter):
132 class plainformatter(baseformatter):
121 '''the default text output scheme'''
133 '''the default text output scheme'''
122 def __init__(self, ui, topic, opts):
134 def __init__(self, ui, topic, opts):
123 baseformatter.__init__(self, ui, topic, opts, _plainconverter)
135 baseformatter.__init__(self, ui, topic, opts, _plainconverter)
124 if ui.debugflag:
136 if ui.debugflag:
125 self.hexfunc = hex
137 self.hexfunc = hex
126 else:
138 else:
127 self.hexfunc = short
139 self.hexfunc = short
128 def __nonzero__(self):
140 def __nonzero__(self):
129 return False
141 return False
130 def startitem(self):
142 def startitem(self):
131 pass
143 pass
132 def data(self, **data):
144 def data(self, **data):
133 pass
145 pass
134 def write(self, fields, deftext, *fielddata, **opts):
146 def write(self, fields, deftext, *fielddata, **opts):
135 self._ui.write(deftext % fielddata, **opts)
147 self._ui.write(deftext % fielddata, **opts)
136 def condwrite(self, cond, fields, deftext, *fielddata, **opts):
148 def condwrite(self, cond, fields, deftext, *fielddata, **opts):
137 '''do conditional write'''
149 '''do conditional write'''
138 if cond:
150 if cond:
139 self._ui.write(deftext % fielddata, **opts)
151 self._ui.write(deftext % fielddata, **opts)
140 def plain(self, text, **opts):
152 def plain(self, text, **opts):
141 self._ui.write(text, **opts)
153 self._ui.write(text, **opts)
154 def nested(self, field):
155 # nested data will be directly written to ui
156 return self
142 def end(self):
157 def end(self):
143 pass
158 pass
144
159
145 class debugformatter(baseformatter):
160 class debugformatter(baseformatter):
146 def __init__(self, ui, topic, opts):
161 def __init__(self, ui, topic, opts):
147 baseformatter.__init__(self, ui, topic, opts, _nullconverter)
162 baseformatter.__init__(self, ui, topic, opts, _nullconverter)
148 self._ui.write("%s = [\n" % self._topic)
163 self._ui.write("%s = [\n" % self._topic)
149 def _showitem(self):
164 def _showitem(self):
150 self._ui.write(" " + repr(self._item) + ",\n")
165 self._ui.write(" " + repr(self._item) + ",\n")
151 def end(self):
166 def end(self):
152 baseformatter.end(self)
167 baseformatter.end(self)
153 self._ui.write("]\n")
168 self._ui.write("]\n")
154
169
155 class pickleformatter(baseformatter):
170 class pickleformatter(baseformatter):
156 def __init__(self, ui, topic, opts):
171 def __init__(self, ui, topic, opts):
157 baseformatter.__init__(self, ui, topic, opts, _nullconverter)
172 baseformatter.__init__(self, ui, topic, opts, _nullconverter)
158 self._data = []
173 self._data = []
159 def _showitem(self):
174 def _showitem(self):
160 self._data.append(self._item)
175 self._data.append(self._item)
161 def end(self):
176 def end(self):
162 baseformatter.end(self)
177 baseformatter.end(self)
163 self._ui.write(pickle.dumps(self._data))
178 self._ui.write(pickle.dumps(self._data))
164
179
165 def _jsonifyobj(v):
180 def _jsonifyobj(v):
166 if isinstance(v, dict):
181 if isinstance(v, dict):
167 xs = ['"%s": %s' % (encoding.jsonescape(k), _jsonifyobj(u))
182 xs = ['"%s": %s' % (encoding.jsonescape(k), _jsonifyobj(u))
168 for k, u in sorted(v.iteritems())]
183 for k, u in sorted(v.iteritems())]
169 return '{' + ', '.join(xs) + '}'
184 return '{' + ', '.join(xs) + '}'
170 elif isinstance(v, (list, tuple)):
185 elif isinstance(v, (list, tuple)):
171 return '[' + ', '.join(_jsonifyobj(e) for e in v) + ']'
186 return '[' + ', '.join(_jsonifyobj(e) for e in v) + ']'
172 elif v is None:
187 elif v is None:
173 return 'null'
188 return 'null'
174 elif v is True:
189 elif v is True:
175 return 'true'
190 return 'true'
176 elif v is False:
191 elif v is False:
177 return 'false'
192 return 'false'
178 elif isinstance(v, (int, float)):
193 elif isinstance(v, (int, float)):
179 return str(v)
194 return str(v)
180 else:
195 else:
181 return '"%s"' % encoding.jsonescape(v)
196 return '"%s"' % encoding.jsonescape(v)
182
197
183 class jsonformatter(baseformatter):
198 class jsonformatter(baseformatter):
184 def __init__(self, ui, topic, opts):
199 def __init__(self, ui, topic, opts):
185 baseformatter.__init__(self, ui, topic, opts, _nullconverter)
200 baseformatter.__init__(self, ui, topic, opts, _nullconverter)
186 self._ui.write("[")
201 self._ui.write("[")
187 self._ui._first = True
202 self._ui._first = True
188 def _showitem(self):
203 def _showitem(self):
189 if self._ui._first:
204 if self._ui._first:
190 self._ui._first = False
205 self._ui._first = False
191 else:
206 else:
192 self._ui.write(",")
207 self._ui.write(",")
193
208
194 self._ui.write("\n {\n")
209 self._ui.write("\n {\n")
195 first = True
210 first = True
196 for k, v in sorted(self._item.items()):
211 for k, v in sorted(self._item.items()):
197 if first:
212 if first:
198 first = False
213 first = False
199 else:
214 else:
200 self._ui.write(",\n")
215 self._ui.write(",\n")
201 self._ui.write(' "%s": %s' % (k, _jsonifyobj(v)))
216 self._ui.write(' "%s": %s' % (k, _jsonifyobj(v)))
202 self._ui.write("\n }")
217 self._ui.write("\n }")
203 def end(self):
218 def end(self):
204 baseformatter.end(self)
219 baseformatter.end(self)
205 self._ui.write("\n]\n")
220 self._ui.write("\n]\n")
206
221
207 class _templateconverter(object):
222 class _templateconverter(object):
208 '''convert non-primitive data types to be processed by templater'''
223 '''convert non-primitive data types to be processed by templater'''
209 @staticmethod
224 @staticmethod
210 def formatdate(date, fmt):
225 def formatdate(date, fmt):
211 '''return date tuple'''
226 '''return date tuple'''
212 return date
227 return date
213 @staticmethod
228 @staticmethod
214 def formatdict(data, key, value, fmt, sep):
229 def formatdict(data, key, value, fmt, sep):
215 '''build object that can be evaluated as either plain string or dict'''
230 '''build object that can be evaluated as either plain string or dict'''
216 data = util.sortdict(_iteritems(data))
231 data = util.sortdict(_iteritems(data))
217 def f():
232 def f():
218 yield _plainconverter.formatdict(data, key, value, fmt, sep)
233 yield _plainconverter.formatdict(data, key, value, fmt, sep)
219 return templatekw._hybrid(f(), data, lambda k: {key: k, value: data[k]},
234 return templatekw._hybrid(f(), data, lambda k: {key: k, value: data[k]},
220 lambda d: fmt % (d[key], d[value]))
235 lambda d: fmt % (d[key], d[value]))
221 @staticmethod
236 @staticmethod
222 def formatlist(data, name, fmt, sep):
237 def formatlist(data, name, fmt, sep):
223 '''build object that can be evaluated as either plain string or list'''
238 '''build object that can be evaluated as either plain string or list'''
224 data = list(data)
239 data = list(data)
225 def f():
240 def f():
226 yield _plainconverter.formatlist(data, name, fmt, sep)
241 yield _plainconverter.formatlist(data, name, fmt, sep)
227 return templatekw._hybrid(f(), data, lambda x: {name: x},
242 return templatekw._hybrid(f(), data, lambda x: {name: x},
228 lambda d: fmt % d[name])
243 lambda d: fmt % d[name])
229
244
230 class templateformatter(baseformatter):
245 class templateformatter(baseformatter):
231 def __init__(self, ui, topic, opts):
246 def __init__(self, ui, topic, opts):
232 baseformatter.__init__(self, ui, topic, opts, _templateconverter)
247 baseformatter.__init__(self, ui, topic, opts, _templateconverter)
233 self._topic = topic
248 self._topic = topic
234 self._t = gettemplater(ui, topic, opts.get('template', ''))
249 self._t = gettemplater(ui, topic, opts.get('template', ''))
235 def _showitem(self):
250 def _showitem(self):
236 g = self._t(self._topic, ui=self._ui, **self._item)
251 g = self._t(self._topic, ui=self._ui, **self._item)
237 self._ui.write(templater.stringify(g))
252 self._ui.write(templater.stringify(g))
238
253
239 def lookuptemplate(ui, topic, tmpl):
254 def lookuptemplate(ui, topic, tmpl):
240 # looks like a literal template?
255 # looks like a literal template?
241 if '{' in tmpl:
256 if '{' in tmpl:
242 return tmpl, None
257 return tmpl, None
243
258
244 # perhaps a stock style?
259 # perhaps a stock style?
245 if not os.path.split(tmpl)[0]:
260 if not os.path.split(tmpl)[0]:
246 mapname = (templater.templatepath('map-cmdline.' + tmpl)
261 mapname = (templater.templatepath('map-cmdline.' + tmpl)
247 or templater.templatepath(tmpl))
262 or templater.templatepath(tmpl))
248 if mapname and os.path.isfile(mapname):
263 if mapname and os.path.isfile(mapname):
249 return None, mapname
264 return None, mapname
250
265
251 # perhaps it's a reference to [templates]
266 # perhaps it's a reference to [templates]
252 t = ui.config('templates', tmpl)
267 t = ui.config('templates', tmpl)
253 if t:
268 if t:
254 return templater.unquotestring(t), None
269 return templater.unquotestring(t), None
255
270
256 if tmpl == 'list':
271 if tmpl == 'list':
257 ui.write(_("available styles: %s\n") % templater.stylelist())
272 ui.write(_("available styles: %s\n") % templater.stylelist())
258 raise error.Abort(_("specify a template"))
273 raise error.Abort(_("specify a template"))
259
274
260 # perhaps it's a path to a map or a template
275 # perhaps it's a path to a map or a template
261 if ('/' in tmpl or '\\' in tmpl) and os.path.isfile(tmpl):
276 if ('/' in tmpl or '\\' in tmpl) and os.path.isfile(tmpl):
262 # is it a mapfile for a style?
277 # is it a mapfile for a style?
263 if os.path.basename(tmpl).startswith("map-"):
278 if os.path.basename(tmpl).startswith("map-"):
264 return None, os.path.realpath(tmpl)
279 return None, os.path.realpath(tmpl)
265 tmpl = open(tmpl).read()
280 tmpl = open(tmpl).read()
266 return tmpl, None
281 return tmpl, None
267
282
268 # constant string?
283 # constant string?
269 return tmpl, None
284 return tmpl, None
270
285
271 def gettemplater(ui, topic, spec):
286 def gettemplater(ui, topic, spec):
272 tmpl, mapfile = lookuptemplate(ui, topic, spec)
287 tmpl, mapfile = lookuptemplate(ui, topic, spec)
273 assert not (tmpl and mapfile)
288 assert not (tmpl and mapfile)
274 if mapfile:
289 if mapfile:
275 return templater.templater.frommapfile(mapfile)
290 return templater.templater.frommapfile(mapfile)
276 return maketemplater(ui, topic, tmpl)
291 return maketemplater(ui, topic, tmpl)
277
292
278 def maketemplater(ui, topic, tmpl, filters=None, cache=None):
293 def maketemplater(ui, topic, tmpl, filters=None, cache=None):
279 """Create a templater from a string template 'tmpl'"""
294 """Create a templater from a string template 'tmpl'"""
280 aliases = ui.configitems('templatealias')
295 aliases = ui.configitems('templatealias')
281 t = templater.templater(filters=filters, cache=cache, aliases=aliases)
296 t = templater.templater(filters=filters, cache=cache, aliases=aliases)
282 if tmpl:
297 if tmpl:
283 t.cache[topic] = tmpl
298 t.cache[topic] = tmpl
284 return t
299 return t
285
300
286 def formatter(ui, topic, opts):
301 def formatter(ui, topic, opts):
287 template = opts.get("template", "")
302 template = opts.get("template", "")
288 if template == "json":
303 if template == "json":
289 return jsonformatter(ui, topic, opts)
304 return jsonformatter(ui, topic, opts)
290 elif template == "pickle":
305 elif template == "pickle":
291 return pickleformatter(ui, topic, opts)
306 return pickleformatter(ui, topic, opts)
292 elif template == "debug":
307 elif template == "debug":
293 return debugformatter(ui, topic, opts)
308 return debugformatter(ui, topic, opts)
294 elif template != "":
309 elif template != "":
295 return templateformatter(ui, topic, opts)
310 return templateformatter(ui, topic, opts)
296 # developer config: ui.formatdebug
311 # developer config: ui.formatdebug
297 elif ui.configbool('ui', 'formatdebug'):
312 elif ui.configbool('ui', 'formatdebug'):
298 return debugformatter(ui, topic, opts)
313 return debugformatter(ui, topic, opts)
299 # deprecated config: ui.formatjson
314 # deprecated config: ui.formatjson
300 elif ui.configbool('ui', 'formatjson'):
315 elif ui.configbool('ui', 'formatjson'):
301 return jsonformatter(ui, topic, opts)
316 return jsonformatter(ui, topic, opts)
302 return plainformatter(ui, topic, opts)
317 return plainformatter(ui, topic, opts)
General Comments 0
You need to be logged in to leave comments. Login now