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