##// END OF EJS Templates
help: check if a subtopic exists and raise an error if it doesn't (issue6145)...
Nathan Goldbaum -
r42585:a84564b1 default
parent child Browse files
Show More
@@ -1,869 +1,871
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 re
13 13 import textwrap
14 14
15 15 from .i18n import (
16 16 _,
17 17 gettext,
18 18 )
19 19 from . import (
20 20 cmdutil,
21 21 encoding,
22 22 error,
23 23 extensions,
24 24 fancyopts,
25 25 filemerge,
26 26 fileset,
27 27 minirst,
28 28 pycompat,
29 29 registrar,
30 30 revset,
31 31 templatefilters,
32 32 templatefuncs,
33 33 templatekw,
34 34 ui as uimod,
35 35 util,
36 36 )
37 37 from .hgweb import (
38 38 webcommands,
39 39 )
40 40 from .utils import (
41 41 compression,
42 42 )
43 43
44 44 _exclkeywords = {
45 45 "(ADVANCED)",
46 46 "(DEPRECATED)",
47 47 "(EXPERIMENTAL)",
48 48 # i18n: "(ADVANCED)" is a keyword, must be translated consistently
49 49 _("(ADVANCED)"),
50 50 # i18n: "(DEPRECATED)" is a keyword, must be translated consistently
51 51 _("(DEPRECATED)"),
52 52 # i18n: "(EXPERIMENTAL)" is a keyword, must be translated consistently
53 53 _("(EXPERIMENTAL)"),
54 54 }
55 55
56 56 # The order in which command categories will be displayed.
57 57 # Extensions with custom categories should insert them into this list
58 58 # after/before the appropriate item, rather than replacing the list or
59 59 # assuming absolute positions.
60 60 CATEGORY_ORDER = [
61 61 registrar.command.CATEGORY_REPO_CREATION,
62 62 registrar.command.CATEGORY_REMOTE_REPO_MANAGEMENT,
63 63 registrar.command.CATEGORY_COMMITTING,
64 64 registrar.command.CATEGORY_CHANGE_MANAGEMENT,
65 65 registrar.command.CATEGORY_CHANGE_ORGANIZATION,
66 66 registrar.command.CATEGORY_FILE_CONTENTS,
67 67 registrar.command.CATEGORY_CHANGE_NAVIGATION ,
68 68 registrar.command.CATEGORY_WORKING_DIRECTORY,
69 69 registrar.command.CATEGORY_IMPORT_EXPORT,
70 70 registrar.command.CATEGORY_MAINTENANCE,
71 71 registrar.command.CATEGORY_HELP,
72 72 registrar.command.CATEGORY_MISC,
73 73 registrar.command.CATEGORY_NONE,
74 74 ]
75 75
76 76 # Human-readable category names. These are translated.
77 77 # Extensions with custom categories should add their names here.
78 78 CATEGORY_NAMES = {
79 79 registrar.command.CATEGORY_REPO_CREATION: 'Repository creation',
80 80 registrar.command.CATEGORY_REMOTE_REPO_MANAGEMENT:
81 81 'Remote repository management',
82 82 registrar.command.CATEGORY_COMMITTING: 'Change creation',
83 83 registrar.command.CATEGORY_CHANGE_NAVIGATION: 'Change navigation',
84 84 registrar.command.CATEGORY_CHANGE_MANAGEMENT: 'Change manipulation',
85 85 registrar.command.CATEGORY_CHANGE_ORGANIZATION: 'Change organization',
86 86 registrar.command.CATEGORY_WORKING_DIRECTORY:
87 87 'Working directory management',
88 88 registrar.command.CATEGORY_FILE_CONTENTS: 'File content management',
89 89 registrar.command.CATEGORY_IMPORT_EXPORT: 'Change import/export',
90 90 registrar.command.CATEGORY_MAINTENANCE: 'Repository maintenance',
91 91 registrar.command.CATEGORY_HELP: 'Help',
92 92 registrar.command.CATEGORY_MISC: 'Miscellaneous commands',
93 93 registrar.command.CATEGORY_NONE: 'Uncategorized commands',
94 94 }
95 95
96 96 # Topic categories.
97 97 TOPIC_CATEGORY_IDS = 'ids'
98 98 TOPIC_CATEGORY_OUTPUT = 'output'
99 99 TOPIC_CATEGORY_CONFIG = 'config'
100 100 TOPIC_CATEGORY_CONCEPTS = 'concepts'
101 101 TOPIC_CATEGORY_MISC = 'misc'
102 102 TOPIC_CATEGORY_NONE = 'none'
103 103
104 104 # The order in which topic categories will be displayed.
105 105 # Extensions with custom categories should insert them into this list
106 106 # after/before the appropriate item, rather than replacing the list or
107 107 # assuming absolute positions.
108 108 TOPIC_CATEGORY_ORDER = [
109 109 TOPIC_CATEGORY_IDS,
110 110 TOPIC_CATEGORY_OUTPUT,
111 111 TOPIC_CATEGORY_CONFIG,
112 112 TOPIC_CATEGORY_CONCEPTS,
113 113 TOPIC_CATEGORY_MISC,
114 114 TOPIC_CATEGORY_NONE,
115 115 ]
116 116
117 117 # Human-readable topic category names. These are translated.
118 118 TOPIC_CATEGORY_NAMES = {
119 119 TOPIC_CATEGORY_IDS: 'Mercurial identifiers',
120 120 TOPIC_CATEGORY_OUTPUT: 'Mercurial output',
121 121 TOPIC_CATEGORY_CONFIG: 'Mercurial configuration',
122 122 TOPIC_CATEGORY_CONCEPTS: 'Concepts',
123 123 TOPIC_CATEGORY_MISC: 'Miscellaneous',
124 124 TOPIC_CATEGORY_NONE: 'Uncategorized topics',
125 125 }
126 126
127 127 def listexts(header, exts, indent=1, showdeprecated=False):
128 128 '''return a text listing of the given extensions'''
129 129 rst = []
130 130 if exts:
131 131 for name, desc in sorted(exts.iteritems()):
132 132 if not showdeprecated and any(w in desc for w in _exclkeywords):
133 133 continue
134 134 rst.append('%s:%s: %s\n' % (' ' * indent, name, desc))
135 135 if rst:
136 136 rst.insert(0, '\n%s\n\n' % header)
137 137 return rst
138 138
139 139 def extshelp(ui):
140 140 rst = loaddoc('extensions')(ui).splitlines(True)
141 141 rst.extend(listexts(
142 142 _('enabled extensions:'), extensions.enabled(), showdeprecated=True))
143 143 rst.extend(listexts(_('disabled extensions:'), extensions.disabled(),
144 144 showdeprecated=ui.verbose))
145 145 doc = ''.join(rst)
146 146 return doc
147 147
148 148 def optrst(header, options, verbose):
149 149 data = []
150 150 multioccur = False
151 151 for option in options:
152 152 if len(option) == 5:
153 153 shortopt, longopt, default, desc, optlabel = option
154 154 else:
155 155 shortopt, longopt, default, desc = option
156 156 optlabel = _("VALUE") # default label
157 157
158 158 if not verbose and any(w in desc for w in _exclkeywords):
159 159 continue
160 160
161 161 so = ''
162 162 if shortopt:
163 163 so = '-' + shortopt
164 164 lo = '--' + longopt
165 165 if default is True:
166 166 lo = '--[no-]' + longopt
167 167
168 168 if isinstance(default, fancyopts.customopt):
169 169 default = default.getdefaultvalue()
170 170 if default and not callable(default):
171 171 # default is of unknown type, and in Python 2 we abused
172 172 # the %s-shows-repr property to handle integers etc. To
173 173 # match that behavior on Python 3, we do str(default) and
174 174 # then convert it to bytes.
175 175 defaultstr = pycompat.bytestr(default)
176 176 if default is True:
177 177 defaultstr = _("on")
178 178 desc += _(" (default: %s)") % defaultstr
179 179
180 180 if isinstance(default, list):
181 181 lo += " %s [+]" % optlabel
182 182 multioccur = True
183 183 elif (default is not None) and not isinstance(default, bool):
184 184 lo += " %s" % optlabel
185 185
186 186 data.append((so, lo, desc))
187 187
188 188 if multioccur:
189 189 header += (_(" ([+] can be repeated)"))
190 190
191 191 rst = ['\n%s:\n\n' % header]
192 192 rst.extend(minirst.maketable(data, 1))
193 193
194 194 return ''.join(rst)
195 195
196 196 def indicateomitted(rst, omitted, notomitted=None):
197 197 rst.append('\n\n.. container:: omitted\n\n %s\n\n' % omitted)
198 198 if notomitted:
199 199 rst.append('\n\n.. container:: notomitted\n\n %s\n\n' % notomitted)
200 200
201 201 def filtercmd(ui, cmd, func, kw, doc):
202 202 if not ui.debugflag and cmd.startswith("debug") and kw != "debug":
203 203 # Debug command, and user is not looking for those.
204 204 return True
205 205 if not ui.verbose:
206 206 if not kw and not doc:
207 207 # Command had no documentation, no point in showing it by default.
208 208 return True
209 209 if getattr(func, 'alias', False) and not getattr(func, 'owndoc', False):
210 210 # Alias didn't have its own documentation.
211 211 return True
212 212 if doc and any(w in doc for w in _exclkeywords):
213 213 # Documentation has excluded keywords.
214 214 return True
215 215 if kw == "shortlist" and not getattr(func, 'helpbasic', False):
216 216 # We're presenting the short list but the command is not basic.
217 217 return True
218 218 if ui.configbool('help', 'hidden-command.%s' % cmd):
219 219 # Configuration explicitly hides the command.
220 220 return True
221 221 return False
222 222
223 223 def filtertopic(ui, topic):
224 224 return ui.configbool('help', 'hidden-topic.%s' % topic, False)
225 225
226 226 def topicmatch(ui, commands, kw):
227 227 """Return help topics matching kw.
228 228
229 229 Returns {'section': [(name, summary), ...], ...} where section is
230 230 one of topics, commands, extensions, or extensioncommands.
231 231 """
232 232 kw = encoding.lower(kw)
233 233 def lowercontains(container):
234 234 return kw in encoding.lower(container) # translated in helptable
235 235 results = {'topics': [],
236 236 'commands': [],
237 237 'extensions': [],
238 238 'extensioncommands': [],
239 239 }
240 240 for topic in helptable:
241 241 names, header, doc = topic[0:3]
242 242 # Old extensions may use a str as doc.
243 243 if (sum(map(lowercontains, names))
244 244 or lowercontains(header)
245 245 or (callable(doc) and lowercontains(doc(ui)))):
246 246 name = names[0]
247 247 if not filtertopic(ui, name):
248 248 results['topics'].append((names[0], header))
249 249 for cmd, entry in commands.table.iteritems():
250 250 if len(entry) == 3:
251 251 summary = entry[2]
252 252 else:
253 253 summary = ''
254 254 # translate docs *before* searching there
255 255 func = entry[0]
256 256 docs = _(pycompat.getdoc(func)) or ''
257 257 if kw in cmd or lowercontains(summary) or lowercontains(docs):
258 258 doclines = docs.splitlines()
259 259 if doclines:
260 260 summary = doclines[0]
261 261 cmdname = cmdutil.parsealiases(cmd)[0]
262 262 if filtercmd(ui, cmdname, func, kw, docs):
263 263 continue
264 264 results['commands'].append((cmdname, summary))
265 265 for name, docs in itertools.chain(
266 266 extensions.enabled(False).iteritems(),
267 267 extensions.disabled().iteritems()):
268 268 if not docs:
269 269 continue
270 270 name = name.rpartition('.')[-1]
271 271 if lowercontains(name) or lowercontains(docs):
272 272 # extension docs are already translated
273 273 results['extensions'].append((name, docs.splitlines()[0]))
274 274 try:
275 275 mod = extensions.load(ui, name, '')
276 276 except ImportError:
277 277 # debug message would be printed in extensions.load()
278 278 continue
279 279 for cmd, entry in getattr(mod, 'cmdtable', {}).iteritems():
280 280 if kw in cmd or (len(entry) > 2 and lowercontains(entry[2])):
281 281 cmdname = cmdutil.parsealiases(cmd)[0]
282 282 func = entry[0]
283 283 cmddoc = pycompat.getdoc(func)
284 284 if cmddoc:
285 285 cmddoc = gettext(cmddoc).splitlines()[0]
286 286 else:
287 287 cmddoc = _('(no help text available)')
288 288 if filtercmd(ui, cmdname, func, kw, cmddoc):
289 289 continue
290 290 results['extensioncommands'].append((cmdname, cmddoc))
291 291 return results
292 292
293 293 def loaddoc(topic, subdir=None):
294 294 """Return a delayed loader for help/topic.txt."""
295 295
296 296 def loader(ui):
297 297 docdir = os.path.join(util.datapath, 'help')
298 298 if subdir:
299 299 docdir = os.path.join(docdir, subdir)
300 300 path = os.path.join(docdir, topic + ".txt")
301 301 doc = gettext(util.readfile(path))
302 302 for rewriter in helphooks.get(topic, []):
303 303 doc = rewriter(ui, topic, doc)
304 304 return doc
305 305
306 306 return loader
307 307
308 308 internalstable = sorted([
309 309 (['bundle2'], _('Bundle2'),
310 310 loaddoc('bundle2', subdir='internals')),
311 311 (['bundles'], _('Bundles'),
312 312 loaddoc('bundles', subdir='internals')),
313 313 (['cbor'], _('CBOR'),
314 314 loaddoc('cbor', subdir='internals')),
315 315 (['censor'], _('Censor'),
316 316 loaddoc('censor', subdir='internals')),
317 317 (['changegroups'], _('Changegroups'),
318 318 loaddoc('changegroups', subdir='internals')),
319 319 (['config'], _('Config Registrar'),
320 320 loaddoc('config', subdir='internals')),
321 321 (['extensions', 'extension'], _('Extension API'),
322 322 loaddoc('extensions', subdir='internals')),
323 323 (['requirements'], _('Repository Requirements'),
324 324 loaddoc('requirements', subdir='internals')),
325 325 (['revlogs'], _('Revision Logs'),
326 326 loaddoc('revlogs', subdir='internals')),
327 327 (['wireprotocol'], _('Wire Protocol'),
328 328 loaddoc('wireprotocol', subdir='internals')),
329 329 (['wireprotocolrpc'], _('Wire Protocol RPC'),
330 330 loaddoc('wireprotocolrpc', subdir='internals')),
331 331 (['wireprotocolv2'], _('Wire Protocol Version 2'),
332 332 loaddoc('wireprotocolv2', subdir='internals')),
333 333 ])
334 334
335 335 def internalshelp(ui):
336 336 """Generate the index for the "internals" topic."""
337 337 lines = ['To access a subtopic, use "hg help internals.{subtopic-name}"\n',
338 338 '\n']
339 339 for names, header, doc in internalstable:
340 340 lines.append(' :%s: %s\n' % (names[0], header))
341 341
342 342 return ''.join(lines)
343 343
344 344 helptable = sorted([
345 345 (['bundlespec'], _("Bundle File Formats"), loaddoc('bundlespec'),
346 346 TOPIC_CATEGORY_CONCEPTS),
347 347 (['color'], _("Colorizing Outputs"), loaddoc('color'),
348 348 TOPIC_CATEGORY_OUTPUT),
349 349 (["config", "hgrc"], _("Configuration Files"), loaddoc('config'),
350 350 TOPIC_CATEGORY_CONFIG),
351 351 (['deprecated'], _("Deprecated Features"), loaddoc('deprecated'),
352 352 TOPIC_CATEGORY_MISC),
353 353 (["dates"], _("Date Formats"), loaddoc('dates'), TOPIC_CATEGORY_OUTPUT),
354 354 (["flags"], _("Command-line flags"), loaddoc('flags'),
355 355 TOPIC_CATEGORY_CONFIG),
356 356 (["patterns"], _("File Name Patterns"), loaddoc('patterns'),
357 357 TOPIC_CATEGORY_IDS),
358 358 (['environment', 'env'], _('Environment Variables'),
359 359 loaddoc('environment'), TOPIC_CATEGORY_CONFIG),
360 360 (['revisions', 'revs', 'revsets', 'revset', 'multirevs', 'mrevs'],
361 361 _('Specifying Revisions'), loaddoc('revisions'), TOPIC_CATEGORY_IDS),
362 362 (['filesets', 'fileset'], _("Specifying File Sets"), loaddoc('filesets'),
363 363 TOPIC_CATEGORY_IDS),
364 364 (['diffs'], _('Diff Formats'), loaddoc('diffs'), TOPIC_CATEGORY_OUTPUT),
365 365 (['merge-tools', 'mergetools', 'mergetool'], _('Merge Tools'),
366 366 loaddoc('merge-tools'), TOPIC_CATEGORY_CONFIG),
367 367 (['templating', 'templates', 'template', 'style'], _('Template Usage'),
368 368 loaddoc('templates'), TOPIC_CATEGORY_OUTPUT),
369 369 (['urls'], _('URL Paths'), loaddoc('urls'), TOPIC_CATEGORY_IDS),
370 370 (["extensions"], _("Using Additional Features"), extshelp,
371 371 TOPIC_CATEGORY_CONFIG),
372 372 (["subrepos", "subrepo"], _("Subrepositories"), loaddoc('subrepos'),
373 373 TOPIC_CATEGORY_CONCEPTS),
374 374 (["hgweb"], _("Configuring hgweb"), loaddoc('hgweb'),
375 375 TOPIC_CATEGORY_CONFIG),
376 376 (["glossary"], _("Glossary"), loaddoc('glossary'), TOPIC_CATEGORY_CONCEPTS),
377 377 (["hgignore", "ignore"], _("Syntax for Mercurial Ignore Files"),
378 378 loaddoc('hgignore'), TOPIC_CATEGORY_IDS),
379 379 (["phases"], _("Working with Phases"), loaddoc('phases'),
380 380 TOPIC_CATEGORY_CONCEPTS),
381 381 (['scripting'], _('Using Mercurial from scripts and automation'),
382 382 loaddoc('scripting'), TOPIC_CATEGORY_MISC),
383 383 (['internals'], _("Technical implementation topics"), internalshelp,
384 384 TOPIC_CATEGORY_MISC),
385 385 (['pager'], _("Pager Support"), loaddoc('pager'), TOPIC_CATEGORY_CONFIG),
386 386 ])
387 387
388 388 # Maps topics with sub-topics to a list of their sub-topics.
389 389 subtopics = {
390 390 'internals': internalstable,
391 391 }
392 392
393 393 # Map topics to lists of callable taking the current topic help and
394 394 # returning the updated version
395 395 helphooks = {}
396 396
397 397 def addtopichook(topic, rewriter):
398 398 helphooks.setdefault(topic, []).append(rewriter)
399 399
400 400 def makeitemsdoc(ui, topic, doc, marker, items, dedent=False):
401 401 """Extract docstring from the items key to function mapping, build a
402 402 single documentation block and use it to overwrite the marker in doc.
403 403 """
404 404 entries = []
405 405 for name in sorted(items):
406 406 text = (pycompat.getdoc(items[name]) or '').rstrip()
407 407 if (not text
408 408 or not ui.verbose and any(w in text for w in _exclkeywords)):
409 409 continue
410 410 text = gettext(text)
411 411 if dedent:
412 412 # Abuse latin1 to use textwrap.dedent() on bytes.
413 413 text = textwrap.dedent(text.decode('latin1')).encode('latin1')
414 414 lines = text.splitlines()
415 415 doclines = [(lines[0])]
416 416 for l in lines[1:]:
417 417 # Stop once we find some Python doctest
418 418 if l.strip().startswith('>>>'):
419 419 break
420 420 if dedent:
421 421 doclines.append(l.rstrip())
422 422 else:
423 423 doclines.append(' ' + l.strip())
424 424 entries.append('\n'.join(doclines))
425 425 entries = '\n\n'.join(entries)
426 426 return doc.replace(marker, entries)
427 427
428 428 def addtopicsymbols(topic, marker, symbols, dedent=False):
429 429 def add(ui, topic, doc):
430 430 return makeitemsdoc(ui, topic, doc, marker, symbols, dedent=dedent)
431 431 addtopichook(topic, add)
432 432
433 433 addtopicsymbols('bundlespec', '.. bundlecompressionmarker',
434 434 compression.bundlecompressiontopics())
435 435 addtopicsymbols('filesets', '.. predicatesmarker', fileset.symbols)
436 436 addtopicsymbols('merge-tools', '.. internaltoolsmarker',
437 437 filemerge.internalsdoc)
438 438 addtopicsymbols('revisions', '.. predicatesmarker', revset.symbols)
439 439 addtopicsymbols('templates', '.. keywordsmarker', templatekw.keywords)
440 440 addtopicsymbols('templates', '.. filtersmarker', templatefilters.filters)
441 441 addtopicsymbols('templates', '.. functionsmarker', templatefuncs.funcs)
442 442 addtopicsymbols('hgweb', '.. webcommandsmarker', webcommands.commands,
443 443 dedent=True)
444 444
445 445 def inserttweakrc(ui, topic, doc):
446 446 marker = '.. tweakdefaultsmarker'
447 447 repl = uimod.tweakrc
448 448 def sub(m):
449 449 lines = [m.group(1) + s for s in repl.splitlines()]
450 450 return '\n'.join(lines)
451 451 return re.sub(br'( *)%s' % re.escape(marker), sub, doc)
452 452
453 453 addtopichook('config', inserttweakrc)
454 454
455 455 def help_(ui, commands, name, unknowncmd=False, full=True, subtopic=None,
456 456 **opts):
457 457 '''
458 458 Generate the help for 'name' as unformatted restructured text. If
459 459 'name' is None, describe the commands available.
460 460 '''
461 461
462 462 opts = pycompat.byteskwargs(opts)
463 463
464 464 def helpcmd(name, subtopic=None):
465 465 try:
466 466 aliases, entry = cmdutil.findcmd(name, commands.table,
467 467 strict=unknowncmd)
468 468 except error.AmbiguousCommand as inst:
469 469 # py3 fix: except vars can't be used outside the scope of the
470 470 # except block, nor can be used inside a lambda. python issue4617
471 471 prefix = inst.args[0]
472 472 select = lambda c: cmdutil.parsealiases(c)[0].startswith(prefix)
473 473 rst = helplist(select)
474 474 return rst
475 475
476 476 rst = []
477 477
478 478 # check if it's an invalid alias and display its error if it is
479 479 if getattr(entry[0], 'badalias', None):
480 480 rst.append(entry[0].badalias + '\n')
481 481 if entry[0].unknowncmd:
482 482 try:
483 483 rst.extend(helpextcmd(entry[0].cmdname))
484 484 except error.UnknownCommand:
485 485 pass
486 486 return rst
487 487
488 488 # synopsis
489 489 if len(entry) > 2:
490 490 if entry[2].startswith('hg'):
491 491 rst.append("%s\n" % entry[2])
492 492 else:
493 493 rst.append('hg %s %s\n' % (aliases[0], entry[2]))
494 494 else:
495 495 rst.append('hg %s\n' % aliases[0])
496 496 # aliases
497 497 if full and not ui.quiet and len(aliases) > 1:
498 498 rst.append(_("\naliases: %s\n") % ', '.join(aliases[1:]))
499 499 rst.append('\n')
500 500
501 501 # description
502 502 doc = gettext(pycompat.getdoc(entry[0]))
503 503 if not doc:
504 504 doc = _("(no help text available)")
505 505 if util.safehasattr(entry[0], 'definition'): # aliased command
506 506 source = entry[0].source
507 507 if entry[0].definition.startswith('!'): # shell alias
508 508 doc = (_('shell alias for: %s\n\n%s\n\ndefined by: %s\n') %
509 509 (entry[0].definition[1:], doc, source))
510 510 else:
511 511 doc = (_('alias for: hg %s\n\n%s\n\ndefined by: %s\n') %
512 512 (entry[0].definition, doc, source))
513 513 doc = doc.splitlines(True)
514 514 if ui.quiet or not full:
515 515 rst.append(doc[0])
516 516 else:
517 517 rst.extend(doc)
518 518 rst.append('\n')
519 519
520 520 # check if this command shadows a non-trivial (multi-line)
521 521 # extension help text
522 522 try:
523 523 mod = extensions.find(name)
524 524 doc = gettext(pycompat.getdoc(mod)) or ''
525 525 if '\n' in doc.strip():
526 526 msg = _("(use 'hg help -e %s' to show help for "
527 527 "the %s extension)") % (name, name)
528 528 rst.append('\n%s\n' % msg)
529 529 except KeyError:
530 530 pass
531 531
532 532 # options
533 533 if not ui.quiet and entry[1]:
534 534 rst.append(optrst(_("options"), entry[1], ui.verbose))
535 535
536 536 if ui.verbose:
537 537 rst.append(optrst(_("global options"),
538 538 commands.globalopts, ui.verbose))
539 539
540 540 if not ui.verbose:
541 541 if not full:
542 542 rst.append(_("\n(use 'hg %s -h' to show more help)\n")
543 543 % name)
544 544 elif not ui.quiet:
545 545 rst.append(_('\n(some details hidden, use --verbose '
546 546 'to show complete help)'))
547 547
548 548 return rst
549 549
550 550 def helplist(select=None, **opts):
551 551 # Category -> list of commands
552 552 cats = {}
553 553 # Command -> short description
554 554 h = {}
555 555 # Command -> string showing synonyms
556 556 syns = {}
557 557 for c, e in commands.table.iteritems():
558 558 fs = cmdutil.parsealiases(c)
559 559 f = fs[0]
560 560 syns[f] = ', '.join(fs)
561 561 func = e[0]
562 562 if select and not select(f):
563 563 continue
564 564 doc = pycompat.getdoc(func)
565 565 if filtercmd(ui, f, func, name, doc):
566 566 continue
567 567 doc = gettext(doc)
568 568 if not doc:
569 569 doc = _("(no help text available)")
570 570 h[f] = doc.splitlines()[0].rstrip()
571 571
572 572 cat = getattr(func, 'helpcategory', None) or (
573 573 registrar.command.CATEGORY_NONE)
574 574 cats.setdefault(cat, []).append(f)
575 575
576 576 rst = []
577 577 if not h:
578 578 if not ui.quiet:
579 579 rst.append(_('no commands defined\n'))
580 580 return rst
581 581
582 582 # Output top header.
583 583 if not ui.quiet:
584 584 if name == "shortlist":
585 585 rst.append(_('basic commands:\n\n'))
586 586 elif name == "debug":
587 587 rst.append(_('debug commands (internal and unsupported):\n\n'))
588 588 else:
589 589 rst.append(_('list of commands:\n'))
590 590
591 591 def appendcmds(cmds):
592 592 cmds = sorted(cmds)
593 593 for c in cmds:
594 594 if ui.verbose:
595 595 rst.append(" :%s: %s\n" % (syns[c], h[c]))
596 596 else:
597 597 rst.append(' :%s: %s\n' % (c, h[c]))
598 598
599 599 if name in ('shortlist', 'debug'):
600 600 # List without categories.
601 601 appendcmds(h)
602 602 else:
603 603 # Check that all categories have an order.
604 604 missing_order = set(cats.keys()) - set(CATEGORY_ORDER)
605 605 if missing_order:
606 606 ui.develwarn('help categories missing from CATEGORY_ORDER: %s' %
607 607 missing_order)
608 608
609 609 # List per category.
610 610 for cat in CATEGORY_ORDER:
611 611 catfns = cats.get(cat, [])
612 612 if catfns:
613 613 if len(cats) > 1:
614 614 catname = gettext(CATEGORY_NAMES[cat])
615 615 rst.append("\n%s:\n" % catname)
616 616 rst.append("\n")
617 617 appendcmds(catfns)
618 618
619 619 ex = opts.get
620 620 anyopts = (ex(r'keyword') or not (ex(r'command') or ex(r'extension')))
621 621 if not name and anyopts:
622 622 exts = listexts(_('enabled extensions:'), extensions.enabled(),
623 623 showdeprecated=ui.verbose)
624 624 if exts:
625 625 rst.append('\n')
626 626 rst.extend(exts)
627 627
628 628 rst.append(_("\nadditional help topics:\n"))
629 629 # Group commands by category.
630 630 topiccats = {}
631 631 for topic in helptable:
632 632 names, header, doc = topic[0:3]
633 633 if len(topic) > 3 and topic[3]:
634 634 category = topic[3]
635 635 else:
636 636 category = TOPIC_CATEGORY_NONE
637 637
638 638 topicname = names[0]
639 639 if not filtertopic(ui, topicname):
640 640 topiccats.setdefault(category, []).append(
641 641 (topicname, header))
642 642
643 643 # Check that all categories have an order.
644 644 missing_order = set(topiccats.keys()) - set(TOPIC_CATEGORY_ORDER)
645 645 if missing_order:
646 646 ui.develwarn(
647 647 'help categories missing from TOPIC_CATEGORY_ORDER: %s' %
648 648 missing_order)
649 649
650 650 # Output topics per category.
651 651 for cat in TOPIC_CATEGORY_ORDER:
652 652 topics = topiccats.get(cat, [])
653 653 if topics:
654 654 if len(topiccats) > 1:
655 655 catname = gettext(TOPIC_CATEGORY_NAMES[cat])
656 656 rst.append("\n%s:\n" % catname)
657 657 rst.append("\n")
658 658 for t, desc in topics:
659 659 rst.append(" :%s: %s\n" % (t, desc))
660 660
661 661 if ui.quiet:
662 662 pass
663 663 elif ui.verbose:
664 664 rst.append('\n%s\n' % optrst(_("global options"),
665 665 commands.globalopts, ui.verbose))
666 666 if name == 'shortlist':
667 667 rst.append(_("\n(use 'hg help' for the full list "
668 668 "of commands)\n"))
669 669 else:
670 670 if name == 'shortlist':
671 671 rst.append(_("\n(use 'hg help' for the full list of commands "
672 672 "or 'hg -v' for details)\n"))
673 673 elif name and not full:
674 674 rst.append(_("\n(use 'hg help %s' to show the full help "
675 675 "text)\n") % name)
676 676 elif name and syns and name in syns.keys():
677 677 rst.append(_("\n(use 'hg help -v -e %s' to show built-in "
678 678 "aliases and global options)\n") % name)
679 679 else:
680 680 rst.append(_("\n(use 'hg help -v%s' to show built-in aliases "
681 681 "and global options)\n")
682 682 % (name and " " + name or ""))
683 683 return rst
684 684
685 685 def helptopic(name, subtopic=None):
686 686 # Look for sub-topic entry first.
687 687 header, doc = None, None
688 688 if subtopic and name in subtopics:
689 689 for names, header, doc in subtopics[name]:
690 690 if subtopic in names:
691 691 break
692 if not any(subtopic in s[0] for s in subtopics[name]):
693 raise error.UnknownCommand(name)
692 694
693 695 if not header:
694 696 for topic in helptable:
695 697 names, header, doc = topic[0:3]
696 698 if name in names:
697 699 break
698 700 else:
699 701 raise error.UnknownCommand(name)
700 702
701 703 rst = [minirst.section(header)]
702 704
703 705 # description
704 706 if not doc:
705 707 rst.append(" %s\n" % _("(no help text available)"))
706 708 if callable(doc):
707 709 rst += [" %s\n" % l for l in doc(ui).splitlines()]
708 710
709 711 if not ui.verbose:
710 712 omitted = _('(some details hidden, use --verbose'
711 713 ' to show complete help)')
712 714 indicateomitted(rst, omitted)
713 715
714 716 try:
715 717 cmdutil.findcmd(name, commands.table)
716 718 rst.append(_("\nuse 'hg help -c %s' to see help for "
717 719 "the %s command\n") % (name, name))
718 720 except error.UnknownCommand:
719 721 pass
720 722 return rst
721 723
722 724 def helpext(name, subtopic=None):
723 725 try:
724 726 mod = extensions.find(name)
725 727 doc = gettext(pycompat.getdoc(mod)) or _('no help text available')
726 728 except KeyError:
727 729 mod = None
728 730 doc = extensions.disabledext(name)
729 731 if not doc:
730 732 raise error.UnknownCommand(name)
731 733
732 734 if '\n' not in doc:
733 735 head, tail = doc, ""
734 736 else:
735 737 head, tail = doc.split('\n', 1)
736 738 rst = [_('%s extension - %s\n\n') % (name.rpartition('.')[-1], head)]
737 739 if tail:
738 740 rst.extend(tail.splitlines(True))
739 741 rst.append('\n')
740 742
741 743 if not ui.verbose:
742 744 omitted = _('(some details hidden, use --verbose'
743 745 ' to show complete help)')
744 746 indicateomitted(rst, omitted)
745 747
746 748 if mod:
747 749 try:
748 750 ct = mod.cmdtable
749 751 except AttributeError:
750 752 ct = {}
751 753 modcmds = {c.partition('|')[0] for c in ct}
752 754 rst.extend(helplist(modcmds.__contains__))
753 755 else:
754 756 rst.append(_("(use 'hg help extensions' for information on enabling"
755 757 " extensions)\n"))
756 758 return rst
757 759
758 760 def helpextcmd(name, subtopic=None):
759 761 cmd, ext, doc = extensions.disabledcmd(ui, name,
760 762 ui.configbool('ui', 'strict'))
761 763 doc = doc.splitlines()[0]
762 764
763 765 rst = listexts(_("'%s' is provided by the following "
764 766 "extension:") % cmd, {ext: doc}, indent=4,
765 767 showdeprecated=True)
766 768 rst.append('\n')
767 769 rst.append(_("(use 'hg help extensions' for information on enabling "
768 770 "extensions)\n"))
769 771 return rst
770 772
771 773
772 774 rst = []
773 775 kw = opts.get('keyword')
774 776 if kw or name is None and any(opts[o] for o in opts):
775 777 matches = topicmatch(ui, commands, name or '')
776 778 helpareas = []
777 779 if opts.get('extension'):
778 780 helpareas += [('extensions', _('Extensions'))]
779 781 if opts.get('command'):
780 782 helpareas += [('commands', _('Commands'))]
781 783 if not helpareas:
782 784 helpareas = [('topics', _('Topics')),
783 785 ('commands', _('Commands')),
784 786 ('extensions', _('Extensions')),
785 787 ('extensioncommands', _('Extension Commands'))]
786 788 for t, title in helpareas:
787 789 if matches[t]:
788 790 rst.append('%s:\n\n' % title)
789 791 rst.extend(minirst.maketable(sorted(matches[t]), 1))
790 792 rst.append('\n')
791 793 if not rst:
792 794 msg = _('no matches')
793 795 hint = _("try 'hg help' for a list of topics")
794 796 raise error.Abort(msg, hint=hint)
795 797 elif name and name != 'shortlist':
796 798 queries = []
797 799 if unknowncmd:
798 800 queries += [helpextcmd]
799 801 if opts.get('extension'):
800 802 queries += [helpext]
801 803 if opts.get('command'):
802 804 queries += [helpcmd]
803 805 if not queries:
804 806 queries = (helptopic, helpcmd, helpext, helpextcmd)
805 807 for f in queries:
806 808 try:
807 809 rst = f(name, subtopic)
808 810 break
809 811 except error.UnknownCommand:
810 812 pass
811 813 else:
812 814 if unknowncmd:
813 815 raise error.UnknownCommand(name)
814 816 else:
815 817 msg = _('no such help topic: %s') % name
816 818 hint = _("try 'hg help --keyword %s'") % name
817 819 raise error.Abort(msg, hint=hint)
818 820 else:
819 821 # program name
820 822 if not ui.quiet:
821 823 rst = [_("Mercurial Distributed SCM\n"), '\n']
822 824 rst.extend(helplist(None, **pycompat.strkwargs(opts)))
823 825
824 826 return ''.join(rst)
825 827
826 828 def formattedhelp(ui, commands, fullname, keep=None, unknowncmd=False,
827 829 full=True, **opts):
828 830 """get help for a given topic (as a dotted name) as rendered rst
829 831
830 832 Either returns the rendered help text or raises an exception.
831 833 """
832 834 if keep is None:
833 835 keep = []
834 836 else:
835 837 keep = list(keep) # make a copy so we can mutate this later
836 838
837 839 # <fullname> := <name>[.<subtopic][.<section>]
838 840 name = subtopic = section = None
839 841 if fullname is not None:
840 842 nameparts = fullname.split('.')
841 843 name = nameparts.pop(0)
842 844 if nameparts and name in subtopics:
843 845 subtopic = nameparts.pop(0)
844 846 if nameparts:
845 847 section = encoding.lower('.'.join(nameparts))
846 848
847 849 textwidth = ui.configint('ui', 'textwidth')
848 850 termwidth = ui.termwidth() - 2
849 851 if textwidth <= 0 or termwidth < textwidth:
850 852 textwidth = termwidth
851 853 text = help_(ui, commands, name,
852 854 subtopic=subtopic, unknowncmd=unknowncmd, full=full, **opts)
853 855
854 856 blocks, pruned = minirst.parse(text, keep=keep)
855 857 if 'verbose' in pruned:
856 858 keep.append('omitted')
857 859 else:
858 860 keep.append('notomitted')
859 861 blocks, pruned = minirst.parse(text, keep=keep)
860 862 if section:
861 863 blocks = minirst.filtersections(blocks, section)
862 864
863 865 # We could have been given a weird ".foo" section without a name
864 866 # to look for, or we could have simply failed to found "foo.bar"
865 867 # because bar isn't a section of foo
866 868 if section and not (blocks and name):
867 869 raise error.Abort(_("help section not found: %s") % fullname)
868 870
869 871 return minirst.formatplain(blocks, textwidth)
General Comments 0
You need to be logged in to leave comments. Login now