##// END OF EJS Templates
templater: fix little problem from stylemap() changes
Dirkjan Ochtman -
r8223:02145b70 default
parent child Browse files
Show More
@@ -1,215 +1,215
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
5 # This software may be used and distributed according to the terms
6 # of the GNU General Public License, incorporated herein by reference.
6 # of the GNU General Public License, 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 from mercurial import util, config
10 from mercurial import util, config
11
11
12 path = ['templates', '../templates']
12 path = ['templates', '../templates']
13
13
14 def parsestring(s, quoted=True):
14 def parsestring(s, quoted=True):
15 '''parse a string using simple c-like syntax.
15 '''parse a string using simple c-like syntax.
16 string must be in quotes if quoted is True.'''
16 string must be in quotes if quoted is True.'''
17 if quoted:
17 if quoted:
18 if len(s) < 2 or s[0] != s[-1]:
18 if len(s) < 2 or s[0] != s[-1]:
19 raise SyntaxError(_('unmatched quotes'))
19 raise SyntaxError(_('unmatched quotes'))
20 return s[1:-1].decode('string_escape')
20 return s[1:-1].decode('string_escape')
21
21
22 return s.decode('string_escape')
22 return s.decode('string_escape')
23
23
24 class engine(object):
24 class engine(object):
25 '''template expansion engine.
25 '''template expansion engine.
26
26
27 template expansion works like this. a map file contains key=value
27 template expansion works like this. a map file contains key=value
28 pairs. if value is quoted, it is treated as string. otherwise, it
28 pairs. if value is quoted, it is treated as string. otherwise, it
29 is treated as name of template file.
29 is treated as name of template file.
30
30
31 templater is asked to expand a key in map. it looks up key, and
31 templater is asked to expand a key in map. it looks up key, and
32 looks for strings like this: {foo}. it expands {foo} by looking up
32 looks for strings like this: {foo}. it expands {foo} by looking up
33 foo in map, and substituting it. expansion is recursive: it stops
33 foo in map, and substituting it. expansion is recursive: it stops
34 when there is no more {foo} to replace.
34 when there is no more {foo} to replace.
35
35
36 expansion also allows formatting and filtering.
36 expansion also allows formatting and filtering.
37
37
38 format uses key to expand each item in list. syntax is
38 format uses key to expand each item in list. syntax is
39 {key%format}.
39 {key%format}.
40
40
41 filter uses function to transform value. syntax is
41 filter uses function to transform value. syntax is
42 {key|filter1|filter2|...}.'''
42 {key|filter1|filter2|...}.'''
43
43
44 template_re = re.compile(r"(?:(?:#(?=[\w\|%]+#))|(?:{(?=[\w\|%]+})))"
44 template_re = re.compile(r"(?:(?:#(?=[\w\|%]+#))|(?:{(?=[\w\|%]+})))"
45 r"(\w+)(?:(?:%(\w+))|((?:\|\w+)*))[#}]")
45 r"(\w+)(?:(?:%(\w+))|((?:\|\w+)*))[#}]")
46
46
47 def __init__(self, loader, filters={}, defaults={}):
47 def __init__(self, loader, filters={}, defaults={}):
48 self.loader = loader
48 self.loader = loader
49 self.filters = filters
49 self.filters = filters
50 self.defaults = defaults
50 self.defaults = defaults
51
51
52 def process(self, t, map):
52 def process(self, t, map):
53 '''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
54 added elements for use during expansion. Is a generator.'''
54 added elements for use during expansion. Is a generator.'''
55 tmpl = self.loader(t)
55 tmpl = self.loader(t)
56 iters = [self._process(tmpl, map)]
56 iters = [self._process(tmpl, map)]
57 while iters:
57 while iters:
58 try:
58 try:
59 item = iters[0].next()
59 item = iters[0].next()
60 except StopIteration:
60 except StopIteration:
61 iters.pop(0)
61 iters.pop(0)
62 continue
62 continue
63 if isinstance(item, str):
63 if isinstance(item, str):
64 yield item
64 yield item
65 elif item is None:
65 elif item is None:
66 yield ''
66 yield ''
67 elif hasattr(item, '__iter__'):
67 elif hasattr(item, '__iter__'):
68 iters.insert(0, iter(item))
68 iters.insert(0, iter(item))
69 else:
69 else:
70 yield str(item)
70 yield str(item)
71
71
72 def _process(self, tmpl, map):
72 def _process(self, tmpl, map):
73 '''Render a template. Returns a generator.'''
73 '''Render a template. Returns a generator.'''
74 while tmpl:
74 while tmpl:
75 m = self.template_re.search(tmpl)
75 m = self.template_re.search(tmpl)
76 if not m:
76 if not m:
77 yield tmpl
77 yield tmpl
78 break
78 break
79
79
80 start, end = m.span(0)
80 start, end = m.span(0)
81 key, format, fl = m.groups()
81 key, format, fl = m.groups()
82
82
83 if start:
83 if start:
84 yield tmpl[:start]
84 yield tmpl[:start]
85 tmpl = tmpl[end:]
85 tmpl = tmpl[end:]
86
86
87 if key in map:
87 if key in map:
88 v = map[key]
88 v = map[key]
89 else:
89 else:
90 v = self.defaults.get(key, "")
90 v = self.defaults.get(key, "")
91 if callable(v):
91 if callable(v):
92 v = v(**map)
92 v = v(**map)
93 if format:
93 if format:
94 if not hasattr(v, '__iter__'):
94 if not hasattr(v, '__iter__'):
95 raise SyntaxError(_("Error expanding '%s%%%s'")
95 raise SyntaxError(_("Error expanding '%s%%%s'")
96 % (key, format))
96 % (key, format))
97 lm = map.copy()
97 lm = map.copy()
98 for i in v:
98 for i in v:
99 lm.update(i)
99 lm.update(i)
100 yield self.process(format, lm)
100 yield self.process(format, lm)
101 else:
101 else:
102 if fl:
102 if fl:
103 for f in fl.split("|")[1:]:
103 for f in fl.split("|")[1:]:
104 v = self.filters[f](v)
104 v = self.filters[f](v)
105 yield v
105 yield v
106
106
107 class templater(object):
107 class templater(object):
108
108
109 def __init__(self, mapfile, filters={}, defaults={}, cache={},
109 def __init__(self, mapfile, filters={}, defaults={}, cache={},
110 minchunk=1024, maxchunk=65536):
110 minchunk=1024, maxchunk=65536):
111 '''set up template engine.
111 '''set up template engine.
112 mapfile is name of file to read map definitions from.
112 mapfile is name of file to read map definitions from.
113 filters is dict of functions. each transforms a value into another.
113 filters is dict of functions. each transforms a value into another.
114 defaults is dict of default map definitions.'''
114 defaults is dict of default map definitions.'''
115 self.mapfile = mapfile or 'template'
115 self.mapfile = mapfile or 'template'
116 self.cache = cache.copy()
116 self.cache = cache.copy()
117 self.map = {}
117 self.map = {}
118 self.base = (mapfile and os.path.dirname(mapfile)) or ''
118 self.base = (mapfile and os.path.dirname(mapfile)) or ''
119 self.filters = filters
119 self.filters = filters
120 self.defaults = defaults
120 self.defaults = defaults
121 self.minchunk, self.maxchunk = minchunk, maxchunk
121 self.minchunk, self.maxchunk = minchunk, maxchunk
122
122
123 if not mapfile:
123 if not mapfile:
124 return
124 return
125 if not os.path.exists(mapfile):
125 if not os.path.exists(mapfile):
126 raise util.Abort(_('style not found: %s') % mapfile)
126 raise util.Abort(_('style not found: %s') % mapfile)
127
127
128 conf = config.config()
128 conf = config.config()
129 conf.read(mapfile)
129 conf.read(mapfile)
130
130
131 for key, val in conf[''].items():
131 for key, val in conf[''].items():
132 if val[0] in "'\"":
132 if val[0] in "'\"":
133 try:
133 try:
134 self.cache[key] = parsestring(val)
134 self.cache[key] = parsestring(val)
135 except SyntaxError, inst:
135 except SyntaxError, inst:
136 raise SyntaxError('%s: %s' %
136 raise SyntaxError('%s: %s' %
137 (conf.source('', key), inst.args[0]))
137 (conf.source('', key), inst.args[0]))
138 else:
138 else:
139 self.map[key] = os.path.join(self.base, val)
139 self.map[key] = os.path.join(self.base, val)
140
140
141 def __contains__(self, key):
141 def __contains__(self, key):
142 return key in self.cache or key in self.map
142 return key in self.cache or key in self.map
143
143
144 def load(self, t):
144 def load(self, t):
145 '''Get the template for the given template name. Use a local cache.'''
145 '''Get the template for the given template name. Use a local cache.'''
146 if not t in self.cache:
146 if not t in self.cache:
147 try:
147 try:
148 self.cache[t] = file(self.map[t]).read()
148 self.cache[t] = file(self.map[t]).read()
149 except IOError, inst:
149 except IOError, inst:
150 raise IOError(inst.args[0], _('template file %s: %s') %
150 raise IOError(inst.args[0], _('template file %s: %s') %
151 (self.map[t], inst.args[1]))
151 (self.map[t], inst.args[1]))
152 return self.cache[t]
152 return self.cache[t]
153
153
154 def __call__(self, t, **map):
154 def __call__(self, t, **map):
155 proc = engine(self.load, self.filters, self.defaults)
155 proc = engine(self.load, self.filters, self.defaults)
156 stream = proc.process(t, map)
156 stream = proc.process(t, map)
157 if self.minchunk:
157 if self.minchunk:
158 stream = util.increasingchunks(stream, min=self.minchunk,
158 stream = util.increasingchunks(stream, min=self.minchunk,
159 max=self.maxchunk)
159 max=self.maxchunk)
160 return stream
160 return stream
161
161
162 def templatepath(name=None):
162 def templatepath(name=None):
163 '''return location of template file or directory (if no name).
163 '''return location of template file or directory (if no name).
164 returns None if not found.'''
164 returns None if not found.'''
165 normpaths = []
165 normpaths = []
166
166
167 # executable version (py2exe) doesn't support __file__
167 # executable version (py2exe) doesn't support __file__
168 if hasattr(sys, 'frozen'):
168 if hasattr(sys, 'frozen'):
169 module = sys.executable
169 module = sys.executable
170 else:
170 else:
171 module = __file__
171 module = __file__
172 for f in path:
172 for f in path:
173 if f.startswith('/'):
173 if f.startswith('/'):
174 p = f
174 p = f
175 else:
175 else:
176 fl = f.split('/')
176 fl = f.split('/')
177 p = os.path.join(os.path.dirname(module), *fl)
177 p = os.path.join(os.path.dirname(module), *fl)
178 if name:
178 if name:
179 p = os.path.join(p, name)
179 p = os.path.join(p, name)
180 if name and os.path.exists(p):
180 if name and os.path.exists(p):
181 return os.path.normpath(p)
181 return os.path.normpath(p)
182 elif os.path.isdir(p):
182 elif os.path.isdir(p):
183 normpaths.append(os.path.normpath(p))
183 normpaths.append(os.path.normpath(p))
184
184
185 return normpaths
185 return normpaths
186
186
187 def stylemap(style, paths=None):
187 def stylemap(style, paths=None):
188 """Return path to mapfile for a given style.
188 """Return path to mapfile for a given style.
189
189
190 Searches mapfile in the following locations:
190 Searches mapfile in the following locations:
191 1. templatepath/style/map
191 1. templatepath/style/map
192 2. templatepath/map-style
192 2. templatepath/map-style
193 3. templatepath/map
193 3. templatepath/map
194 """
194 """
195
195
196 if paths is None:
196 if paths is None:
197 paths = templatepath()
197 paths = templatepath()
198 elif isinstance(paths, str):
198 elif isinstance(paths, str):
199 paths = [templatepath]
199 paths = [paths]
200
200
201 locations = style and [os.path.join(style, "map"), "map-" + style] or []
201 locations = style and [os.path.join(style, "map"), "map-" + style] or []
202 locations.append("map")
202 locations.append("map")
203 for path in paths:
203 for path in paths:
204 for location in locations:
204 for location in locations:
205 mapfile = os.path.join(path, location)
205 mapfile = os.path.join(path, location)
206 if os.path.isfile(mapfile):
206 if os.path.isfile(mapfile):
207 return mapfile
207 return mapfile
208
208
209 raise RuntimeError("No hgweb templates found in %r" % paths)
209 raise RuntimeError("No hgweb templates found in %r" % paths)
210
210
211 def stringify(thing):
211 def stringify(thing):
212 '''turn nested template iterator into string.'''
212 '''turn nested template iterator into string.'''
213 if hasattr(thing, '__iter__') and not isinstance(thing, str):
213 if hasattr(thing, '__iter__') and not isinstance(thing, str):
214 return "".join([stringify(t) for t in thing if t is not None])
214 return "".join([stringify(t) for t in thing if t is not None])
215 return str(thing)
215 return str(thing)
General Comments 0
You need to be logged in to leave comments. Login now