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