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