##// END OF EJS Templates
templater: lowercase error message...
Martin Geisler -
r8517:110763cc default
parent child Browse files
Show More
@@ -1,245 +1,245 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 of the
6 6 # GNU General Public License version 2, incorporated herein by reference.
7 7
8 8 from i18n import _
9 9 import re, sys, os
10 10 import util, config, templatefilters
11 11
12 12 path = ['templates', '../templates']
13 13 stringify = templatefilters.stringify
14 14
15 15 def parsestring(s, quoted=True):
16 16 '''parse a string using simple c-like syntax.
17 17 string must be in quotes if quoted is True.'''
18 18 if quoted:
19 19 if len(s) < 2 or s[0] != s[-1]:
20 20 raise SyntaxError(_('unmatched quotes'))
21 21 return s[1:-1].decode('string_escape')
22 22
23 23 return s.decode('string_escape')
24 24
25 25 class engine(object):
26 26 '''template expansion engine.
27 27
28 28 template expansion works like this. a map file contains key=value
29 29 pairs. if value is quoted, it is treated as string. otherwise, it
30 30 is treated as name of template file.
31 31
32 32 templater is asked to expand a key in map. it looks up key, and
33 33 looks for strings like this: {foo}. it expands {foo} by looking up
34 34 foo in map, and substituting it. expansion is recursive: it stops
35 35 when there is no more {foo} to replace.
36 36
37 37 expansion also allows formatting and filtering.
38 38
39 39 format uses key to expand each item in list. syntax is
40 40 {key%format}.
41 41
42 42 filter uses function to transform value. syntax is
43 43 {key|filter1|filter2|...}.'''
44 44
45 45 template_re = re.compile(r'{([\w\|%]+)}|#([\w\|%]+)#')
46 46
47 47 def __init__(self, loader, filters={}, defaults={}):
48 48 self.loader = loader
49 49 self.filters = filters
50 50 self.defaults = defaults
51 51 self.cache = {}
52 52
53 53 def process(self, t, map):
54 54 '''Perform expansion. t is name of map element to expand. map contains
55 55 added elements for use during expansion. Is a generator.'''
56 56 tmpl = self.loader(t)
57 57 iters = [self._process(tmpl, map)]
58 58 while iters:
59 59 try:
60 60 item = iters[0].next()
61 61 except StopIteration:
62 62 iters.pop(0)
63 63 continue
64 64 if isinstance(item, str):
65 65 yield item
66 66 elif item is None:
67 67 yield ''
68 68 elif hasattr(item, '__iter__'):
69 69 iters.insert(0, iter(item))
70 70 else:
71 71 yield str(item)
72 72
73 73 def _format(self, expr, get, map):
74 74 key, format = expr.split('%')
75 75 v = get(key)
76 76 if not hasattr(v, '__iter__'):
77 raise SyntaxError(_("Error expanding '%s%%%s'") % (key, format))
77 raise SyntaxError(_("error expanding '%s%%%s'") % (key, format))
78 78 lm = map.copy()
79 79 for i in v:
80 80 lm.update(i)
81 81 yield self.process(format, lm)
82 82
83 83 def _filter(self, expr, get, map):
84 84 if expr not in self.cache:
85 85 parts = expr.split('|')
86 86 val = parts[0]
87 87 try:
88 88 filters = [self.filters[f] for f in parts[1:]]
89 89 except KeyError, i:
90 90 raise SyntaxError(_("unknown filter '%s'") % i[0])
91 91 def apply(get):
92 92 x = get(val)
93 93 for f in filters:
94 94 x = f(x)
95 95 return x
96 96 self.cache[expr] = apply
97 97 return self.cache[expr](get)
98 98
99 99 def _process(self, tmpl, map):
100 100 '''Render a template. Returns a generator.'''
101 101
102 102 def get(key):
103 103 v = map.get(key)
104 104 if v is None:
105 105 v = self.defaults.get(key, '')
106 106 if hasattr(v, '__call__'):
107 107 v = v(**map)
108 108 return v
109 109
110 110 while tmpl:
111 111 m = self.template_re.search(tmpl)
112 112 if not m:
113 113 yield tmpl
114 114 break
115 115
116 116 start, end = m.span(0)
117 117 variants = m.groups()
118 118 expr = variants[0] or variants[1]
119 119
120 120 if start:
121 121 yield tmpl[:start]
122 122 tmpl = tmpl[end:]
123 123
124 124 if '%' in expr:
125 125 yield self._format(expr, get, map)
126 126 elif '|' in expr:
127 127 yield self._filter(expr, get, map)
128 128 else:
129 129 yield get(expr)
130 130
131 131 engines = {'default': engine}
132 132
133 133 class templater(object):
134 134
135 135 def __init__(self, mapfile, filters={}, defaults={}, cache={},
136 136 minchunk=1024, maxchunk=65536):
137 137 '''set up template engine.
138 138 mapfile is name of file to read map definitions from.
139 139 filters is dict of functions. each transforms a value into another.
140 140 defaults is dict of default map definitions.'''
141 141 self.mapfile = mapfile or 'template'
142 142 self.cache = cache.copy()
143 143 self.map = {}
144 144 self.base = (mapfile and os.path.dirname(mapfile)) or ''
145 145 self.filters = templatefilters.filters.copy()
146 146 self.filters.update(filters)
147 147 self.defaults = defaults
148 148 self.minchunk, self.maxchunk = minchunk, maxchunk
149 149 self.engines = {}
150 150
151 151 if not mapfile:
152 152 return
153 153 if not os.path.exists(mapfile):
154 154 raise util.Abort(_('style not found: %s') % mapfile)
155 155
156 156 conf = config.config()
157 157 conf.read(mapfile)
158 158
159 159 for key, val in conf[''].items():
160 160 if val[0] in "'\"":
161 161 try:
162 162 self.cache[key] = parsestring(val)
163 163 except SyntaxError, inst:
164 164 raise SyntaxError('%s: %s' %
165 165 (conf.source('', key), inst.args[0]))
166 166 else:
167 167 val = 'default', val
168 168 if ':' in val[1]:
169 169 val = val[1].split(':', 1)
170 170 self.map[key] = val[0], os.path.join(self.base, val[1])
171 171
172 172 def __contains__(self, key):
173 173 return key in self.cache or key in self.map
174 174
175 175 def load(self, t):
176 176 '''Get the template for the given template name. Use a local cache.'''
177 177 if not t in self.cache:
178 178 try:
179 179 self.cache[t] = open(self.map[t][1]).read()
180 180 except IOError, inst:
181 181 raise IOError(inst.args[0], _('template file %s: %s') %
182 182 (self.map[t][1], inst.args[1]))
183 183 return self.cache[t]
184 184
185 185 def __call__(self, t, **map):
186 186 ttype = t in self.map and self.map[t][0] or 'default'
187 187 proc = self.engines.get(ttype)
188 188 if proc is None:
189 189 proc = engines[ttype](self.load, self.filters, self.defaults)
190 190 self.engines[ttype] = proc
191 191
192 192 stream = proc.process(t, map)
193 193 if self.minchunk:
194 194 stream = util.increasingchunks(stream, min=self.minchunk,
195 195 max=self.maxchunk)
196 196 return stream
197 197
198 198 def templatepath(name=None):
199 199 '''return location of template file or directory (if no name).
200 200 returns None if not found.'''
201 201 normpaths = []
202 202
203 203 # executable version (py2exe) doesn't support __file__
204 204 if hasattr(sys, 'frozen'):
205 205 module = sys.executable
206 206 else:
207 207 module = __file__
208 208 for f in path:
209 209 if f.startswith('/'):
210 210 p = f
211 211 else:
212 212 fl = f.split('/')
213 213 p = os.path.join(os.path.dirname(module), *fl)
214 214 if name:
215 215 p = os.path.join(p, name)
216 216 if name and os.path.exists(p):
217 217 return os.path.normpath(p)
218 218 elif os.path.isdir(p):
219 219 normpaths.append(os.path.normpath(p))
220 220
221 221 return normpaths
222 222
223 223 def stylemap(style, paths=None):
224 224 """Return path to mapfile for a given style.
225 225
226 226 Searches mapfile in the following locations:
227 227 1. templatepath/style/map
228 228 2. templatepath/map-style
229 229 3. templatepath/map
230 230 """
231 231
232 232 if paths is None:
233 233 paths = templatepath()
234 234 elif isinstance(paths, str):
235 235 paths = [paths]
236 236
237 237 locations = style and [os.path.join(style, "map"), "map-" + style] or []
238 238 locations.append("map")
239 239 for path in paths:
240 240 for location in locations:
241 241 mapfile = os.path.join(path, location)
242 242 if os.path.isfile(mapfile):
243 243 return mapfile
244 244
245 245 raise RuntimeError("No hgweb templates found in %r" % paths)
General Comments 0
You need to be logged in to leave comments. Login now