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