##// END OF EJS Templates
templatekw: remove dockeywords hack...
Yuya Nishihara -
r26436:a2291c9c default
parent child Browse files
Show More
@@ -1,535 +1,535 b''
1 1 # help.py - help data for mercurial
2 2 #
3 3 # Copyright 2006 Matt Mackall <mpm@selenic.com>
4 4 #
5 5 # This software may be used and distributed according to the terms of the
6 6 # GNU General Public License version 2 or any later version.
7 7
8 8 from i18n import gettext, _
9 9 import itertools, os, textwrap
10 10 import error
11 11 import extensions, revset, fileset, templatekw, templatefilters, filemerge
12 12 import templater
13 13 import encoding, util, minirst
14 14 import cmdutil
15 15 import hgweb.webcommands as webcommands
16 16
17 17 _exclkeywords = [
18 18 "(DEPRECATED)",
19 19 "(EXPERIMENTAL)",
20 20 # i18n: "(DEPRECATED)" is a keyword, must be translated consistently
21 21 _("(DEPRECATED)"),
22 22 # i18n: "(EXPERIMENTAL)" is a keyword, must be translated consistently
23 23 _("(EXPERIMENTAL)"),
24 24 ]
25 25
26 26 def listexts(header, exts, indent=1, showdeprecated=False):
27 27 '''return a text listing of the given extensions'''
28 28 rst = []
29 29 if exts:
30 30 rst.append('\n%s\n\n' % header)
31 31 for name, desc in sorted(exts.iteritems()):
32 32 if not showdeprecated and any(w in desc for w in _exclkeywords):
33 33 continue
34 34 rst.append('%s:%s: %s\n' % (' ' * indent, name, desc))
35 35 return rst
36 36
37 37 def extshelp(ui):
38 38 rst = loaddoc('extensions')(ui).splitlines(True)
39 39 rst.extend(listexts(
40 40 _('enabled extensions:'), extensions.enabled(), showdeprecated=True))
41 41 rst.extend(listexts(_('disabled extensions:'), extensions.disabled()))
42 42 doc = ''.join(rst)
43 43 return doc
44 44
45 45 def optrst(header, options, verbose):
46 46 data = []
47 47 multioccur = False
48 48 for option in options:
49 49 if len(option) == 5:
50 50 shortopt, longopt, default, desc, optlabel = option
51 51 else:
52 52 shortopt, longopt, default, desc = option
53 53 optlabel = _("VALUE") # default label
54 54
55 55 if not verbose and any(w in desc for w in _exclkeywords):
56 56 continue
57 57
58 58 so = ''
59 59 if shortopt:
60 60 so = '-' + shortopt
61 61 lo = '--' + longopt
62 62 if default:
63 63 desc += _(" (default: %s)") % default
64 64
65 65 if isinstance(default, list):
66 66 lo += " %s [+]" % optlabel
67 67 multioccur = True
68 68 elif (default is not None) and not isinstance(default, bool):
69 69 lo += " %s" % optlabel
70 70
71 71 data.append((so, lo, desc))
72 72
73 73 if multioccur:
74 74 header += (_(" ([+] can be repeated)"))
75 75
76 76 rst = ['\n%s:\n\n' % header]
77 77 rst.extend(minirst.maketable(data, 1))
78 78
79 79 return ''.join(rst)
80 80
81 81 def indicateomitted(rst, omitted, notomitted=None):
82 82 rst.append('\n\n.. container:: omitted\n\n %s\n\n' % omitted)
83 83 if notomitted:
84 84 rst.append('\n\n.. container:: notomitted\n\n %s\n\n' % notomitted)
85 85
86 86 def topicmatch(ui, kw):
87 87 """Return help topics matching kw.
88 88
89 89 Returns {'section': [(name, summary), ...], ...} where section is
90 90 one of topics, commands, extensions, or extensioncommands.
91 91 """
92 92 kw = encoding.lower(kw)
93 93 def lowercontains(container):
94 94 return kw in encoding.lower(container) # translated in helptable
95 95 results = {'topics': [],
96 96 'commands': [],
97 97 'extensions': [],
98 98 'extensioncommands': [],
99 99 }
100 100 for names, header, doc in helptable:
101 101 # Old extensions may use a str as doc.
102 102 if (sum(map(lowercontains, names))
103 103 or lowercontains(header)
104 104 or (callable(doc) and lowercontains(doc(ui)))):
105 105 results['topics'].append((names[0], header))
106 106 import commands # avoid cycle
107 107 for cmd, entry in commands.table.iteritems():
108 108 if len(entry) == 3:
109 109 summary = entry[2]
110 110 else:
111 111 summary = ''
112 112 # translate docs *before* searching there
113 113 docs = _(getattr(entry[0], '__doc__', None)) or ''
114 114 if kw in cmd or lowercontains(summary) or lowercontains(docs):
115 115 doclines = docs.splitlines()
116 116 if doclines:
117 117 summary = doclines[0]
118 118 cmdname = cmd.split('|')[0].lstrip('^')
119 119 results['commands'].append((cmdname, summary))
120 120 for name, docs in itertools.chain(
121 121 extensions.enabled(False).iteritems(),
122 122 extensions.disabled().iteritems()):
123 123 # extensions.load ignores the UI argument
124 124 mod = extensions.load(None, name, '')
125 125 name = name.split('.')[-1]
126 126 if lowercontains(name) or lowercontains(docs):
127 127 # extension docs are already translated
128 128 results['extensions'].append((name, docs.splitlines()[0]))
129 129 for cmd, entry in getattr(mod, 'cmdtable', {}).iteritems():
130 130 if kw in cmd or (len(entry) > 2 and lowercontains(entry[2])):
131 131 cmdname = cmd.split('|')[0].lstrip('^')
132 132 if entry[0].__doc__:
133 133 cmddoc = gettext(entry[0].__doc__).splitlines()[0]
134 134 else:
135 135 cmddoc = _('(no help text available)')
136 136 results['extensioncommands'].append((cmdname, cmddoc))
137 137 return results
138 138
139 139 def loaddoc(topic):
140 140 """Return a delayed loader for help/topic.txt."""
141 141
142 142 def loader(ui):
143 143 docdir = os.path.join(util.datapath, 'help')
144 144 path = os.path.join(docdir, topic + ".txt")
145 145 doc = gettext(util.readfile(path))
146 146 for rewriter in helphooks.get(topic, []):
147 147 doc = rewriter(ui, topic, doc)
148 148 return doc
149 149
150 150 return loader
151 151
152 152 helptable = sorted([
153 153 (["config", "hgrc"], _("Configuration Files"), loaddoc('config')),
154 154 (["dates"], _("Date Formats"), loaddoc('dates')),
155 155 (["patterns"], _("File Name Patterns"), loaddoc('patterns')),
156 156 (['environment', 'env'], _('Environment Variables'),
157 157 loaddoc('environment')),
158 158 (['revisions', 'revs'], _('Specifying Single Revisions'),
159 159 loaddoc('revisions')),
160 160 (['multirevs', 'mrevs'], _('Specifying Multiple Revisions'),
161 161 loaddoc('multirevs')),
162 162 (['revsets', 'revset'], _("Specifying Revision Sets"), loaddoc('revsets')),
163 163 (['filesets', 'fileset'], _("Specifying File Sets"), loaddoc('filesets')),
164 164 (['diffs'], _('Diff Formats'), loaddoc('diffs')),
165 165 (['merge-tools', 'mergetools'], _('Merge Tools'), loaddoc('merge-tools')),
166 166 (['templating', 'templates', 'template', 'style'], _('Template Usage'),
167 167 loaddoc('templates')),
168 168 (['urls'], _('URL Paths'), loaddoc('urls')),
169 169 (["extensions"], _("Using Additional Features"), extshelp),
170 170 (["subrepos", "subrepo"], _("Subrepositories"), loaddoc('subrepos')),
171 171 (["hgweb"], _("Configuring hgweb"), loaddoc('hgweb')),
172 172 (["glossary"], _("Glossary"), loaddoc('glossary')),
173 173 (["hgignore", "ignore"], _("Syntax for Mercurial Ignore Files"),
174 174 loaddoc('hgignore')),
175 175 (["phases"], _("Working with Phases"), loaddoc('phases')),
176 176 (['scripting'], _('Using Mercurial from scripts and automation'),
177 177 loaddoc('scripting')),
178 178 ])
179 179
180 180 # Map topics to lists of callable taking the current topic help and
181 181 # returning the updated version
182 182 helphooks = {}
183 183
184 184 def addtopichook(topic, rewriter):
185 185 helphooks.setdefault(topic, []).append(rewriter)
186 186
187 187 def makeitemsdoc(ui, topic, doc, marker, items, dedent=False):
188 188 """Extract docstring from the items key to function mapping, build a
189 189 single documentation block and use it to overwrite the marker in doc.
190 190 """
191 191 entries = []
192 192 for name in sorted(items):
193 193 text = (items[name].__doc__ or '').rstrip()
194 194 if (not text
195 195 or not ui.verbose and any(w in text for w in _exclkeywords)):
196 196 continue
197 197 text = gettext(text)
198 198 if dedent:
199 199 text = textwrap.dedent(text)
200 200 lines = text.splitlines()
201 201 doclines = [(lines[0])]
202 202 for l in lines[1:]:
203 203 # Stop once we find some Python doctest
204 204 if l.strip().startswith('>>>'):
205 205 break
206 206 if dedent:
207 207 doclines.append(l.rstrip())
208 208 else:
209 209 doclines.append(' ' + l.strip())
210 210 entries.append('\n'.join(doclines))
211 211 entries = '\n\n'.join(entries)
212 212 return doc.replace(marker, entries)
213 213
214 214 def addtopicsymbols(topic, marker, symbols, dedent=False):
215 215 def add(ui, topic, doc):
216 216 return makeitemsdoc(ui, topic, doc, marker, symbols, dedent=dedent)
217 217 addtopichook(topic, add)
218 218
219 219 addtopicsymbols('filesets', '.. predicatesmarker', fileset.symbols)
220 220 addtopicsymbols('merge-tools', '.. internaltoolsmarker',
221 221 filemerge.internalsdoc)
222 222 addtopicsymbols('revsets', '.. predicatesmarker', revset.symbols)
223 addtopicsymbols('templates', '.. keywordsmarker', templatekw.dockeywords)
223 addtopicsymbols('templates', '.. keywordsmarker', templatekw.keywords)
224 224 addtopicsymbols('templates', '.. filtersmarker', templatefilters.filters)
225 225 addtopicsymbols('templates', '.. functionsmarker', templater.funcs)
226 226 addtopicsymbols('hgweb', '.. webcommandsmarker', webcommands.commands,
227 227 dedent=True)
228 228
229 229 def help_(ui, name, unknowncmd=False, full=True, **opts):
230 230 '''
231 231 Generate the help for 'name' as unformatted restructured text. If
232 232 'name' is None, describe the commands available.
233 233 '''
234 234
235 235 import commands # avoid cycle
236 236
237 237 def helpcmd(name):
238 238 try:
239 239 aliases, entry = cmdutil.findcmd(name, commands.table,
240 240 strict=unknowncmd)
241 241 except error.AmbiguousCommand as inst:
242 242 # py3k fix: except vars can't be used outside the scope of the
243 243 # except block, nor can be used inside a lambda. python issue4617
244 244 prefix = inst.args[0]
245 245 select = lambda c: c.lstrip('^').startswith(prefix)
246 246 rst = helplist(select)
247 247 return rst
248 248
249 249 rst = []
250 250
251 251 # check if it's an invalid alias and display its error if it is
252 252 if getattr(entry[0], 'badalias', None):
253 253 rst.append(entry[0].badalias + '\n')
254 254 if entry[0].unknowncmd:
255 255 try:
256 256 rst.extend(helpextcmd(entry[0].cmdname))
257 257 except error.UnknownCommand:
258 258 pass
259 259 return rst
260 260
261 261 # synopsis
262 262 if len(entry) > 2:
263 263 if entry[2].startswith('hg'):
264 264 rst.append("%s\n" % entry[2])
265 265 else:
266 266 rst.append('hg %s %s\n' % (aliases[0], entry[2]))
267 267 else:
268 268 rst.append('hg %s\n' % aliases[0])
269 269 # aliases
270 270 if full and not ui.quiet and len(aliases) > 1:
271 271 rst.append(_("\naliases: %s\n") % ', '.join(aliases[1:]))
272 272 rst.append('\n')
273 273
274 274 # description
275 275 doc = gettext(entry[0].__doc__)
276 276 if not doc:
277 277 doc = _("(no help text available)")
278 278 if util.safehasattr(entry[0], 'definition'): # aliased command
279 279 if entry[0].definition.startswith('!'): # shell alias
280 280 doc = _('shell alias for::\n\n %s') % entry[0].definition[1:]
281 281 else:
282 282 doc = _('alias for: hg %s\n\n%s') % (entry[0].definition, doc)
283 283 doc = doc.splitlines(True)
284 284 if ui.quiet or not full:
285 285 rst.append(doc[0])
286 286 else:
287 287 rst.extend(doc)
288 288 rst.append('\n')
289 289
290 290 # check if this command shadows a non-trivial (multi-line)
291 291 # extension help text
292 292 try:
293 293 mod = extensions.find(name)
294 294 doc = gettext(mod.__doc__) or ''
295 295 if '\n' in doc.strip():
296 296 msg = _('(use "hg help -e %s" to show help for '
297 297 'the %s extension)') % (name, name)
298 298 rst.append('\n%s\n' % msg)
299 299 except KeyError:
300 300 pass
301 301
302 302 # options
303 303 if not ui.quiet and entry[1]:
304 304 rst.append(optrst(_("options"), entry[1], ui.verbose))
305 305
306 306 if ui.verbose:
307 307 rst.append(optrst(_("global options"),
308 308 commands.globalopts, ui.verbose))
309 309
310 310 if not ui.verbose:
311 311 if not full:
312 312 rst.append(_('\n(use "hg %s -h" to show more help)\n')
313 313 % name)
314 314 elif not ui.quiet:
315 315 rst.append(_('\n(some details hidden, use --verbose '
316 316 'to show complete help)'))
317 317
318 318 return rst
319 319
320 320
321 321 def helplist(select=None):
322 322 # list of commands
323 323 if name == "shortlist":
324 324 header = _('basic commands:\n\n')
325 325 elif name == "debug":
326 326 header = _('debug commands (internal and unsupported):\n\n')
327 327 else:
328 328 header = _('list of commands:\n\n')
329 329
330 330 h = {}
331 331 cmds = {}
332 332 for c, e in commands.table.iteritems():
333 333 f = c.split("|", 1)[0]
334 334 if select and not select(f):
335 335 continue
336 336 if (not select and name != 'shortlist' and
337 337 e[0].__module__ != commands.__name__):
338 338 continue
339 339 if name == "shortlist" and not f.startswith("^"):
340 340 continue
341 341 f = f.lstrip("^")
342 342 if not ui.debugflag and f.startswith("debug") and name != "debug":
343 343 continue
344 344 doc = e[0].__doc__
345 345 if not ui.verbose and doc and any(w in doc for w in _exclkeywords):
346 346 continue
347 347 doc = gettext(doc)
348 348 if not doc:
349 349 doc = _("(no help text available)")
350 350 h[f] = doc.splitlines()[0].rstrip()
351 351 cmds[f] = c.lstrip("^")
352 352
353 353 rst = []
354 354 if not h:
355 355 if not ui.quiet:
356 356 rst.append(_('no commands defined\n'))
357 357 return rst
358 358
359 359 if not ui.quiet:
360 360 rst.append(header)
361 361 fns = sorted(h)
362 362 for f in fns:
363 363 if ui.verbose:
364 364 commacmds = cmds[f].replace("|",", ")
365 365 rst.append(" :%s: %s\n" % (commacmds, h[f]))
366 366 else:
367 367 rst.append(' :%s: %s\n' % (f, h[f]))
368 368
369 369 if not name:
370 370 exts = listexts(_('enabled extensions:'), extensions.enabled())
371 371 if exts:
372 372 rst.append('\n')
373 373 rst.extend(exts)
374 374
375 375 rst.append(_("\nadditional help topics:\n\n"))
376 376 topics = []
377 377 for names, header, doc in helptable:
378 378 topics.append((names[0], header))
379 379 for t, desc in topics:
380 380 rst.append(" :%s: %s\n" % (t, desc))
381 381
382 382 if ui.quiet:
383 383 pass
384 384 elif ui.verbose:
385 385 rst.append('\n%s\n' % optrst(_("global options"),
386 386 commands.globalopts, ui.verbose))
387 387 if name == 'shortlist':
388 388 rst.append(_('\n(use "hg help" for the full list '
389 389 'of commands)\n'))
390 390 else:
391 391 if name == 'shortlist':
392 392 rst.append(_('\n(use "hg help" for the full list of commands '
393 393 'or "hg -v" for details)\n'))
394 394 elif name and not full:
395 395 rst.append(_('\n(use "hg help %s" to show the full help '
396 396 'text)\n') % name)
397 397 elif name and cmds and name in cmds.keys():
398 398 rst.append(_('\n(use "hg help -v -e %s" to show built-in '
399 399 'aliases and global options)\n') % name)
400 400 else:
401 401 rst.append(_('\n(use "hg help -v%s" to show built-in aliases '
402 402 'and global options)\n')
403 403 % (name and " " + name or ""))
404 404 return rst
405 405
406 406 def helptopic(name):
407 407 for names, header, doc in helptable:
408 408 if name in names:
409 409 break
410 410 else:
411 411 raise error.UnknownCommand(name)
412 412
413 413 rst = [minirst.section(header)]
414 414
415 415 # description
416 416 if not doc:
417 417 rst.append(" %s\n" % _("(no help text available)"))
418 418 if callable(doc):
419 419 rst += [" %s\n" % l for l in doc(ui).splitlines()]
420 420
421 421 if not ui.verbose:
422 422 omitted = _('(some details hidden, use --verbose'
423 423 ' to show complete help)')
424 424 indicateomitted(rst, omitted)
425 425
426 426 try:
427 427 cmdutil.findcmd(name, commands.table)
428 428 rst.append(_('\nuse "hg help -c %s" to see help for '
429 429 'the %s command\n') % (name, name))
430 430 except error.UnknownCommand:
431 431 pass
432 432 return rst
433 433
434 434 def helpext(name):
435 435 try:
436 436 mod = extensions.find(name)
437 437 doc = gettext(mod.__doc__) or _('no help text available')
438 438 except KeyError:
439 439 mod = None
440 440 doc = extensions.disabledext(name)
441 441 if not doc:
442 442 raise error.UnknownCommand(name)
443 443
444 444 if '\n' not in doc:
445 445 head, tail = doc, ""
446 446 else:
447 447 head, tail = doc.split('\n', 1)
448 448 rst = [_('%s extension - %s\n\n') % (name.split('.')[-1], head)]
449 449 if tail:
450 450 rst.extend(tail.splitlines(True))
451 451 rst.append('\n')
452 452
453 453 if not ui.verbose:
454 454 omitted = _('(some details hidden, use --verbose'
455 455 ' to show complete help)')
456 456 indicateomitted(rst, omitted)
457 457
458 458 if mod:
459 459 try:
460 460 ct = mod.cmdtable
461 461 except AttributeError:
462 462 ct = {}
463 463 modcmds = set([c.split('|', 1)[0] for c in ct])
464 464 rst.extend(helplist(modcmds.__contains__))
465 465 else:
466 466 rst.append(_('(use "hg help extensions" for information on enabling'
467 467 ' extensions)\n'))
468 468 return rst
469 469
470 470 def helpextcmd(name):
471 471 cmd, ext, mod = extensions.disabledcmd(ui, name,
472 472 ui.configbool('ui', 'strict'))
473 473 doc = gettext(mod.__doc__).splitlines()[0]
474 474
475 475 rst = listexts(_("'%s' is provided by the following "
476 476 "extension:") % cmd, {ext: doc}, indent=4)
477 477 rst.append('\n')
478 478 rst.append(_('(use "hg help extensions" for information on enabling '
479 479 'extensions)\n'))
480 480 return rst
481 481
482 482
483 483 rst = []
484 484 kw = opts.get('keyword')
485 485 if kw:
486 486 matches = topicmatch(ui, name)
487 487 helpareas = []
488 488 if opts.get('extension'):
489 489 helpareas += [('extensions', _('Extensions'))]
490 490 if opts.get('command'):
491 491 helpareas += [('commands', _('Commands'))]
492 492 if not helpareas:
493 493 helpareas = [('topics', _('Topics')),
494 494 ('commands', _('Commands')),
495 495 ('extensions', _('Extensions')),
496 496 ('extensioncommands', _('Extension Commands'))]
497 497 for t, title in helpareas:
498 498 if matches[t]:
499 499 rst.append('%s:\n\n' % title)
500 500 rst.extend(minirst.maketable(sorted(matches[t]), 1))
501 501 rst.append('\n')
502 502 if not rst:
503 503 msg = _('no matches')
504 504 hint = _('try "hg help" for a list of topics')
505 505 raise util.Abort(msg, hint=hint)
506 506 elif name and name != 'shortlist':
507 507 queries = []
508 508 if unknowncmd:
509 509 queries += [helpextcmd]
510 510 if opts.get('extension'):
511 511 queries += [helpext]
512 512 if opts.get('command'):
513 513 queries += [helpcmd]
514 514 if not queries:
515 515 queries = (helptopic, helpcmd, helpext, helpextcmd)
516 516 for f in queries:
517 517 try:
518 518 rst = f(name)
519 519 break
520 520 except error.UnknownCommand:
521 521 pass
522 522 else:
523 523 if unknowncmd:
524 524 raise error.UnknownCommand(name)
525 525 else:
526 526 msg = _('no such help topic: %s') % name
527 527 hint = _('try "hg help --keyword %s"') % name
528 528 raise util.Abort(msg, hint=hint)
529 529 else:
530 530 # program name
531 531 if not ui.quiet:
532 532 rst = [_("Mercurial Distributed SCM\n"), '\n']
533 533 rst.extend(helplist())
534 534
535 535 return ''.join(rst)
@@ -1,514 +1,509 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 __future__ import absolute_import
9 9
10 10 from .node import hex
11 11 from . import (
12 12 error,
13 13 hbisect,
14 14 patch,
15 15 scmutil,
16 16 util,
17 17 )
18 18
19 19 # This helper class allows us to handle both:
20 20 # "{files}" (legacy command-line-specific list hack) and
21 21 # "{files % '{file}\n'}" (hgweb-style with inlining and function support)
22 22 # and to access raw values:
23 23 # "{ifcontains(file, files, ...)}", "{ifcontains(key, extras, ...)}"
24 24 # "{get(extras, key)}"
25 25
26 26 class _hybrid(object):
27 27 def __init__(self, gen, values, makemap, joinfmt=None):
28 28 self.gen = gen
29 29 self.values = values
30 30 self._makemap = makemap
31 31 if joinfmt:
32 32 self.joinfmt = joinfmt
33 33 else:
34 34 self.joinfmt = lambda x: x.values()[0]
35 35 def __iter__(self):
36 36 return self.gen
37 37 def __call__(self):
38 38 makemap = self._makemap
39 39 for x in self.values:
40 40 yield makemap(x)
41 41 def __contains__(self, x):
42 42 return x in self.values
43 43 def __len__(self):
44 44 return len(self.values)
45 45 def __getattr__(self, name):
46 46 if name != 'get':
47 47 raise AttributeError(name)
48 48 return getattr(self.values, name)
49 49
50 50 def showlist(name, values, plural=None, element=None, separator=' ', **args):
51 51 if not element:
52 52 element = name
53 53 f = _showlist(name, values, plural, separator, **args)
54 54 return _hybrid(f, values, lambda x: {element: x})
55 55
56 56 def _showlist(name, values, plural=None, separator=' ', **args):
57 57 '''expand set of values.
58 58 name is name of key in template map.
59 59 values is list of strings or dicts.
60 60 plural is plural of name, if not simply name + 's'.
61 61 separator is used to join values as a string
62 62
63 63 expansion works like this, given name 'foo'.
64 64
65 65 if values is empty, expand 'no_foos'.
66 66
67 67 if 'foo' not in template map, return values as a string,
68 68 joined by 'separator'.
69 69
70 70 expand 'start_foos'.
71 71
72 72 for each value, expand 'foo'. if 'last_foo' in template
73 73 map, expand it instead of 'foo' for last key.
74 74
75 75 expand 'end_foos'.
76 76 '''
77 77 templ = args['templ']
78 78 if plural:
79 79 names = plural
80 80 else: names = name + 's'
81 81 if not values:
82 82 noname = 'no_' + names
83 83 if noname in templ:
84 84 yield templ(noname, **args)
85 85 return
86 86 if name not in templ:
87 87 if isinstance(values[0], str):
88 88 yield separator.join(values)
89 89 else:
90 90 for v in values:
91 91 yield dict(v, **args)
92 92 return
93 93 startname = 'start_' + names
94 94 if startname in templ:
95 95 yield templ(startname, **args)
96 96 vargs = args.copy()
97 97 def one(v, tag=name):
98 98 try:
99 99 vargs.update(v)
100 100 except (AttributeError, ValueError):
101 101 try:
102 102 for a, b in v:
103 103 vargs[a] = b
104 104 except ValueError:
105 105 vargs[name] = v
106 106 return templ(tag, **vargs)
107 107 lastname = 'last_' + name
108 108 if lastname in templ:
109 109 last = values.pop()
110 110 else:
111 111 last = None
112 112 for v in values:
113 113 yield one(v)
114 114 if last is not None:
115 115 yield one(last, tag=lastname)
116 116 endname = 'end_' + names
117 117 if endname in templ:
118 118 yield templ(endname, **args)
119 119
120 120 def getfiles(repo, ctx, revcache):
121 121 if 'files' not in revcache:
122 122 revcache['files'] = repo.status(ctx.p1(), ctx)[:3]
123 123 return revcache['files']
124 124
125 125 def getlatesttags(repo, ctx, cache):
126 126 '''return date, distance and name for the latest tag of rev'''
127 127
128 128 if 'latesttags' not in cache:
129 129 # Cache mapping from rev to a tuple with tag date, tag
130 130 # distance and tag name
131 131 cache['latesttags'] = {-1: (0, 0, ['null'])}
132 132 latesttags = cache['latesttags']
133 133
134 134 rev = ctx.rev()
135 135 todo = [rev]
136 136 while todo:
137 137 rev = todo.pop()
138 138 if rev in latesttags:
139 139 continue
140 140 ctx = repo[rev]
141 141 tags = [t for t in ctx.tags()
142 142 if (repo.tagtype(t) and repo.tagtype(t) != 'local')]
143 143 if tags:
144 144 latesttags[rev] = ctx.date()[0], 0, [t for t in sorted(tags)]
145 145 continue
146 146 try:
147 147 # The tuples are laid out so the right one can be found by
148 148 # comparison.
149 149 pdate, pdist, ptag = max(
150 150 latesttags[p.rev()] for p in ctx.parents())
151 151 except KeyError:
152 152 # Cache miss - recurse
153 153 todo.append(rev)
154 154 todo.extend(p.rev() for p in ctx.parents())
155 155 continue
156 156 latesttags[rev] = pdate, pdist + 1, ptag
157 157 return latesttags[rev]
158 158
159 159 def getrenamedfn(repo, endrev=None):
160 160 rcache = {}
161 161 if endrev is None:
162 162 endrev = len(repo)
163 163
164 164 def getrenamed(fn, rev):
165 165 '''looks up all renames for a file (up to endrev) the first
166 166 time the file is given. It indexes on the changerev and only
167 167 parses the manifest if linkrev != changerev.
168 168 Returns rename info for fn at changerev rev.'''
169 169 if fn not in rcache:
170 170 rcache[fn] = {}
171 171 fl = repo.file(fn)
172 172 for i in fl:
173 173 lr = fl.linkrev(i)
174 174 renamed = fl.renamed(fl.node(i))
175 175 rcache[fn][lr] = renamed
176 176 if lr >= endrev:
177 177 break
178 178 if rev in rcache[fn]:
179 179 return rcache[fn][rev]
180 180
181 181 # If linkrev != rev (i.e. rev not found in rcache) fallback to
182 182 # filectx logic.
183 183 try:
184 184 return repo[rev][fn].renamed()
185 185 except error.LookupError:
186 186 return None
187 187
188 188 return getrenamed
189 189
190 190
191 191 def showauthor(repo, ctx, templ, **args):
192 192 """:author: String. The unmodified author of the changeset."""
193 193 return ctx.user()
194 194
195 195 def showbisect(repo, ctx, templ, **args):
196 196 """:bisect: String. The changeset bisection status."""
197 197 return hbisect.label(repo, ctx.node())
198 198
199 199 def showbranch(**args):
200 200 """:branch: String. The name of the branch on which the changeset was
201 201 committed.
202 202 """
203 203 return args['ctx'].branch()
204 204
205 205 def showbranches(**args):
206 206 """:branches: List of strings. The name of the branch on which the
207 207 changeset was committed. Will be empty if the branch name was
208 208 default.
209 209 """
210 210 branch = args['ctx'].branch()
211 211 if branch != 'default':
212 212 return showlist('branch', [branch], plural='branches', **args)
213 213 return showlist('branch', [], plural='branches', **args)
214 214
215 215 def showbookmarks(**args):
216 216 """:bookmarks: List of strings. Any bookmarks associated with the
217 217 changeset. Also sets 'active', the name of the active bookmark.
218 218 """
219 219 repo = args['ctx']._repo
220 220 bookmarks = args['ctx'].bookmarks()
221 221 active = repo._activebookmark
222 222 makemap = lambda v: {'bookmark': v, 'active': active, 'current': active}
223 223 f = _showlist('bookmark', bookmarks, **args)
224 224 return _hybrid(f, bookmarks, makemap, lambda x: x['bookmark'])
225 225
226 226 def showchildren(**args):
227 227 """:children: List of strings. The children of the changeset."""
228 228 ctx = args['ctx']
229 229 childrevs = ['%d:%s' % (cctx, cctx) for cctx in ctx.children()]
230 230 return showlist('children', childrevs, element='child', **args)
231 231
232 232 # Deprecated, but kept alive for help generation a purpose.
233 233 def showcurrentbookmark(**args):
234 234 """:currentbookmark: String. The active bookmark, if it is
235 235 associated with the changeset (DEPRECATED)"""
236 236 return showactivebookmark(**args)
237 237
238 238 def showactivebookmark(**args):
239 239 """:activebookmark: String. The active bookmark, if it is
240 240 associated with the changeset"""
241 241 active = args['repo']._activebookmark
242 242 if active and active in args['ctx'].bookmarks():
243 243 return active
244 244 return ''
245 245
246 246 def showdate(repo, ctx, templ, **args):
247 247 """:date: Date information. The date when the changeset was committed."""
248 248 return ctx.date()
249 249
250 250 def showdescription(repo, ctx, templ, **args):
251 251 """:desc: String. The text of the changeset description."""
252 252 return ctx.description().strip()
253 253
254 254 def showdiffstat(repo, ctx, templ, **args):
255 255 """:diffstat: String. Statistics of changes with the following format:
256 256 "modified files: +added/-removed lines"
257 257 """
258 258 stats = patch.diffstatdata(util.iterlines(ctx.diff()))
259 259 maxname, maxtotal, adds, removes, binary = patch.diffstatsum(stats)
260 260 return '%s: +%s/-%s' % (len(stats), adds, removes)
261 261
262 262 def showextras(**args):
263 263 """:extras: List of dicts with key, value entries of the 'extras'
264 264 field of this changeset."""
265 265 extras = args['ctx'].extra()
266 266 extras = util.sortdict((k, extras[k]) for k in sorted(extras))
267 267 makemap = lambda k: {'key': k, 'value': extras[k]}
268 268 c = [makemap(k) for k in extras]
269 269 f = _showlist('extra', c, plural='extras', **args)
270 270 return _hybrid(f, extras, makemap,
271 271 lambda x: '%s=%s' % (x['key'], x['value']))
272 272
273 273 def showfileadds(**args):
274 274 """:file_adds: List of strings. Files added by this changeset."""
275 275 repo, ctx, revcache = args['repo'], args['ctx'], args['revcache']
276 276 return showlist('file_add', getfiles(repo, ctx, revcache)[1],
277 277 element='file', **args)
278 278
279 279 def showfilecopies(**args):
280 280 """:file_copies: List of strings. Files copied in this changeset with
281 281 their sources.
282 282 """
283 283 cache, ctx = args['cache'], args['ctx']
284 284 copies = args['revcache'].get('copies')
285 285 if copies is None:
286 286 if 'getrenamed' not in cache:
287 287 cache['getrenamed'] = getrenamedfn(args['repo'])
288 288 copies = []
289 289 getrenamed = cache['getrenamed']
290 290 for fn in ctx.files():
291 291 rename = getrenamed(fn, ctx.rev())
292 292 if rename:
293 293 copies.append((fn, rename[0]))
294 294
295 295 copies = util.sortdict(copies)
296 296 makemap = lambda k: {'name': k, 'source': copies[k]}
297 297 c = [makemap(k) for k in copies]
298 298 f = _showlist('file_copy', c, plural='file_copies', **args)
299 299 return _hybrid(f, copies, makemap,
300 300 lambda x: '%s (%s)' % (x['name'], x['source']))
301 301
302 302 # showfilecopiesswitch() displays file copies only if copy records are
303 303 # provided before calling the templater, usually with a --copies
304 304 # command line switch.
305 305 def showfilecopiesswitch(**args):
306 306 """:file_copies_switch: List of strings. Like "file_copies" but displayed
307 307 only if the --copied switch is set.
308 308 """
309 309 copies = args['revcache'].get('copies') or []
310 310 copies = util.sortdict(copies)
311 311 makemap = lambda k: {'name': k, 'source': copies[k]}
312 312 c = [makemap(k) for k in copies]
313 313 f = _showlist('file_copy', c, plural='file_copies', **args)
314 314 return _hybrid(f, copies, makemap,
315 315 lambda x: '%s (%s)' % (x['name'], x['source']))
316 316
317 317 def showfiledels(**args):
318 318 """:file_dels: List of strings. Files removed by this changeset."""
319 319 repo, ctx, revcache = args['repo'], args['ctx'], args['revcache']
320 320 return showlist('file_del', getfiles(repo, ctx, revcache)[2],
321 321 element='file', **args)
322 322
323 323 def showfilemods(**args):
324 324 """:file_mods: List of strings. Files modified by this changeset."""
325 325 repo, ctx, revcache = args['repo'], args['ctx'], args['revcache']
326 326 return showlist('file_mod', getfiles(repo, ctx, revcache)[0],
327 327 element='file', **args)
328 328
329 329 def showfiles(**args):
330 330 """:files: List of strings. All files modified, added, or removed by this
331 331 changeset.
332 332 """
333 333 return showlist('file', args['ctx'].files(), **args)
334 334
335 335 def showlatesttag(**args):
336 336 """:latesttag: List of strings. The global tags on the most recent globally
337 337 tagged ancestor of this changeset.
338 338 """
339 339 repo, ctx = args['repo'], args['ctx']
340 340 cache = args['cache']
341 341 latesttags = getlatesttags(repo, ctx, cache)[2]
342 342
343 343 return showlist('latesttag', latesttags, separator=':', **args)
344 344
345 345 def showlatesttagdistance(repo, ctx, templ, cache, **args):
346 346 """:latesttagdistance: Integer. Longest path to the latest tag."""
347 347 return getlatesttags(repo, ctx, cache)[1]
348 348
349 349 def showchangessincelatesttag(repo, ctx, templ, cache, **args):
350 350 """:changessincelatesttag: Integer. All ancestors not in the latest tag."""
351 351 latesttag = getlatesttags(repo, ctx, cache)[2][0]
352 352 offset = 0
353 353 revs = [ctx.rev()]
354 354
355 355 # The only() revset doesn't currently support wdir()
356 356 if ctx.rev() is None:
357 357 offset = 1
358 358 revs = [p.rev() for p in ctx.parents()]
359 359
360 360 return len(repo.revs('only(%ld, %s)', revs, latesttag)) + offset
361 361
362 362 def showmanifest(**args):
363 363 repo, ctx, templ = args['repo'], args['ctx'], args['templ']
364 364 mnode = ctx.manifestnode()
365 365 if mnode is None:
366 366 # just avoid crash, we might want to use the 'ff...' hash in future
367 367 return
368 368 args = args.copy()
369 369 args.update({'rev': repo.manifest.rev(mnode), 'node': hex(mnode)})
370 370 return templ('manifest', **args)
371 371
372 372 def shownode(repo, ctx, templ, **args):
373 373 """:node: String. The changeset identification hash, as a 40 hexadecimal
374 374 digit string.
375 375 """
376 376 return ctx.hex()
377 377
378 378 def showp1rev(repo, ctx, templ, **args):
379 379 """:p1rev: Integer. The repository-local revision number of the changeset's
380 380 first parent, or -1 if the changeset has no parents."""
381 381 return ctx.p1().rev()
382 382
383 383 def showp2rev(repo, ctx, templ, **args):
384 384 """:p2rev: Integer. The repository-local revision number of the changeset's
385 385 second parent, or -1 if the changeset has no second parent."""
386 386 return ctx.p2().rev()
387 387
388 388 def showp1node(repo, ctx, templ, **args):
389 389 """:p1node: String. The identification hash of the changeset's first parent,
390 390 as a 40 digit hexadecimal string. If the changeset has no parents, all
391 391 digits are 0."""
392 392 return ctx.p1().hex()
393 393
394 394 def showp2node(repo, ctx, templ, **args):
395 395 """:p2node: String. The identification hash of the changeset's second
396 396 parent, as a 40 digit hexadecimal string. If the changeset has no second
397 397 parent, all digits are 0."""
398 398 return ctx.p2().hex()
399 399
400 400 def showparents(**args):
401 401 """:parents: List of strings. The parents of the changeset in "rev:node"
402 402 format. If the changeset has only one "natural" parent (the predecessor
403 403 revision) nothing is shown."""
404 404 repo = args['repo']
405 405 ctx = args['ctx']
406 406 parents = [[('rev', p.rev()),
407 407 ('node', p.hex()),
408 408 ('phase', p.phasestr())]
409 409 for p in scmutil.meaningfulparents(repo, ctx)]
410 410 return showlist('parent', parents, **args)
411 411
412 412 def showphase(repo, ctx, templ, **args):
413 413 """:phase: String. The changeset phase name."""
414 414 return ctx.phasestr()
415 415
416 416 def showphaseidx(repo, ctx, templ, **args):
417 417 """:phaseidx: Integer. The changeset phase index."""
418 418 return ctx.phase()
419 419
420 420 def showrev(repo, ctx, templ, **args):
421 421 """:rev: Integer. The repository-local changeset revision number."""
422 422 return scmutil.intrev(ctx.rev())
423 423
424 424 def showrevslist(name, revs, **args):
425 425 """helper to generate a list of revisions in which a mapped template will
426 426 be evaluated"""
427 427 repo = args['ctx'].repo()
428 428 f = _showlist(name, revs, **args)
429 429 return _hybrid(f, revs,
430 430 lambda x: {name: x, 'ctx': repo[x], 'revcache': {}})
431 431
432 432 def showsubrepos(**args):
433 433 """:subrepos: List of strings. Updated subrepositories in the changeset."""
434 434 ctx = args['ctx']
435 435 substate = ctx.substate
436 436 if not substate:
437 437 return showlist('subrepo', [], **args)
438 438 psubstate = ctx.parents()[0].substate or {}
439 439 subrepos = []
440 440 for sub in substate:
441 441 if sub not in psubstate or substate[sub] != psubstate[sub]:
442 442 subrepos.append(sub) # modified or newly added in ctx
443 443 for sub in psubstate:
444 444 if sub not in substate:
445 445 subrepos.append(sub) # removed in ctx
446 446 return showlist('subrepo', sorted(subrepos), **args)
447 447
448 448 def shownames(namespace, **args):
449 449 """helper method to generate a template keyword for a namespace"""
450 450 ctx = args['ctx']
451 451 repo = ctx.repo()
452 452 ns = repo.names[namespace]
453 453 names = ns.names(repo, ctx.node())
454 454 return showlist(ns.templatename, names, plural=namespace, **args)
455 455
456 456 # don't remove "showtags" definition, even though namespaces will put
457 457 # a helper function for "tags" keyword into "keywords" map automatically,
458 458 # because online help text is built without namespaces initialization
459 459 def showtags(**args):
460 460 """:tags: List of strings. Any tags associated with the changeset."""
461 461 return shownames('tags', **args)
462 462
463 463 # keywords are callables like:
464 464 # fn(repo, ctx, templ, cache, revcache, **args)
465 465 # with:
466 466 # repo - current repository instance
467 467 # ctx - the changectx being displayed
468 468 # templ - the templater instance
469 469 # cache - a cache dictionary for the whole templater run
470 470 # revcache - a cache dictionary for the current revision
471 471 keywords = {
472 472 'activebookmark': showactivebookmark,
473 473 'author': showauthor,
474 474 'bisect': showbisect,
475 475 'branch': showbranch,
476 476 'branches': showbranches,
477 477 'bookmarks': showbookmarks,
478 478 'changessincelatesttag': showchangessincelatesttag,
479 479 'children': showchildren,
480 480 # currentbookmark is deprecated
481 481 'currentbookmark': showcurrentbookmark,
482 482 'date': showdate,
483 483 'desc': showdescription,
484 484 'diffstat': showdiffstat,
485 485 'extras': showextras,
486 486 'file_adds': showfileadds,
487 487 'file_copies': showfilecopies,
488 488 'file_copies_switch': showfilecopiesswitch,
489 489 'file_dels': showfiledels,
490 490 'file_mods': showfilemods,
491 491 'files': showfiles,
492 492 'latesttag': showlatesttag,
493 493 'latesttagdistance': showlatesttagdistance,
494 494 'manifest': showmanifest,
495 495 'node': shownode,
496 496 'p1rev': showp1rev,
497 497 'p1node': showp1node,
498 498 'p2rev': showp2rev,
499 499 'p2node': showp2node,
500 500 'parents': showparents,
501 501 'phase': showphase,
502 502 'phaseidx': showphaseidx,
503 503 'rev': showrev,
504 504 'subrepos': showsubrepos,
505 505 'tags': showtags,
506 506 }
507 507
508 dockeywords = {
509 }
510 dockeywords.update(keywords)
511 del dockeywords['branches']
512
513 508 # tell hggettext to extract docstrings from these functions:
514 i18nfunctions = dockeywords.values()
509 i18nfunctions = keywords.values()
General Comments 0
You need to be logged in to leave comments. Login now