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