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