|
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 _ |
4 demandload(globals(), "cStringIO cgi os time urllib util") |
11 demandload(globals(), "cStringIO cgi os time urllib util") |
5 |
12 |
10 'n': '\n', |
17 'n': '\n', |
11 'v': '\v', |
18 'v': '\v', |
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] |
18 if len(s) < 2: raise SyntaxError(_('string too short')) |
27 if len(s) < 2: raise SyntaxError(_('string too short')) |
19 if first not in "'\"": raise SyntaxError(_('invalid quote')) |
28 if first not in "'\"": raise SyntaxError(_('invalid quote')) |
29 else: fp.write(c) |
38 else: fp.write(c) |
30 if escape: raise SyntaxError(_('unterminated escape')) |
39 if escape: raise SyntaxError(_('unterminated escape')) |
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 = {} |
38 self.base = (mapfile and os.path.dirname(mapfile)) or '' |
70 self.base = (mapfile and os.path.dirname(mapfile)) or '' |
39 self.filters = filters |
71 self.filters = filters |
62 |
94 |
63 def __contains__(self, key): |
95 def __contains__(self, key): |
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: |
70 tmpl = self.cache[t] |
105 tmpl = self.cache[t] |
71 except KeyError: |
106 except KeyError: |
125 ("month", 3600 * 24 * 30), |
160 ("month", 3600 * 24 * 30), |
126 ("year", 3600 * 24 * 365)] |
161 ("year", 3600 * 24 * 365)] |
127 |
162 |
128 agescales.reverse() |
163 agescales.reverse() |
129 |
164 |
130 def age(x): |
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 |
134 return t + "s" |
171 return t + "s" |
135 def fmt(t, c): |
172 def fmt(t, c): |
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 = x[0] |
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: |
143 n = delta / s |
180 n = delta / s |
144 if n >= 2 or s == 1: |
181 if n >= 2 or s == 1: |
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:] |
160 f = author.find('>') |
200 f = author.find('>') |
161 if f >= 0: author = author[:f] |
201 if f >= 0: author = author[:f] |
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() |
168 |
209 |
169 common_filters = { |
210 common_filters = { |
183 "urlescape": urllib.quote, |
224 "urlescape": urllib.quote, |
184 "user": util.shortuser, |
225 "user": util.shortuser, |
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) |
191 p = os.path.join(os.path.dirname(__file__), *fl) |
234 p = os.path.join(os.path.dirname(__file__), *fl) |
192 if (name and os.path.exists(p)) or os.path.isdir(p): |
235 if (name and os.path.exists(p)) or os.path.isdir(p): |