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