##// END OF EJS Templates
templatekw: convert list of key/value pairs to sortdict...
Yuya Nishihara -
r24237:9ad02823 default
parent child Browse files
Show More
@@ -1,446 +1,449
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 current = repo._bookmarkcurrent
203 203 c = [{'bookmark': x, 'current': current} for x in bookmarks]
204 204 f = _showlist('bookmark', bookmarks, **args)
205 205 return _hybrid(f, c, lambda x: x['bookmark'])
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 c = [{'key': x[0], 'value': x[1]} for x in sorted(extras.items())]
244 extras = util.sortdict((k, extras[k]) for k in sorted(extras))
245 c = [{'key': k, 'value': extras[k]} for k in extras]
245 246 f = _showlist('extra', c, plural='extras', **args)
246 247 return _hybrid(f, c, lambda x: '%s=%s' % (x['key'], x['value']))
247 248
248 249 def showfileadds(**args):
249 250 """:file_adds: List of strings. Files added by this changeset."""
250 251 repo, ctx, revcache = args['repo'], args['ctx'], args['revcache']
251 252 return showlist('file_add', getfiles(repo, ctx, revcache)[1],
252 253 element='file', **args)
253 254
254 255 def showfilecopies(**args):
255 256 """:file_copies: List of strings. Files copied in this changeset with
256 257 their sources.
257 258 """
258 259 cache, ctx = args['cache'], args['ctx']
259 260 copies = args['revcache'].get('copies')
260 261 if copies is None:
261 262 if 'getrenamed' not in cache:
262 263 cache['getrenamed'] = getrenamedfn(args['repo'])
263 264 copies = []
264 265 getrenamed = cache['getrenamed']
265 266 for fn in ctx.files():
266 267 rename = getrenamed(fn, ctx.rev())
267 268 if rename:
268 269 copies.append((fn, rename[0]))
269 270
270 c = [{'name': x[0], 'source': x[1]} for x in copies]
271 copies = util.sortdict(copies)
272 c = [{'name': k, 'source': copies[k]} for k in copies]
271 273 f = _showlist('file_copy', c, plural='file_copies', **args)
272 274 return _hybrid(f, c, lambda x: '%s (%s)' % (x['name'], x['source']))
273 275
274 276 # showfilecopiesswitch() displays file copies only if copy records are
275 277 # provided before calling the templater, usually with a --copies
276 278 # command line switch.
277 279 def showfilecopiesswitch(**args):
278 280 """:file_copies_switch: List of strings. Like "file_copies" but displayed
279 281 only if the --copied switch is set.
280 282 """
281 283 copies = args['revcache'].get('copies') or []
282 c = [{'name': x[0], 'source': x[1]} for x in copies]
284 copies = util.sortdict(copies)
285 c = [{'name': k, 'source': copies[k]} for k in copies]
283 286 f = _showlist('file_copy', c, plural='file_copies', **args)
284 287 return _hybrid(f, c, lambda x: '%s (%s)' % (x['name'], x['source']))
285 288
286 289 def showfiledels(**args):
287 290 """:file_dels: List of strings. Files removed by this changeset."""
288 291 repo, ctx, revcache = args['repo'], args['ctx'], args['revcache']
289 292 return showlist('file_del', getfiles(repo, ctx, revcache)[2],
290 293 element='file', **args)
291 294
292 295 def showfilemods(**args):
293 296 """:file_mods: List of strings. Files modified by this changeset."""
294 297 repo, ctx, revcache = args['repo'], args['ctx'], args['revcache']
295 298 return showlist('file_mod', getfiles(repo, ctx, revcache)[0],
296 299 element='file', **args)
297 300
298 301 def showfiles(**args):
299 302 """:files: List of strings. All files modified, added, or removed by this
300 303 changeset.
301 304 """
302 305 return showlist('file', args['ctx'].files(), **args)
303 306
304 307 def showlatesttag(repo, ctx, templ, cache, **args):
305 308 """:latesttag: String. Most recent global tag in the ancestors of this
306 309 changeset.
307 310 """
308 311 return getlatesttags(repo, ctx, cache)[2]
309 312
310 313 def showlatesttagdistance(repo, ctx, templ, cache, **args):
311 314 """:latesttagdistance: Integer. Longest path to the latest tag."""
312 315 return getlatesttags(repo, ctx, cache)[1]
313 316
314 317 def showmanifest(**args):
315 318 repo, ctx, templ = args['repo'], args['ctx'], args['templ']
316 319 args = args.copy()
317 320 args.update({'rev': repo.manifest.rev(ctx.changeset()[0]),
318 321 'node': hex(ctx.changeset()[0])})
319 322 return templ('manifest', **args)
320 323
321 324 def shownode(repo, ctx, templ, **args):
322 325 """:node: String. The changeset identification hash, as a 40 hexadecimal
323 326 digit string.
324 327 """
325 328 return ctx.hex()
326 329
327 330 def showp1rev(repo, ctx, templ, **args):
328 331 """:p1rev: Integer. The repository-local revision number of the changeset's
329 332 first parent, or -1 if the changeset has no parents."""
330 333 return ctx.p1().rev()
331 334
332 335 def showp2rev(repo, ctx, templ, **args):
333 336 """:p2rev: Integer. The repository-local revision number of the changeset's
334 337 second parent, or -1 if the changeset has no second parent."""
335 338 return ctx.p2().rev()
336 339
337 340 def showp1node(repo, ctx, templ, **args):
338 341 """:p1node: String. The identification hash of the changeset's first parent,
339 342 as a 40 digit hexadecimal string. If the changeset has no parents, all
340 343 digits are 0."""
341 344 return ctx.p1().hex()
342 345
343 346 def showp2node(repo, ctx, templ, **args):
344 347 """:p2node: String. The identification hash of the changeset's second
345 348 parent, as a 40 digit hexadecimal string. If the changeset has no second
346 349 parent, all digits are 0."""
347 350 return ctx.p2().hex()
348 351
349 352 def showphase(repo, ctx, templ, **args):
350 353 """:phase: String. The changeset phase name."""
351 354 return ctx.phasestr()
352 355
353 356 def showphaseidx(repo, ctx, templ, **args):
354 357 """:phaseidx: Integer. The changeset phase index."""
355 358 return ctx.phase()
356 359
357 360 def showrev(repo, ctx, templ, **args):
358 361 """:rev: Integer. The repository-local changeset revision number."""
359 362 return ctx.rev()
360 363
361 364 def showsubrepos(**args):
362 365 """:subrepos: List of strings. Updated subrepositories in the changeset."""
363 366 ctx = args['ctx']
364 367 substate = ctx.substate
365 368 if not substate:
366 369 return showlist('subrepo', [], **args)
367 370 psubstate = ctx.parents()[0].substate or {}
368 371 subrepos = []
369 372 for sub in substate:
370 373 if sub not in psubstate or substate[sub] != psubstate[sub]:
371 374 subrepos.append(sub) # modified or newly added in ctx
372 375 for sub in psubstate:
373 376 if sub not in substate:
374 377 subrepos.append(sub) # removed in ctx
375 378 return showlist('subrepo', sorted(subrepos), **args)
376 379
377 380 def shownames(namespace, **args):
378 381 """helper method to generate a template keyword for a namespace"""
379 382 ctx = args['ctx']
380 383 repo = ctx._repo
381 384 ns = repo.names[namespace]
382 385 names = ns.names(repo, ctx.node())
383 386 return showlist(ns.templatename, names, plural=namespace, **args)
384 387
385 388 # don't remove "showtags" definition, even though namespaces will put
386 389 # a helper function for "tags" keyword into "keywords" map automatically,
387 390 # because online help text is built without namespaces initialization
388 391 def showtags(**args):
389 392 """:tags: List of strings. Any tags associated with the changeset."""
390 393 return shownames('tags', **args)
391 394
392 395 # keywords are callables like:
393 396 # fn(repo, ctx, templ, cache, revcache, **args)
394 397 # with:
395 398 # repo - current repository instance
396 399 # ctx - the changectx being displayed
397 400 # templ - the templater instance
398 401 # cache - a cache dictionary for the whole templater run
399 402 # revcache - a cache dictionary for the current revision
400 403 keywords = {
401 404 'author': showauthor,
402 405 'bisect': showbisect,
403 406 'branch': showbranch,
404 407 'branches': showbranches,
405 408 'bookmarks': showbookmarks,
406 409 'children': showchildren,
407 410 'currentbookmark': showcurrentbookmark,
408 411 'date': showdate,
409 412 'desc': showdescription,
410 413 'diffstat': showdiffstat,
411 414 'extras': showextras,
412 415 'file_adds': showfileadds,
413 416 'file_copies': showfilecopies,
414 417 'file_copies_switch': showfilecopiesswitch,
415 418 'file_dels': showfiledels,
416 419 'file_mods': showfilemods,
417 420 'files': showfiles,
418 421 'latesttag': showlatesttag,
419 422 'latesttagdistance': showlatesttagdistance,
420 423 'manifest': showmanifest,
421 424 'node': shownode,
422 425 'p1rev': showp1rev,
423 426 'p1node': showp1node,
424 427 'p2rev': showp2rev,
425 428 'p2node': showp2node,
426 429 'phase': showphase,
427 430 'phaseidx': showphaseidx,
428 431 'rev': showrev,
429 432 'subrepos': showsubrepos,
430 433 'tags': showtags,
431 434 }
432 435
433 436 def _showparents(**args):
434 437 """:parents: List of strings. The parents of the changeset in "rev:node"
435 438 format. If the changeset has only one "natural" parent (the predecessor
436 439 revision) nothing is shown."""
437 440 pass
438 441
439 442 dockeywords = {
440 443 'parents': _showparents,
441 444 }
442 445 dockeywords.update(keywords)
443 446 del dockeywords['branches']
444 447
445 448 # tell hggettext to extract docstrings from these functions:
446 449 i18nfunctions = dockeywords.values()
General Comments 0
You need to be logged in to leave comments. Login now