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