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