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