1# help.py - help data for mercurial 2# 3# Copyright 2006 Olivia Mackall <olivia@selenic.com> 4# 5# This software may be used and distributed according to the terms of the 6# GNU General Public License version 2 or any later version. 7 8from __future__ import absolute_import 9 10import itertools 11import re 12import textwrap 13 14from .i18n import ( 15 _, 16 gettext, 17) 18from .pycompat import getattr 19from . import ( 20 cmdutil, 21 encoding, 22 error, 23 extensions, 24 fancyopts, 25 filemerge, 26 fileset, 27 minirst, 28 pycompat, 29 registrar, 30 revset, 31 templatefilters, 32 templatefuncs, 33 templatekw, 34 ui as uimod, 35 util, 36) 37from .hgweb import webcommands 38from .utils import ( 39 compression, 40 resourceutil, 41) 42 43_exclkeywords = { 44 b"(ADVANCED)", 45 b"(DEPRECATED)", 46 b"(EXPERIMENTAL)", 47 # i18n: "(ADVANCED)" is a keyword, must be translated consistently 48 _(b"(ADVANCED)"), 49 # i18n: "(DEPRECATED)" is a keyword, must be translated consistently 50 _(b"(DEPRECATED)"), 51 # i18n: "(EXPERIMENTAL)" is a keyword, must be translated consistently 52 _(b"(EXPERIMENTAL)"), 53} 54 55# The order in which command categories will be displayed. 56# Extensions with custom categories should insert them into this list 57# after/before the appropriate item, rather than replacing the list or 58# assuming absolute positions. 59CATEGORY_ORDER = [ 60 registrar.command.CATEGORY_REPO_CREATION, 61 registrar.command.CATEGORY_REMOTE_REPO_MANAGEMENT, 62 registrar.command.CATEGORY_COMMITTING, 63 registrar.command.CATEGORY_CHANGE_MANAGEMENT, 64 registrar.command.CATEGORY_CHANGE_ORGANIZATION, 65 registrar.command.CATEGORY_FILE_CONTENTS, 66 registrar.command.CATEGORY_CHANGE_NAVIGATION, 67 registrar.command.CATEGORY_WORKING_DIRECTORY, 68 registrar.command.CATEGORY_IMPORT_EXPORT, 69 registrar.command.CATEGORY_MAINTENANCE, 70 registrar.command.CATEGORY_HELP, 71 registrar.command.CATEGORY_MISC, 72 registrar.command.CATEGORY_NONE, 73] 74 75# Human-readable category names. These are translated. 76# Extensions with custom categories should add their names here. 77CATEGORY_NAMES = { 78 registrar.command.CATEGORY_REPO_CREATION: b'Repository creation', 79 registrar.command.CATEGORY_REMOTE_REPO_MANAGEMENT: b'Remote repository management', 80 registrar.command.CATEGORY_COMMITTING: b'Change creation', 81 registrar.command.CATEGORY_CHANGE_NAVIGATION: b'Change navigation', 82 registrar.command.CATEGORY_CHANGE_MANAGEMENT: b'Change manipulation', 83 registrar.command.CATEGORY_CHANGE_ORGANIZATION: b'Change organization', 84 registrar.command.CATEGORY_WORKING_DIRECTORY: b'Working directory management', 85 registrar.command.CATEGORY_FILE_CONTENTS: b'File content management', 86 registrar.command.CATEGORY_IMPORT_EXPORT: b'Change import/export', 87 registrar.command.CATEGORY_MAINTENANCE: b'Repository maintenance', 88 registrar.command.CATEGORY_HELP: b'Help', 89 registrar.command.CATEGORY_MISC: b'Miscellaneous commands', 90 registrar.command.CATEGORY_NONE: b'Uncategorized commands', 91} 92 93# Topic categories. 94TOPIC_CATEGORY_IDS = b'ids' 95TOPIC_CATEGORY_OUTPUT = b'output' 96TOPIC_CATEGORY_CONFIG = b'config' 97TOPIC_CATEGORY_CONCEPTS = b'concepts' 98TOPIC_CATEGORY_MISC = b'misc' 99TOPIC_CATEGORY_NONE = b'none' 100 101# The order in which topic categories will be displayed. 102# Extensions with custom categories should insert them into this list 103# after/before the appropriate item, rather than replacing the list or 104# assuming absolute positions. 105TOPIC_CATEGORY_ORDER = [ 106 TOPIC_CATEGORY_IDS, 107 TOPIC_CATEGORY_OUTPUT, 108 TOPIC_CATEGORY_CONFIG, 109 TOPIC_CATEGORY_CONCEPTS, 110 TOPIC_CATEGORY_MISC, 111 TOPIC_CATEGORY_NONE, 112] 113 114# Human-readable topic category names. These are translated. 115TOPIC_CATEGORY_NAMES = { 116 TOPIC_CATEGORY_IDS: b'Mercurial identifiers', 117 TOPIC_CATEGORY_OUTPUT: b'Mercurial output', 118 TOPIC_CATEGORY_CONFIG: b'Mercurial configuration', 119 TOPIC_CATEGORY_CONCEPTS: b'Concepts', 120 TOPIC_CATEGORY_MISC: b'Miscellaneous', 121 TOPIC_CATEGORY_NONE: b'Uncategorized topics', 122} 123 124 125def listexts(header, exts, indent=1, showdeprecated=False): 126 '''return a text listing of the given extensions''' 127 rst = [] 128 if exts: 129 for name, desc in sorted(pycompat.iteritems(exts)): 130 if not showdeprecated and any(w in desc for w in _exclkeywords): 131 continue 132 rst.append(b'%s:%s: %s\n' % (b' ' * indent, name, desc)) 133 if rst: 134 rst.insert(0, b'\n%s\n\n' % header) 135 return rst 136 137 138def extshelp(ui): 139 rst = loaddoc(b'extensions')(ui).splitlines(True) 140 rst.extend( 141 listexts( 142 _(b'enabled extensions:'), extensions.enabled(), showdeprecated=True 143 ) 144 ) 145 rst.extend( 146 listexts( 147 _(b'disabled extensions:'), 148 extensions.disabled(), 149 showdeprecated=ui.verbose, 150 ) 151 ) 152 doc = b''.join(rst) 153 return doc 154 155 156def parsedefaultmarker(text): 157 """given a text 'abc (DEFAULT: def.ghi)', 158 returns (b'abc', (b'def', b'ghi')). Otherwise return None""" 159 if text[-1:] == b')': 160 marker = b' (DEFAULT: ' 161 pos = text.find(marker) 162 if pos >= 0: 163 item = text[pos + len(marker) : -1] 164 return text[:pos], item.split(b'.', 2) 165 166 167def optrst(header, options, verbose, ui): 168 data = [] 169 multioccur = False 170 for option in options: 171 if len(option) == 5: 172 shortopt, longopt, default, desc, optlabel = option 173 else: 174 shortopt, longopt, default, desc = option 175 optlabel = _(b"VALUE") # default label 176 177 if not verbose and any(w in desc for w in _exclkeywords): 178 continue 179 defaultstrsuffix = b'' 180 if default is None: 181 parseresult = parsedefaultmarker(desc) 182 if parseresult is not None: 183 (desc, (section, name)) = parseresult 184 if ui.configbool(section, name): 185 default = True 186 defaultstrsuffix = _(b' from config') 187 so = b'' 188 if shortopt: 189 so = b'-' + shortopt 190 lo = b'--' + longopt 191 if default is True: 192 lo = b'--[no-]' + longopt 193 194 if isinstance(default, fancyopts.customopt): 195 default = default.getdefaultvalue() 196 if default and not callable(default): 197 # default is of unknown type, and in Python 2 we abused 198 # the %s-shows-repr property to handle integers etc. To 199 # match that behavior on Python 3, we do str(default) and 200 # then convert it to bytes. 201 defaultstr = pycompat.bytestr(default) 202 if default is True: 203 defaultstr = _(b"on") 204 desc += _(b" (default: %s)") % (defaultstr + defaultstrsuffix) 205 206 if isinstance(default, list): 207 lo += b" %s [+]" % optlabel 208 multioccur = True 209 elif (default is not None) and not isinstance(default, bool): 210 lo += b" %s" % optlabel 211 212 data.append((so, lo, desc)) 213 214 if multioccur: 215 header += _(b" ([+] can be repeated)") 216 217 rst = [b'\n%s:\n\n' % header] 218 rst.extend(minirst.maketable(data, 1)) 219 220 return b''.join(rst) 221 222 223def indicateomitted(rst, omitted, notomitted=None): 224 rst.append(b'\n\n.. container:: omitted\n\n %s\n\n' % omitted) 225 if notomitted: 226 rst.append(b'\n\n.. container:: notomitted\n\n %s\n\n' % notomitted) 227 228 229def filtercmd(ui, cmd, func, kw, doc): 230 if not ui.debugflag and cmd.startswith(b"debug") and kw != b"debug": 231 # Debug command, and user is not looking for those. 232 return True 233 if not ui.verbose: 234 if not kw and not doc: 235 # Command had no documentation, no point in showing it by default. 236 return True 237 if getattr(func, 'alias', False) and not getattr(func, 'owndoc', False): 238 # Alias didn't have its own documentation. 239 return True 240 if doc and any(w in doc for w in _exclkeywords): 241 # Documentation has excluded keywords. 242 return True 243 if kw == b"shortlist" and not getattr(func, 'helpbasic', False): 244 # We're presenting the short list but the command is not basic. 245 return True 246 if ui.configbool(b'help', b'hidden-command.%s' % cmd): 247 # Configuration explicitly hides the command. 248 return True 249 return False 250 251 252def filtertopic(ui, topic): 253 return ui.configbool(b'help', b'hidden-topic.%s' % topic, False) 254 255 256def topicmatch(ui, commands, kw): 257 """Return help topics matching kw. 258 259 Returns {'section': [(name, summary), ...], ...} where section is 260 one of topics, commands, extensions, or extensioncommands. 261 """ 262 kw = encoding.lower(kw) 263 264 def lowercontains(container): 265 return kw in encoding.lower(container) # translated in helptable 266 267 results = { 268 b'topics': [], 269 b'commands': [], 270 b'extensions': [], 271 b'extensioncommands': [], 272 } 273 for topic in helptable: 274 names, header, doc = topic[0:3] 275 # Old extensions may use a str as doc. 276 if ( 277 sum(map(lowercontains, names)) 278 or lowercontains(header) 279 or (callable(doc) and lowercontains(doc(ui))) 280 ): 281 name = names[0] 282 if not filtertopic(ui, name): 283 results[b'topics'].append((names[0], header)) 284 for cmd, entry in pycompat.iteritems(commands.table): 285 if len(entry) == 3: 286 summary = entry[2] 287 else: 288 summary = b'' 289 # translate docs *before* searching there 290 func = entry[0] 291 docs = _(pycompat.getdoc(func)) or b'' 292 if kw in cmd or lowercontains(summary) or lowercontains(docs): 293 doclines = docs.splitlines() 294 if doclines: 295 summary = doclines[0] 296 cmdname = cmdutil.parsealiases(cmd)[0] 297 if filtercmd(ui, cmdname, func, kw, docs): 298 continue 299 results[b'commands'].append((cmdname, summary)) 300 for name, docs in itertools.chain( 301 pycompat.iteritems(extensions.enabled(False)), 302 pycompat.iteritems(extensions.disabled()), 303 ): 304 if not docs: 305 continue 306 name = name.rpartition(b'.')[-1] 307 if lowercontains(name) or lowercontains(docs): 308 # extension docs are already translated 309 results[b'extensions'].append((name, docs.splitlines()[0])) 310 try: 311 mod = extensions.load(ui, name, b'') 312 except ImportError: 313 # debug message would be printed in extensions.load() 314 continue 315 for cmd, entry in pycompat.iteritems(getattr(mod, 'cmdtable', {})): 316 if kw in cmd or (len(entry) > 2 and lowercontains(entry[2])): 317 cmdname = cmdutil.parsealiases(cmd)[0] 318 func = entry[0] 319 cmddoc = pycompat.getdoc(func) 320 if cmddoc: 321 cmddoc = gettext(cmddoc).splitlines()[0] 322 else: 323 cmddoc = _(b'(no help text available)') 324 if filtercmd(ui, cmdname, func, kw, cmddoc): 325 continue 326 results[b'extensioncommands'].append((cmdname, cmddoc)) 327 return results 328 329 330def loaddoc(topic, subdir=None): 331 """Return a delayed loader for help/topic.txt.""" 332 333 def loader(ui): 334 package = b'mercurial.helptext' 335 if subdir: 336 package += b'.' + subdir 337 with resourceutil.open_resource(package, topic + b'.txt') as fp: 338 doc = gettext(fp.read()) 339 for rewriter in helphooks.get(topic, []): 340 doc = rewriter(ui, topic, doc) 341 return doc 342 343 return loader 344 345 346internalstable = sorted( 347 [ 348 ( 349 [b'bid-merge'], 350 _(b'Bid Merge Algorithm'), 351 loaddoc(b'bid-merge', subdir=b'internals'), 352 ), 353 ([b'bundle2'], _(b'Bundle2'), loaddoc(b'bundle2', subdir=b'internals')), 354 ([b'bundles'], _(b'Bundles'), loaddoc(b'bundles', subdir=b'internals')), 355 ([b'cbor'], _(b'CBOR'), loaddoc(b'cbor', subdir=b'internals')), 356 ([b'censor'], _(b'Censor'), loaddoc(b'censor', subdir=b'internals')), 357 ( 358 [b'changegroups'], 359 _(b'Changegroups'), 360 loaddoc(b'changegroups', subdir=b'internals'), 361 ), 362 ( 363 [b'config'], 364 _(b'Config Registrar'), 365 loaddoc(b'config', subdir=b'internals'), 366 ), 367 ( 368 [b'dirstate-v2'], 369 _(b'dirstate-v2 file format'), 370 loaddoc(b'dirstate-v2', subdir=b'internals'), 371 ), 372 ( 373 [b'extensions', b'extension'], 374 _(b'Extension API'), 375 loaddoc(b'extensions', subdir=b'internals'), 376 ), 377 ( 378 [b'mergestate'], 379 _(b'Mergestate'), 380 loaddoc(b'mergestate', subdir=b'internals'), 381 ), 382 ( 383 [b'requirements'], 384 _(b'Repository Requirements'), 385 loaddoc(b'requirements', subdir=b'internals'), 386 ), 387 ( 388 [b'revlogs'], 389 _(b'Revision Logs'), 390 loaddoc(b'revlogs', subdir=b'internals'), 391 ), 392 ( 393 [b'wireprotocol'], 394 _(b'Wire Protocol'), 395 loaddoc(b'wireprotocol', subdir=b'internals'), 396 ), 397 ( 398 [b'wireprotocolrpc'], 399 _(b'Wire Protocol RPC'), 400 loaddoc(b'wireprotocolrpc', subdir=b'internals'), 401 ), 402 ( 403 [b'wireprotocolv2'], 404 _(b'Wire Protocol Version 2'), 405 loaddoc(b'wireprotocolv2', subdir=b'internals'), 406 ), 407 ] 408) 409 410 411def internalshelp(ui): 412 """Generate the index for the "internals" topic.""" 413 lines = [ 414 b'To access a subtopic, use "hg help internals.{subtopic-name}"\n', 415 b'\n', 416 ] 417 for names, header, doc in internalstable: 418 lines.append(b' :%s: %s\n' % (names[0], header)) 419 420 return b''.join(lines) 421 422 423helptable = sorted( 424 [ 425 ( 426 [b'bundlespec'], 427 _(b"Bundle File Formats"), 428 loaddoc(b'bundlespec'), 429 TOPIC_CATEGORY_CONCEPTS, 430 ), 431 ( 432 [b'color'], 433 _(b"Colorizing Outputs"), 434 loaddoc(b'color'), 435 TOPIC_CATEGORY_OUTPUT, 436 ), 437 ( 438 [b"config", b"hgrc"], 439 _(b"Configuration Files"), 440 loaddoc(b'config'), 441 TOPIC_CATEGORY_CONFIG, 442 ), 443 ( 444 [b'deprecated'], 445 _(b"Deprecated Features"), 446 loaddoc(b'deprecated'), 447 TOPIC_CATEGORY_MISC, 448 ), 449 ( 450 [b"dates"], 451 _(b"Date Formats"), 452 loaddoc(b'dates'), 453 TOPIC_CATEGORY_OUTPUT, 454 ), 455 ( 456 [b"flags"], 457 _(b"Command-line flags"), 458 loaddoc(b'flags'), 459 TOPIC_CATEGORY_CONFIG, 460 ), 461 ( 462 [b"patterns"], 463 _(b"File Name Patterns"), 464 loaddoc(b'patterns'), 465 TOPIC_CATEGORY_IDS, 466 ), 467 ( 468 [b'environment', b'env'], 469 _(b'Environment Variables'), 470 loaddoc(b'environment'), 471 TOPIC_CATEGORY_CONFIG, 472 ), 473 ( 474 [ 475 b'revisions', 476 b'revs', 477 b'revsets', 478 b'revset', 479 b'multirevs', 480 b'mrevs', 481 ], 482 _(b'Specifying Revisions'), 483 loaddoc(b'revisions'), 484 TOPIC_CATEGORY_IDS, 485 ), 486 ( 487 [b'filesets', b'fileset'], 488 _(b"Specifying File Sets"), 489 loaddoc(b'filesets'), 490 TOPIC_CATEGORY_IDS, 491 ), 492 ( 493 [b'diffs'], 494 _(b'Diff Formats'), 495 loaddoc(b'diffs'), 496 TOPIC_CATEGORY_OUTPUT, 497 ), 498 ( 499 [b'merge-tools', b'mergetools', b'mergetool'], 500 _(b'Merge Tools'), 501 loaddoc(b'merge-tools'), 502 TOPIC_CATEGORY_CONFIG, 503 ), 504 ( 505 [b'templating', b'templates', b'template', b'style'], 506 _(b'Template Usage'), 507 loaddoc(b'templates'), 508 TOPIC_CATEGORY_OUTPUT, 509 ), 510 ([b'urls'], _(b'URL Paths'), loaddoc(b'urls'), TOPIC_CATEGORY_IDS), 511 ( 512 [b"extensions"], 513 _(b"Using Additional Features"), 514 extshelp, 515 TOPIC_CATEGORY_CONFIG, 516 ), 517 ( 518 [b"subrepos", b"subrepo"], 519 _(b"Subrepositories"), 520 loaddoc(b'subrepos'), 521 TOPIC_CATEGORY_CONCEPTS, 522 ), 523 ( 524 [b"hgweb"], 525 _(b"Configuring hgweb"), 526 loaddoc(b'hgweb'), 527 TOPIC_CATEGORY_CONFIG, 528 ), 529 ( 530 [b"glossary"], 531 _(b"Glossary"), 532 loaddoc(b'glossary'), 533 TOPIC_CATEGORY_CONCEPTS, 534 ), 535 ( 536 [b"hgignore", b"ignore"], 537 _(b"Syntax for Mercurial Ignore Files"), 538 loaddoc(b'hgignore'), 539 TOPIC_CATEGORY_IDS, 540 ), 541 ( 542 [b"phases"], 543 _(b"Working with Phases"), 544 loaddoc(b'phases'), 545 TOPIC_CATEGORY_CONCEPTS, 546 ), 547 ( 548 [b"evolution"], 549 _(b"Safely rewriting history (EXPERIMENTAL)"), 550 loaddoc(b'evolution'), 551 TOPIC_CATEGORY_CONCEPTS, 552 ), 553 ( 554 [b'scripting'], 555 _(b'Using Mercurial from scripts and automation'), 556 loaddoc(b'scripting'), 557 TOPIC_CATEGORY_MISC, 558 ), 559 ( 560 [b'internals'], 561 _(b"Technical implementation topics"), 562 internalshelp, 563 TOPIC_CATEGORY_MISC, 564 ), 565 ( 566 [b'pager'], 567 _(b"Pager Support"), 568 loaddoc(b'pager'), 569 TOPIC_CATEGORY_CONFIG, 570 ), 571 ] 572) 573 574# Maps topics with sub-topics to a list of their sub-topics. 575subtopics = { 576 b'internals': internalstable, 577} 578 579# Map topics to lists of callable taking the current topic help and 580# returning the updated version 581helphooks = {} 582 583 584def addtopichook(topic, rewriter): 585 helphooks.setdefault(topic, []).append(rewriter) 586 587 588def makeitemsdoc(ui, topic, doc, marker, items, dedent=False): 589 """Extract docstring from the items key to function mapping, build a 590 single documentation block and use it to overwrite the marker in doc. 591 """ 592 entries = [] 593 for name in sorted(items): 594 text = (pycompat.getdoc(items[name]) or b'').rstrip() 595 if not text or not ui.verbose and any(w in text for w in _exclkeywords): 596 continue 597 text = gettext(text) 598 if dedent: 599 # Abuse latin1 to use textwrap.dedent() on bytes. 600 text = textwrap.dedent(text.decode('latin1')).encode('latin1') 601 lines = text.splitlines() 602 doclines = [(lines[0])] 603 for l in lines[1:]: 604 # Stop once we find some Python doctest 605 if l.strip().startswith(b'>>>'): 606 break 607 if dedent: 608 doclines.append(l.rstrip()) 609 else: 610 doclines.append(b' ' + l.strip()) 611 entries.append(b'\n'.join(doclines)) 612 entries = b'\n\n'.join(entries) 613 return doc.replace(marker, entries) 614 615 616def addtopicsymbols(topic, marker, symbols, dedent=False): 617 def add(ui, topic, doc): 618 return makeitemsdoc(ui, topic, doc, marker, symbols, dedent=dedent) 619 620 addtopichook(topic, add) 621 622 623addtopicsymbols( 624 b'bundlespec', 625 b'.. bundlecompressionmarker', 626 compression.bundlecompressiontopics(), 627) 628addtopicsymbols(b'filesets', b'.. predicatesmarker', fileset.symbols) 629addtopicsymbols( 630 b'merge-tools', b'.. internaltoolsmarker', filemerge.internalsdoc 631) 632addtopicsymbols(b'revisions', b'.. predicatesmarker', revset.symbols) 633addtopicsymbols(b'templates', b'.. keywordsmarker', templatekw.keywords) 634addtopicsymbols(b'templates', b'.. filtersmarker', templatefilters.filters) 635addtopicsymbols(b'templates', b'.. functionsmarker', templatefuncs.funcs) 636addtopicsymbols( 637 b'hgweb', b'.. webcommandsmarker', webcommands.commands, dedent=True 638) 639 640 641def inserttweakrc(ui, topic, doc): 642 marker = b'.. tweakdefaultsmarker' 643 repl = uimod.tweakrc 644 645 def sub(m): 646 lines = [m.group(1) + s for s in repl.splitlines()] 647 return b'\n'.join(lines) 648 649 return re.sub(br'( *)%s' % re.escape(marker), sub, doc) 650 651 652def _getcategorizedhelpcmds(ui, cmdtable, name, select=None): 653 # Category -> list of commands 654 cats = {} 655 # Command -> short description 656 h = {} 657 # Command -> string showing synonyms 658 syns = {} 659 for c, e in pycompat.iteritems(cmdtable): 660 fs = cmdutil.parsealiases(c) 661 f = fs[0] 662 syns[f] = fs 663 func = e[0] 664 if select and not select(f): 665 continue 666 doc = pycompat.getdoc(func) 667 if filtercmd(ui, f, func, name, doc): 668 continue 669 doc = gettext(doc) 670 if not doc: 671 doc = _(b"(no help text available)") 672 h[f] = doc.splitlines()[0].rstrip() 673 674 cat = getattr(func, 'helpcategory', None) or ( 675 registrar.command.CATEGORY_NONE 676 ) 677 cats.setdefault(cat, []).append(f) 678 return cats, h, syns 679 680 681def _getcategorizedhelptopics(ui, topictable): 682 # Group commands by category. 683 topiccats = {} 684 syns = {} 685 for topic in topictable: 686 names, header, doc = topic[0:3] 687 if len(topic) > 3 and topic[3]: 688 category = topic[3] 689 else: 690 category = TOPIC_CATEGORY_NONE 691 692 topicname = names[0] 693 syns[topicname] = list(names) 694 if not filtertopic(ui, topicname): 695 topiccats.setdefault(category, []).append((topicname, header)) 696 return topiccats, syns 697 698 699addtopichook(b'config', inserttweakrc) 700 701 702def help_( 703 ui, 704 commands, 705 name, 706 unknowncmd=False, 707 full=True, 708 subtopic=None, 709 fullname=None, 710 **opts 711): 712 """ 713 Generate the help for 'name' as unformatted restructured text. If 714 'name' is None, describe the commands available. 715 """ 716 717 opts = pycompat.byteskwargs(opts) 718 719 def helpcmd(name, subtopic=None): 720 try: 721 aliases, entry = cmdutil.findcmd( 722 name, commands.table, strict=unknowncmd 723 ) 724 except error.AmbiguousCommand as inst: 725 # py3 fix: except vars can't be used outside the scope of the 726 # except block, nor can be used inside a lambda. python issue4617 727 prefix = inst.prefix 728 select = lambda c: cmdutil.parsealiases(c)[0].startswith(prefix) 729 rst = helplist(select) 730 return rst 731 732 rst = [] 733 734 # check if it's an invalid alias and display its error if it is 735 if getattr(entry[0], 'badalias', None): 736 rst.append(entry[0].badalias + b'\n') 737 if entry[0].unknowncmd: 738 try: 739 rst.extend(helpextcmd(entry[0].cmdname)) 740 except error.UnknownCommand: 741 pass 742 return rst 743 744 # synopsis 745 if len(entry) > 2: 746 if entry[2].startswith(b'hg'): 747 rst.append(b"%s\n" % entry[2]) 748 else: 749 rst.append(b'hg %s %s\n' % (aliases[0], entry[2])) 750 else: 751 rst.append(b'hg %s\n' % aliases[0]) 752 # aliases 753 if full and not ui.quiet and len(aliases) > 1: 754 rst.append(_(b"\naliases: %s\n") % b', '.join(aliases[1:])) 755 rst.append(b'\n') 756 757 # description 758 doc = gettext(pycompat.getdoc(entry[0])) 759 if not doc: 760 doc = _(b"(no help text available)") 761 if util.safehasattr(entry[0], b'definition'): # aliased command 762 source = entry[0].source 763 if entry[0].definition.startswith(b'!'): # shell alias 764 doc = _(b'shell alias for: %s\n\n%s\n\ndefined by: %s\n') % ( 765 entry[0].definition[1:], 766 doc, 767 source, 768 ) 769 else: 770 doc = _(b'alias for: hg %s\n\n%s\n\ndefined by: %s\n') % ( 771 entry[0].definition, 772 doc, 773 source, 774 ) 775 doc = doc.splitlines(True) 776 if ui.quiet or not full: 777 rst.append(doc[0]) 778 else: 779 rst.extend(doc) 780 rst.append(b'\n') 781 782 # check if this command shadows a non-trivial (multi-line) 783 # extension help text 784 try: 785 mod = extensions.find(name) 786 doc = gettext(pycompat.getdoc(mod)) or b'' 787 if b'\n' in doc.strip(): 788 msg = _( 789 b"(use 'hg help -e %s' to show help for " 790 b"the %s extension)" 791 ) % (name, name) 792 rst.append(b'\n%s\n' % msg) 793 except KeyError: 794 pass 795 796 # options 797 if not ui.quiet and entry[1]: 798 rst.append(optrst(_(b"options"), entry[1], ui.verbose, ui)) 799 800 if ui.verbose: 801 rst.append( 802 optrst( 803 _(b"global options"), commands.globalopts, ui.verbose, ui 804 ) 805 ) 806 807 if not ui.verbose: 808 if not full: 809 rst.append(_(b"\n(use 'hg %s -h' to show more help)\n") % name) 810 elif not ui.quiet: 811 rst.append( 812 _( 813 b'\n(some details hidden, use --verbose ' 814 b'to show complete help)' 815 ) 816 ) 817 818 return rst 819 820 def helplist(select=None, **opts): 821 cats, h, syns = _getcategorizedhelpcmds( 822 ui, commands.table, name, select 823 ) 824 825 rst = [] 826 if not h: 827 if not ui.quiet: 828 rst.append(_(b'no commands defined\n')) 829 return rst 830 831 # Output top header. 832 if not ui.quiet: 833 if name == b"shortlist": 834 rst.append(_(b'basic commands:\n\n')) 835 elif name == b"debug": 836 rst.append(_(b'debug commands (internal and unsupported):\n\n')) 837 else: 838 rst.append(_(b'list of commands:\n')) 839 840 def appendcmds(cmds): 841 cmds = sorted(cmds) 842 for c in cmds: 843 display_cmd = c 844 if ui.verbose: 845 display_cmd = b', '.join(syns[c]) 846 display_cmd = display_cmd.replace(b':', br'\:') 847 rst.append(b' :%s: %s\n' % (display_cmd, h[c])) 848 849 if name in (b'shortlist', b'debug'): 850 # List without categories. 851 appendcmds(h) 852 else: 853 # Check that all categories have an order. 854 missing_order = set(cats.keys()) - set(CATEGORY_ORDER) 855 if missing_order: 856 ui.develwarn( 857 b'help categories missing from CATEGORY_ORDER: %s' 858 % missing_order 859 ) 860 861 # List per category. 862 for cat in CATEGORY_ORDER: 863 catfns = cats.get(cat, []) 864 if catfns: 865 if len(cats) > 1: 866 catname = gettext(CATEGORY_NAMES[cat]) 867 rst.append(b"\n%s:\n" % catname) 868 rst.append(b"\n") 869 appendcmds(catfns) 870 871 ex = opts.get 872 anyopts = ex('keyword') or not (ex('command') or ex('extension')) 873 if not name and anyopts: 874 exts = listexts( 875 _(b'enabled extensions:'), 876 extensions.enabled(), 877 showdeprecated=ui.verbose, 878 ) 879 if exts: 880 rst.append(b'\n') 881 rst.extend(exts) 882 883 rst.append(_(b"\nadditional help topics:\n")) 884 topiccats, topicsyns = _getcategorizedhelptopics(ui, helptable) 885 886 # Check that all categories have an order. 887 missing_order = set(topiccats.keys()) - set(TOPIC_CATEGORY_ORDER) 888 if missing_order: 889 ui.develwarn( 890 b'help categories missing from TOPIC_CATEGORY_ORDER: %s' 891 % missing_order 892 ) 893 894 # Output topics per category. 895 for cat in TOPIC_CATEGORY_ORDER: 896 topics = topiccats.get(cat, []) 897 if topics: 898 if len(topiccats) > 1: 899 catname = gettext(TOPIC_CATEGORY_NAMES[cat]) 900 rst.append(b"\n%s:\n" % catname) 901 rst.append(b"\n") 902 for t, desc in topics: 903 rst.append(b" :%s: %s\n" % (t, desc)) 904 905 if ui.quiet: 906 pass 907 elif ui.verbose: 908 rst.append( 909 b'\n%s\n' 910 % optrst( 911 _(b"global options"), commands.globalopts, ui.verbose, ui 912 ) 913 ) 914 if name == b'shortlist': 915 rst.append( 916 _(b"\n(use 'hg help' for the full list of commands)\n") 917 ) 918 else: 919 if name == b'shortlist': 920 rst.append( 921 _( 922 b"\n(use 'hg help' for the full list of commands " 923 b"or 'hg -v' for details)\n" 924 ) 925 ) 926 elif name and not full: 927 rst.append( 928 _(b"\n(use 'hg help %s' to show the full help text)\n") 929 % name 930 ) 931 elif name and syns and name in syns.keys(): 932 rst.append( 933 _( 934 b"\n(use 'hg help -v -e %s' to show built-in " 935 b"aliases and global options)\n" 936 ) 937 % name 938 ) 939 else: 940 rst.append( 941 _( 942 b"\n(use 'hg help -v%s' to show built-in aliases " 943 b"and global options)\n" 944 ) 945 % (name and b" " + name or b"") 946 ) 947 return rst 948 949 def helptopic(name, subtopic=None): 950 # Look for sub-topic entry first. 951 header, doc = None, None 952 if subtopic and name in subtopics: 953 for names, header, doc in subtopics[name]: 954 if subtopic in names: 955 break 956 if not any(subtopic in s[0] for s in subtopics[name]): 957 raise error.UnknownCommand(name) 958 959 if not header: 960 for topic in helptable: 961 names, header, doc = topic[0:3] 962 if name in names: 963 break 964 else: 965 raise error.UnknownCommand(name) 966 967 rst = [minirst.section(header)] 968 969 # description 970 if not doc: 971 rst.append(b" %s\n" % _(b"(no help text available)")) 972 if callable(doc): 973 rst += [b" %s\n" % l for l in doc(ui).splitlines()] 974 975 if not ui.verbose: 976 omitted = _( 977 b'(some details hidden, use --verbose' 978 b' to show complete help)' 979 ) 980 indicateomitted(rst, omitted) 981 982 try: 983 cmdutil.findcmd(name, commands.table) 984 rst.append( 985 _(b"\nuse 'hg help -c %s' to see help for the %s command\n") 986 % (name, name) 987 ) 988 except error.UnknownCommand: 989 pass 990 return rst 991 992 def helpext(name, subtopic=None): 993 try: 994 mod = extensions.find(name) 995 doc = gettext(pycompat.getdoc(mod)) or _(b'no help text available') 996 except KeyError: 997 mod = None 998 doc = extensions.disabled_help(name) 999 if not doc: 1000 raise error.UnknownCommand(name) 1001 1002 if b'\n' not in doc: 1003 head, tail = doc, b"" 1004 else: 1005 head, tail = doc.split(b'\n', 1) 1006 rst = [_(b'%s extension - %s\n\n') % (name.rpartition(b'.')[-1], head)] 1007 if tail: 1008 rst.extend(tail.splitlines(True)) 1009 rst.append(b'\n') 1010 1011 if not ui.verbose: 1012 omitted = _( 1013 b'(some details hidden, use --verbose' 1014 b' to show complete help)' 1015 ) 1016 indicateomitted(rst, omitted) 1017 1018 if mod: 1019 try: 1020 ct = mod.cmdtable 1021 except AttributeError: 1022 ct = {} 1023 modcmds = {c.partition(b'|')[0] for c in ct} 1024 rst.extend(helplist(modcmds.__contains__)) 1025 else: 1026 rst.append( 1027 _( 1028 b"(use 'hg help extensions' for information on enabling" 1029 b" extensions)\n" 1030 ) 1031 ) 1032 return rst 1033 1034 def helpextcmd(name, subtopic=None): 1035 cmd, ext, doc = extensions.disabledcmd( 1036 ui, name, ui.configbool(b'ui', b'strict') 1037 ) 1038 doc = doc.splitlines()[0] 1039 1040 rst = listexts( 1041 _(b"'%s' is provided by the following extension:") % cmd, 1042 {ext: doc}, 1043 indent=4, 1044 showdeprecated=True, 1045 ) 1046 rst.append(b'\n') 1047 rst.append( 1048 _( 1049 b"(use 'hg help extensions' for information on enabling " 1050 b"extensions)\n" 1051 ) 1052 ) 1053 return rst 1054 1055 rst = [] 1056 kw = opts.get(b'keyword') 1057 if kw or name is None and any(opts[o] for o in opts): 1058 matches = topicmatch(ui, commands, name or b'') 1059 helpareas = [] 1060 if opts.get(b'extension'): 1061 helpareas += [(b'extensions', _(b'Extensions'))] 1062 if opts.get(b'command'): 1063 helpareas += [(b'commands', _(b'Commands'))] 1064 if not helpareas: 1065 helpareas = [ 1066 (b'topics', _(b'Topics')), 1067 (b'commands', _(b'Commands')), 1068 (b'extensions', _(b'Extensions')), 1069 (b'extensioncommands', _(b'Extension Commands')), 1070 ] 1071 for t, title in helpareas: 1072 if matches[t]: 1073 rst.append(b'%s:\n\n' % title) 1074 rst.extend(minirst.maketable(sorted(matches[t]), 1)) 1075 rst.append(b'\n') 1076 if not rst: 1077 msg = _(b'no matches') 1078 hint = _(b"try 'hg help' for a list of topics") 1079 raise error.InputError(msg, hint=hint) 1080 elif name and name != b'shortlist': 1081 queries = [] 1082 if unknowncmd: 1083 queries += [helpextcmd] 1084 if opts.get(b'extension'): 1085 queries += [helpext] 1086 if opts.get(b'command'): 1087 queries += [helpcmd] 1088 if not queries: 1089 queries = (helptopic, helpcmd, helpext, helpextcmd) 1090 for f in queries: 1091 try: 1092 rst = f(name, subtopic) 1093 break 1094 except error.UnknownCommand: 1095 pass 1096 else: 1097 if unknowncmd: 1098 raise error.UnknownCommand(name) 1099 else: 1100 if fullname: 1101 formatname = fullname 1102 else: 1103 formatname = name 1104 if subtopic: 1105 hintname = subtopic 1106 else: 1107 hintname = name 1108 msg = _(b'no such help topic: %s') % formatname 1109 hint = _(b"try 'hg help --keyword %s'") % hintname 1110 raise error.InputError(msg, hint=hint) 1111 else: 1112 # program name 1113 if not ui.quiet: 1114 rst = [_(b"Mercurial Distributed SCM\n"), b'\n'] 1115 rst.extend(helplist(None, **pycompat.strkwargs(opts))) 1116 1117 return b''.join(rst) 1118 1119 1120def formattedhelp( 1121 ui, commands, fullname, keep=None, unknowncmd=False, full=True, **opts 1122): 1123 """get help for a given topic (as a dotted name) as rendered rst 1124 1125 Either returns the rendered help text or raises an exception. 1126 """ 1127 if keep is None: 1128 keep = [] 1129 else: 1130 keep = list(keep) # make a copy so we can mutate this later 1131 1132 # <fullname> := <name>[.<subtopic][.<section>] 1133 name = subtopic = section = None 1134 if fullname is not None: 1135 nameparts = fullname.split(b'.') 1136 name = nameparts.pop(0) 1137 if nameparts and name in subtopics: 1138 subtopic = nameparts.pop(0) 1139 if nameparts: 1140 section = encoding.lower(b'.'.join(nameparts)) 1141 1142 textwidth = ui.configint(b'ui', b'textwidth') 1143 termwidth = ui.termwidth() - 2 1144 if textwidth <= 0 or termwidth < textwidth: 1145 textwidth = termwidth 1146 text = help_( 1147 ui, 1148 commands, 1149 name, 1150 fullname=fullname, 1151 subtopic=subtopic, 1152 unknowncmd=unknowncmd, 1153 full=full, 1154 **opts 1155 ) 1156 1157 blocks, pruned = minirst.parse(text, keep=keep) 1158 if b'verbose' in pruned: 1159 keep.append(b'omitted') 1160 else: 1161 keep.append(b'notomitted') 1162 blocks, pruned = minirst.parse(text, keep=keep) 1163 if section: 1164 blocks = minirst.filtersections(blocks, section) 1165 1166 # We could have been given a weird ".foo" section without a name 1167 # to look for, or we could have simply failed to found "foo.bar" 1168 # because bar isn't a section of foo 1169 if section and not (blocks and name): 1170 raise error.InputError(_(b"help section not found: %s") % fullname) 1171 1172 return minirst.formatplain(blocks, textwidth) 1173