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