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