##// END OF EJS Templates
templater: replace regex complexity by simple str containment checks
Dirkjan Ochtman -
r8475:caaf1e77 default
parent child Browse files
Show More
@@ -1,233 +1,235 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 template_re = re.compile(r"(?:(?:#(?=[\w\|%]+#))|(?:{(?=[\w\|%]+})))"
46 r"(\w+)(?:(?:%(\w+))|((?:\|\w+)*))[#}]")
45 template_re = re.compile(r'{([\w\|%]+)}|#([\w\|%]+)#')
47 46
48 47 def __init__(self, loader, filters={}, defaults={}):
49 48 self.loader = loader
50 49 self.filters = filters
51 50 self.defaults = defaults
52 51
53 52 def process(self, t, map):
54 53 '''Perform expansion. t is name of map element to expand. map contains
55 54 added elements for use during expansion. Is a generator.'''
56 55 tmpl = self.loader(t)
57 56 iters = [self._process(tmpl, map)]
58 57 while iters:
59 58 try:
60 59 item = iters[0].next()
61 60 except StopIteration:
62 61 iters.pop(0)
63 62 continue
64 63 if isinstance(item, str):
65 64 yield item
66 65 elif item is None:
67 66 yield ''
68 67 elif hasattr(item, '__iter__'):
69 68 iters.insert(0, iter(item))
70 69 else:
71 70 yield str(item)
72 71
73 def _format(self, key, format, get, map):
72 def _format(self, expr, get, map):
73 key, format = expr.split('%')
74 74 v = get(key)
75 75 if not hasattr(v, '__iter__'):
76 76 raise SyntaxError(_("Error expanding '%s%%%s'") % (key, format))
77 77 lm = map.copy()
78 78 for i in v:
79 79 lm.update(i)
80 80 yield self.process(format, lm)
81 81
82 def _filter(self, key, filters, get, map):
83 v = get(key)
84 for f in filters.split('|')[1:]:
82 def _filter(self, expr, get, map):
83 parts = expr.split('|')
84 v = get(parts[0])
85 for f in parts[1:]:
85 86 v = self.filters[f](v)
86 87 return v
87 88
88 89 def _process(self, tmpl, map):
89 90 '''Render a template. Returns a generator.'''
90 91
91 92 def get(key):
92 93 v = map.get(key)
93 94 if v is None:
94 95 v = self.defaults.get(key, '')
95 96 if hasattr(v, '__call__'):
96 97 v = v(**map)
97 98 return v
98 99
99 100 while tmpl:
100 101 m = self.template_re.search(tmpl)
101 102 if not m:
102 103 yield tmpl
103 104 break
104 105
105 106 start, end = m.span(0)
106 key, fmt, fl = m.groups()
107 variants = m.groups()
108 expr = variants[0] or variants[1]
107 109
108 110 if start:
109 111 yield tmpl[:start]
110 112 tmpl = tmpl[end:]
111 113
112 if fmt:
113 yield self._format(key, fmt, get, map)
114 elif fl:
115 yield self._filter(key, fl, get, map)
114 if '%' in expr:
115 yield self._format(expr, get, map)
116 elif '|' in expr:
117 yield self._filter(expr, get, map)
116 118 else:
117 yield get(key)
119 yield get(expr)
118 120
119 121 engines = {'default': engine}
120 122
121 123 class templater(object):
122 124
123 125 def __init__(self, mapfile, filters={}, defaults={}, cache={},
124 126 minchunk=1024, maxchunk=65536):
125 127 '''set up template engine.
126 128 mapfile is name of file to read map definitions from.
127 129 filters is dict of functions. each transforms a value into another.
128 130 defaults is dict of default map definitions.'''
129 131 self.mapfile = mapfile or 'template'
130 132 self.cache = cache.copy()
131 133 self.map = {}
132 134 self.base = (mapfile and os.path.dirname(mapfile)) or ''
133 135 self.filters = templatefilters.filters.copy()
134 136 self.filters.update(filters)
135 137 self.defaults = defaults
136 138 self.minchunk, self.maxchunk = minchunk, maxchunk
137 139 self.engines = {}
138 140
139 141 if not mapfile:
140 142 return
141 143 if not os.path.exists(mapfile):
142 144 raise util.Abort(_('style not found: %s') % mapfile)
143 145
144 146 conf = config.config()
145 147 conf.read(mapfile)
146 148
147 149 for key, val in conf[''].items():
148 150 if val[0] in "'\"":
149 151 try:
150 152 self.cache[key] = parsestring(val)
151 153 except SyntaxError, inst:
152 154 raise SyntaxError('%s: %s' %
153 155 (conf.source('', key), inst.args[0]))
154 156 else:
155 157 val = 'default', val
156 158 if ':' in val[1]:
157 159 val = val[1].split(':', 1)
158 160 self.map[key] = val[0], os.path.join(self.base, val[1])
159 161
160 162 def __contains__(self, key):
161 163 return key in self.cache or key in self.map
162 164
163 165 def load(self, t):
164 166 '''Get the template for the given template name. Use a local cache.'''
165 167 if not t in self.cache:
166 168 try:
167 169 self.cache[t] = open(self.map[t][1]).read()
168 170 except IOError, inst:
169 171 raise IOError(inst.args[0], _('template file %s: %s') %
170 172 (self.map[t][1], inst.args[1]))
171 173 return self.cache[t]
172 174
173 175 def __call__(self, t, **map):
174 176 ttype = t in self.map and self.map[t][0] or 'default'
175 177 proc = self.engines.get(ttype)
176 178 if proc is None:
177 179 proc = engines[ttype](self.load, self.filters, self.defaults)
178 180 self.engines[ttype] = proc
179 181
180 182 stream = proc.process(t, map)
181 183 if self.minchunk:
182 184 stream = util.increasingchunks(stream, min=self.minchunk,
183 185 max=self.maxchunk)
184 186 return stream
185 187
186 188 def templatepath(name=None):
187 189 '''return location of template file or directory (if no name).
188 190 returns None if not found.'''
189 191 normpaths = []
190 192
191 193 # executable version (py2exe) doesn't support __file__
192 194 if hasattr(sys, 'frozen'):
193 195 module = sys.executable
194 196 else:
195 197 module = __file__
196 198 for f in path:
197 199 if f.startswith('/'):
198 200 p = f
199 201 else:
200 202 fl = f.split('/')
201 203 p = os.path.join(os.path.dirname(module), *fl)
202 204 if name:
203 205 p = os.path.join(p, name)
204 206 if name and os.path.exists(p):
205 207 return os.path.normpath(p)
206 208 elif os.path.isdir(p):
207 209 normpaths.append(os.path.normpath(p))
208 210
209 211 return normpaths
210 212
211 213 def stylemap(style, paths=None):
212 214 """Return path to mapfile for a given style.
213 215
214 216 Searches mapfile in the following locations:
215 217 1. templatepath/style/map
216 218 2. templatepath/map-style
217 219 3. templatepath/map
218 220 """
219 221
220 222 if paths is None:
221 223 paths = templatepath()
222 224 elif isinstance(paths, str):
223 225 paths = [paths]
224 226
225 227 locations = style and [os.path.join(style, "map"), "map-" + style] or []
226 228 locations.append("map")
227 229 for path in paths:
228 230 for location in locations:
229 231 mapfile = os.path.join(path, location)
230 232 if os.path.isfile(mapfile):
231 233 return mapfile
232 234
233 235 raise RuntimeError("No hgweb templates found in %r" % paths)
General Comments 0
You need to be logged in to leave comments. Login now