##// END OF EJS Templates
templater: replace 'ctx._repo' with 'ctx.repo()'
Matt Harbison -
r24337:696ab1a2 default
parent child Browse files
Show More
@@ -1,466 +1,466 b''
1 # templatekw.py - common changeset template keywords
1 # templatekw.py - common changeset template keywords
2 #
2 #
3 # Copyright 2005-2009 Matt Mackall <mpm@selenic.com>
3 # Copyright 2005-2009 Matt Mackall <mpm@selenic.com>
4 #
4 #
5 # This software may be used and distributed according to the terms of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version.
6 # GNU General Public License version 2 or any later version.
7
7
8 from node import hex
8 from node import hex
9 import patch, util, error
9 import patch, util, error
10 import hbisect
10 import hbisect
11
11
12 # This helper class allows us to handle both:
12 # This helper class allows us to handle both:
13 # "{files}" (legacy command-line-specific list hack) and
13 # "{files}" (legacy command-line-specific list hack) and
14 # "{files % '{file}\n'}" (hgweb-style with inlining and function support)
14 # "{files % '{file}\n'}" (hgweb-style with inlining and function support)
15 # and to access raw values:
15 # and to access raw values:
16 # "{ifcontains(file, files, ...)}", "{ifcontains(key, extras, ...)}"
16 # "{ifcontains(file, files, ...)}", "{ifcontains(key, extras, ...)}"
17 # "{get(extras, key)}"
17 # "{get(extras, key)}"
18
18
19 class _hybrid(object):
19 class _hybrid(object):
20 def __init__(self, gen, values, makemap, joinfmt=None):
20 def __init__(self, gen, values, makemap, joinfmt=None):
21 self.gen = gen
21 self.gen = gen
22 self.values = values
22 self.values = values
23 self._makemap = makemap
23 self._makemap = makemap
24 if joinfmt:
24 if joinfmt:
25 self.joinfmt = joinfmt
25 self.joinfmt = joinfmt
26 else:
26 else:
27 self.joinfmt = lambda x: x.values()[0]
27 self.joinfmt = lambda x: x.values()[0]
28 def __iter__(self):
28 def __iter__(self):
29 return self.gen
29 return self.gen
30 def __call__(self):
30 def __call__(self):
31 makemap = self._makemap
31 makemap = self._makemap
32 for x in self.values:
32 for x in self.values:
33 yield makemap(x)
33 yield makemap(x)
34 def __contains__(self, x):
34 def __contains__(self, x):
35 return x in self.values
35 return x in self.values
36 def __len__(self):
36 def __len__(self):
37 return len(self.values)
37 return len(self.values)
38 def __getattr__(self, name):
38 def __getattr__(self, name):
39 if name != 'get':
39 if name != 'get':
40 raise AttributeError(name)
40 raise AttributeError(name)
41 return getattr(self.values, name)
41 return getattr(self.values, name)
42
42
43 def showlist(name, values, plural=None, element=None, **args):
43 def showlist(name, values, plural=None, element=None, **args):
44 if not element:
44 if not element:
45 element = name
45 element = name
46 f = _showlist(name, values, plural, **args)
46 f = _showlist(name, values, plural, **args)
47 return _hybrid(f, values, lambda x: {element: x})
47 return _hybrid(f, values, lambda x: {element: x})
48
48
49 def _showlist(name, values, plural=None, **args):
49 def _showlist(name, values, plural=None, **args):
50 '''expand set of values.
50 '''expand set of values.
51 name is name of key in template map.
51 name is name of key in template map.
52 values is list of strings or dicts.
52 values is list of strings or dicts.
53 plural is plural of name, if not simply name + 's'.
53 plural is plural of name, if not simply name + 's'.
54
54
55 expansion works like this, given name 'foo'.
55 expansion works like this, given name 'foo'.
56
56
57 if values is empty, expand 'no_foos'.
57 if values is empty, expand 'no_foos'.
58
58
59 if 'foo' not in template map, return values as a string,
59 if 'foo' not in template map, return values as a string,
60 joined by space.
60 joined by space.
61
61
62 expand 'start_foos'.
62 expand 'start_foos'.
63
63
64 for each value, expand 'foo'. if 'last_foo' in template
64 for each value, expand 'foo'. if 'last_foo' in template
65 map, expand it instead of 'foo' for last key.
65 map, expand it instead of 'foo' for last key.
66
66
67 expand 'end_foos'.
67 expand 'end_foos'.
68 '''
68 '''
69 templ = args['templ']
69 templ = args['templ']
70 if plural:
70 if plural:
71 names = plural
71 names = plural
72 else: names = name + 's'
72 else: names = name + 's'
73 if not values:
73 if not values:
74 noname = 'no_' + names
74 noname = 'no_' + names
75 if noname in templ:
75 if noname in templ:
76 yield templ(noname, **args)
76 yield templ(noname, **args)
77 return
77 return
78 if name not in templ:
78 if name not in templ:
79 if isinstance(values[0], str):
79 if isinstance(values[0], str):
80 yield ' '.join(values)
80 yield ' '.join(values)
81 else:
81 else:
82 for v in values:
82 for v in values:
83 yield dict(v, **args)
83 yield dict(v, **args)
84 return
84 return
85 startname = 'start_' + names
85 startname = 'start_' + names
86 if startname in templ:
86 if startname in templ:
87 yield templ(startname, **args)
87 yield templ(startname, **args)
88 vargs = args.copy()
88 vargs = args.copy()
89 def one(v, tag=name):
89 def one(v, tag=name):
90 try:
90 try:
91 vargs.update(v)
91 vargs.update(v)
92 except (AttributeError, ValueError):
92 except (AttributeError, ValueError):
93 try:
93 try:
94 for a, b in v:
94 for a, b in v:
95 vargs[a] = b
95 vargs[a] = b
96 except ValueError:
96 except ValueError:
97 vargs[name] = v
97 vargs[name] = v
98 return templ(tag, **vargs)
98 return templ(tag, **vargs)
99 lastname = 'last_' + name
99 lastname = 'last_' + name
100 if lastname in templ:
100 if lastname in templ:
101 last = values.pop()
101 last = values.pop()
102 else:
102 else:
103 last = None
103 last = None
104 for v in values:
104 for v in values:
105 yield one(v)
105 yield one(v)
106 if last is not None:
106 if last is not None:
107 yield one(last, tag=lastname)
107 yield one(last, tag=lastname)
108 endname = 'end_' + names
108 endname = 'end_' + names
109 if endname in templ:
109 if endname in templ:
110 yield templ(endname, **args)
110 yield templ(endname, **args)
111
111
112 def getfiles(repo, ctx, revcache):
112 def getfiles(repo, ctx, revcache):
113 if 'files' not in revcache:
113 if 'files' not in revcache:
114 revcache['files'] = repo.status(ctx.p1().node(), ctx.node())[:3]
114 revcache['files'] = repo.status(ctx.p1().node(), ctx.node())[:3]
115 return revcache['files']
115 return revcache['files']
116
116
117 def getlatesttags(repo, ctx, cache):
117 def getlatesttags(repo, ctx, cache):
118 '''return date, distance and name for the latest tag of rev'''
118 '''return date, distance and name for the latest tag of rev'''
119
119
120 if 'latesttags' not in cache:
120 if 'latesttags' not in cache:
121 # Cache mapping from rev to a tuple with tag date, tag
121 # Cache mapping from rev to a tuple with tag date, tag
122 # distance and tag name
122 # distance and tag name
123 cache['latesttags'] = {-1: (0, 0, 'null')}
123 cache['latesttags'] = {-1: (0, 0, 'null')}
124 latesttags = cache['latesttags']
124 latesttags = cache['latesttags']
125
125
126 rev = ctx.rev()
126 rev = ctx.rev()
127 todo = [rev]
127 todo = [rev]
128 while todo:
128 while todo:
129 rev = todo.pop()
129 rev = todo.pop()
130 if rev in latesttags:
130 if rev in latesttags:
131 continue
131 continue
132 ctx = repo[rev]
132 ctx = repo[rev]
133 tags = [t for t in ctx.tags()
133 tags = [t for t in ctx.tags()
134 if (repo.tagtype(t) and repo.tagtype(t) != 'local')]
134 if (repo.tagtype(t) and repo.tagtype(t) != 'local')]
135 if tags:
135 if tags:
136 latesttags[rev] = ctx.date()[0], 0, ':'.join(sorted(tags))
136 latesttags[rev] = ctx.date()[0], 0, ':'.join(sorted(tags))
137 continue
137 continue
138 try:
138 try:
139 # The tuples are laid out so the right one can be found by
139 # The tuples are laid out so the right one can be found by
140 # comparison.
140 # comparison.
141 pdate, pdist, ptag = max(
141 pdate, pdist, ptag = max(
142 latesttags[p.rev()] for p in ctx.parents())
142 latesttags[p.rev()] for p in ctx.parents())
143 except KeyError:
143 except KeyError:
144 # Cache miss - recurse
144 # Cache miss - recurse
145 todo.append(rev)
145 todo.append(rev)
146 todo.extend(p.rev() for p in ctx.parents())
146 todo.extend(p.rev() for p in ctx.parents())
147 continue
147 continue
148 latesttags[rev] = pdate, pdist + 1, ptag
148 latesttags[rev] = pdate, pdist + 1, ptag
149 return latesttags[rev]
149 return latesttags[rev]
150
150
151 def getrenamedfn(repo, endrev=None):
151 def getrenamedfn(repo, endrev=None):
152 rcache = {}
152 rcache = {}
153 if endrev is None:
153 if endrev is None:
154 endrev = len(repo)
154 endrev = len(repo)
155
155
156 def getrenamed(fn, rev):
156 def getrenamed(fn, rev):
157 '''looks up all renames for a file (up to endrev) the first
157 '''looks up all renames for a file (up to endrev) the first
158 time the file is given. It indexes on the changerev and only
158 time the file is given. It indexes on the changerev and only
159 parses the manifest if linkrev != changerev.
159 parses the manifest if linkrev != changerev.
160 Returns rename info for fn at changerev rev.'''
160 Returns rename info for fn at changerev rev.'''
161 if fn not in rcache:
161 if fn not in rcache:
162 rcache[fn] = {}
162 rcache[fn] = {}
163 fl = repo.file(fn)
163 fl = repo.file(fn)
164 for i in fl:
164 for i in fl:
165 lr = fl.linkrev(i)
165 lr = fl.linkrev(i)
166 renamed = fl.renamed(fl.node(i))
166 renamed = fl.renamed(fl.node(i))
167 rcache[fn][lr] = renamed
167 rcache[fn][lr] = renamed
168 if lr >= endrev:
168 if lr >= endrev:
169 break
169 break
170 if rev in rcache[fn]:
170 if rev in rcache[fn]:
171 return rcache[fn][rev]
171 return rcache[fn][rev]
172
172
173 # If linkrev != rev (i.e. rev not found in rcache) fallback to
173 # If linkrev != rev (i.e. rev not found in rcache) fallback to
174 # filectx logic.
174 # filectx logic.
175 try:
175 try:
176 return repo[rev][fn].renamed()
176 return repo[rev][fn].renamed()
177 except error.LookupError:
177 except error.LookupError:
178 return None
178 return None
179
179
180 return getrenamed
180 return getrenamed
181
181
182
182
183 def showauthor(repo, ctx, templ, **args):
183 def showauthor(repo, ctx, templ, **args):
184 """:author: String. The unmodified author of the changeset."""
184 """:author: String. The unmodified author of the changeset."""
185 return ctx.user()
185 return ctx.user()
186
186
187 def showbisect(repo, ctx, templ, **args):
187 def showbisect(repo, ctx, templ, **args):
188 """:bisect: String. The changeset bisection status."""
188 """:bisect: String. The changeset bisection status."""
189 return hbisect.label(repo, ctx.node())
189 return hbisect.label(repo, ctx.node())
190
190
191 def showbranch(**args):
191 def showbranch(**args):
192 """:branch: String. The name of the branch on which the changeset was
192 """:branch: String. The name of the branch on which the changeset was
193 committed.
193 committed.
194 """
194 """
195 return args['ctx'].branch()
195 return args['ctx'].branch()
196
196
197 def showbranches(**args):
197 def showbranches(**args):
198 """:branches: List of strings. The name of the branch on which the
198 """:branches: List of strings. The name of the branch on which the
199 changeset was committed. Will be empty if the branch name was
199 changeset was committed. Will be empty if the branch name was
200 default.
200 default.
201 """
201 """
202 branch = args['ctx'].branch()
202 branch = args['ctx'].branch()
203 if branch != 'default':
203 if branch != 'default':
204 return showlist('branch', [branch], plural='branches', **args)
204 return showlist('branch', [branch], plural='branches', **args)
205 return showlist('branch', [], plural='branches', **args)
205 return showlist('branch', [], plural='branches', **args)
206
206
207 def showbookmarks(**args):
207 def showbookmarks(**args):
208 """:bookmarks: List of strings. Any bookmarks associated with the
208 """:bookmarks: List of strings. Any bookmarks associated with the
209 changeset.
209 changeset.
210 """
210 """
211 repo = args['ctx']._repo
211 repo = args['ctx']._repo
212 bookmarks = args['ctx'].bookmarks()
212 bookmarks = args['ctx'].bookmarks()
213 current = repo._bookmarkcurrent
213 current = repo._bookmarkcurrent
214 makemap = lambda v: {'bookmark': v, 'current': current}
214 makemap = lambda v: {'bookmark': v, 'current': current}
215 f = _showlist('bookmark', bookmarks, **args)
215 f = _showlist('bookmark', bookmarks, **args)
216 return _hybrid(f, bookmarks, makemap, lambda x: x['bookmark'])
216 return _hybrid(f, bookmarks, makemap, lambda x: x['bookmark'])
217
217
218 def showchildren(**args):
218 def showchildren(**args):
219 """:children: List of strings. The children of the changeset."""
219 """:children: List of strings. The children of the changeset."""
220 ctx = args['ctx']
220 ctx = args['ctx']
221 childrevs = ['%d:%s' % (cctx, cctx) for cctx in ctx.children()]
221 childrevs = ['%d:%s' % (cctx, cctx) for cctx in ctx.children()]
222 return showlist('children', childrevs, element='child', **args)
222 return showlist('children', childrevs, element='child', **args)
223
223
224 def showcurrentbookmark(**args):
224 def showcurrentbookmark(**args):
225 """:currentbookmark: String. The active bookmark, if it is
225 """:currentbookmark: String. The active bookmark, if it is
226 associated with the changeset"""
226 associated with the changeset"""
227 import bookmarks as bookmarks # to avoid circular import issues
227 import bookmarks as bookmarks # to avoid circular import issues
228 repo = args['repo']
228 repo = args['repo']
229 if bookmarks.iscurrent(repo):
229 if bookmarks.iscurrent(repo):
230 current = repo._bookmarkcurrent
230 current = repo._bookmarkcurrent
231 if current in args['ctx'].bookmarks():
231 if current in args['ctx'].bookmarks():
232 return current
232 return current
233 return ''
233 return ''
234
234
235 def showdate(repo, ctx, templ, **args):
235 def showdate(repo, ctx, templ, **args):
236 """:date: Date information. The date when the changeset was committed."""
236 """:date: Date information. The date when the changeset was committed."""
237 return ctx.date()
237 return ctx.date()
238
238
239 def showdescription(repo, ctx, templ, **args):
239 def showdescription(repo, ctx, templ, **args):
240 """:desc: String. The text of the changeset description."""
240 """:desc: String. The text of the changeset description."""
241 return ctx.description().strip()
241 return ctx.description().strip()
242
242
243 def showdiffstat(repo, ctx, templ, **args):
243 def showdiffstat(repo, ctx, templ, **args):
244 """:diffstat: String. Statistics of changes with the following format:
244 """:diffstat: String. Statistics of changes with the following format:
245 "modified files: +added/-removed lines"
245 "modified files: +added/-removed lines"
246 """
246 """
247 stats = patch.diffstatdata(util.iterlines(ctx.diff()))
247 stats = patch.diffstatdata(util.iterlines(ctx.diff()))
248 maxname, maxtotal, adds, removes, binary = patch.diffstatsum(stats)
248 maxname, maxtotal, adds, removes, binary = patch.diffstatsum(stats)
249 return '%s: +%s/-%s' % (len(stats), adds, removes)
249 return '%s: +%s/-%s' % (len(stats), adds, removes)
250
250
251 def showextras(**args):
251 def showextras(**args):
252 """:extras: List of dicts with key, value entries of the 'extras'
252 """:extras: List of dicts with key, value entries of the 'extras'
253 field of this changeset."""
253 field of this changeset."""
254 extras = args['ctx'].extra()
254 extras = args['ctx'].extra()
255 extras = util.sortdict((k, extras[k]) for k in sorted(extras))
255 extras = util.sortdict((k, extras[k]) for k in sorted(extras))
256 makemap = lambda k: {'key': k, 'value': extras[k]}
256 makemap = lambda k: {'key': k, 'value': extras[k]}
257 c = [makemap(k) for k in extras]
257 c = [makemap(k) for k in extras]
258 f = _showlist('extra', c, plural='extras', **args)
258 f = _showlist('extra', c, plural='extras', **args)
259 return _hybrid(f, extras, makemap,
259 return _hybrid(f, extras, makemap,
260 lambda x: '%s=%s' % (x['key'], x['value']))
260 lambda x: '%s=%s' % (x['key'], x['value']))
261
261
262 def showfileadds(**args):
262 def showfileadds(**args):
263 """:file_adds: List of strings. Files added by this changeset."""
263 """:file_adds: List of strings. Files added by this changeset."""
264 repo, ctx, revcache = args['repo'], args['ctx'], args['revcache']
264 repo, ctx, revcache = args['repo'], args['ctx'], args['revcache']
265 return showlist('file_add', getfiles(repo, ctx, revcache)[1],
265 return showlist('file_add', getfiles(repo, ctx, revcache)[1],
266 element='file', **args)
266 element='file', **args)
267
267
268 def showfilecopies(**args):
268 def showfilecopies(**args):
269 """:file_copies: List of strings. Files copied in this changeset with
269 """:file_copies: List of strings. Files copied in this changeset with
270 their sources.
270 their sources.
271 """
271 """
272 cache, ctx = args['cache'], args['ctx']
272 cache, ctx = args['cache'], args['ctx']
273 copies = args['revcache'].get('copies')
273 copies = args['revcache'].get('copies')
274 if copies is None:
274 if copies is None:
275 if 'getrenamed' not in cache:
275 if 'getrenamed' not in cache:
276 cache['getrenamed'] = getrenamedfn(args['repo'])
276 cache['getrenamed'] = getrenamedfn(args['repo'])
277 copies = []
277 copies = []
278 getrenamed = cache['getrenamed']
278 getrenamed = cache['getrenamed']
279 for fn in ctx.files():
279 for fn in ctx.files():
280 rename = getrenamed(fn, ctx.rev())
280 rename = getrenamed(fn, ctx.rev())
281 if rename:
281 if rename:
282 copies.append((fn, rename[0]))
282 copies.append((fn, rename[0]))
283
283
284 copies = util.sortdict(copies)
284 copies = util.sortdict(copies)
285 makemap = lambda k: {'name': k, 'source': copies[k]}
285 makemap = lambda k: {'name': k, 'source': copies[k]}
286 c = [makemap(k) for k in copies]
286 c = [makemap(k) for k in copies]
287 f = _showlist('file_copy', c, plural='file_copies', **args)
287 f = _showlist('file_copy', c, plural='file_copies', **args)
288 return _hybrid(f, copies, makemap,
288 return _hybrid(f, copies, makemap,
289 lambda x: '%s (%s)' % (x['name'], x['source']))
289 lambda x: '%s (%s)' % (x['name'], x['source']))
290
290
291 # showfilecopiesswitch() displays file copies only if copy records are
291 # showfilecopiesswitch() displays file copies only if copy records are
292 # provided before calling the templater, usually with a --copies
292 # provided before calling the templater, usually with a --copies
293 # command line switch.
293 # command line switch.
294 def showfilecopiesswitch(**args):
294 def showfilecopiesswitch(**args):
295 """:file_copies_switch: List of strings. Like "file_copies" but displayed
295 """:file_copies_switch: List of strings. Like "file_copies" but displayed
296 only if the --copied switch is set.
296 only if the --copied switch is set.
297 """
297 """
298 copies = args['revcache'].get('copies') or []
298 copies = args['revcache'].get('copies') or []
299 copies = util.sortdict(copies)
299 copies = util.sortdict(copies)
300 makemap = lambda k: {'name': k, 'source': copies[k]}
300 makemap = lambda k: {'name': k, 'source': copies[k]}
301 c = [makemap(k) for k in copies]
301 c = [makemap(k) for k in copies]
302 f = _showlist('file_copy', c, plural='file_copies', **args)
302 f = _showlist('file_copy', c, plural='file_copies', **args)
303 return _hybrid(f, copies, makemap,
303 return _hybrid(f, copies, makemap,
304 lambda x: '%s (%s)' % (x['name'], x['source']))
304 lambda x: '%s (%s)' % (x['name'], x['source']))
305
305
306 def showfiledels(**args):
306 def showfiledels(**args):
307 """:file_dels: List of strings. Files removed by this changeset."""
307 """:file_dels: List of strings. Files removed by this changeset."""
308 repo, ctx, revcache = args['repo'], args['ctx'], args['revcache']
308 repo, ctx, revcache = args['repo'], args['ctx'], args['revcache']
309 return showlist('file_del', getfiles(repo, ctx, revcache)[2],
309 return showlist('file_del', getfiles(repo, ctx, revcache)[2],
310 element='file', **args)
310 element='file', **args)
311
311
312 def showfilemods(**args):
312 def showfilemods(**args):
313 """:file_mods: List of strings. Files modified by this changeset."""
313 """:file_mods: List of strings. Files modified by this changeset."""
314 repo, ctx, revcache = args['repo'], args['ctx'], args['revcache']
314 repo, ctx, revcache = args['repo'], args['ctx'], args['revcache']
315 return showlist('file_mod', getfiles(repo, ctx, revcache)[0],
315 return showlist('file_mod', getfiles(repo, ctx, revcache)[0],
316 element='file', **args)
316 element='file', **args)
317
317
318 def showfiles(**args):
318 def showfiles(**args):
319 """:files: List of strings. All files modified, added, or removed by this
319 """:files: List of strings. All files modified, added, or removed by this
320 changeset.
320 changeset.
321 """
321 """
322 return showlist('file', args['ctx'].files(), **args)
322 return showlist('file', args['ctx'].files(), **args)
323
323
324 def showlatesttag(repo, ctx, templ, cache, **args):
324 def showlatesttag(repo, ctx, templ, cache, **args):
325 """:latesttag: String. Most recent global tag in the ancestors of this
325 """:latesttag: String. Most recent global tag in the ancestors of this
326 changeset.
326 changeset.
327 """
327 """
328 return getlatesttags(repo, ctx, cache)[2]
328 return getlatesttags(repo, ctx, cache)[2]
329
329
330 def showlatesttagdistance(repo, ctx, templ, cache, **args):
330 def showlatesttagdistance(repo, ctx, templ, cache, **args):
331 """:latesttagdistance: Integer. Longest path to the latest tag."""
331 """:latesttagdistance: Integer. Longest path to the latest tag."""
332 return getlatesttags(repo, ctx, cache)[1]
332 return getlatesttags(repo, ctx, cache)[1]
333
333
334 def showmanifest(**args):
334 def showmanifest(**args):
335 repo, ctx, templ = args['repo'], args['ctx'], args['templ']
335 repo, ctx, templ = args['repo'], args['ctx'], args['templ']
336 args = args.copy()
336 args = args.copy()
337 args.update({'rev': repo.manifest.rev(ctx.changeset()[0]),
337 args.update({'rev': repo.manifest.rev(ctx.changeset()[0]),
338 'node': hex(ctx.changeset()[0])})
338 'node': hex(ctx.changeset()[0])})
339 return templ('manifest', **args)
339 return templ('manifest', **args)
340
340
341 def shownode(repo, ctx, templ, **args):
341 def shownode(repo, ctx, templ, **args):
342 """:node: String. The changeset identification hash, as a 40 hexadecimal
342 """:node: String. The changeset identification hash, as a 40 hexadecimal
343 digit string.
343 digit string.
344 """
344 """
345 return ctx.hex()
345 return ctx.hex()
346
346
347 def showp1rev(repo, ctx, templ, **args):
347 def showp1rev(repo, ctx, templ, **args):
348 """:p1rev: Integer. The repository-local revision number of the changeset's
348 """:p1rev: Integer. The repository-local revision number of the changeset's
349 first parent, or -1 if the changeset has no parents."""
349 first parent, or -1 if the changeset has no parents."""
350 return ctx.p1().rev()
350 return ctx.p1().rev()
351
351
352 def showp2rev(repo, ctx, templ, **args):
352 def showp2rev(repo, ctx, templ, **args):
353 """:p2rev: Integer. The repository-local revision number of the changeset's
353 """:p2rev: Integer. The repository-local revision number of the changeset's
354 second parent, or -1 if the changeset has no second parent."""
354 second parent, or -1 if the changeset has no second parent."""
355 return ctx.p2().rev()
355 return ctx.p2().rev()
356
356
357 def showp1node(repo, ctx, templ, **args):
357 def showp1node(repo, ctx, templ, **args):
358 """:p1node: String. The identification hash of the changeset's first parent,
358 """:p1node: String. The identification hash of the changeset's first parent,
359 as a 40 digit hexadecimal string. If the changeset has no parents, all
359 as a 40 digit hexadecimal string. If the changeset has no parents, all
360 digits are 0."""
360 digits are 0."""
361 return ctx.p1().hex()
361 return ctx.p1().hex()
362
362
363 def showp2node(repo, ctx, templ, **args):
363 def showp2node(repo, ctx, templ, **args):
364 """:p2node: String. The identification hash of the changeset's second
364 """:p2node: String. The identification hash of the changeset's second
365 parent, as a 40 digit hexadecimal string. If the changeset has no second
365 parent, as a 40 digit hexadecimal string. If the changeset has no second
366 parent, all digits are 0."""
366 parent, all digits are 0."""
367 return ctx.p2().hex()
367 return ctx.p2().hex()
368
368
369 def showphase(repo, ctx, templ, **args):
369 def showphase(repo, ctx, templ, **args):
370 """:phase: String. The changeset phase name."""
370 """:phase: String. The changeset phase name."""
371 return ctx.phasestr()
371 return ctx.phasestr()
372
372
373 def showphaseidx(repo, ctx, templ, **args):
373 def showphaseidx(repo, ctx, templ, **args):
374 """:phaseidx: Integer. The changeset phase index."""
374 """:phaseidx: Integer. The changeset phase index."""
375 return ctx.phase()
375 return ctx.phase()
376
376
377 def showrev(repo, ctx, templ, **args):
377 def showrev(repo, ctx, templ, **args):
378 """:rev: Integer. The repository-local changeset revision number."""
378 """:rev: Integer. The repository-local changeset revision number."""
379 return ctx.rev()
379 return ctx.rev()
380
380
381 def showsubrepos(**args):
381 def showsubrepos(**args):
382 """:subrepos: List of strings. Updated subrepositories in the changeset."""
382 """:subrepos: List of strings. Updated subrepositories in the changeset."""
383 ctx = args['ctx']
383 ctx = args['ctx']
384 substate = ctx.substate
384 substate = ctx.substate
385 if not substate:
385 if not substate:
386 return showlist('subrepo', [], **args)
386 return showlist('subrepo', [], **args)
387 psubstate = ctx.parents()[0].substate or {}
387 psubstate = ctx.parents()[0].substate or {}
388 subrepos = []
388 subrepos = []
389 for sub in substate:
389 for sub in substate:
390 if sub not in psubstate or substate[sub] != psubstate[sub]:
390 if sub not in psubstate or substate[sub] != psubstate[sub]:
391 subrepos.append(sub) # modified or newly added in ctx
391 subrepos.append(sub) # modified or newly added in ctx
392 for sub in psubstate:
392 for sub in psubstate:
393 if sub not in substate:
393 if sub not in substate:
394 subrepos.append(sub) # removed in ctx
394 subrepos.append(sub) # removed in ctx
395 return showlist('subrepo', sorted(subrepos), **args)
395 return showlist('subrepo', sorted(subrepos), **args)
396
396
397 def shownames(namespace, **args):
397 def shownames(namespace, **args):
398 """helper method to generate a template keyword for a namespace"""
398 """helper method to generate a template keyword for a namespace"""
399 ctx = args['ctx']
399 ctx = args['ctx']
400 repo = ctx._repo
400 repo = ctx.repo()
401 ns = repo.names[namespace]
401 ns = repo.names[namespace]
402 names = ns.names(repo, ctx.node())
402 names = ns.names(repo, ctx.node())
403 return showlist(ns.templatename, names, plural=namespace, **args)
403 return showlist(ns.templatename, names, plural=namespace, **args)
404
404
405 # don't remove "showtags" definition, even though namespaces will put
405 # don't remove "showtags" definition, even though namespaces will put
406 # a helper function for "tags" keyword into "keywords" map automatically,
406 # a helper function for "tags" keyword into "keywords" map automatically,
407 # because online help text is built without namespaces initialization
407 # because online help text is built without namespaces initialization
408 def showtags(**args):
408 def showtags(**args):
409 """:tags: List of strings. Any tags associated with the changeset."""
409 """:tags: List of strings. Any tags associated with the changeset."""
410 return shownames('tags', **args)
410 return shownames('tags', **args)
411
411
412 # keywords are callables like:
412 # keywords are callables like:
413 # fn(repo, ctx, templ, cache, revcache, **args)
413 # fn(repo, ctx, templ, cache, revcache, **args)
414 # with:
414 # with:
415 # repo - current repository instance
415 # repo - current repository instance
416 # ctx - the changectx being displayed
416 # ctx - the changectx being displayed
417 # templ - the templater instance
417 # templ - the templater instance
418 # cache - a cache dictionary for the whole templater run
418 # cache - a cache dictionary for the whole templater run
419 # revcache - a cache dictionary for the current revision
419 # revcache - a cache dictionary for the current revision
420 keywords = {
420 keywords = {
421 'author': showauthor,
421 'author': showauthor,
422 'bisect': showbisect,
422 'bisect': showbisect,
423 'branch': showbranch,
423 'branch': showbranch,
424 'branches': showbranches,
424 'branches': showbranches,
425 'bookmarks': showbookmarks,
425 'bookmarks': showbookmarks,
426 'children': showchildren,
426 'children': showchildren,
427 'currentbookmark': showcurrentbookmark,
427 'currentbookmark': showcurrentbookmark,
428 'date': showdate,
428 'date': showdate,
429 'desc': showdescription,
429 'desc': showdescription,
430 'diffstat': showdiffstat,
430 'diffstat': showdiffstat,
431 'extras': showextras,
431 'extras': showextras,
432 'file_adds': showfileadds,
432 'file_adds': showfileadds,
433 'file_copies': showfilecopies,
433 'file_copies': showfilecopies,
434 'file_copies_switch': showfilecopiesswitch,
434 'file_copies_switch': showfilecopiesswitch,
435 'file_dels': showfiledels,
435 'file_dels': showfiledels,
436 'file_mods': showfilemods,
436 'file_mods': showfilemods,
437 'files': showfiles,
437 'files': showfiles,
438 'latesttag': showlatesttag,
438 'latesttag': showlatesttag,
439 'latesttagdistance': showlatesttagdistance,
439 'latesttagdistance': showlatesttagdistance,
440 'manifest': showmanifest,
440 'manifest': showmanifest,
441 'node': shownode,
441 'node': shownode,
442 'p1rev': showp1rev,
442 'p1rev': showp1rev,
443 'p1node': showp1node,
443 'p1node': showp1node,
444 'p2rev': showp2rev,
444 'p2rev': showp2rev,
445 'p2node': showp2node,
445 'p2node': showp2node,
446 'phase': showphase,
446 'phase': showphase,
447 'phaseidx': showphaseidx,
447 'phaseidx': showphaseidx,
448 'rev': showrev,
448 'rev': showrev,
449 'subrepos': showsubrepos,
449 'subrepos': showsubrepos,
450 'tags': showtags,
450 'tags': showtags,
451 }
451 }
452
452
453 def _showparents(**args):
453 def _showparents(**args):
454 """:parents: List of strings. The parents of the changeset in "rev:node"
454 """:parents: List of strings. The parents of the changeset in "rev:node"
455 format. If the changeset has only one "natural" parent (the predecessor
455 format. If the changeset has only one "natural" parent (the predecessor
456 revision) nothing is shown."""
456 revision) nothing is shown."""
457 pass
457 pass
458
458
459 dockeywords = {
459 dockeywords = {
460 'parents': _showparents,
460 'parents': _showparents,
461 }
461 }
462 dockeywords.update(keywords)
462 dockeywords.update(keywords)
463 del dockeywords['branches']
463 del dockeywords['branches']
464
464
465 # tell hggettext to extract docstrings from these functions:
465 # tell hggettext to extract docstrings from these functions:
466 i18nfunctions = dockeywords.values()
466 i18nfunctions = dockeywords.values()
@@ -1,770 +1,770 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 of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version.
6 # GNU General Public License version 2 or any later version.
7
7
8 from i18n import _
8 from i18n import _
9 import os, re
9 import os, re
10 import util, config, templatefilters, templatekw, parser, error
10 import util, config, templatefilters, templatekw, parser, error
11 import revset as revsetmod
11 import revset as revsetmod
12 import types
12 import types
13 import minirst
13 import minirst
14
14
15 # template parsing
15 # template parsing
16
16
17 elements = {
17 elements = {
18 "(": (20, ("group", 1, ")"), ("func", 1, ")")),
18 "(": (20, ("group", 1, ")"), ("func", 1, ")")),
19 ",": (2, None, ("list", 2)),
19 ",": (2, None, ("list", 2)),
20 "|": (5, None, ("|", 5)),
20 "|": (5, None, ("|", 5)),
21 "%": (6, None, ("%", 6)),
21 "%": (6, None, ("%", 6)),
22 ")": (0, None, None),
22 ")": (0, None, None),
23 "symbol": (0, ("symbol",), None),
23 "symbol": (0, ("symbol",), None),
24 "string": (0, ("string",), None),
24 "string": (0, ("string",), None),
25 "rawstring": (0, ("rawstring",), None),
25 "rawstring": (0, ("rawstring",), None),
26 "end": (0, None, None),
26 "end": (0, None, None),
27 }
27 }
28
28
29 def tokenizer(data):
29 def tokenizer(data):
30 program, start, end = data
30 program, start, end = data
31 pos = start
31 pos = start
32 while pos < end:
32 while pos < end:
33 c = program[pos]
33 c = program[pos]
34 if c.isspace(): # skip inter-token whitespace
34 if c.isspace(): # skip inter-token whitespace
35 pass
35 pass
36 elif c in "(,)%|": # handle simple operators
36 elif c in "(,)%|": # handle simple operators
37 yield (c, None, pos)
37 yield (c, None, pos)
38 elif (c in '"\'' or c == 'r' and
38 elif (c in '"\'' or c == 'r' and
39 program[pos:pos + 2] in ("r'", 'r"')): # handle quoted strings
39 program[pos:pos + 2] in ("r'", 'r"')): # handle quoted strings
40 if c == 'r':
40 if c == 'r':
41 pos += 1
41 pos += 1
42 c = program[pos]
42 c = program[pos]
43 decode = False
43 decode = False
44 else:
44 else:
45 decode = True
45 decode = True
46 pos += 1
46 pos += 1
47 s = pos
47 s = pos
48 while pos < end: # find closing quote
48 while pos < end: # find closing quote
49 d = program[pos]
49 d = program[pos]
50 if decode and d == '\\': # skip over escaped characters
50 if decode and d == '\\': # skip over escaped characters
51 pos += 2
51 pos += 2
52 continue
52 continue
53 if d == c:
53 if d == c:
54 if not decode:
54 if not decode:
55 yield ('rawstring', program[s:pos], s)
55 yield ('rawstring', program[s:pos], s)
56 break
56 break
57 yield ('string', program[s:pos], s)
57 yield ('string', program[s:pos], s)
58 break
58 break
59 pos += 1
59 pos += 1
60 else:
60 else:
61 raise error.ParseError(_("unterminated string"), s)
61 raise error.ParseError(_("unterminated string"), s)
62 elif c.isalnum() or c in '_':
62 elif c.isalnum() or c in '_':
63 s = pos
63 s = pos
64 pos += 1
64 pos += 1
65 while pos < end: # find end of symbol
65 while pos < end: # find end of symbol
66 d = program[pos]
66 d = program[pos]
67 if not (d.isalnum() or d == "_"):
67 if not (d.isalnum() or d == "_"):
68 break
68 break
69 pos += 1
69 pos += 1
70 sym = program[s:pos]
70 sym = program[s:pos]
71 yield ('symbol', sym, s)
71 yield ('symbol', sym, s)
72 pos -= 1
72 pos -= 1
73 elif c == '}':
73 elif c == '}':
74 pos += 1
74 pos += 1
75 break
75 break
76 else:
76 else:
77 raise error.ParseError(_("syntax error"), pos)
77 raise error.ParseError(_("syntax error"), pos)
78 pos += 1
78 pos += 1
79 yield ('end', None, pos)
79 yield ('end', None, pos)
80
80
81 def compiletemplate(tmpl, context, strtoken="string"):
81 def compiletemplate(tmpl, context, strtoken="string"):
82 parsed = []
82 parsed = []
83 pos, stop = 0, len(tmpl)
83 pos, stop = 0, len(tmpl)
84 p = parser.parser(tokenizer, elements)
84 p = parser.parser(tokenizer, elements)
85 while pos < stop:
85 while pos < stop:
86 n = tmpl.find('{', pos)
86 n = tmpl.find('{', pos)
87 if n < 0:
87 if n < 0:
88 parsed.append((strtoken, tmpl[pos:]))
88 parsed.append((strtoken, tmpl[pos:]))
89 break
89 break
90 if n > 0 and tmpl[n - 1] == '\\':
90 if n > 0 and tmpl[n - 1] == '\\':
91 # escaped
91 # escaped
92 parsed.append((strtoken, (tmpl[pos:n - 1] + "{")))
92 parsed.append((strtoken, (tmpl[pos:n - 1] + "{")))
93 pos = n + 1
93 pos = n + 1
94 continue
94 continue
95 if n > pos:
95 if n > pos:
96 parsed.append((strtoken, tmpl[pos:n]))
96 parsed.append((strtoken, tmpl[pos:n]))
97
97
98 pd = [tmpl, n + 1, stop]
98 pd = [tmpl, n + 1, stop]
99 parseres, pos = p.parse(pd)
99 parseres, pos = p.parse(pd)
100 parsed.append(parseres)
100 parsed.append(parseres)
101
101
102 return [compileexp(e, context) for e in parsed]
102 return [compileexp(e, context) for e in parsed]
103
103
104 def compileexp(exp, context):
104 def compileexp(exp, context):
105 t = exp[0]
105 t = exp[0]
106 if t in methods:
106 if t in methods:
107 return methods[t](exp, context)
107 return methods[t](exp, context)
108 raise error.ParseError(_("unknown method '%s'") % t)
108 raise error.ParseError(_("unknown method '%s'") % t)
109
109
110 # template evaluation
110 # template evaluation
111
111
112 def getsymbol(exp):
112 def getsymbol(exp):
113 if exp[0] == 'symbol':
113 if exp[0] == 'symbol':
114 return exp[1]
114 return exp[1]
115 raise error.ParseError(_("expected a symbol, got '%s'") % exp[0])
115 raise error.ParseError(_("expected a symbol, got '%s'") % exp[0])
116
116
117 def getlist(x):
117 def getlist(x):
118 if not x:
118 if not x:
119 return []
119 return []
120 if x[0] == 'list':
120 if x[0] == 'list':
121 return getlist(x[1]) + [x[2]]
121 return getlist(x[1]) + [x[2]]
122 return [x]
122 return [x]
123
123
124 def getfilter(exp, context):
124 def getfilter(exp, context):
125 f = getsymbol(exp)
125 f = getsymbol(exp)
126 if f not in context._filters:
126 if f not in context._filters:
127 raise error.ParseError(_("unknown function '%s'") % f)
127 raise error.ParseError(_("unknown function '%s'") % f)
128 return context._filters[f]
128 return context._filters[f]
129
129
130 def gettemplate(exp, context):
130 def gettemplate(exp, context):
131 if exp[0] == 'string' or exp[0] == 'rawstring':
131 if exp[0] == 'string' or exp[0] == 'rawstring':
132 return compiletemplate(exp[1], context, strtoken=exp[0])
132 return compiletemplate(exp[1], context, strtoken=exp[0])
133 if exp[0] == 'symbol':
133 if exp[0] == 'symbol':
134 return context._load(exp[1])
134 return context._load(exp[1])
135 raise error.ParseError(_("expected template specifier"))
135 raise error.ParseError(_("expected template specifier"))
136
136
137 def runstring(context, mapping, data):
137 def runstring(context, mapping, data):
138 return data.decode("string-escape")
138 return data.decode("string-escape")
139
139
140 def runrawstring(context, mapping, data):
140 def runrawstring(context, mapping, data):
141 return data
141 return data
142
142
143 def runsymbol(context, mapping, key):
143 def runsymbol(context, mapping, key):
144 v = mapping.get(key)
144 v = mapping.get(key)
145 if v is None:
145 if v is None:
146 v = context._defaults.get(key)
146 v = context._defaults.get(key)
147 if v is None:
147 if v is None:
148 try:
148 try:
149 v = context.process(key, mapping)
149 v = context.process(key, mapping)
150 except TemplateNotFound:
150 except TemplateNotFound:
151 v = ''
151 v = ''
152 if callable(v):
152 if callable(v):
153 return v(**mapping)
153 return v(**mapping)
154 if isinstance(v, types.GeneratorType):
154 if isinstance(v, types.GeneratorType):
155 v = list(v)
155 v = list(v)
156 return v
156 return v
157
157
158 def buildfilter(exp, context):
158 def buildfilter(exp, context):
159 func, data = compileexp(exp[1], context)
159 func, data = compileexp(exp[1], context)
160 filt = getfilter(exp[2], context)
160 filt = getfilter(exp[2], context)
161 return (runfilter, (func, data, filt))
161 return (runfilter, (func, data, filt))
162
162
163 def runfilter(context, mapping, data):
163 def runfilter(context, mapping, data):
164 func, data, filt = data
164 func, data, filt = data
165 # func() may return string, generator of strings or arbitrary object such
165 # func() may return string, generator of strings or arbitrary object such
166 # as date tuple, but filter does not want generator.
166 # as date tuple, but filter does not want generator.
167 thing = func(context, mapping, data)
167 thing = func(context, mapping, data)
168 if isinstance(thing, types.GeneratorType):
168 if isinstance(thing, types.GeneratorType):
169 thing = stringify(thing)
169 thing = stringify(thing)
170 try:
170 try:
171 return filt(thing)
171 return filt(thing)
172 except (ValueError, AttributeError, TypeError):
172 except (ValueError, AttributeError, TypeError):
173 if isinstance(data, tuple):
173 if isinstance(data, tuple):
174 dt = data[1]
174 dt = data[1]
175 else:
175 else:
176 dt = data
176 dt = data
177 raise util.Abort(_("template filter '%s' is not compatible with "
177 raise util.Abort(_("template filter '%s' is not compatible with "
178 "keyword '%s'") % (filt.func_name, dt))
178 "keyword '%s'") % (filt.func_name, dt))
179
179
180 def buildmap(exp, context):
180 def buildmap(exp, context):
181 func, data = compileexp(exp[1], context)
181 func, data = compileexp(exp[1], context)
182 ctmpl = gettemplate(exp[2], context)
182 ctmpl = gettemplate(exp[2], context)
183 return (runmap, (func, data, ctmpl))
183 return (runmap, (func, data, ctmpl))
184
184
185 def runtemplate(context, mapping, template):
185 def runtemplate(context, mapping, template):
186 for func, data in template:
186 for func, data in template:
187 yield func(context, mapping, data)
187 yield func(context, mapping, data)
188
188
189 def runmap(context, mapping, data):
189 def runmap(context, mapping, data):
190 func, data, ctmpl = data
190 func, data, ctmpl = data
191 d = func(context, mapping, data)
191 d = func(context, mapping, data)
192 if callable(d):
192 if callable(d):
193 d = d()
193 d = d()
194
194
195 lm = mapping.copy()
195 lm = mapping.copy()
196
196
197 for i in d:
197 for i in d:
198 if isinstance(i, dict):
198 if isinstance(i, dict):
199 lm.update(i)
199 lm.update(i)
200 lm['originalnode'] = mapping.get('node')
200 lm['originalnode'] = mapping.get('node')
201 yield runtemplate(context, lm, ctmpl)
201 yield runtemplate(context, lm, ctmpl)
202 else:
202 else:
203 # v is not an iterable of dicts, this happen when 'key'
203 # v is not an iterable of dicts, this happen when 'key'
204 # has been fully expanded already and format is useless.
204 # has been fully expanded already and format is useless.
205 # If so, return the expanded value.
205 # If so, return the expanded value.
206 yield i
206 yield i
207
207
208 def buildfunc(exp, context):
208 def buildfunc(exp, context):
209 n = getsymbol(exp[1])
209 n = getsymbol(exp[1])
210 args = [compileexp(x, context) for x in getlist(exp[2])]
210 args = [compileexp(x, context) for x in getlist(exp[2])]
211 if n in funcs:
211 if n in funcs:
212 f = funcs[n]
212 f = funcs[n]
213 return (f, args)
213 return (f, args)
214 if n in context._filters:
214 if n in context._filters:
215 if len(args) != 1:
215 if len(args) != 1:
216 raise error.ParseError(_("filter %s expects one argument") % n)
216 raise error.ParseError(_("filter %s expects one argument") % n)
217 f = context._filters[n]
217 f = context._filters[n]
218 return (runfilter, (args[0][0], args[0][1], f))
218 return (runfilter, (args[0][0], args[0][1], f))
219 raise error.ParseError(_("unknown function '%s'") % n)
219 raise error.ParseError(_("unknown function '%s'") % n)
220
220
221 def date(context, mapping, args):
221 def date(context, mapping, args):
222 if not (1 <= len(args) <= 2):
222 if not (1 <= len(args) <= 2):
223 # i18n: "date" is a keyword
223 # i18n: "date" is a keyword
224 raise error.ParseError(_("date expects one or two arguments"))
224 raise error.ParseError(_("date expects one or two arguments"))
225
225
226 date = args[0][0](context, mapping, args[0][1])
226 date = args[0][0](context, mapping, args[0][1])
227 if len(args) == 2:
227 if len(args) == 2:
228 fmt = stringify(args[1][0](context, mapping, args[1][1]))
228 fmt = stringify(args[1][0](context, mapping, args[1][1]))
229 return util.datestr(date, fmt)
229 return util.datestr(date, fmt)
230 return util.datestr(date)
230 return util.datestr(date)
231
231
232 def diff(context, mapping, args):
232 def diff(context, mapping, args):
233 if len(args) > 2:
233 if len(args) > 2:
234 # i18n: "diff" is a keyword
234 # i18n: "diff" is a keyword
235 raise error.ParseError(_("diff expects one, two or no arguments"))
235 raise error.ParseError(_("diff expects one, two or no arguments"))
236
236
237 def getpatterns(i):
237 def getpatterns(i):
238 if i < len(args):
238 if i < len(args):
239 s = args[i][1].strip()
239 s = args[i][1].strip()
240 if s:
240 if s:
241 return [s]
241 return [s]
242 return []
242 return []
243
243
244 ctx = mapping['ctx']
244 ctx = mapping['ctx']
245 chunks = ctx.diff(match=ctx.match([], getpatterns(0), getpatterns(1)))
245 chunks = ctx.diff(match=ctx.match([], getpatterns(0), getpatterns(1)))
246
246
247 return ''.join(chunks)
247 return ''.join(chunks)
248
248
249 def fill(context, mapping, args):
249 def fill(context, mapping, args):
250 if not (1 <= len(args) <= 4):
250 if not (1 <= len(args) <= 4):
251 # i18n: "fill" is a keyword
251 # i18n: "fill" is a keyword
252 raise error.ParseError(_("fill expects one to four arguments"))
252 raise error.ParseError(_("fill expects one to four arguments"))
253
253
254 text = stringify(args[0][0](context, mapping, args[0][1]))
254 text = stringify(args[0][0](context, mapping, args[0][1]))
255 width = 76
255 width = 76
256 initindent = ''
256 initindent = ''
257 hangindent = ''
257 hangindent = ''
258 if 2 <= len(args) <= 4:
258 if 2 <= len(args) <= 4:
259 try:
259 try:
260 width = int(stringify(args[1][0](context, mapping, args[1][1])))
260 width = int(stringify(args[1][0](context, mapping, args[1][1])))
261 except ValueError:
261 except ValueError:
262 # i18n: "fill" is a keyword
262 # i18n: "fill" is a keyword
263 raise error.ParseError(_("fill expects an integer width"))
263 raise error.ParseError(_("fill expects an integer width"))
264 try:
264 try:
265 initindent = stringify(_evalifliteral(args[2], context, mapping))
265 initindent = stringify(_evalifliteral(args[2], context, mapping))
266 hangindent = stringify(_evalifliteral(args[3], context, mapping))
266 hangindent = stringify(_evalifliteral(args[3], context, mapping))
267 except IndexError:
267 except IndexError:
268 pass
268 pass
269
269
270 return templatefilters.fill(text, width, initindent, hangindent)
270 return templatefilters.fill(text, width, initindent, hangindent)
271
271
272 def pad(context, mapping, args):
272 def pad(context, mapping, args):
273 """usage: pad(text, width, fillchar=' ', right=False)
273 """usage: pad(text, width, fillchar=' ', right=False)
274 """
274 """
275 if not (2 <= len(args) <= 4):
275 if not (2 <= len(args) <= 4):
276 # i18n: "pad" is a keyword
276 # i18n: "pad" is a keyword
277 raise error.ParseError(_("pad() expects two to four arguments"))
277 raise error.ParseError(_("pad() expects two to four arguments"))
278
278
279 width = int(args[1][1])
279 width = int(args[1][1])
280
280
281 text = stringify(args[0][0](context, mapping, args[0][1]))
281 text = stringify(args[0][0](context, mapping, args[0][1]))
282 if args[0][0] == runstring:
282 if args[0][0] == runstring:
283 text = stringify(runtemplate(context, mapping,
283 text = stringify(runtemplate(context, mapping,
284 compiletemplate(text, context)))
284 compiletemplate(text, context)))
285
285
286 right = False
286 right = False
287 fillchar = ' '
287 fillchar = ' '
288 if len(args) > 2:
288 if len(args) > 2:
289 fillchar = stringify(args[2][0](context, mapping, args[2][1]))
289 fillchar = stringify(args[2][0](context, mapping, args[2][1]))
290 if len(args) > 3:
290 if len(args) > 3:
291 right = util.parsebool(args[3][1])
291 right = util.parsebool(args[3][1])
292
292
293 if right:
293 if right:
294 return text.rjust(width, fillchar)
294 return text.rjust(width, fillchar)
295 else:
295 else:
296 return text.ljust(width, fillchar)
296 return text.ljust(width, fillchar)
297
297
298 def get(context, mapping, args):
298 def get(context, mapping, args):
299 if len(args) != 2:
299 if len(args) != 2:
300 # i18n: "get" is a keyword
300 # i18n: "get" is a keyword
301 raise error.ParseError(_("get() expects two arguments"))
301 raise error.ParseError(_("get() expects two arguments"))
302
302
303 dictarg = args[0][0](context, mapping, args[0][1])
303 dictarg = args[0][0](context, mapping, args[0][1])
304 if not util.safehasattr(dictarg, 'get'):
304 if not util.safehasattr(dictarg, 'get'):
305 # i18n: "get" is a keyword
305 # i18n: "get" is a keyword
306 raise error.ParseError(_("get() expects a dict as first argument"))
306 raise error.ParseError(_("get() expects a dict as first argument"))
307
307
308 key = args[1][0](context, mapping, args[1][1])
308 key = args[1][0](context, mapping, args[1][1])
309 yield dictarg.get(key)
309 yield dictarg.get(key)
310
310
311 def _evalifliteral(arg, context, mapping):
311 def _evalifliteral(arg, context, mapping):
312 t = stringify(arg[0](context, mapping, arg[1]))
312 t = stringify(arg[0](context, mapping, arg[1]))
313 if arg[0] == runstring or arg[0] == runrawstring:
313 if arg[0] == runstring or arg[0] == runrawstring:
314 yield runtemplate(context, mapping,
314 yield runtemplate(context, mapping,
315 compiletemplate(t, context, strtoken='rawstring'))
315 compiletemplate(t, context, strtoken='rawstring'))
316 else:
316 else:
317 yield t
317 yield t
318
318
319 def if_(context, mapping, args):
319 def if_(context, mapping, args):
320 if not (2 <= len(args) <= 3):
320 if not (2 <= len(args) <= 3):
321 # i18n: "if" is a keyword
321 # i18n: "if" is a keyword
322 raise error.ParseError(_("if expects two or three arguments"))
322 raise error.ParseError(_("if expects two or three arguments"))
323
323
324 test = stringify(args[0][0](context, mapping, args[0][1]))
324 test = stringify(args[0][0](context, mapping, args[0][1]))
325 if test:
325 if test:
326 yield _evalifliteral(args[1], context, mapping)
326 yield _evalifliteral(args[1], context, mapping)
327 elif len(args) == 3:
327 elif len(args) == 3:
328 yield _evalifliteral(args[2], context, mapping)
328 yield _evalifliteral(args[2], context, mapping)
329
329
330 def ifcontains(context, mapping, args):
330 def ifcontains(context, mapping, args):
331 if not (3 <= len(args) <= 4):
331 if not (3 <= len(args) <= 4):
332 # i18n: "ifcontains" is a keyword
332 # i18n: "ifcontains" is a keyword
333 raise error.ParseError(_("ifcontains expects three or four arguments"))
333 raise error.ParseError(_("ifcontains expects three or four arguments"))
334
334
335 item = stringify(args[0][0](context, mapping, args[0][1]))
335 item = stringify(args[0][0](context, mapping, args[0][1]))
336 items = args[1][0](context, mapping, args[1][1])
336 items = args[1][0](context, mapping, args[1][1])
337
337
338 if item in items:
338 if item in items:
339 yield _evalifliteral(args[2], context, mapping)
339 yield _evalifliteral(args[2], context, mapping)
340 elif len(args) == 4:
340 elif len(args) == 4:
341 yield _evalifliteral(args[3], context, mapping)
341 yield _evalifliteral(args[3], context, mapping)
342
342
343 def ifeq(context, mapping, args):
343 def ifeq(context, mapping, args):
344 if not (3 <= len(args) <= 4):
344 if not (3 <= len(args) <= 4):
345 # i18n: "ifeq" is a keyword
345 # i18n: "ifeq" is a keyword
346 raise error.ParseError(_("ifeq expects three or four arguments"))
346 raise error.ParseError(_("ifeq expects three or four arguments"))
347
347
348 test = stringify(args[0][0](context, mapping, args[0][1]))
348 test = stringify(args[0][0](context, mapping, args[0][1]))
349 match = stringify(args[1][0](context, mapping, args[1][1]))
349 match = stringify(args[1][0](context, mapping, args[1][1]))
350 if test == match:
350 if test == match:
351 yield _evalifliteral(args[2], context, mapping)
351 yield _evalifliteral(args[2], context, mapping)
352 elif len(args) == 4:
352 elif len(args) == 4:
353 yield _evalifliteral(args[3], context, mapping)
353 yield _evalifliteral(args[3], context, mapping)
354
354
355 def join(context, mapping, args):
355 def join(context, mapping, args):
356 if not (1 <= len(args) <= 2):
356 if not (1 <= len(args) <= 2):
357 # i18n: "join" is a keyword
357 # i18n: "join" is a keyword
358 raise error.ParseError(_("join expects one or two arguments"))
358 raise error.ParseError(_("join expects one or two arguments"))
359
359
360 joinset = args[0][0](context, mapping, args[0][1])
360 joinset = args[0][0](context, mapping, args[0][1])
361 if callable(joinset):
361 if callable(joinset):
362 jf = joinset.joinfmt
362 jf = joinset.joinfmt
363 joinset = [jf(x) for x in joinset()]
363 joinset = [jf(x) for x in joinset()]
364
364
365 joiner = " "
365 joiner = " "
366 if len(args) > 1:
366 if len(args) > 1:
367 joiner = stringify(args[1][0](context, mapping, args[1][1]))
367 joiner = stringify(args[1][0](context, mapping, args[1][1]))
368
368
369 first = True
369 first = True
370 for x in joinset:
370 for x in joinset:
371 if first:
371 if first:
372 first = False
372 first = False
373 else:
373 else:
374 yield joiner
374 yield joiner
375 yield x
375 yield x
376
376
377 def label(context, mapping, args):
377 def label(context, mapping, args):
378 if len(args) != 2:
378 if len(args) != 2:
379 # i18n: "label" is a keyword
379 # i18n: "label" is a keyword
380 raise error.ParseError(_("label expects two arguments"))
380 raise error.ParseError(_("label expects two arguments"))
381
381
382 # ignore args[0] (the label string) since this is supposed to be a a no-op
382 # ignore args[0] (the label string) since this is supposed to be a a no-op
383 yield _evalifliteral(args[1], context, mapping)
383 yield _evalifliteral(args[1], context, mapping)
384
384
385 def revset(context, mapping, args):
385 def revset(context, mapping, args):
386 """usage: revset(query[, formatargs...])
386 """usage: revset(query[, formatargs...])
387 """
387 """
388 if not len(args) > 0:
388 if not len(args) > 0:
389 # i18n: "revset" is a keyword
389 # i18n: "revset" is a keyword
390 raise error.ParseError(_("revset expects one or more arguments"))
390 raise error.ParseError(_("revset expects one or more arguments"))
391
391
392 raw = args[0][1]
392 raw = args[0][1]
393 ctx = mapping['ctx']
393 ctx = mapping['ctx']
394 repo = ctx._repo
394 repo = ctx.repo()
395
395
396 def query(expr):
396 def query(expr):
397 m = revsetmod.match(repo.ui, expr)
397 m = revsetmod.match(repo.ui, expr)
398 return m(repo)
398 return m(repo)
399
399
400 if len(args) > 1:
400 if len(args) > 1:
401 formatargs = list([a[0](context, mapping, a[1]) for a in args[1:]])
401 formatargs = list([a[0](context, mapping, a[1]) for a in args[1:]])
402 revs = query(revsetmod.formatspec(raw, *formatargs))
402 revs = query(revsetmod.formatspec(raw, *formatargs))
403 revs = list([str(r) for r in revs])
403 revs = list([str(r) for r in revs])
404 else:
404 else:
405 revsetcache = mapping['cache'].setdefault("revsetcache", {})
405 revsetcache = mapping['cache'].setdefault("revsetcache", {})
406 if raw in revsetcache:
406 if raw in revsetcache:
407 revs = revsetcache[raw]
407 revs = revsetcache[raw]
408 else:
408 else:
409 revs = query(raw)
409 revs = query(raw)
410 revs = list([str(r) for r in revs])
410 revs = list([str(r) for r in revs])
411 revsetcache[raw] = revs
411 revsetcache[raw] = revs
412
412
413 return templatekw.showlist("revision", revs, **mapping)
413 return templatekw.showlist("revision", revs, **mapping)
414
414
415 def rstdoc(context, mapping, args):
415 def rstdoc(context, mapping, args):
416 if len(args) != 2:
416 if len(args) != 2:
417 # i18n: "rstdoc" is a keyword
417 # i18n: "rstdoc" is a keyword
418 raise error.ParseError(_("rstdoc expects two arguments"))
418 raise error.ParseError(_("rstdoc expects two arguments"))
419
419
420 text = stringify(args[0][0](context, mapping, args[0][1]))
420 text = stringify(args[0][0](context, mapping, args[0][1]))
421 style = stringify(args[1][0](context, mapping, args[1][1]))
421 style = stringify(args[1][0](context, mapping, args[1][1]))
422
422
423 return minirst.format(text, style=style, keep=['verbose'])
423 return minirst.format(text, style=style, keep=['verbose'])
424
424
425 def shortest(context, mapping, args):
425 def shortest(context, mapping, args):
426 """usage: shortest(node, minlength=4)
426 """usage: shortest(node, minlength=4)
427 """
427 """
428 if not (1 <= len(args) <= 2):
428 if not (1 <= len(args) <= 2):
429 # i18n: "shortest" is a keyword
429 # i18n: "shortest" is a keyword
430 raise error.ParseError(_("shortest() expects one or two arguments"))
430 raise error.ParseError(_("shortest() expects one or two arguments"))
431
431
432 node = stringify(args[0][0](context, mapping, args[0][1]))
432 node = stringify(args[0][0](context, mapping, args[0][1]))
433
433
434 minlength = 4
434 minlength = 4
435 if len(args) > 1:
435 if len(args) > 1:
436 minlength = int(args[1][1])
436 minlength = int(args[1][1])
437
437
438 cl = mapping['ctx']._repo.changelog
438 cl = mapping['ctx']._repo.changelog
439 def isvalid(test):
439 def isvalid(test):
440 try:
440 try:
441 try:
441 try:
442 cl.index.partialmatch(test)
442 cl.index.partialmatch(test)
443 except AttributeError:
443 except AttributeError:
444 # Pure mercurial doesn't support partialmatch on the index.
444 # Pure mercurial doesn't support partialmatch on the index.
445 # Fallback to the slow way.
445 # Fallback to the slow way.
446 if cl._partialmatch(test) is None:
446 if cl._partialmatch(test) is None:
447 return False
447 return False
448
448
449 try:
449 try:
450 i = int(test)
450 i = int(test)
451 # if we are a pure int, then starting with zero will not be
451 # if we are a pure int, then starting with zero will not be
452 # confused as a rev; or, obviously, if the int is larger than
452 # confused as a rev; or, obviously, if the int is larger than
453 # the value of the tip rev
453 # the value of the tip rev
454 if test[0] == '0' or i > len(cl):
454 if test[0] == '0' or i > len(cl):
455 return True
455 return True
456 return False
456 return False
457 except ValueError:
457 except ValueError:
458 return True
458 return True
459 except error.RevlogError:
459 except error.RevlogError:
460 return False
460 return False
461
461
462 shortest = node
462 shortest = node
463 startlength = max(6, minlength)
463 startlength = max(6, minlength)
464 length = startlength
464 length = startlength
465 while True:
465 while True:
466 test = node[:length]
466 test = node[:length]
467 if isvalid(test):
467 if isvalid(test):
468 shortest = test
468 shortest = test
469 if length == minlength or length > startlength:
469 if length == minlength or length > startlength:
470 return shortest
470 return shortest
471 length -= 1
471 length -= 1
472 else:
472 else:
473 length += 1
473 length += 1
474 if len(shortest) <= length:
474 if len(shortest) <= length:
475 return shortest
475 return shortest
476
476
477 def strip(context, mapping, args):
477 def strip(context, mapping, args):
478 if not (1 <= len(args) <= 2):
478 if not (1 <= len(args) <= 2):
479 # i18n: "strip" is a keyword
479 # i18n: "strip" is a keyword
480 raise error.ParseError(_("strip expects one or two arguments"))
480 raise error.ParseError(_("strip expects one or two arguments"))
481
481
482 text = stringify(args[0][0](context, mapping, args[0][1]))
482 text = stringify(args[0][0](context, mapping, args[0][1]))
483 if len(args) == 2:
483 if len(args) == 2:
484 chars = stringify(args[1][0](context, mapping, args[1][1]))
484 chars = stringify(args[1][0](context, mapping, args[1][1]))
485 return text.strip(chars)
485 return text.strip(chars)
486 return text.strip()
486 return text.strip()
487
487
488 def sub(context, mapping, args):
488 def sub(context, mapping, args):
489 if len(args) != 3:
489 if len(args) != 3:
490 # i18n: "sub" is a keyword
490 # i18n: "sub" is a keyword
491 raise error.ParseError(_("sub expects three arguments"))
491 raise error.ParseError(_("sub expects three arguments"))
492
492
493 pat = stringify(args[0][0](context, mapping, args[0][1]))
493 pat = stringify(args[0][0](context, mapping, args[0][1]))
494 rpl = stringify(args[1][0](context, mapping, args[1][1]))
494 rpl = stringify(args[1][0](context, mapping, args[1][1]))
495 src = stringify(_evalifliteral(args[2], context, mapping))
495 src = stringify(_evalifliteral(args[2], context, mapping))
496 yield re.sub(pat, rpl, src)
496 yield re.sub(pat, rpl, src)
497
497
498 def startswith(context, mapping, args):
498 def startswith(context, mapping, args):
499 if len(args) != 2:
499 if len(args) != 2:
500 # i18n: "startswith" is a keyword
500 # i18n: "startswith" is a keyword
501 raise error.ParseError(_("startswith expects two arguments"))
501 raise error.ParseError(_("startswith expects two arguments"))
502
502
503 patn = stringify(args[0][0](context, mapping, args[0][1]))
503 patn = stringify(args[0][0](context, mapping, args[0][1]))
504 text = stringify(args[1][0](context, mapping, args[1][1]))
504 text = stringify(args[1][0](context, mapping, args[1][1]))
505 if text.startswith(patn):
505 if text.startswith(patn):
506 return text
506 return text
507 return ''
507 return ''
508
508
509
509
510 def word(context, mapping, args):
510 def word(context, mapping, args):
511 """return nth word from a string"""
511 """return nth word from a string"""
512 if not (2 <= len(args) <= 3):
512 if not (2 <= len(args) <= 3):
513 # i18n: "word" is a keyword
513 # i18n: "word" is a keyword
514 raise error.ParseError(_("word expects two or three arguments, got %d")
514 raise error.ParseError(_("word expects two or three arguments, got %d")
515 % len(args))
515 % len(args))
516
516
517 num = int(stringify(args[0][0](context, mapping, args[0][1])))
517 num = int(stringify(args[0][0](context, mapping, args[0][1])))
518 text = stringify(args[1][0](context, mapping, args[1][1]))
518 text = stringify(args[1][0](context, mapping, args[1][1]))
519 if len(args) == 3:
519 if len(args) == 3:
520 splitter = stringify(args[2][0](context, mapping, args[2][1]))
520 splitter = stringify(args[2][0](context, mapping, args[2][1]))
521 else:
521 else:
522 splitter = None
522 splitter = None
523
523
524 tokens = text.split(splitter)
524 tokens = text.split(splitter)
525 if num >= len(tokens):
525 if num >= len(tokens):
526 return ''
526 return ''
527 else:
527 else:
528 return tokens[num]
528 return tokens[num]
529
529
530 methods = {
530 methods = {
531 "string": lambda e, c: (runstring, e[1]),
531 "string": lambda e, c: (runstring, e[1]),
532 "rawstring": lambda e, c: (runrawstring, e[1]),
532 "rawstring": lambda e, c: (runrawstring, e[1]),
533 "symbol": lambda e, c: (runsymbol, e[1]),
533 "symbol": lambda e, c: (runsymbol, e[1]),
534 "group": lambda e, c: compileexp(e[1], c),
534 "group": lambda e, c: compileexp(e[1], c),
535 # ".": buildmember,
535 # ".": buildmember,
536 "|": buildfilter,
536 "|": buildfilter,
537 "%": buildmap,
537 "%": buildmap,
538 "func": buildfunc,
538 "func": buildfunc,
539 }
539 }
540
540
541 funcs = {
541 funcs = {
542 "date": date,
542 "date": date,
543 "diff": diff,
543 "diff": diff,
544 "fill": fill,
544 "fill": fill,
545 "get": get,
545 "get": get,
546 "if": if_,
546 "if": if_,
547 "ifcontains": ifcontains,
547 "ifcontains": ifcontains,
548 "ifeq": ifeq,
548 "ifeq": ifeq,
549 "join": join,
549 "join": join,
550 "label": label,
550 "label": label,
551 "pad": pad,
551 "pad": pad,
552 "revset": revset,
552 "revset": revset,
553 "rstdoc": rstdoc,
553 "rstdoc": rstdoc,
554 "shortest": shortest,
554 "shortest": shortest,
555 "startswith": startswith,
555 "startswith": startswith,
556 "strip": strip,
556 "strip": strip,
557 "sub": sub,
557 "sub": sub,
558 "word": word,
558 "word": word,
559 }
559 }
560
560
561 # template engine
561 # template engine
562
562
563 stringify = templatefilters.stringify
563 stringify = templatefilters.stringify
564
564
565 def _flatten(thing):
565 def _flatten(thing):
566 '''yield a single stream from a possibly nested set of iterators'''
566 '''yield a single stream from a possibly nested set of iterators'''
567 if isinstance(thing, str):
567 if isinstance(thing, str):
568 yield thing
568 yield thing
569 elif not util.safehasattr(thing, '__iter__'):
569 elif not util.safehasattr(thing, '__iter__'):
570 if thing is not None:
570 if thing is not None:
571 yield str(thing)
571 yield str(thing)
572 else:
572 else:
573 for i in thing:
573 for i in thing:
574 if isinstance(i, str):
574 if isinstance(i, str):
575 yield i
575 yield i
576 elif not util.safehasattr(i, '__iter__'):
576 elif not util.safehasattr(i, '__iter__'):
577 if i is not None:
577 if i is not None:
578 yield str(i)
578 yield str(i)
579 elif i is not None:
579 elif i is not None:
580 for j in _flatten(i):
580 for j in _flatten(i):
581 yield j
581 yield j
582
582
583 def parsestring(s, quoted=True):
583 def parsestring(s, quoted=True):
584 '''parse a string using simple c-like syntax.
584 '''parse a string using simple c-like syntax.
585 string must be in quotes if quoted is True.'''
585 string must be in quotes if quoted is True.'''
586 if quoted:
586 if quoted:
587 if len(s) < 2 or s[0] != s[-1]:
587 if len(s) < 2 or s[0] != s[-1]:
588 raise SyntaxError(_('unmatched quotes'))
588 raise SyntaxError(_('unmatched quotes'))
589 return s[1:-1].decode('string_escape')
589 return s[1:-1].decode('string_escape')
590
590
591 return s.decode('string_escape')
591 return s.decode('string_escape')
592
592
593 class engine(object):
593 class engine(object):
594 '''template expansion engine.
594 '''template expansion engine.
595
595
596 template expansion works like this. a map file contains key=value
596 template expansion works like this. a map file contains key=value
597 pairs. if value is quoted, it is treated as string. otherwise, it
597 pairs. if value is quoted, it is treated as string. otherwise, it
598 is treated as name of template file.
598 is treated as name of template file.
599
599
600 templater is asked to expand a key in map. it looks up key, and
600 templater is asked to expand a key in map. it looks up key, and
601 looks for strings like this: {foo}. it expands {foo} by looking up
601 looks for strings like this: {foo}. it expands {foo} by looking up
602 foo in map, and substituting it. expansion is recursive: it stops
602 foo in map, and substituting it. expansion is recursive: it stops
603 when there is no more {foo} to replace.
603 when there is no more {foo} to replace.
604
604
605 expansion also allows formatting and filtering.
605 expansion also allows formatting and filtering.
606
606
607 format uses key to expand each item in list. syntax is
607 format uses key to expand each item in list. syntax is
608 {key%format}.
608 {key%format}.
609
609
610 filter uses function to transform value. syntax is
610 filter uses function to transform value. syntax is
611 {key|filter1|filter2|...}.'''
611 {key|filter1|filter2|...}.'''
612
612
613 def __init__(self, loader, filters={}, defaults={}):
613 def __init__(self, loader, filters={}, defaults={}):
614 self._loader = loader
614 self._loader = loader
615 self._filters = filters
615 self._filters = filters
616 self._defaults = defaults
616 self._defaults = defaults
617 self._cache = {}
617 self._cache = {}
618
618
619 def _load(self, t):
619 def _load(self, t):
620 '''load, parse, and cache a template'''
620 '''load, parse, and cache a template'''
621 if t not in self._cache:
621 if t not in self._cache:
622 self._cache[t] = compiletemplate(self._loader(t), self)
622 self._cache[t] = compiletemplate(self._loader(t), self)
623 return self._cache[t]
623 return self._cache[t]
624
624
625 def process(self, t, mapping):
625 def process(self, t, mapping):
626 '''Perform expansion. t is name of map element to expand.
626 '''Perform expansion. t is name of map element to expand.
627 mapping contains added elements for use during expansion. Is a
627 mapping contains added elements for use during expansion. Is a
628 generator.'''
628 generator.'''
629 return _flatten(runtemplate(self, mapping, self._load(t)))
629 return _flatten(runtemplate(self, mapping, self._load(t)))
630
630
631 engines = {'default': engine}
631 engines = {'default': engine}
632
632
633 def stylelist():
633 def stylelist():
634 paths = templatepaths()
634 paths = templatepaths()
635 if not paths:
635 if not paths:
636 return _('no templates found, try `hg debuginstall` for more info')
636 return _('no templates found, try `hg debuginstall` for more info')
637 dirlist = os.listdir(paths[0])
637 dirlist = os.listdir(paths[0])
638 stylelist = []
638 stylelist = []
639 for file in dirlist:
639 for file in dirlist:
640 split = file.split(".")
640 split = file.split(".")
641 if split[0] == "map-cmdline":
641 if split[0] == "map-cmdline":
642 stylelist.append(split[1])
642 stylelist.append(split[1])
643 return ", ".join(sorted(stylelist))
643 return ", ".join(sorted(stylelist))
644
644
645 class TemplateNotFound(util.Abort):
645 class TemplateNotFound(util.Abort):
646 pass
646 pass
647
647
648 class templater(object):
648 class templater(object):
649
649
650 def __init__(self, mapfile, filters={}, defaults={}, cache={},
650 def __init__(self, mapfile, filters={}, defaults={}, cache={},
651 minchunk=1024, maxchunk=65536):
651 minchunk=1024, maxchunk=65536):
652 '''set up template engine.
652 '''set up template engine.
653 mapfile is name of file to read map definitions from.
653 mapfile is name of file to read map definitions from.
654 filters is dict of functions. each transforms a value into another.
654 filters is dict of functions. each transforms a value into another.
655 defaults is dict of default map definitions.'''
655 defaults is dict of default map definitions.'''
656 self.mapfile = mapfile or 'template'
656 self.mapfile = mapfile or 'template'
657 self.cache = cache.copy()
657 self.cache = cache.copy()
658 self.map = {}
658 self.map = {}
659 if mapfile:
659 if mapfile:
660 self.base = os.path.dirname(mapfile)
660 self.base = os.path.dirname(mapfile)
661 else:
661 else:
662 self.base = ''
662 self.base = ''
663 self.filters = templatefilters.filters.copy()
663 self.filters = templatefilters.filters.copy()
664 self.filters.update(filters)
664 self.filters.update(filters)
665 self.defaults = defaults
665 self.defaults = defaults
666 self.minchunk, self.maxchunk = minchunk, maxchunk
666 self.minchunk, self.maxchunk = minchunk, maxchunk
667 self.ecache = {}
667 self.ecache = {}
668
668
669 if not mapfile:
669 if not mapfile:
670 return
670 return
671 if not os.path.exists(mapfile):
671 if not os.path.exists(mapfile):
672 raise util.Abort(_("style '%s' not found") % mapfile,
672 raise util.Abort(_("style '%s' not found") % mapfile,
673 hint=_("available styles: %s") % stylelist())
673 hint=_("available styles: %s") % stylelist())
674
674
675 conf = config.config()
675 conf = config.config()
676 conf.read(mapfile)
676 conf.read(mapfile)
677
677
678 for key, val in conf[''].items():
678 for key, val in conf[''].items():
679 if not val:
679 if not val:
680 raise SyntaxError(_('%s: missing value') % conf.source('', key))
680 raise SyntaxError(_('%s: missing value') % conf.source('', key))
681 if val[0] in "'\"":
681 if val[0] in "'\"":
682 try:
682 try:
683 self.cache[key] = parsestring(val)
683 self.cache[key] = parsestring(val)
684 except SyntaxError, inst:
684 except SyntaxError, inst:
685 raise SyntaxError('%s: %s' %
685 raise SyntaxError('%s: %s' %
686 (conf.source('', key), inst.args[0]))
686 (conf.source('', key), inst.args[0]))
687 else:
687 else:
688 val = 'default', val
688 val = 'default', val
689 if ':' in val[1]:
689 if ':' in val[1]:
690 val = val[1].split(':', 1)
690 val = val[1].split(':', 1)
691 self.map[key] = val[0], os.path.join(self.base, val[1])
691 self.map[key] = val[0], os.path.join(self.base, val[1])
692
692
693 def __contains__(self, key):
693 def __contains__(self, key):
694 return key in self.cache or key in self.map
694 return key in self.cache or key in self.map
695
695
696 def load(self, t):
696 def load(self, t):
697 '''Get the template for the given template name. Use a local cache.'''
697 '''Get the template for the given template name. Use a local cache.'''
698 if t not in self.cache:
698 if t not in self.cache:
699 try:
699 try:
700 self.cache[t] = util.readfile(self.map[t][1])
700 self.cache[t] = util.readfile(self.map[t][1])
701 except KeyError, inst:
701 except KeyError, inst:
702 raise TemplateNotFound(_('"%s" not in template map') %
702 raise TemplateNotFound(_('"%s" not in template map') %
703 inst.args[0])
703 inst.args[0])
704 except IOError, inst:
704 except IOError, inst:
705 raise IOError(inst.args[0], _('template file %s: %s') %
705 raise IOError(inst.args[0], _('template file %s: %s') %
706 (self.map[t][1], inst.args[1]))
706 (self.map[t][1], inst.args[1]))
707 return self.cache[t]
707 return self.cache[t]
708
708
709 def __call__(self, t, **mapping):
709 def __call__(self, t, **mapping):
710 ttype = t in self.map and self.map[t][0] or 'default'
710 ttype = t in self.map and self.map[t][0] or 'default'
711 if ttype not in self.ecache:
711 if ttype not in self.ecache:
712 self.ecache[ttype] = engines[ttype](self.load,
712 self.ecache[ttype] = engines[ttype](self.load,
713 self.filters, self.defaults)
713 self.filters, self.defaults)
714 proc = self.ecache[ttype]
714 proc = self.ecache[ttype]
715
715
716 stream = proc.process(t, mapping)
716 stream = proc.process(t, mapping)
717 if self.minchunk:
717 if self.minchunk:
718 stream = util.increasingchunks(stream, min=self.minchunk,
718 stream = util.increasingchunks(stream, min=self.minchunk,
719 max=self.maxchunk)
719 max=self.maxchunk)
720 return stream
720 return stream
721
721
722 def templatepaths():
722 def templatepaths():
723 '''return locations used for template files.'''
723 '''return locations used for template files.'''
724 pathsrel = ['templates']
724 pathsrel = ['templates']
725 paths = [os.path.normpath(os.path.join(util.datapath, f))
725 paths = [os.path.normpath(os.path.join(util.datapath, f))
726 for f in pathsrel]
726 for f in pathsrel]
727 return [p for p in paths if os.path.isdir(p)]
727 return [p for p in paths if os.path.isdir(p)]
728
728
729 def templatepath(name):
729 def templatepath(name):
730 '''return location of template file. returns None if not found.'''
730 '''return location of template file. returns None if not found.'''
731 for p in templatepaths():
731 for p in templatepaths():
732 f = os.path.join(p, name)
732 f = os.path.join(p, name)
733 if os.path.exists(f):
733 if os.path.exists(f):
734 return f
734 return f
735 return None
735 return None
736
736
737 def stylemap(styles, paths=None):
737 def stylemap(styles, paths=None):
738 """Return path to mapfile for a given style.
738 """Return path to mapfile for a given style.
739
739
740 Searches mapfile in the following locations:
740 Searches mapfile in the following locations:
741 1. templatepath/style/map
741 1. templatepath/style/map
742 2. templatepath/map-style
742 2. templatepath/map-style
743 3. templatepath/map
743 3. templatepath/map
744 """
744 """
745
745
746 if paths is None:
746 if paths is None:
747 paths = templatepaths()
747 paths = templatepaths()
748 elif isinstance(paths, str):
748 elif isinstance(paths, str):
749 paths = [paths]
749 paths = [paths]
750
750
751 if isinstance(styles, str):
751 if isinstance(styles, str):
752 styles = [styles]
752 styles = [styles]
753
753
754 for style in styles:
754 for style in styles:
755 # only plain name is allowed to honor template paths
755 # only plain name is allowed to honor template paths
756 if (not style
756 if (not style
757 or style in (os.curdir, os.pardir)
757 or style in (os.curdir, os.pardir)
758 or os.sep in style
758 or os.sep in style
759 or os.altsep and os.altsep in style):
759 or os.altsep and os.altsep in style):
760 continue
760 continue
761 locations = [os.path.join(style, 'map'), 'map-' + style]
761 locations = [os.path.join(style, 'map'), 'map-' + style]
762 locations.append('map')
762 locations.append('map')
763
763
764 for path in paths:
764 for path in paths:
765 for location in locations:
765 for location in locations:
766 mapfile = os.path.join(path, location)
766 mapfile = os.path.join(path, location)
767 if os.path.isfile(mapfile):
767 if os.path.isfile(mapfile):
768 return style, mapfile
768 return style, mapfile
769
769
770 raise RuntimeError("No hgweb templates found in %r" % paths)
770 raise RuntimeError("No hgweb templates found in %r" % paths)
General Comments 0
You need to be logged in to leave comments. Login now