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