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