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