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