##// END OF EJS Templates
templatekw: add helper method to generate a template keyword for a namespace...
Sean Farley -
r23609:40fcf6c0 default
parent child Browse files
Show More
@@ -1,435 +1,443 b''
1 1 # templatekw.py - common changeset template keywords
2 2 #
3 3 # Copyright 2005-2009 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 or any later version.
7 7
8 8 from node import hex
9 9 import patch, util, error
10 10 import hbisect
11 11
12 12 # This helper class allows us to handle both:
13 13 # "{files}" (legacy command-line-specific list hack) and
14 14 # "{files % '{file}\n'}" (hgweb-style with inlining and function support)
15 15
16 16 class _hybrid(object):
17 17 def __init__(self, gen, values, joinfmt=None):
18 18 self.gen = gen
19 19 self.values = values
20 20 if joinfmt:
21 21 self.joinfmt = joinfmt
22 22 else:
23 23 self.joinfmt = lambda x: x.values()[0]
24 24 def __iter__(self):
25 25 return self.gen
26 26 def __call__(self):
27 27 for x in self.values:
28 28 yield x
29 29 def __len__(self):
30 30 return len(self.values)
31 31
32 32 def showlist(name, values, plural=None, element=None, **args):
33 33 if not element:
34 34 element = name
35 35 f = _showlist(name, values, plural, **args)
36 36 return _hybrid(f, [{element: x} for x in values])
37 37
38 38 def _showlist(name, values, plural=None, **args):
39 39 '''expand set of values.
40 40 name is name of key in template map.
41 41 values is list of strings or dicts.
42 42 plural is plural of name, if not simply name + 's'.
43 43
44 44 expansion works like this, given name 'foo'.
45 45
46 46 if values is empty, expand 'no_foos'.
47 47
48 48 if 'foo' not in template map, return values as a string,
49 49 joined by space.
50 50
51 51 expand 'start_foos'.
52 52
53 53 for each value, expand 'foo'. if 'last_foo' in template
54 54 map, expand it instead of 'foo' for last key.
55 55
56 56 expand 'end_foos'.
57 57 '''
58 58 templ = args['templ']
59 59 if plural:
60 60 names = plural
61 61 else: names = name + 's'
62 62 if not values:
63 63 noname = 'no_' + names
64 64 if noname in templ:
65 65 yield templ(noname, **args)
66 66 return
67 67 if name not in templ:
68 68 if isinstance(values[0], str):
69 69 yield ' '.join(values)
70 70 else:
71 71 for v in values:
72 72 yield dict(v, **args)
73 73 return
74 74 startname = 'start_' + names
75 75 if startname in templ:
76 76 yield templ(startname, **args)
77 77 vargs = args.copy()
78 78 def one(v, tag=name):
79 79 try:
80 80 vargs.update(v)
81 81 except (AttributeError, ValueError):
82 82 try:
83 83 for a, b in v:
84 84 vargs[a] = b
85 85 except ValueError:
86 86 vargs[name] = v
87 87 return templ(tag, **vargs)
88 88 lastname = 'last_' + name
89 89 if lastname in templ:
90 90 last = values.pop()
91 91 else:
92 92 last = None
93 93 for v in values:
94 94 yield one(v)
95 95 if last is not None:
96 96 yield one(last, tag=lastname)
97 97 endname = 'end_' + names
98 98 if endname in templ:
99 99 yield templ(endname, **args)
100 100
101 101 def getfiles(repo, ctx, revcache):
102 102 if 'files' not in revcache:
103 103 revcache['files'] = repo.status(ctx.p1().node(), ctx.node())[:3]
104 104 return revcache['files']
105 105
106 106 def getlatesttags(repo, ctx, cache):
107 107 '''return date, distance and name for the latest tag of rev'''
108 108
109 109 if 'latesttags' not in cache:
110 110 # Cache mapping from rev to a tuple with tag date, tag
111 111 # distance and tag name
112 112 cache['latesttags'] = {-1: (0, 0, 'null')}
113 113 latesttags = cache['latesttags']
114 114
115 115 rev = ctx.rev()
116 116 todo = [rev]
117 117 while todo:
118 118 rev = todo.pop()
119 119 if rev in latesttags:
120 120 continue
121 121 ctx = repo[rev]
122 122 tags = [t for t in ctx.tags()
123 123 if (repo.tagtype(t) and repo.tagtype(t) != 'local')]
124 124 if tags:
125 125 latesttags[rev] = ctx.date()[0], 0, ':'.join(sorted(tags))
126 126 continue
127 127 try:
128 128 # The tuples are laid out so the right one can be found by
129 129 # comparison.
130 130 pdate, pdist, ptag = max(
131 131 latesttags[p.rev()] for p in ctx.parents())
132 132 except KeyError:
133 133 # Cache miss - recurse
134 134 todo.append(rev)
135 135 todo.extend(p.rev() for p in ctx.parents())
136 136 continue
137 137 latesttags[rev] = pdate, pdist + 1, ptag
138 138 return latesttags[rev]
139 139
140 140 def getrenamedfn(repo, endrev=None):
141 141 rcache = {}
142 142 if endrev is None:
143 143 endrev = len(repo)
144 144
145 145 def getrenamed(fn, rev):
146 146 '''looks up all renames for a file (up to endrev) the first
147 147 time the file is given. It indexes on the changerev and only
148 148 parses the manifest if linkrev != changerev.
149 149 Returns rename info for fn at changerev rev.'''
150 150 if fn not in rcache:
151 151 rcache[fn] = {}
152 152 fl = repo.file(fn)
153 153 for i in fl:
154 154 lr = fl.linkrev(i)
155 155 renamed = fl.renamed(fl.node(i))
156 156 rcache[fn][lr] = renamed
157 157 if lr >= endrev:
158 158 break
159 159 if rev in rcache[fn]:
160 160 return rcache[fn][rev]
161 161
162 162 # If linkrev != rev (i.e. rev not found in rcache) fallback to
163 163 # filectx logic.
164 164 try:
165 165 return repo[rev][fn].renamed()
166 166 except error.LookupError:
167 167 return None
168 168
169 169 return getrenamed
170 170
171 171
172 172 def showauthor(repo, ctx, templ, **args):
173 173 """:author: String. The unmodified author of the changeset."""
174 174 return ctx.user()
175 175
176 176 def showbisect(repo, ctx, templ, **args):
177 177 """:bisect: String. The changeset bisection status."""
178 178 return hbisect.label(repo, ctx.node())
179 179
180 180 def showbranch(**args):
181 181 """:branch: String. The name of the branch on which the changeset was
182 182 committed.
183 183 """
184 184 return args['ctx'].branch()
185 185
186 186 def showbranches(**args):
187 187 """:branches: List of strings. The name of the branch on which the
188 188 changeset was committed. Will be empty if the branch name was
189 189 default.
190 190 """
191 191 branch = args['ctx'].branch()
192 192 if branch != 'default':
193 193 return showlist('branch', [branch], plural='branches', **args)
194 194 return showlist('branch', [], plural='branches', **args)
195 195
196 196 def showbookmarks(**args):
197 197 """:bookmarks: List of strings. Any bookmarks associated with the
198 198 changeset.
199 199 """
200 200 repo = args['ctx']._repo
201 201 bookmarks = args['ctx'].bookmarks()
202 202 hybrid = showlist('bookmark', bookmarks, **args)
203 203 for value in hybrid.values:
204 204 value['current'] = repo._bookmarkcurrent
205 205 return hybrid
206 206
207 207 def showchildren(**args):
208 208 """:children: List of strings. The children of the changeset."""
209 209 ctx = args['ctx']
210 210 childrevs = ['%d:%s' % (cctx, cctx) for cctx in ctx.children()]
211 211 return showlist('children', childrevs, element='child', **args)
212 212
213 213 def showcurrentbookmark(**args):
214 214 """:currentbookmark: String. The active bookmark, if it is
215 215 associated with the changeset"""
216 216 import bookmarks as bookmarks # to avoid circular import issues
217 217 repo = args['repo']
218 218 if bookmarks.iscurrent(repo):
219 219 current = repo._bookmarkcurrent
220 220 if current in args['ctx'].bookmarks():
221 221 return current
222 222 return ''
223 223
224 224 def showdate(repo, ctx, templ, **args):
225 225 """:date: Date information. The date when the changeset was committed."""
226 226 return ctx.date()
227 227
228 228 def showdescription(repo, ctx, templ, **args):
229 229 """:desc: String. The text of the changeset description."""
230 230 return ctx.description().strip()
231 231
232 232 def showdiffstat(repo, ctx, templ, **args):
233 233 """:diffstat: String. Statistics of changes with the following format:
234 234 "modified files: +added/-removed lines"
235 235 """
236 236 stats = patch.diffstatdata(util.iterlines(ctx.diff()))
237 237 maxname, maxtotal, adds, removes, binary = patch.diffstatsum(stats)
238 238 return '%s: +%s/-%s' % (len(stats), adds, removes)
239 239
240 240 def showextras(**args):
241 241 """:extras: List of dicts with key, value entries of the 'extras'
242 242 field of this changeset."""
243 243 extras = args['ctx'].extra()
244 244 c = [{'key': x[0], 'value': x[1]} for x in sorted(extras.items())]
245 245 f = _showlist('extra', c, plural='extras', **args)
246 246 return _hybrid(f, c, lambda x: '%s=%s' % (x['key'], x['value']))
247 247
248 248 def showfileadds(**args):
249 249 """:file_adds: List of strings. Files added by this changeset."""
250 250 repo, ctx, revcache = args['repo'], args['ctx'], args['revcache']
251 251 return showlist('file_add', getfiles(repo, ctx, revcache)[1],
252 252 element='file', **args)
253 253
254 254 def showfilecopies(**args):
255 255 """:file_copies: List of strings. Files copied in this changeset with
256 256 their sources.
257 257 """
258 258 cache, ctx = args['cache'], args['ctx']
259 259 copies = args['revcache'].get('copies')
260 260 if copies is None:
261 261 if 'getrenamed' not in cache:
262 262 cache['getrenamed'] = getrenamedfn(args['repo'])
263 263 copies = []
264 264 getrenamed = cache['getrenamed']
265 265 for fn in ctx.files():
266 266 rename = getrenamed(fn, ctx.rev())
267 267 if rename:
268 268 copies.append((fn, rename[0]))
269 269
270 270 c = [{'name': x[0], 'source': x[1]} for x in copies]
271 271 f = _showlist('file_copy', c, plural='file_copies', **args)
272 272 return _hybrid(f, c, lambda x: '%s (%s)' % (x['name'], x['source']))
273 273
274 274 # showfilecopiesswitch() displays file copies only if copy records are
275 275 # provided before calling the templater, usually with a --copies
276 276 # command line switch.
277 277 def showfilecopiesswitch(**args):
278 278 """:file_copies_switch: List of strings. Like "file_copies" but displayed
279 279 only if the --copied switch is set.
280 280 """
281 281 copies = args['revcache'].get('copies') or []
282 282 c = [{'name': x[0], 'source': x[1]} for x in copies]
283 283 f = _showlist('file_copy', c, plural='file_copies', **args)
284 284 return _hybrid(f, c, lambda x: '%s (%s)' % (x['name'], x['source']))
285 285
286 286 def showfiledels(**args):
287 287 """:file_dels: List of strings. Files removed by this changeset."""
288 288 repo, ctx, revcache = args['repo'], args['ctx'], args['revcache']
289 289 return showlist('file_del', getfiles(repo, ctx, revcache)[2],
290 290 element='file', **args)
291 291
292 292 def showfilemods(**args):
293 293 """:file_mods: List of strings. Files modified by this changeset."""
294 294 repo, ctx, revcache = args['repo'], args['ctx'], args['revcache']
295 295 return showlist('file_mod', getfiles(repo, ctx, revcache)[0],
296 296 element='file', **args)
297 297
298 298 def showfiles(**args):
299 299 """:files: List of strings. All files modified, added, or removed by this
300 300 changeset.
301 301 """
302 302 return showlist('file', args['ctx'].files(), **args)
303 303
304 304 def showlatesttag(repo, ctx, templ, cache, **args):
305 305 """:latesttag: String. Most recent global tag in the ancestors of this
306 306 changeset.
307 307 """
308 308 return getlatesttags(repo, ctx, cache)[2]
309 309
310 310 def showlatesttagdistance(repo, ctx, templ, cache, **args):
311 311 """:latesttagdistance: Integer. Longest path to the latest tag."""
312 312 return getlatesttags(repo, ctx, cache)[1]
313 313
314 314 def showmanifest(**args):
315 315 repo, ctx, templ = args['repo'], args['ctx'], args['templ']
316 316 args = args.copy()
317 317 args.update({'rev': repo.manifest.rev(ctx.changeset()[0]),
318 318 'node': hex(ctx.changeset()[0])})
319 319 return templ('manifest', **args)
320 320
321 321 def shownode(repo, ctx, templ, **args):
322 322 """:node: String. The changeset identification hash, as a 40 hexadecimal
323 323 digit string.
324 324 """
325 325 return ctx.hex()
326 326
327 327 def showp1rev(repo, ctx, templ, **args):
328 328 """:p1rev: Integer. The repository-local revision number of the changeset's
329 329 first parent, or -1 if the changeset has no parents."""
330 330 return ctx.p1().rev()
331 331
332 332 def showp2rev(repo, ctx, templ, **args):
333 333 """:p2rev: Integer. The repository-local revision number of the changeset's
334 334 second parent, or -1 if the changeset has no second parent."""
335 335 return ctx.p2().rev()
336 336
337 337 def showp1node(repo, ctx, templ, **args):
338 338 """:p1node: String. The identification hash of the changeset's first parent,
339 339 as a 40 digit hexadecimal string. If the changeset has no parents, all
340 340 digits are 0."""
341 341 return ctx.p1().hex()
342 342
343 343 def showp2node(repo, ctx, templ, **args):
344 344 """:p2node: String. The identification hash of the changeset's second
345 345 parent, as a 40 digit hexadecimal string. If the changeset has no second
346 346 parent, all digits are 0."""
347 347 return ctx.p2().hex()
348 348
349 349 def showphase(repo, ctx, templ, **args):
350 350 """:phase: String. The changeset phase name."""
351 351 return ctx.phasestr()
352 352
353 353 def showphaseidx(repo, ctx, templ, **args):
354 354 """:phaseidx: Integer. The changeset phase index."""
355 355 return ctx.phase()
356 356
357 357 def showrev(repo, ctx, templ, **args):
358 358 """:rev: Integer. The repository-local changeset revision number."""
359 359 return ctx.rev()
360 360
361 361 def showsubrepos(**args):
362 362 """:subrepos: List of strings. Updated subrepositories in the changeset."""
363 363 ctx = args['ctx']
364 364 substate = ctx.substate
365 365 if not substate:
366 366 return showlist('subrepo', [], **args)
367 367 psubstate = ctx.parents()[0].substate or {}
368 368 subrepos = []
369 369 for sub in substate:
370 370 if sub not in psubstate or substate[sub] != psubstate[sub]:
371 371 subrepos.append(sub) # modified or newly added in ctx
372 372 for sub in psubstate:
373 373 if sub not in substate:
374 374 subrepos.append(sub) # removed in ctx
375 375 return showlist('subrepo', sorted(subrepos), **args)
376 376
377 377 def showtags(**args):
378 378 """:tags: List of strings. Any tags associated with the changeset."""
379 379 return showlist('tag', args['ctx'].tags(), **args)
380 380
381 def shownames(namespace, **args):
382 """helper method to generate a template keyword for a namespace"""
383 ctx = args['ctx']
384 repo = ctx._repo
385 names = repo.names.names(repo, namespace, ctx.node())
386 return showlist(repo.names.templatename(namespace), names,
387 plural=namespace, **args)
388
381 389 # keywords are callables like:
382 390 # fn(repo, ctx, templ, cache, revcache, **args)
383 391 # with:
384 392 # repo - current repository instance
385 393 # ctx - the changectx being displayed
386 394 # templ - the templater instance
387 395 # cache - a cache dictionary for the whole templater run
388 396 # revcache - a cache dictionary for the current revision
389 397 keywords = {
390 398 'author': showauthor,
391 399 'bisect': showbisect,
392 400 'branch': showbranch,
393 401 'branches': showbranches,
394 402 'bookmarks': showbookmarks,
395 403 'children': showchildren,
396 404 'currentbookmark': showcurrentbookmark,
397 405 'date': showdate,
398 406 'desc': showdescription,
399 407 'diffstat': showdiffstat,
400 408 'extras': showextras,
401 409 'file_adds': showfileadds,
402 410 'file_copies': showfilecopies,
403 411 'file_copies_switch': showfilecopiesswitch,
404 412 'file_dels': showfiledels,
405 413 'file_mods': showfilemods,
406 414 'files': showfiles,
407 415 'latesttag': showlatesttag,
408 416 'latesttagdistance': showlatesttagdistance,
409 417 'manifest': showmanifest,
410 418 'node': shownode,
411 419 'p1rev': showp1rev,
412 420 'p1node': showp1node,
413 421 'p2rev': showp2rev,
414 422 'p2node': showp2node,
415 423 'phase': showphase,
416 424 'phaseidx': showphaseidx,
417 425 'rev': showrev,
418 426 'subrepos': showsubrepos,
419 427 'tags': showtags,
420 428 }
421 429
422 430 def _showparents(**args):
423 431 """:parents: List of strings. The parents of the changeset in "rev:node"
424 432 format. If the changeset has only one "natural" parent (the predecessor
425 433 revision) nothing is shown."""
426 434 pass
427 435
428 436 dockeywords = {
429 437 'parents': _showparents,
430 438 }
431 439 dockeywords.update(keywords)
432 440 del dockeywords['branches']
433 441
434 442 # tell hggettext to extract docstrings from these functions:
435 443 i18nfunctions = dockeywords.values()
General Comments 0
You need to be logged in to leave comments. Login now