##// END OF EJS Templates
templater: take cStringIO out of indent
Matt Mackall -
r3635:7af1f54c default
parent child Browse files
Show More
@@ -1,537 +1,538 b''
1 # templater.py - template expansion for output
1 # templater.py - template expansion for output
2 #
2 #
3 # Copyright 2005, 2006 Matt Mackall <mpm@selenic.com>
3 # Copyright 2005, 2006 Matt Mackall <mpm@selenic.com>
4 #
4 #
5 # This software may be used and distributed according to the terms
5 # This software may be used and distributed according to the terms
6 # of the GNU General Public License, incorporated herein by reference.
6 # of the GNU General Public License, incorporated herein by reference.
7
7
8 from demandload import demandload
8 from demandload import demandload
9 from i18n import gettext as _
9 from i18n import gettext as _
10 from node import *
10 from node import *
11 demandload(globals(), "cStringIO cgi re sys os time urllib util textwrap")
11 demandload(globals(), "cStringIO cgi re sys os time urllib util textwrap")
12
12
13 def parsestring(s, quoted=True):
13 def parsestring(s, quoted=True):
14 '''parse a string using simple c-like syntax.
14 '''parse a string using simple c-like syntax.
15 string must be in quotes if quoted is True.'''
15 string must be in quotes if quoted is True.'''
16 if quoted:
16 if quoted:
17 first = s[0]
17 first = s[0]
18 if len(s) < 2: raise SyntaxError(_('string too short'))
18 if len(s) < 2: raise SyntaxError(_('string too short'))
19 if first not in "'\"": raise SyntaxError(_('invalid quote'))
19 if first not in "'\"": raise SyntaxError(_('invalid quote'))
20 if s[-1] != first: raise SyntaxError(_('unmatched quotes'))
20 if s[-1] != first: raise SyntaxError(_('unmatched quotes'))
21 s = s[1:-1].decode('string_escape')
21 s = s[1:-1].decode('string_escape')
22 if first in s: raise SyntaxError(_('string ends early'))
22 if first in s: raise SyntaxError(_('string ends early'))
23 return s
23 return s
24
24
25 return s.decode('string_escape')
25 return s.decode('string_escape')
26
26
27 class templater(object):
27 class templater(object):
28 '''template expansion engine.
28 '''template expansion engine.
29
29
30 template expansion works like this. a map file contains key=value
30 template expansion works like this. a map file contains key=value
31 pairs. if value is quoted, it is treated as string. otherwise, it
31 pairs. if value is quoted, it is treated as string. otherwise, it
32 is treated as name of template file.
32 is treated as name of template file.
33
33
34 templater is asked to expand a key in map. it looks up key, and
34 templater is asked to expand a key in map. it looks up key, and
35 looks for atrings like this: {foo}. it expands {foo} by looking up
35 looks for atrings like this: {foo}. it expands {foo} by looking up
36 foo in map, and substituting it. expansion is recursive: it stops
36 foo in map, and substituting it. expansion is recursive: it stops
37 when there is no more {foo} to replace.
37 when there is no more {foo} to replace.
38
38
39 expansion also allows formatting and filtering.
39 expansion also allows formatting and filtering.
40
40
41 format uses key to expand each item in list. syntax is
41 format uses key to expand each item in list. syntax is
42 {key%format}.
42 {key%format}.
43
43
44 filter uses function to transform value. syntax is
44 filter uses function to transform value. syntax is
45 {key|filter1|filter2|...}.'''
45 {key|filter1|filter2|...}.'''
46
46
47 def __init__(self, mapfile, filters={}, defaults={}, cache={}):
47 def __init__(self, mapfile, filters={}, defaults={}, cache={}):
48 '''set up template engine.
48 '''set up template engine.
49 mapfile is name of file to read map definitions from.
49 mapfile is name of file to read map definitions from.
50 filters is dict of functions. each transforms a value into another.
50 filters is dict of functions. each transforms a value into another.
51 defaults is dict of default map definitions.'''
51 defaults is dict of default map definitions.'''
52 self.mapfile = mapfile or 'template'
52 self.mapfile = mapfile or 'template'
53 self.cache = cache.copy()
53 self.cache = cache.copy()
54 self.map = {}
54 self.map = {}
55 self.base = (mapfile and os.path.dirname(mapfile)) or ''
55 self.base = (mapfile and os.path.dirname(mapfile)) or ''
56 self.filters = filters
56 self.filters = filters
57 self.defaults = defaults
57 self.defaults = defaults
58
58
59 if not mapfile:
59 if not mapfile:
60 return
60 return
61 i = 0
61 i = 0
62 for l in file(mapfile):
62 for l in file(mapfile):
63 l = l.strip()
63 l = l.strip()
64 i += 1
64 i += 1
65 if not l or l[0] in '#;': continue
65 if not l or l[0] in '#;': continue
66 m = re.match(r'([a-zA-Z_][a-zA-Z0-9_]*)\s*=\s*(.+)$', l)
66 m = re.match(r'([a-zA-Z_][a-zA-Z0-9_]*)\s*=\s*(.+)$', l)
67 if m:
67 if m:
68 key, val = m.groups()
68 key, val = m.groups()
69 if val[0] in "'\"":
69 if val[0] in "'\"":
70 try:
70 try:
71 self.cache[key] = parsestring(val)
71 self.cache[key] = parsestring(val)
72 except SyntaxError, inst:
72 except SyntaxError, inst:
73 raise SyntaxError('%s:%s: %s' %
73 raise SyntaxError('%s:%s: %s' %
74 (mapfile, i, inst.args[0]))
74 (mapfile, i, inst.args[0]))
75 else:
75 else:
76 self.map[key] = os.path.join(self.base, val)
76 self.map[key] = os.path.join(self.base, val)
77 else:
77 else:
78 raise SyntaxError(_("%s:%s: parse error") % (mapfile, i))
78 raise SyntaxError(_("%s:%s: parse error") % (mapfile, i))
79
79
80 def __contains__(self, key):
80 def __contains__(self, key):
81 return key in self.cache
81 return key in self.cache
82
82
83 def __call__(self, t, **map):
83 def __call__(self, t, **map):
84 '''perform expansion.
84 '''perform expansion.
85 t is name of map element to expand.
85 t is name of map element to expand.
86 map is added elements to use during expansion.'''
86 map is added elements to use during expansion.'''
87 m = self.defaults.copy()
87 m = self.defaults.copy()
88 m.update(map)
88 m.update(map)
89 try:
89 try:
90 tmpl = self.cache[t]
90 tmpl = self.cache[t]
91 except KeyError:
91 except KeyError:
92 try:
92 try:
93 tmpl = self.cache[t] = file(self.map[t]).read()
93 tmpl = self.cache[t] = file(self.map[t]).read()
94 except IOError, inst:
94 except IOError, inst:
95 raise IOError(inst.args[0], _('template file %s: %s') %
95 raise IOError(inst.args[0], _('template file %s: %s') %
96 (self.map[t], inst.args[1]))
96 (self.map[t], inst.args[1]))
97 return self.template(tmpl, self.filters, **m)
97 return self.template(tmpl, self.filters, **m)
98
98
99 template_re = re.compile(r"[#{]([a-zA-Z_][a-zA-Z0-9_]*)"
99 template_re = re.compile(r"[#{]([a-zA-Z_][a-zA-Z0-9_]*)"
100 r"((%[a-zA-Z_][a-zA-Z0-9_]*)*)"
100 r"((%[a-zA-Z_][a-zA-Z0-9_]*)*)"
101 r"((\|[a-zA-Z_][a-zA-Z0-9_]*)*)[#}]")
101 r"((\|[a-zA-Z_][a-zA-Z0-9_]*)*)[#}]")
102
102
103 def template(self, tmpl, filters={}, **map):
103 def template(self, tmpl, filters={}, **map):
104 lm = map.copy()
104 lm = map.copy()
105 while tmpl:
105 while tmpl:
106 m = self.template_re.search(tmpl)
106 m = self.template_re.search(tmpl)
107 if m:
107 if m:
108 start, end = m.span(0)
108 start, end = m.span(0)
109 s, e = tmpl[start], tmpl[end - 1]
109 s, e = tmpl[start], tmpl[end - 1]
110 key = m.group(1)
110 key = m.group(1)
111 if ((s == '#' and e != '#') or (s == '{' and e != '}')):
111 if ((s == '#' and e != '#') or (s == '{' and e != '}')):
112 raise SyntaxError(_("'%s'/'%s' mismatch expanding '%s'") %
112 raise SyntaxError(_("'%s'/'%s' mismatch expanding '%s'") %
113 (s, e, key))
113 (s, e, key))
114 if start:
114 if start:
115 yield tmpl[:start]
115 yield tmpl[:start]
116 v = map.get(key, "")
116 v = map.get(key, "")
117 v = callable(v) and v(**map) or v
117 v = callable(v) and v(**map) or v
118
118
119 format = m.group(2)
119 format = m.group(2)
120 fl = m.group(4)
120 fl = m.group(4)
121
121
122 if format:
122 if format:
123 try:
123 try:
124 q = v.__iter__
124 q = v.__iter__
125 except AttributeError:
125 except AttributeError:
126 raise SyntaxError(_("Error expanding '%s%s'")
126 raise SyntaxError(_("Error expanding '%s%s'")
127 % (key, format))
127 % (key, format))
128 for i in q():
128 for i in q():
129 lm.update(i)
129 lm.update(i)
130 yield self(format[1:], **lm)
130 yield self(format[1:], **lm)
131
131
132 v = ""
132 v = ""
133
133
134 elif fl:
134 elif fl:
135 for f in fl.split("|")[1:]:
135 for f in fl.split("|")[1:]:
136 v = filters[f](v)
136 v = filters[f](v)
137
137
138 yield v
138 yield v
139 tmpl = tmpl[end:]
139 tmpl = tmpl[end:]
140 else:
140 else:
141 yield tmpl
141 yield tmpl
142 break
142 break
143
143
144 agescales = [("second", 1),
144 agescales = [("second", 1),
145 ("minute", 60),
145 ("minute", 60),
146 ("hour", 3600),
146 ("hour", 3600),
147 ("day", 3600 * 24),
147 ("day", 3600 * 24),
148 ("week", 3600 * 24 * 7),
148 ("week", 3600 * 24 * 7),
149 ("month", 3600 * 24 * 30),
149 ("month", 3600 * 24 * 30),
150 ("year", 3600 * 24 * 365)]
150 ("year", 3600 * 24 * 365)]
151
151
152 agescales.reverse()
152 agescales.reverse()
153
153
154 def age(date):
154 def age(date):
155 '''turn a (timestamp, tzoff) tuple into an age string.'''
155 '''turn a (timestamp, tzoff) tuple into an age string.'''
156
156
157 def plural(t, c):
157 def plural(t, c):
158 if c == 1:
158 if c == 1:
159 return t
159 return t
160 return t + "s"
160 return t + "s"
161 def fmt(t, c):
161 def fmt(t, c):
162 return "%d %s" % (c, plural(t, c))
162 return "%d %s" % (c, plural(t, c))
163
163
164 now = time.time()
164 now = time.time()
165 then = date[0]
165 then = date[0]
166 delta = max(1, int(now - then))
166 delta = max(1, int(now - then))
167
167
168 for t, s in agescales:
168 for t, s in agescales:
169 n = delta / s
169 n = delta / s
170 if n >= 2 or s == 1:
170 if n >= 2 or s == 1:
171 return fmt(t, n)
171 return fmt(t, n)
172
172
173 def stringify(thing):
173 def stringify(thing):
174 '''turn nested template iterator into string.'''
174 '''turn nested template iterator into string.'''
175 def flatten(thing):
175 def flatten(thing):
176 if hasattr(thing, '__iter__'):
176 if hasattr(thing, '__iter__'):
177 for t in thing:
177 for t in thing:
178 for i in flatten(t):
178 for i in flatten(t):
179 yield i
179 yield i
180 else:
180 else:
181 yield str(thing)
181 yield str(thing)
182 return "".join(flatten(thing))
182 return "".join(flatten(thing))
183
183
184 para_re = None
184 para_re = None
185 space_re = None
185 space_re = None
186
186
187 def fill(text, width):
187 def fill(text, width):
188 '''fill many paragraphs.'''
188 '''fill many paragraphs.'''
189 global para_re, space_re
189 global para_re, space_re
190 if para_re is None:
190 if para_re is None:
191 para_re = re.compile('(\n\n|\n\\s*[-*]\\s*)', re.M)
191 para_re = re.compile('(\n\n|\n\\s*[-*]\\s*)', re.M)
192 space_re = re.compile(r' +')
192 space_re = re.compile(r' +')
193
193
194 def findparas():
194 def findparas():
195 start = 0
195 start = 0
196 while True:
196 while True:
197 m = para_re.search(text, start)
197 m = para_re.search(text, start)
198 if not m:
198 if not m:
199 w = len(text)
199 w = len(text)
200 while w > start and text[w-1].isspace(): w -= 1
200 while w > start and text[w-1].isspace(): w -= 1
201 yield text[start:w], text[w:]
201 yield text[start:w], text[w:]
202 break
202 break
203 yield text[start:m.start(0)], m.group(1)
203 yield text[start:m.start(0)], m.group(1)
204 start = m.end(1)
204 start = m.end(1)
205
205
206 return "".join([space_re.sub(' ', textwrap.fill(para, width)) + rest
206 return "".join([space_re.sub(' ', textwrap.fill(para, width)) + rest
207 for para, rest in findparas()])
207 for para, rest in findparas()])
208
208
209 def firstline(text):
209 def firstline(text):
210 '''return the first line of text'''
210 '''return the first line of text'''
211 try:
211 try:
212 return text.splitlines(1)[0].rstrip('\r\n')
212 return text.splitlines(1)[0].rstrip('\r\n')
213 except IndexError:
213 except IndexError:
214 return ''
214 return ''
215
215
216 def isodate(date):
216 def isodate(date):
217 '''turn a (timestamp, tzoff) tuple into an iso 8631 date and time.'''
217 '''turn a (timestamp, tzoff) tuple into an iso 8631 date and time.'''
218 return util.datestr(date, format='%Y-%m-%d %H:%M')
218 return util.datestr(date, format='%Y-%m-%d %H:%M')
219
219
220 def hgdate(date):
220 def hgdate(date):
221 '''turn a (timestamp, tzoff) tuple into an hg cset timestamp.'''
221 '''turn a (timestamp, tzoff) tuple into an hg cset timestamp.'''
222 return "%d %d" % date
222 return "%d %d" % date
223
223
224 def nl2br(text):
224 def nl2br(text):
225 '''replace raw newlines with xhtml line breaks.'''
225 '''replace raw newlines with xhtml line breaks.'''
226 return text.replace('\n', '<br/>\n')
226 return text.replace('\n', '<br/>\n')
227
227
228 def obfuscate(text):
228 def obfuscate(text):
229 text = unicode(text, 'utf-8', 'replace')
229 text = unicode(text, 'utf-8', 'replace')
230 return ''.join(['&#%d;' % ord(c) for c in text])
230 return ''.join(['&#%d;' % ord(c) for c in text])
231
231
232 def domain(author):
232 def domain(author):
233 '''get domain of author, or empty string if none.'''
233 '''get domain of author, or empty string if none.'''
234 f = author.find('@')
234 f = author.find('@')
235 if f == -1: return ''
235 if f == -1: return ''
236 author = author[f+1:]
236 author = author[f+1:]
237 f = author.find('>')
237 f = author.find('>')
238 if f >= 0: author = author[:f]
238 if f >= 0: author = author[:f]
239 return author
239 return author
240
240
241 def email(author):
241 def email(author):
242 '''get email of author.'''
242 '''get email of author.'''
243 r = author.find('>')
243 r = author.find('>')
244 if r == -1: r = None
244 if r == -1: r = None
245 return author[author.find('<')+1:r]
245 return author[author.find('<')+1:r]
246
246
247 def person(author):
247 def person(author):
248 '''get name of author, or else username.'''
248 '''get name of author, or else username.'''
249 f = author.find('<')
249 f = author.find('<')
250 if f == -1: return util.shortuser(author)
250 if f == -1: return util.shortuser(author)
251 return author[:f].rstrip()
251 return author[:f].rstrip()
252
252
253 def shortdate(date):
253 def shortdate(date):
254 '''turn (timestamp, tzoff) tuple into iso 8631 date.'''
254 '''turn (timestamp, tzoff) tuple into iso 8631 date.'''
255 return util.datestr(date, format='%Y-%m-%d', timezone=False)
255 return util.datestr(date, format='%Y-%m-%d', timezone=False)
256
256
257 def indent(text, prefix):
257 def indent(text, prefix):
258 '''indent each non-empty line of text after first with prefix.'''
258 '''indent each non-empty line of text after first with prefix.'''
259 fp = cStringIO.StringIO()
260 lines = text.splitlines()
259 lines = text.splitlines()
261 num_lines = len(lines)
260 num_lines = len(lines)
262 for i in xrange(num_lines):
261 def indenter():
263 l = lines[i]
262 for i in xrange(num_lines):
264 if i and l.strip(): fp.write(prefix)
263 l = lines[i]
265 fp.write(l)
264 if i and l.strip():
266 if i < num_lines - 1 or text.endswith('\n'):
265 yield prefix
267 fp.write('\n')
266 yield l
268 return fp.getvalue()
267 if i < num_lines - 1 or text.endswith('\n'):
268 yield '\n'
269 return "".join(indenter())
269
270
270 common_filters = {
271 common_filters = {
271 "addbreaks": nl2br,
272 "addbreaks": nl2br,
272 "basename": os.path.basename,
273 "basename": os.path.basename,
273 "age": age,
274 "age": age,
274 "date": lambda x: util.datestr(x),
275 "date": lambda x: util.datestr(x),
275 "domain": domain,
276 "domain": domain,
276 "email": email,
277 "email": email,
277 "escape": lambda x: cgi.escape(x, True),
278 "escape": lambda x: cgi.escape(x, True),
278 "fill68": lambda x: fill(x, width=68),
279 "fill68": lambda x: fill(x, width=68),
279 "fill76": lambda x: fill(x, width=76),
280 "fill76": lambda x: fill(x, width=76),
280 "firstline": firstline,
281 "firstline": firstline,
281 "tabindent": lambda x: indent(x, '\t'),
282 "tabindent": lambda x: indent(x, '\t'),
282 "hgdate": hgdate,
283 "hgdate": hgdate,
283 "isodate": isodate,
284 "isodate": isodate,
284 "obfuscate": obfuscate,
285 "obfuscate": obfuscate,
285 "permissions": lambda x: x and "-rwxr-xr-x" or "-rw-r--r--",
286 "permissions": lambda x: x and "-rwxr-xr-x" or "-rw-r--r--",
286 "person": person,
287 "person": person,
287 "rfc822date": lambda x: util.datestr(x, "%a, %d %b %Y %H:%M:%S"),
288 "rfc822date": lambda x: util.datestr(x, "%a, %d %b %Y %H:%M:%S"),
288 "short": lambda x: x[:12],
289 "short": lambda x: x[:12],
289 "shortdate": shortdate,
290 "shortdate": shortdate,
290 "stringify": stringify,
291 "stringify": stringify,
291 "strip": lambda x: x.strip(),
292 "strip": lambda x: x.strip(),
292 "urlescape": lambda x: urllib.quote(x),
293 "urlescape": lambda x: urllib.quote(x),
293 "user": lambda x: util.shortuser(x),
294 "user": lambda x: util.shortuser(x),
294 "stringescape": lambda x: x.encode('string_escape'),
295 "stringescape": lambda x: x.encode('string_escape'),
295 }
296 }
296
297
297 def templatepath(name=None):
298 def templatepath(name=None):
298 '''return location of template file or directory (if no name).
299 '''return location of template file or directory (if no name).
299 returns None if not found.'''
300 returns None if not found.'''
300
301
301 # executable version (py2exe) doesn't support __file__
302 # executable version (py2exe) doesn't support __file__
302 if hasattr(sys, 'frozen'):
303 if hasattr(sys, 'frozen'):
303 module = sys.executable
304 module = sys.executable
304 else:
305 else:
305 module = __file__
306 module = __file__
306 for f in 'templates', '../templates':
307 for f in 'templates', '../templates':
307 fl = f.split('/')
308 fl = f.split('/')
308 if name: fl.append(name)
309 if name: fl.append(name)
309 p = os.path.join(os.path.dirname(module), *fl)
310 p = os.path.join(os.path.dirname(module), *fl)
310 if (name and os.path.exists(p)) or os.path.isdir(p):
311 if (name and os.path.exists(p)) or os.path.isdir(p):
311 return os.path.normpath(p)
312 return os.path.normpath(p)
312
313
313 class changeset_templater(object):
314 class changeset_templater(object):
314 '''format changeset information.'''
315 '''format changeset information.'''
315
316
316 def __init__(self, ui, repo, mapfile, dest=None):
317 def __init__(self, ui, repo, mapfile, dest=None):
317 self.t = templater(mapfile, common_filters,
318 self.t = templater(mapfile, common_filters,
318 cache={'parent': '{rev}:{node|short} ',
319 cache={'parent': '{rev}:{node|short} ',
319 'manifest': '{rev}:{node|short}',
320 'manifest': '{rev}:{node|short}',
320 'filecopy': '{name} ({source})'})
321 'filecopy': '{name} ({source})'})
321 self.ui = ui
322 self.ui = ui
322 self.dest = dest
323 self.dest = dest
323 self.repo = repo
324 self.repo = repo
324
325
325 def use_template(self, t):
326 def use_template(self, t):
326 '''set template string to use'''
327 '''set template string to use'''
327 self.t.cache['changeset'] = t
328 self.t.cache['changeset'] = t
328
329
329 def write(self, thing, header=False):
330 def write(self, thing, header=False):
330 '''write expanded template.
331 '''write expanded template.
331 uses in-order recursive traverse of iterators.'''
332 uses in-order recursive traverse of iterators.'''
332 dest = self.dest or self.ui
333 dest = self.dest or self.ui
333 for t in thing:
334 for t in thing:
334 if hasattr(t, '__iter__'):
335 if hasattr(t, '__iter__'):
335 self.write(t, header=header)
336 self.write(t, header=header)
336 elif header:
337 elif header:
337 dest.write_header(t)
338 dest.write_header(t)
338 else:
339 else:
339 dest.write(t)
340 dest.write(t)
340
341
341 def write_header(self, thing):
342 def write_header(self, thing):
342 self.write(thing, header=True)
343 self.write(thing, header=True)
343
344
344 def show(self, rev=0, changenode=None, brinfo=None, changes=None,
345 def show(self, rev=0, changenode=None, brinfo=None, changes=None,
345 copies=[], **props):
346 copies=[], **props):
346 '''show a single changeset or file revision'''
347 '''show a single changeset or file revision'''
347 log = self.repo.changelog
348 log = self.repo.changelog
348 if changenode is None:
349 if changenode is None:
349 changenode = log.node(rev)
350 changenode = log.node(rev)
350 elif not rev:
351 elif not rev:
351 rev = log.rev(changenode)
352 rev = log.rev(changenode)
352 if changes is None:
353 if changes is None:
353 changes = log.read(changenode)
354 changes = log.read(changenode)
354
355
355 def showlist(name, values, plural=None, **args):
356 def showlist(name, values, plural=None, **args):
356 '''expand set of values.
357 '''expand set of values.
357 name is name of key in template map.
358 name is name of key in template map.
358 values is list of strings or dicts.
359 values is list of strings or dicts.
359 plural is plural of name, if not simply name + 's'.
360 plural is plural of name, if not simply name + 's'.
360
361
361 expansion works like this, given name 'foo'.
362 expansion works like this, given name 'foo'.
362
363
363 if values is empty, expand 'no_foos'.
364 if values is empty, expand 'no_foos'.
364
365
365 if 'foo' not in template map, return values as a string,
366 if 'foo' not in template map, return values as a string,
366 joined by space.
367 joined by space.
367
368
368 expand 'start_foos'.
369 expand 'start_foos'.
369
370
370 for each value, expand 'foo'. if 'last_foo' in template
371 for each value, expand 'foo'. if 'last_foo' in template
371 map, expand it instead of 'foo' for last key.
372 map, expand it instead of 'foo' for last key.
372
373
373 expand 'end_foos'.
374 expand 'end_foos'.
374 '''
375 '''
375 if plural: names = plural
376 if plural: names = plural
376 else: names = name + 's'
377 else: names = name + 's'
377 if not values:
378 if not values:
378 noname = 'no_' + names
379 noname = 'no_' + names
379 if noname in self.t:
380 if noname in self.t:
380 yield self.t(noname, **args)
381 yield self.t(noname, **args)
381 return
382 return
382 if name not in self.t:
383 if name not in self.t:
383 if isinstance(values[0], str):
384 if isinstance(values[0], str):
384 yield ' '.join(values)
385 yield ' '.join(values)
385 else:
386 else:
386 for v in values:
387 for v in values:
387 yield dict(v, **args)
388 yield dict(v, **args)
388 return
389 return
389 startname = 'start_' + names
390 startname = 'start_' + names
390 if startname in self.t:
391 if startname in self.t:
391 yield self.t(startname, **args)
392 yield self.t(startname, **args)
392 vargs = args.copy()
393 vargs = args.copy()
393 def one(v, tag=name):
394 def one(v, tag=name):
394 try:
395 try:
395 vargs.update(v)
396 vargs.update(v)
396 except (AttributeError, ValueError):
397 except (AttributeError, ValueError):
397 try:
398 try:
398 for a, b in v:
399 for a, b in v:
399 vargs[a] = b
400 vargs[a] = b
400 except ValueError:
401 except ValueError:
401 vargs[name] = v
402 vargs[name] = v
402 return self.t(tag, **vargs)
403 return self.t(tag, **vargs)
403 lastname = 'last_' + name
404 lastname = 'last_' + name
404 if lastname in self.t:
405 if lastname in self.t:
405 last = values.pop()
406 last = values.pop()
406 else:
407 else:
407 last = None
408 last = None
408 for v in values:
409 for v in values:
409 yield one(v)
410 yield one(v)
410 if last is not None:
411 if last is not None:
411 yield one(last, tag=lastname)
412 yield one(last, tag=lastname)
412 endname = 'end_' + names
413 endname = 'end_' + names
413 if endname in self.t:
414 if endname in self.t:
414 yield self.t(endname, **args)
415 yield self.t(endname, **args)
415
416
416 def showbranches(**args):
417 def showbranches(**args):
417 branch = changes[5].get("branch")
418 branch = changes[5].get("branch")
418 if branch:
419 if branch:
419 yield showlist('branch', [branch], plural='branches', **args)
420 yield showlist('branch', [branch], plural='branches', **args)
420 # add old style branches if requested
421 # add old style branches if requested
421 if brinfo and changenode in brinfo:
422 if brinfo and changenode in brinfo:
422 for x in showlist('branch', brinfo[changenode],
423 for x in showlist('branch', brinfo[changenode],
423 plural='branches', **args):
424 plural='branches', **args):
424 yield x
425 yield x
425
426
426 if self.ui.debugflag:
427 if self.ui.debugflag:
427 def showmanifest(**args):
428 def showmanifest(**args):
428 args = args.copy()
429 args = args.copy()
429 args.update(dict(rev=self.repo.manifest.rev(changes[0]),
430 args.update(dict(rev=self.repo.manifest.rev(changes[0]),
430 node=hex(changes[0])))
431 node=hex(changes[0])))
431 yield self.t('manifest', **args)
432 yield self.t('manifest', **args)
432 else:
433 else:
433 showmanifest = ''
434 showmanifest = ''
434
435
435 def showparents(**args):
436 def showparents(**args):
436 parents = [[('rev', log.rev(p)), ('node', hex(p))]
437 parents = [[('rev', log.rev(p)), ('node', hex(p))]
437 for p in log.parents(changenode)
438 for p in log.parents(changenode)
438 if self.ui.debugflag or p != nullid]
439 if self.ui.debugflag or p != nullid]
439 if (not self.ui.debugflag and len(parents) == 1 and
440 if (not self.ui.debugflag and len(parents) == 1 and
440 parents[0][0][1] == rev - 1):
441 parents[0][0][1] == rev - 1):
441 return
442 return
442 for x in showlist('parent', parents, **args):
443 for x in showlist('parent', parents, **args):
443 yield x
444 yield x
444
445
445 def showtags(**args):
446 def showtags(**args):
446 for x in showlist('tag', self.repo.nodetags(changenode), **args):
447 for x in showlist('tag', self.repo.nodetags(changenode), **args):
447 yield x
448 yield x
448
449
449 def showextras(**args):
450 def showextras(**args):
450 extras = changes[5].items()
451 extras = changes[5].items()
451 extras.sort()
452 extras.sort()
452 for key, value in extras:
453 for key, value in extras:
453 args = args.copy()
454 args = args.copy()
454 args.update(dict(key=key, value=value))
455 args.update(dict(key=key, value=value))
455 yield self.t('extra', **args)
456 yield self.t('extra', **args)
456
457
457 if self.ui.debugflag:
458 if self.ui.debugflag:
458 files = self.repo.status(log.parents(changenode)[0], changenode)[:3]
459 files = self.repo.status(log.parents(changenode)[0], changenode)[:3]
459 def showfiles(**args):
460 def showfiles(**args):
460 for x in showlist('file', files[0], **args): yield x
461 for x in showlist('file', files[0], **args): yield x
461 def showadds(**args):
462 def showadds(**args):
462 for x in showlist('file_add', files[1], **args): yield x
463 for x in showlist('file_add', files[1], **args): yield x
463 def showdels(**args):
464 def showdels(**args):
464 for x in showlist('file_del', files[2], **args): yield x
465 for x in showlist('file_del', files[2], **args): yield x
465 else:
466 else:
466 def showfiles(**args):
467 def showfiles(**args):
467 for x in showlist('file', changes[3], **args): yield x
468 for x in showlist('file', changes[3], **args): yield x
468 showadds = ''
469 showadds = ''
469 showdels = ''
470 showdels = ''
470
471
471 copies = [{'name': x[0], 'source': x[1]}
472 copies = [{'name': x[0], 'source': x[1]}
472 for x in copies]
473 for x in copies]
473 def showcopies(**args):
474 def showcopies(**args):
474 for x in showlist('file_copy', copies, plural='file_copies',
475 for x in showlist('file_copy', copies, plural='file_copies',
475 **args):
476 **args):
476 yield x
477 yield x
477
478
478 defprops = {
479 defprops = {
479 'author': changes[1],
480 'author': changes[1],
480 'branches': showbranches,
481 'branches': showbranches,
481 'date': changes[2],
482 'date': changes[2],
482 'desc': changes[4],
483 'desc': changes[4],
483 'file_adds': showadds,
484 'file_adds': showadds,
484 'file_dels': showdels,
485 'file_dels': showdels,
485 'files': showfiles,
486 'files': showfiles,
486 'file_copies': showcopies,
487 'file_copies': showcopies,
487 'manifest': showmanifest,
488 'manifest': showmanifest,
488 'node': hex(changenode),
489 'node': hex(changenode),
489 'parents': showparents,
490 'parents': showparents,
490 'rev': rev,
491 'rev': rev,
491 'tags': showtags,
492 'tags': showtags,
492 'extras': showextras,
493 'extras': showextras,
493 }
494 }
494 props = props.copy()
495 props = props.copy()
495 props.update(defprops)
496 props.update(defprops)
496
497
497 try:
498 try:
498 if self.ui.debugflag and 'header_debug' in self.t:
499 if self.ui.debugflag and 'header_debug' in self.t:
499 key = 'header_debug'
500 key = 'header_debug'
500 elif self.ui.quiet and 'header_quiet' in self.t:
501 elif self.ui.quiet and 'header_quiet' in self.t:
501 key = 'header_quiet'
502 key = 'header_quiet'
502 elif self.ui.verbose and 'header_verbose' in self.t:
503 elif self.ui.verbose and 'header_verbose' in self.t:
503 key = 'header_verbose'
504 key = 'header_verbose'
504 elif 'header' in self.t:
505 elif 'header' in self.t:
505 key = 'header'
506 key = 'header'
506 else:
507 else:
507 key = ''
508 key = ''
508 if key:
509 if key:
509 self.write_header(self.t(key, **props))
510 self.write_header(self.t(key, **props))
510 if self.ui.debugflag and 'changeset_debug' in self.t:
511 if self.ui.debugflag and 'changeset_debug' in self.t:
511 key = 'changeset_debug'
512 key = 'changeset_debug'
512 elif self.ui.quiet and 'changeset_quiet' in self.t:
513 elif self.ui.quiet and 'changeset_quiet' in self.t:
513 key = 'changeset_quiet'
514 key = 'changeset_quiet'
514 elif self.ui.verbose and 'changeset_verbose' in self.t:
515 elif self.ui.verbose and 'changeset_verbose' in self.t:
515 key = 'changeset_verbose'
516 key = 'changeset_verbose'
516 else:
517 else:
517 key = 'changeset'
518 key = 'changeset'
518 self.write(self.t(key, **props))
519 self.write(self.t(key, **props))
519 except KeyError, inst:
520 except KeyError, inst:
520 raise util.Abort(_("%s: no key named '%s'") % (self.t.mapfile,
521 raise util.Abort(_("%s: no key named '%s'") % (self.t.mapfile,
521 inst.args[0]))
522 inst.args[0]))
522 except SyntaxError, inst:
523 except SyntaxError, inst:
523 raise util.Abort(_('%s: %s') % (self.t.mapfile, inst.args[0]))
524 raise util.Abort(_('%s: %s') % (self.t.mapfile, inst.args[0]))
524
525
525 class stringio(object):
526 class stringio(object):
526 '''wrap cStringIO for use by changeset_templater.'''
527 '''wrap cStringIO for use by changeset_templater.'''
527 def __init__(self):
528 def __init__(self):
528 self.fp = cStringIO.StringIO()
529 self.fp = cStringIO.StringIO()
529
530
530 def write(self, *args):
531 def write(self, *args):
531 for a in args:
532 for a in args:
532 self.fp.write(a)
533 self.fp.write(a)
533
534
534 write_header = write
535 write_header = write
535
536
536 def __getattr__(self, key):
537 def __getattr__(self, key):
537 return getattr(self.fp, key)
538 return getattr(self.fp, key)
General Comments 0
You need to be logged in to leave comments. Login now