##// END OF EJS Templates
help: make "mergetool" an alias for "merge-tools"...
Gregory Szorc -
r30812:98bfce9b default
parent child Browse files
Show More
@@ -1,606 +1,607 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 __future__ import absolute_import
9 9
10 10 import itertools
11 11 import os
12 12 import textwrap
13 13
14 14 from .i18n import (
15 15 _,
16 16 gettext,
17 17 )
18 18 from . import (
19 19 cmdutil,
20 20 encoding,
21 21 error,
22 22 extensions,
23 23 filemerge,
24 24 fileset,
25 25 minirst,
26 26 revset,
27 27 templatefilters,
28 28 templatekw,
29 29 templater,
30 30 util,
31 31 )
32 32 from .hgweb import (
33 33 webcommands,
34 34 )
35 35
36 36 _exclkeywords = [
37 37 "(DEPRECATED)",
38 38 "(EXPERIMENTAL)",
39 39 # i18n: "(DEPRECATED)" is a keyword, must be translated consistently
40 40 _("(DEPRECATED)"),
41 41 # i18n: "(EXPERIMENTAL)" is a keyword, must be translated consistently
42 42 _("(EXPERIMENTAL)"),
43 43 ]
44 44
45 45 def listexts(header, exts, indent=1, showdeprecated=False):
46 46 '''return a text listing of the given extensions'''
47 47 rst = []
48 48 if exts:
49 49 for name, desc in sorted(exts.iteritems()):
50 50 if not showdeprecated and any(w in desc for w in _exclkeywords):
51 51 continue
52 52 rst.append('%s:%s: %s\n' % (' ' * indent, name, desc))
53 53 if rst:
54 54 rst.insert(0, '\n%s\n\n' % header)
55 55 return rst
56 56
57 57 def extshelp(ui):
58 58 rst = loaddoc('extensions')(ui).splitlines(True)
59 59 rst.extend(listexts(
60 60 _('enabled extensions:'), extensions.enabled(), showdeprecated=True))
61 61 rst.extend(listexts(_('disabled extensions:'), extensions.disabled()))
62 62 doc = ''.join(rst)
63 63 return doc
64 64
65 65 def optrst(header, options, verbose):
66 66 data = []
67 67 multioccur = False
68 68 for option in options:
69 69 if len(option) == 5:
70 70 shortopt, longopt, default, desc, optlabel = option
71 71 else:
72 72 shortopt, longopt, default, desc = option
73 73 optlabel = _("VALUE") # default label
74 74
75 75 if not verbose and any(w in desc for w in _exclkeywords):
76 76 continue
77 77
78 78 so = ''
79 79 if shortopt:
80 80 so = '-' + shortopt
81 81 lo = '--' + longopt
82 82 if default:
83 83 desc += _(" (default: %s)") % default
84 84
85 85 if isinstance(default, list):
86 86 lo += " %s [+]" % optlabel
87 87 multioccur = True
88 88 elif (default is not None) and not isinstance(default, bool):
89 89 lo += " %s" % optlabel
90 90
91 91 data.append((so, lo, desc))
92 92
93 93 if multioccur:
94 94 header += (_(" ([+] can be repeated)"))
95 95
96 96 rst = ['\n%s:\n\n' % header]
97 97 rst.extend(minirst.maketable(data, 1))
98 98
99 99 return ''.join(rst)
100 100
101 101 def indicateomitted(rst, omitted, notomitted=None):
102 102 rst.append('\n\n.. container:: omitted\n\n %s\n\n' % omitted)
103 103 if notomitted:
104 104 rst.append('\n\n.. container:: notomitted\n\n %s\n\n' % notomitted)
105 105
106 106 def filtercmd(ui, cmd, kw, doc):
107 107 if not ui.debugflag and cmd.startswith("debug") and kw != "debug":
108 108 return True
109 109 if not ui.verbose and doc and any(w in doc for w in _exclkeywords):
110 110 return True
111 111 return False
112 112
113 113 def topicmatch(ui, kw):
114 114 """Return help topics matching kw.
115 115
116 116 Returns {'section': [(name, summary), ...], ...} where section is
117 117 one of topics, commands, extensions, or extensioncommands.
118 118 """
119 119 kw = encoding.lower(kw)
120 120 def lowercontains(container):
121 121 return kw in encoding.lower(container) # translated in helptable
122 122 results = {'topics': [],
123 123 'commands': [],
124 124 'extensions': [],
125 125 'extensioncommands': [],
126 126 }
127 127 for names, header, doc in helptable:
128 128 # Old extensions may use a str as doc.
129 129 if (sum(map(lowercontains, names))
130 130 or lowercontains(header)
131 131 or (callable(doc) and lowercontains(doc(ui)))):
132 132 results['topics'].append((names[0], header))
133 133 from . import commands # avoid cycle
134 134 for cmd, entry in commands.table.iteritems():
135 135 if len(entry) == 3:
136 136 summary = entry[2]
137 137 else:
138 138 summary = ''
139 139 # translate docs *before* searching there
140 140 docs = _(getattr(entry[0], '__doc__', None)) or ''
141 141 if kw in cmd or lowercontains(summary) or lowercontains(docs):
142 142 doclines = docs.splitlines()
143 143 if doclines:
144 144 summary = doclines[0]
145 145 cmdname = cmd.partition('|')[0].lstrip('^')
146 146 if filtercmd(ui, cmdname, kw, docs):
147 147 continue
148 148 results['commands'].append((cmdname, summary))
149 149 for name, docs in itertools.chain(
150 150 extensions.enabled(False).iteritems(),
151 151 extensions.disabled().iteritems()):
152 152 if not docs:
153 153 continue
154 154 mod = extensions.load(ui, name, '')
155 155 name = name.rpartition('.')[-1]
156 156 if lowercontains(name) or lowercontains(docs):
157 157 # extension docs are already translated
158 158 results['extensions'].append((name, docs.splitlines()[0]))
159 159 for cmd, entry in getattr(mod, 'cmdtable', {}).iteritems():
160 160 if kw in cmd or (len(entry) > 2 and lowercontains(entry[2])):
161 161 cmdname = cmd.partition('|')[0].lstrip('^')
162 162 if entry[0].__doc__:
163 163 cmddoc = gettext(entry[0].__doc__).splitlines()[0]
164 164 else:
165 165 cmddoc = _('(no help text available)')
166 166 if filtercmd(ui, cmdname, kw, cmddoc):
167 167 continue
168 168 results['extensioncommands'].append((cmdname, cmddoc))
169 169 return results
170 170
171 171 def loaddoc(topic, subdir=None):
172 172 """Return a delayed loader for help/topic.txt."""
173 173
174 174 def loader(ui):
175 175 docdir = os.path.join(util.datapath, 'help')
176 176 if subdir:
177 177 docdir = os.path.join(docdir, subdir)
178 178 path = os.path.join(docdir, topic + ".txt")
179 179 doc = gettext(util.readfile(path))
180 180 for rewriter in helphooks.get(topic, []):
181 181 doc = rewriter(ui, topic, doc)
182 182 return doc
183 183
184 184 return loader
185 185
186 186 internalstable = sorted([
187 187 (['bundles'], _('Bundles'),
188 188 loaddoc('bundles', subdir='internals')),
189 189 (['changegroups'], _('Changegroups'),
190 190 loaddoc('changegroups', subdir='internals')),
191 191 (['requirements'], _('Repository Requirements'),
192 192 loaddoc('requirements', subdir='internals')),
193 193 (['revlogs'], _('Revision Logs'),
194 194 loaddoc('revlogs', subdir='internals')),
195 195 (['wireprotocol'], _('Wire Protocol'),
196 196 loaddoc('wireprotocol', subdir='internals')),
197 197 ])
198 198
199 199 def internalshelp(ui):
200 200 """Generate the index for the "internals" topic."""
201 201 lines = []
202 202 for names, header, doc in internalstable:
203 203 lines.append(' :%s: %s\n' % (names[0], header))
204 204
205 205 return ''.join(lines)
206 206
207 207 helptable = sorted([
208 208 (["config", "hgrc"], _("Configuration Files"), loaddoc('config')),
209 209 (["dates"], _("Date Formats"), loaddoc('dates')),
210 210 (["patterns"], _("File Name Patterns"), loaddoc('patterns')),
211 211 (['environment', 'env'], _('Environment Variables'),
212 212 loaddoc('environment')),
213 213 (['revisions', 'revs', 'revsets', 'revset', 'multirevs', 'mrevs'],
214 214 _('Specifying Revisions'), loaddoc('revisions')),
215 215 (['filesets', 'fileset'], _("Specifying File Sets"), loaddoc('filesets')),
216 216 (['diffs'], _('Diff Formats'), loaddoc('diffs')),
217 (['merge-tools', 'mergetools'], _('Merge Tools'), loaddoc('merge-tools')),
217 (['merge-tools', 'mergetools', 'mergetool'], _('Merge Tools'),
218 loaddoc('merge-tools')),
218 219 (['templating', 'templates', 'template', 'style'], _('Template Usage'),
219 220 loaddoc('templates')),
220 221 (['urls'], _('URL Paths'), loaddoc('urls')),
221 222 (["extensions"], _("Using Additional Features"), extshelp),
222 223 (["subrepos", "subrepo"], _("Subrepositories"), loaddoc('subrepos')),
223 224 (["hgweb"], _("Configuring hgweb"), loaddoc('hgweb')),
224 225 (["glossary"], _("Glossary"), loaddoc('glossary')),
225 226 (["hgignore", "ignore"], _("Syntax for Mercurial Ignore Files"),
226 227 loaddoc('hgignore')),
227 228 (["phases"], _("Working with Phases"), loaddoc('phases')),
228 229 (['scripting'], _('Using Mercurial from scripts and automation'),
229 230 loaddoc('scripting')),
230 231 (['internals'], _("Technical implementation topics"),
231 232 internalshelp),
232 233 ])
233 234
234 235 # Maps topics with sub-topics to a list of their sub-topics.
235 236 subtopics = {
236 237 'internals': internalstable,
237 238 }
238 239
239 240 # Map topics to lists of callable taking the current topic help and
240 241 # returning the updated version
241 242 helphooks = {}
242 243
243 244 def addtopichook(topic, rewriter):
244 245 helphooks.setdefault(topic, []).append(rewriter)
245 246
246 247 def makeitemsdoc(ui, topic, doc, marker, items, dedent=False):
247 248 """Extract docstring from the items key to function mapping, build a
248 249 single documentation block and use it to overwrite the marker in doc.
249 250 """
250 251 entries = []
251 252 for name in sorted(items):
252 253 text = (items[name].__doc__ or '').rstrip()
253 254 if (not text
254 255 or not ui.verbose and any(w in text for w in _exclkeywords)):
255 256 continue
256 257 text = gettext(text)
257 258 if dedent:
258 259 text = textwrap.dedent(text)
259 260 lines = text.splitlines()
260 261 doclines = [(lines[0])]
261 262 for l in lines[1:]:
262 263 # Stop once we find some Python doctest
263 264 if l.strip().startswith('>>>'):
264 265 break
265 266 if dedent:
266 267 doclines.append(l.rstrip())
267 268 else:
268 269 doclines.append(' ' + l.strip())
269 270 entries.append('\n'.join(doclines))
270 271 entries = '\n\n'.join(entries)
271 272 return doc.replace(marker, entries)
272 273
273 274 def addtopicsymbols(topic, marker, symbols, dedent=False):
274 275 def add(ui, topic, doc):
275 276 return makeitemsdoc(ui, topic, doc, marker, symbols, dedent=dedent)
276 277 addtopichook(topic, add)
277 278
278 279 addtopicsymbols('filesets', '.. predicatesmarker', fileset.symbols)
279 280 addtopicsymbols('merge-tools', '.. internaltoolsmarker',
280 281 filemerge.internalsdoc)
281 282 addtopicsymbols('revisions', '.. predicatesmarker', revset.symbols)
282 283 addtopicsymbols('templates', '.. keywordsmarker', templatekw.keywords)
283 284 addtopicsymbols('templates', '.. filtersmarker', templatefilters.filters)
284 285 addtopicsymbols('templates', '.. functionsmarker', templater.funcs)
285 286 addtopicsymbols('hgweb', '.. webcommandsmarker', webcommands.commands,
286 287 dedent=True)
287 288
288 289 def help_(ui, name, unknowncmd=False, full=True, subtopic=None, **opts):
289 290 '''
290 291 Generate the help for 'name' as unformatted restructured text. If
291 292 'name' is None, describe the commands available.
292 293 '''
293 294
294 295 from . import commands # avoid cycle
295 296
296 297 def helpcmd(name, subtopic=None):
297 298 try:
298 299 aliases, entry = cmdutil.findcmd(name, commands.table,
299 300 strict=unknowncmd)
300 301 except error.AmbiguousCommand as inst:
301 302 # py3k fix: except vars can't be used outside the scope of the
302 303 # except block, nor can be used inside a lambda. python issue4617
303 304 prefix = inst.args[0]
304 305 select = lambda c: c.lstrip('^').startswith(prefix)
305 306 rst = helplist(select)
306 307 return rst
307 308
308 309 rst = []
309 310
310 311 # check if it's an invalid alias and display its error if it is
311 312 if getattr(entry[0], 'badalias', None):
312 313 rst.append(entry[0].badalias + '\n')
313 314 if entry[0].unknowncmd:
314 315 try:
315 316 rst.extend(helpextcmd(entry[0].cmdname))
316 317 except error.UnknownCommand:
317 318 pass
318 319 return rst
319 320
320 321 # synopsis
321 322 if len(entry) > 2:
322 323 if entry[2].startswith('hg'):
323 324 rst.append("%s\n" % entry[2])
324 325 else:
325 326 rst.append('hg %s %s\n' % (aliases[0], entry[2]))
326 327 else:
327 328 rst.append('hg %s\n' % aliases[0])
328 329 # aliases
329 330 if full and not ui.quiet and len(aliases) > 1:
330 331 rst.append(_("\naliases: %s\n") % ', '.join(aliases[1:]))
331 332 rst.append('\n')
332 333
333 334 # description
334 335 doc = gettext(entry[0].__doc__)
335 336 if not doc:
336 337 doc = _("(no help text available)")
337 338 if util.safehasattr(entry[0], 'definition'): # aliased command
338 339 source = entry[0].source
339 340 if entry[0].definition.startswith('!'): # shell alias
340 341 doc = (_('shell alias for::\n\n %s\n\ndefined by: %s\n') %
341 342 (entry[0].definition[1:], source))
342 343 else:
343 344 doc = (_('alias for: hg %s\n\n%s\n\ndefined by: %s\n') %
344 345 (entry[0].definition, doc, source))
345 346 doc = doc.splitlines(True)
346 347 if ui.quiet or not full:
347 348 rst.append(doc[0])
348 349 else:
349 350 rst.extend(doc)
350 351 rst.append('\n')
351 352
352 353 # check if this command shadows a non-trivial (multi-line)
353 354 # extension help text
354 355 try:
355 356 mod = extensions.find(name)
356 357 doc = gettext(mod.__doc__) or ''
357 358 if '\n' in doc.strip():
358 359 msg = _("(use 'hg help -e %s' to show help for "
359 360 "the %s extension)") % (name, name)
360 361 rst.append('\n%s\n' % msg)
361 362 except KeyError:
362 363 pass
363 364
364 365 # options
365 366 if not ui.quiet and entry[1]:
366 367 rst.append(optrst(_("options"), entry[1], ui.verbose))
367 368
368 369 if ui.verbose:
369 370 rst.append(optrst(_("global options"),
370 371 commands.globalopts, ui.verbose))
371 372
372 373 if not ui.verbose:
373 374 if not full:
374 375 rst.append(_("\n(use 'hg %s -h' to show more help)\n")
375 376 % name)
376 377 elif not ui.quiet:
377 378 rst.append(_('\n(some details hidden, use --verbose '
378 379 'to show complete help)'))
379 380
380 381 return rst
381 382
382 383
383 384 def helplist(select=None, **opts):
384 385 # list of commands
385 386 if name == "shortlist":
386 387 header = _('basic commands:\n\n')
387 388 elif name == "debug":
388 389 header = _('debug commands (internal and unsupported):\n\n')
389 390 else:
390 391 header = _('list of commands:\n\n')
391 392
392 393 h = {}
393 394 cmds = {}
394 395 for c, e in commands.table.iteritems():
395 396 f = c.partition("|")[0]
396 397 if select and not select(f):
397 398 continue
398 399 if (not select and name != 'shortlist' and
399 400 e[0].__module__ != commands.__name__):
400 401 continue
401 402 if name == "shortlist" and not f.startswith("^"):
402 403 continue
403 404 f = f.lstrip("^")
404 405 doc = e[0].__doc__
405 406 if filtercmd(ui, f, name, doc):
406 407 continue
407 408 doc = gettext(doc)
408 409 if not doc:
409 410 doc = _("(no help text available)")
410 411 h[f] = doc.splitlines()[0].rstrip()
411 412 cmds[f] = c.lstrip("^")
412 413
413 414 rst = []
414 415 if not h:
415 416 if not ui.quiet:
416 417 rst.append(_('no commands defined\n'))
417 418 return rst
418 419
419 420 if not ui.quiet:
420 421 rst.append(header)
421 422 fns = sorted(h)
422 423 for f in fns:
423 424 if ui.verbose:
424 425 commacmds = cmds[f].replace("|",", ")
425 426 rst.append(" :%s: %s\n" % (commacmds, h[f]))
426 427 else:
427 428 rst.append(' :%s: %s\n' % (f, h[f]))
428 429
429 430 ex = opts.get
430 431 anyopts = (ex('keyword') or not (ex('command') or ex('extension')))
431 432 if not name and anyopts:
432 433 exts = listexts(_('enabled extensions:'), extensions.enabled())
433 434 if exts:
434 435 rst.append('\n')
435 436 rst.extend(exts)
436 437
437 438 rst.append(_("\nadditional help topics:\n\n"))
438 439 topics = []
439 440 for names, header, doc in helptable:
440 441 topics.append((names[0], header))
441 442 for t, desc in topics:
442 443 rst.append(" :%s: %s\n" % (t, desc))
443 444
444 445 if ui.quiet:
445 446 pass
446 447 elif ui.verbose:
447 448 rst.append('\n%s\n' % optrst(_("global options"),
448 449 commands.globalopts, ui.verbose))
449 450 if name == 'shortlist':
450 451 rst.append(_("\n(use 'hg help' for the full list "
451 452 "of commands)\n"))
452 453 else:
453 454 if name == 'shortlist':
454 455 rst.append(_("\n(use 'hg help' for the full list of commands "
455 456 "or 'hg -v' for details)\n"))
456 457 elif name and not full:
457 458 rst.append(_("\n(use 'hg help %s' to show the full help "
458 459 "text)\n") % name)
459 460 elif name and cmds and name in cmds.keys():
460 461 rst.append(_("\n(use 'hg help -v -e %s' to show built-in "
461 462 "aliases and global options)\n") % name)
462 463 else:
463 464 rst.append(_("\n(use 'hg help -v%s' to show built-in aliases "
464 465 "and global options)\n")
465 466 % (name and " " + name or ""))
466 467 return rst
467 468
468 469 def helptopic(name, subtopic=None):
469 470 # Look for sub-topic entry first.
470 471 header, doc = None, None
471 472 if subtopic and name in subtopics:
472 473 for names, header, doc in subtopics[name]:
473 474 if subtopic in names:
474 475 break
475 476
476 477 if not header:
477 478 for names, header, doc in helptable:
478 479 if name in names:
479 480 break
480 481 else:
481 482 raise error.UnknownCommand(name)
482 483
483 484 rst = [minirst.section(header)]
484 485
485 486 # description
486 487 if not doc:
487 488 rst.append(" %s\n" % _("(no help text available)"))
488 489 if callable(doc):
489 490 rst += [" %s\n" % l for l in doc(ui).splitlines()]
490 491
491 492 if not ui.verbose:
492 493 omitted = _('(some details hidden, use --verbose'
493 494 ' to show complete help)')
494 495 indicateomitted(rst, omitted)
495 496
496 497 try:
497 498 cmdutil.findcmd(name, commands.table)
498 499 rst.append(_("\nuse 'hg help -c %s' to see help for "
499 500 "the %s command\n") % (name, name))
500 501 except error.UnknownCommand:
501 502 pass
502 503 return rst
503 504
504 505 def helpext(name, subtopic=None):
505 506 try:
506 507 mod = extensions.find(name)
507 508 doc = gettext(mod.__doc__) or _('no help text available')
508 509 except KeyError:
509 510 mod = None
510 511 doc = extensions.disabledext(name)
511 512 if not doc:
512 513 raise error.UnknownCommand(name)
513 514
514 515 if '\n' not in doc:
515 516 head, tail = doc, ""
516 517 else:
517 518 head, tail = doc.split('\n', 1)
518 519 rst = [_('%s extension - %s\n\n') % (name.rpartition('.')[-1], head)]
519 520 if tail:
520 521 rst.extend(tail.splitlines(True))
521 522 rst.append('\n')
522 523
523 524 if not ui.verbose:
524 525 omitted = _('(some details hidden, use --verbose'
525 526 ' to show complete help)')
526 527 indicateomitted(rst, omitted)
527 528
528 529 if mod:
529 530 try:
530 531 ct = mod.cmdtable
531 532 except AttributeError:
532 533 ct = {}
533 534 modcmds = set([c.partition('|')[0] for c in ct])
534 535 rst.extend(helplist(modcmds.__contains__))
535 536 else:
536 537 rst.append(_("(use 'hg help extensions' for information on enabling"
537 538 " extensions)\n"))
538 539 return rst
539 540
540 541 def helpextcmd(name, subtopic=None):
541 542 cmd, ext, mod = extensions.disabledcmd(ui, name,
542 543 ui.configbool('ui', 'strict'))
543 544 doc = gettext(mod.__doc__).splitlines()[0]
544 545
545 546 rst = listexts(_("'%s' is provided by the following "
546 547 "extension:") % cmd, {ext: doc}, indent=4,
547 548 showdeprecated=True)
548 549 rst.append('\n')
549 550 rst.append(_("(use 'hg help extensions' for information on enabling "
550 551 "extensions)\n"))
551 552 return rst
552 553
553 554
554 555 rst = []
555 556 kw = opts.get('keyword')
556 557 if kw or name is None and any(opts[o] for o in opts):
557 558 matches = topicmatch(ui, name or '')
558 559 helpareas = []
559 560 if opts.get('extension'):
560 561 helpareas += [('extensions', _('Extensions'))]
561 562 if opts.get('command'):
562 563 helpareas += [('commands', _('Commands'))]
563 564 if not helpareas:
564 565 helpareas = [('topics', _('Topics')),
565 566 ('commands', _('Commands')),
566 567 ('extensions', _('Extensions')),
567 568 ('extensioncommands', _('Extension Commands'))]
568 569 for t, title in helpareas:
569 570 if matches[t]:
570 571 rst.append('%s:\n\n' % title)
571 572 rst.extend(minirst.maketable(sorted(matches[t]), 1))
572 573 rst.append('\n')
573 574 if not rst:
574 575 msg = _('no matches')
575 576 hint = _("try 'hg help' for a list of topics")
576 577 raise error.Abort(msg, hint=hint)
577 578 elif name and name != 'shortlist':
578 579 queries = []
579 580 if unknowncmd:
580 581 queries += [helpextcmd]
581 582 if opts.get('extension'):
582 583 queries += [helpext]
583 584 if opts.get('command'):
584 585 queries += [helpcmd]
585 586 if not queries:
586 587 queries = (helptopic, helpcmd, helpext, helpextcmd)
587 588 for f in queries:
588 589 try:
589 590 rst = f(name, subtopic)
590 591 break
591 592 except error.UnknownCommand:
592 593 pass
593 594 else:
594 595 if unknowncmd:
595 596 raise error.UnknownCommand(name)
596 597 else:
597 598 msg = _('no such help topic: %s') % name
598 599 hint = _("try 'hg help --keyword %s'") % name
599 600 raise error.Abort(msg, hint=hint)
600 601 else:
601 602 # program name
602 603 if not ui.quiet:
603 604 rst = [_("Mercurial Distributed SCM\n"), '\n']
604 605 rst.extend(helplist(None, **opts))
605 606
606 607 return ''.join(rst)
General Comments 0
You need to be logged in to leave comments. Login now