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