##// END OF EJS Templates
Fix the py2exe template path problem (issue:152) again....
Shun-ichi GOTO -
r1955:2f500a4b default
parent child Browse files
Show More
@@ -1,237 +1,241 b''
1 1 # templater.py - template expansion for output
2 2 #
3 3 # Copyright 2005, 2006 Matt Mackall <mpm@selenic.com>
4 4 #
5 5 # This software may be used and distributed according to the terms
6 6 # of the GNU General Public License, incorporated herein by reference.
7 7
8 8 import re
9 9 from demandload import demandload
10 10 from i18n import gettext as _
11 demandload(globals(), "cStringIO cgi os time urllib util")
11 demandload(globals(), "cStringIO cgi sys os time urllib util")
12 12
13 13 esctable = {
14 14 '\\': '\\',
15 15 'r': '\r',
16 16 't': '\t',
17 17 'n': '\n',
18 18 'v': '\v',
19 19 }
20 20
21 21 def parsestring(s, quoted=True):
22 22 '''parse a string using simple c-like syntax.
23 23 string must be in quotes if quoted is True.'''
24 24 fp = cStringIO.StringIO()
25 25 if quoted:
26 26 first = s[0]
27 27 if len(s) < 2: raise SyntaxError(_('string too short'))
28 28 if first not in "'\"": raise SyntaxError(_('invalid quote'))
29 29 if s[-1] != first: raise SyntaxError(_('unmatched quotes'))
30 30 s = s[1:-1]
31 31 escape = False
32 32 for c in s:
33 33 if escape:
34 34 fp.write(esctable.get(c, c))
35 35 escape = False
36 36 elif c == '\\': escape = True
37 37 elif quoted and c == first: raise SyntaxError(_('string ends early'))
38 38 else: fp.write(c)
39 39 if escape: raise SyntaxError(_('unterminated escape'))
40 40 return fp.getvalue()
41 41
42 42 class templater(object):
43 43 '''template expansion engine.
44 44
45 45 template expansion works like this. a map file contains key=value
46 46 pairs. if value is quoted, it is treated as string. otherwise, it
47 47 is treated as name of template file.
48 48
49 49 templater is asked to expand a key in map. it looks up key, and
50 50 looks for atrings like this: {foo}. it expands {foo} by looking up
51 51 foo in map, and substituting it. expansion is recursive: it stops
52 52 when there is no more {foo} to replace.
53 53
54 54 expansion also allows formatting and filtering.
55 55
56 56 format uses key to expand each item in list. syntax is
57 57 {key%format}.
58 58
59 59 filter uses function to transform value. syntax is
60 60 {key|filter1|filter2|...}.'''
61 61
62 62 def __init__(self, mapfile, filters={}, cache={}):
63 63 '''set up template engine.
64 64 mapfile is name of file to read map definitions from.
65 65 filters is dict of functions. each transforms a value into another.
66 66 defaults is dict of default map definitions.'''
67 67 self.mapfile = mapfile or 'template'
68 68 self.cache = {}
69 69 self.map = {}
70 70 self.base = (mapfile and os.path.dirname(mapfile)) or ''
71 71 self.filters = filters
72 72 self.defaults = {}
73 73 self.cache = cache
74 74
75 75 if not mapfile:
76 76 return
77 77 i = 0
78 78 for l in file(mapfile):
79 79 l = l.strip()
80 80 i += 1
81 81 if not l or l[0] in '#;': continue
82 82 m = re.match(r'([a-zA-Z_][a-zA-Z0-9_]*)\s*=\s*(.+)$', l)
83 83 if m:
84 84 key, val = m.groups()
85 85 if val[0] in "'\"":
86 86 try:
87 87 self.cache[key] = parsestring(val)
88 88 except SyntaxError, inst:
89 89 raise SyntaxError('%s:%s: %s' %
90 90 (mapfile, i, inst.args[0]))
91 91 else:
92 92 self.map[key] = os.path.join(self.base, val)
93 93 else:
94 94 raise SyntaxError(_("%s:%s: parse error") % (mapfile, i))
95 95
96 96 def __contains__(self, key):
97 97 return key in self.cache
98 98
99 99 def __call__(self, t, **map):
100 100 '''perform expansion.
101 101 t is name of map element to expand.
102 102 map is added elements to use during expansion.'''
103 103 m = self.defaults.copy()
104 104 m.update(map)
105 105 try:
106 106 tmpl = self.cache[t]
107 107 except KeyError:
108 108 try:
109 109 tmpl = self.cache[t] = file(self.map[t]).read()
110 110 except IOError, inst:
111 111 raise IOError(inst.args[0], _('template file %s: %s') %
112 112 (self.map[t], inst.args[1]))
113 113 return self.template(tmpl, self.filters, **m)
114 114
115 115 template_re = re.compile(r"[#{]([a-zA-Z_][a-zA-Z0-9_]*)"
116 116 r"((%[a-zA-Z_][a-zA-Z0-9_]*)*)"
117 117 r"((\|[a-zA-Z_][a-zA-Z0-9_]*)*)[#}]")
118 118
119 119 def template(self, tmpl, filters={}, **map):
120 120 lm = map.copy()
121 121 while tmpl:
122 122 m = self.template_re.search(tmpl)
123 123 if m:
124 124 start, end = m.span(0)
125 125 s, e = tmpl[start], tmpl[end - 1]
126 126 key = m.group(1)
127 127 if ((s == '#' and e != '#') or (s == '{' and e != '}')):
128 128 raise SyntaxError(_("'%s'/'%s' mismatch expanding '%s'") %
129 129 (s, e, key))
130 130 if start:
131 131 yield tmpl[:start]
132 132 v = map.get(key, "")
133 133 v = callable(v) and v(**map) or v
134 134
135 135 format = m.group(2)
136 136 fl = m.group(4)
137 137
138 138 if format:
139 139 q = v.__iter__
140 140 for i in q():
141 141 lm.update(i)
142 142 yield self(format[1:], **lm)
143 143
144 144 v = ""
145 145
146 146 elif fl:
147 147 for f in fl.split("|")[1:]:
148 148 v = filters[f](v)
149 149
150 150 yield v
151 151 tmpl = tmpl[end:]
152 152 else:
153 153 yield tmpl
154 154 break
155 155
156 156 agescales = [("second", 1),
157 157 ("minute", 60),
158 158 ("hour", 3600),
159 159 ("day", 3600 * 24),
160 160 ("week", 3600 * 24 * 7),
161 161 ("month", 3600 * 24 * 30),
162 162 ("year", 3600 * 24 * 365)]
163 163
164 164 agescales.reverse()
165 165
166 166 def age(date):
167 167 '''turn a (timestamp, tzoff) tuple into an age string.'''
168 168
169 169 def plural(t, c):
170 170 if c == 1:
171 171 return t
172 172 return t + "s"
173 173 def fmt(t, c):
174 174 return "%d %s" % (c, plural(t, c))
175 175
176 176 now = time.time()
177 177 then = date[0]
178 178 delta = max(1, int(now - then))
179 179
180 180 for t, s in agescales:
181 181 n = delta / s
182 182 if n >= 2 or s == 1:
183 183 return fmt(t, n)
184 184
185 185 def isodate(date):
186 186 '''turn a (timestamp, tzoff) tuple into an iso 8631 date.'''
187 187 return util.datestr(date, format='%Y-%m-%d %H:%M')
188 188
189 189 def nl2br(text):
190 190 '''replace raw newlines with xhtml line breaks.'''
191 191 return text.replace('\n', '<br/>\n')
192 192
193 193 def obfuscate(text):
194 194 return ''.join(['&#%d;' % ord(c) for c in text])
195 195
196 196 def domain(author):
197 197 '''get domain of author, or empty string if none.'''
198 198 f = author.find('@')
199 199 if f == -1: return ''
200 200 author = author[f+1:]
201 201 f = author.find('>')
202 202 if f >= 0: author = author[:f]
203 203 return author
204 204
205 205 def person(author):
206 206 '''get name of author, or else username.'''
207 207 f = author.find('<')
208 208 if f == -1: return util.shortuser(author)
209 209 return author[:f].rstrip()
210 210
211 211 common_filters = {
212 212 "addbreaks": nl2br,
213 213 "age": age,
214 214 "date": lambda x: util.datestr(x),
215 215 "domain": domain,
216 216 "escape": lambda x: cgi.escape(x, True),
217 217 "firstline": lambda x: x.splitlines(1)[0].rstrip('\r\n'),
218 218 "isodate": isodate,
219 219 "obfuscate": obfuscate,
220 220 "permissions": lambda x: x and "-rwxr-xr-x" or "-rw-r--r--",
221 221 "person": person,
222 222 "rfc822date": lambda x: util.datestr(x, "%a, %d %b %Y %H:%M:%S"),
223 223 "short": lambda x: x[:12],
224 224 "strip": lambda x: x.strip(),
225 225 "urlescape": lambda x: urllib.quote(x),
226 226 "user": lambda x: util.shortuser(x),
227 227 }
228 228
229 229 def templatepath(name=None):
230 230 '''return location of template file or directory (if no name).
231 231 returns None if not found.'''
232 232 for f in 'templates', '../templates':
233 233 fl = f.split('/')
234 234 if name: fl.append(name)
235 235 p = os.path.join(os.path.dirname(__file__), *fl)
236 236 if (name and os.path.exists(p)) or os.path.isdir(p):
237 237 return os.path.normpath(p)
238 else:
239 # executable version (py2exe) doesn't support __file__
240 if hasattr(sys, 'frozen'):
241 return os.path.join(sys.prefix, "templates")
General Comments 0
You need to be logged in to leave comments. Login now