Show More
@@ -338,15 +338,20 b' def trimuser(ui, name, rev, revcache):' | |||||
338 | return user |
|
338 | return user | |
339 |
|
339 | |||
340 | class changeset_templater(object): |
|
340 | class changeset_templater(object): | |
|
341 | '''use templater module to format changeset information.''' | |||
|
342 | ||||
341 | def __init__(self, ui, repo, mapfile): |
|
343 | def __init__(self, ui, repo, mapfile): | |
342 | self.t = templater.templater(mapfile, templater.common_filters) |
|
344 | self.t = templater.templater(mapfile, templater.common_filters) | |
343 | self.ui = ui |
|
345 | self.ui = ui | |
344 | self.repo = repo |
|
346 | self.repo = repo | |
345 |
|
347 | |||
346 | def use_template(self, t): |
|
348 | def use_template(self, t): | |
|
349 | '''set template string to use''' | |||
347 | self.t.cache['template'] = t |
|
350 | self.t.cache['template'] = t | |
348 |
|
351 | |||
349 | def write(self, thing): |
|
352 | def write(self, thing): | |
|
353 | '''write expanded template. | |||
|
354 | uses in-order recursive traverse of iterators.''' | |||
350 | for t in thing: |
|
355 | for t in thing: | |
351 | if hasattr(t, '__iter__'): |
|
356 | if hasattr(t, '__iter__'): | |
352 | self.write(t) |
|
357 | self.write(t) | |
@@ -354,7 +359,7 b' class changeset_templater(object):' | |||||
354 | self.ui.write(t) |
|
359 | self.ui.write(t) | |
355 |
|
360 | |||
356 | def show(self, rev=0, changenode=None, brinfo=None): |
|
361 | def show(self, rev=0, changenode=None, brinfo=None): | |
357 |
|
|
362 | '''show a single changeset or file revision''' | |
358 | log = self.repo.changelog |
|
363 | log = self.repo.changelog | |
359 | if changenode is None: |
|
364 | if changenode is None: | |
360 | changenode = log.node(rev) |
|
365 | changenode = log.node(rev) | |
@@ -364,6 +369,25 b' class changeset_templater(object):' | |||||
364 | changes = log.read(changenode) |
|
369 | changes = log.read(changenode) | |
365 |
|
370 | |||
366 | def showlist(name, values, plural=None, **args): |
|
371 | def showlist(name, values, plural=None, **args): | |
|
372 | '''expand set of values. | |||
|
373 | name is name of key in template map. | |||
|
374 | values is list of strings or dicts. | |||
|
375 | plural is plural of name, if not simply name + 's'. | |||
|
376 | ||||
|
377 | expansion works like this, given name 'foo'. | |||
|
378 | ||||
|
379 | if values is empty, expand 'no_foos'. | |||
|
380 | ||||
|
381 | if 'foo' not in template map, return values as a string, | |||
|
382 | joined by space. | |||
|
383 | ||||
|
384 | expand 'start_foos'. | |||
|
385 | ||||
|
386 | for each value, expand 'foo'. if 'last_foo' in template | |||
|
387 | map, expand it instead of 'foo' for last key. | |||
|
388 | ||||
|
389 | expand 'end_foos'. | |||
|
390 | ''' | |||
367 | if plural: names = plural |
|
391 | if plural: names = plural | |
368 | else: names = name + 's' |
|
392 | else: names = name + 's' | |
369 | if not values: |
|
393 | if not values: | |
@@ -371,13 +395,13 b' class changeset_templater(object):' | |||||
371 | if noname in self.t: |
|
395 | if noname in self.t: | |
372 | yield self.t(noname, **args) |
|
396 | yield self.t(noname, **args) | |
373 | return |
|
397 | return | |
374 | vargs = args.copy() |
|
|||
375 | if name not in self.t: |
|
398 | if name not in self.t: | |
376 | yield ' '.join(values) |
|
399 | yield ' '.join(values) | |
377 | return |
|
400 | return | |
378 | startname = 'start_' + names |
|
401 | startname = 'start_' + names | |
379 | if startname in self.t: |
|
402 | if startname in self.t: | |
380 | yield self.t(startname, **args) |
|
403 | yield self.t(startname, **args) | |
|
404 | vargs = args.copy() | |||
381 | def one(v, tag=name): |
|
405 | def one(v, tag=name): | |
382 | try: |
|
406 | try: | |
383 | vargs.update(v) |
|
407 | vargs.update(v) | |
@@ -464,12 +488,14 b' class changeset_templater(object):' | |||||
464 | raise util.Abort(_('%s: %s') % (self.t.mapfile, inst.args[0])) |
|
488 | raise util.Abort(_('%s: %s') % (self.t.mapfile, inst.args[0])) | |
465 |
|
489 | |||
466 | class changeset_printer(object): |
|
490 | class changeset_printer(object): | |
|
491 | '''show changeset information when templating not requested.''' | |||
|
492 | ||||
467 | def __init__(self, ui, repo): |
|
493 | def __init__(self, ui, repo): | |
468 | self.ui = ui |
|
494 | self.ui = ui | |
469 | self.repo = repo |
|
495 | self.repo = repo | |
470 |
|
496 | |||
471 | def show(self, rev=0, changenode=None, brinfo=None): |
|
497 | def show(self, rev=0, changenode=None, brinfo=None): | |
472 |
|
|
498 | '''show a single changeset or file revision''' | |
473 | log = self.repo.changelog |
|
499 | log = self.repo.changelog | |
474 | if changenode is None: |
|
500 | if changenode is None: | |
475 | changenode = log.node(rev) |
|
501 | changenode = log.node(rev) | |
@@ -530,6 +556,9 b' class changeset_printer(object):' | |||||
530 | self.ui.status("\n") |
|
556 | self.ui.status("\n") | |
531 |
|
557 | |||
532 | def show_changeset(ui, repo, opts): |
|
558 | def show_changeset(ui, repo, opts): | |
|
559 | '''show one changeset. uses template or regular display. caller | |||
|
560 | can pass in 'map_file' and 'template' options in opts.''' | |||
|
561 | ||||
533 | tmpl = opts.get('template') |
|
562 | tmpl = opts.get('template') | |
534 | if tmpl: |
|
563 | if tmpl: | |
535 | tmpl = templater.parsestring(tmpl, quoted=False) |
|
564 | tmpl = templater.parsestring(tmpl, quoted=False) |
@@ -1,3 +1,10 b'' | |||||
|
1 | # templater.py - template expansion for output | |||
|
2 | # | |||
|
3 | # Copyright 2005, 2006 Matt Mackall <mpm@selenic.com> | |||
|
4 | # | |||
|
5 | # This software may be used and distributed according to the terms | |||
|
6 | # of the GNU General Public License, incorporated herein by reference. | |||
|
7 | ||||
1 | import re |
|
8 | import re | |
2 | from demandload import demandload |
|
9 | from demandload import demandload | |
3 | from i18n import gettext as _ |
|
10 | from i18n import gettext as _ | |
@@ -12,6 +19,8 b' esctable = {' | |||||
12 | } |
|
19 | } | |
13 |
|
20 | |||
14 | def parsestring(s, quoted=True): |
|
21 | def parsestring(s, quoted=True): | |
|
22 | '''parse a string using simple c-like syntax. | |||
|
23 | string must be in quotes if quoted is True.''' | |||
15 | fp = cStringIO.StringIO() |
|
24 | fp = cStringIO.StringIO() | |
16 | if quoted: |
|
25 | if quoted: | |
17 | first = s[0] |
|
26 | first = s[0] | |
@@ -31,7 +40,30 b' def parsestring(s, quoted=True):' | |||||
31 | return fp.getvalue() |
|
40 | return fp.getvalue() | |
32 |
|
41 | |||
33 | class templater(object): |
|
42 | class templater(object): | |
|
43 | '''template expansion engine. | |||
|
44 | ||||
|
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 | |||
|
47 | is treated as name of template file. | |||
|
48 | ||||
|
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 | |||
|
51 | foo in map, and substituting it. expansion is recursive: it stops | |||
|
52 | when there is no more {foo} to replace. | |||
|
53 | ||||
|
54 | expansion also allows formatting and filtering. | |||
|
55 | ||||
|
56 | format uses key to expand each item in list. syntax is | |||
|
57 | {key%format}. | |||
|
58 | ||||
|
59 | filter uses function to transform value. syntax is | |||
|
60 | {key|filter1|filter2|...}.''' | |||
|
61 | ||||
34 | def __init__(self, mapfile, filters={}, defaults={}): |
|
62 | def __init__(self, mapfile, filters={}, defaults={}): | |
|
63 | '''set up template engine. | |||
|
64 | mapfile is name of file to read map definitions from. | |||
|
65 | filters is dict of functions. each transforms a value into another. | |||
|
66 | defaults is dict of default map definitions.''' | |||
35 | self.mapfile = mapfile or 'template' |
|
67 | self.mapfile = mapfile or 'template' | |
36 | self.cache = {} |
|
68 | self.cache = {} | |
37 | self.map = {} |
|
69 | self.map = {} | |
@@ -64,6 +96,9 b' class templater(object):' | |||||
64 | return key in self.cache |
|
96 | return key in self.cache | |
65 |
|
97 | |||
66 | def __call__(self, t, **map): |
|
98 | def __call__(self, t, **map): | |
|
99 | '''perform expansion. | |||
|
100 | t is name of map element to expand. | |||
|
101 | map is added elements to use during expansion.''' | |||
67 | m = self.defaults.copy() |
|
102 | m = self.defaults.copy() | |
68 | m.update(map) |
|
103 | m.update(map) | |
69 | try: |
|
104 | try: | |
@@ -127,7 +162,9 b' agescales = [("second", 1),' | |||||
127 |
|
162 | |||
128 | agescales.reverse() |
|
163 | agescales.reverse() | |
129 |
|
164 | |||
130 |
def age( |
|
165 | def age(date): | |
|
166 | '''turn a (timestamp, tzoff) tuple into an age string.''' | |||
|
167 | ||||
131 | def plural(t, c): |
|
168 | def plural(t, c): | |
132 | if c == 1: |
|
169 | if c == 1: | |
133 | return t |
|
170 | return t | |
@@ -136,7 +173,7 b' def age(x):' | |||||
136 | return "%d %s" % (c, plural(t, c)) |
|
173 | return "%d %s" % (c, plural(t, c)) | |
137 |
|
174 | |||
138 | now = time.time() |
|
175 | now = time.time() | |
139 |
then = |
|
176 | then = date[0] | |
140 | delta = max(1, int(now - then)) |
|
177 | delta = max(1, int(now - then)) | |
141 |
|
178 | |||
142 | for t, s in agescales: |
|
179 | for t, s in agescales: | |
@@ -145,15 +182,18 b' def age(x):' | |||||
145 | return fmt(t, n) |
|
182 | return fmt(t, n) | |
146 |
|
183 | |||
147 | def isodate(date): |
|
184 | def isodate(date): | |
|
185 | '''turn a (timestamp, tzoff) tuple into an iso 8631 date.''' | |||
148 | return util.datestr(date, format='%Y-%m-%d %H:%M') |
|
186 | return util.datestr(date, format='%Y-%m-%d %H:%M') | |
149 |
|
187 | |||
150 | def nl2br(text): |
|
188 | def nl2br(text): | |
|
189 | '''replace raw newlines with xhtml line breaks.''' | |||
151 | return text.replace('\n', '<br/>\n') |
|
190 | return text.replace('\n', '<br/>\n') | |
152 |
|
191 | |||
153 | def obfuscate(text): |
|
192 | def obfuscate(text): | |
154 | return ''.join(['&#%d;' % ord(c) for c in text]) |
|
193 | return ''.join(['&#%d;' % ord(c) for c in text]) | |
155 |
|
194 | |||
156 | def domain(author): |
|
195 | def domain(author): | |
|
196 | '''get domain of author, or empty string if none.''' | |||
157 | f = author.find('@') |
|
197 | f = author.find('@') | |
158 | if f == -1: return '' |
|
198 | if f == -1: return '' | |
159 | author = author[f+1:] |
|
199 | author = author[f+1:] | |
@@ -162,6 +202,7 b' def domain(author):' | |||||
162 | return author |
|
202 | return author | |
163 |
|
203 | |||
164 | def person(author): |
|
204 | def person(author): | |
|
205 | '''get name of author, or else username.''' | |||
165 | f = author.find('<') |
|
206 | f = author.find('<') | |
166 | if f == -1: return util.shortuser(author) |
|
207 | if f == -1: return util.shortuser(author) | |
167 | return author[:f].rstrip() |
|
208 | return author[:f].rstrip() | |
@@ -185,6 +226,8 b' common_filters = {' | |||||
185 | } |
|
226 | } | |
186 |
|
227 | |||
187 | def templatepath(name=None): |
|
228 | def templatepath(name=None): | |
|
229 | '''return location of template file or directory (if no name). | |||
|
230 | returns None if not found.''' | |||
188 | for f in 'templates', '../templates': |
|
231 | for f in 'templates', '../templates': | |
189 | fl = f.split('/') |
|
232 | fl = f.split('/') | |
190 | if name: fl.append(name) |
|
233 | if name: fl.append(name) |
General Comments 0
You need to be logged in to leave comments.
Login now