comparison mercurial/help.py @ 49763:2a70d1fc70c4

typing: add type hints to mercurial/help.py Was hoping to find more issues like f09bc2ed9100, but it may be that nothing checks the args to that operation. In any event, the work is done and pytype doesn't do a very good job inferring the types. A few of th emore complicated things like the command table are left untyped, because they come from modules that aren't typed yet.
author Matt Harbison <matt_harbison@yahoo.com>
date Sun, 20 Nov 2022 22:54:43 -0500
parents 04e6add9e4dc
children f56873a7284c
comparison
equal deleted inserted replaced
49762:5744ceeb9067 49763:2a70d1fc70c4
7 7
8 8
9 import itertools 9 import itertools
10 import re 10 import re
11 import textwrap 11 import textwrap
12
13 from typing import (
14 Callable,
15 Dict,
16 Iterable,
17 List,
18 Optional,
19 Set,
20 Tuple,
21 Union,
22 cast,
23 )
12 24
13 from .i18n import ( 25 from .i18n import (
14 _, 26 _,
15 gettext, 27 gettext,
16 ) 28 )
38 compression, 50 compression,
39 resourceutil, 51 resourceutil,
40 stringutil, 52 stringutil,
41 ) 53 )
42 54
43 _exclkeywords = { 55 _DocLoader = Callable[[uimod.ui], bytes]
56 # Old extensions may not register with a category
57 _HelpEntry = Union["_HelpEntryNoCategory", "_HelpEntryWithCategory"]
58 _HelpEntryNoCategory = Tuple[List[bytes], bytes, _DocLoader]
59 _HelpEntryWithCategory = Tuple[List[bytes], bytes, _DocLoader, bytes]
60 _SelectFn = Callable[[object], bool]
61 _SynonymTable = Dict[bytes, List[bytes]]
62 _TopicHook = Callable[[uimod.ui, bytes, bytes], bytes]
63
64 _exclkeywords: Set[bytes] = {
44 b"(ADVANCED)", 65 b"(ADVANCED)",
45 b"(DEPRECATED)", 66 b"(DEPRECATED)",
46 b"(EXPERIMENTAL)", 67 b"(EXPERIMENTAL)",
47 # i18n: "(ADVANCED)" is a keyword, must be translated consistently 68 # i18n: "(ADVANCED)" is a keyword, must be translated consistently
48 _(b"(ADVANCED)"), 69 _(b"(ADVANCED)"),
54 75
55 # The order in which command categories will be displayed. 76 # The order in which command categories will be displayed.
56 # Extensions with custom categories should insert them into this list 77 # Extensions with custom categories should insert them into this list
57 # after/before the appropriate item, rather than replacing the list or 78 # after/before the appropriate item, rather than replacing the list or
58 # assuming absolute positions. 79 # assuming absolute positions.
59 CATEGORY_ORDER = [ 80 CATEGORY_ORDER: List[bytes] = [
60 registrar.command.CATEGORY_REPO_CREATION, 81 registrar.command.CATEGORY_REPO_CREATION,
61 registrar.command.CATEGORY_REMOTE_REPO_MANAGEMENT, 82 registrar.command.CATEGORY_REMOTE_REPO_MANAGEMENT,
62 registrar.command.CATEGORY_COMMITTING, 83 registrar.command.CATEGORY_COMMITTING,
63 registrar.command.CATEGORY_CHANGE_MANAGEMENT, 84 registrar.command.CATEGORY_CHANGE_MANAGEMENT,
64 registrar.command.CATEGORY_CHANGE_ORGANIZATION, 85 registrar.command.CATEGORY_CHANGE_ORGANIZATION,
72 registrar.command.CATEGORY_NONE, 93 registrar.command.CATEGORY_NONE,
73 ] 94 ]
74 95
75 # Human-readable category names. These are translated. 96 # Human-readable category names. These are translated.
76 # Extensions with custom categories should add their names here. 97 # Extensions with custom categories should add their names here.
77 CATEGORY_NAMES = { 98 CATEGORY_NAMES: Dict[bytes, bytes] = {
78 registrar.command.CATEGORY_REPO_CREATION: b'Repository creation', 99 registrar.command.CATEGORY_REPO_CREATION: b'Repository creation',
79 registrar.command.CATEGORY_REMOTE_REPO_MANAGEMENT: b'Remote repository management', 100 registrar.command.CATEGORY_REMOTE_REPO_MANAGEMENT: b'Remote repository management',
80 registrar.command.CATEGORY_COMMITTING: b'Change creation', 101 registrar.command.CATEGORY_COMMITTING: b'Change creation',
81 registrar.command.CATEGORY_CHANGE_NAVIGATION: b'Change navigation', 102 registrar.command.CATEGORY_CHANGE_NAVIGATION: b'Change navigation',
82 registrar.command.CATEGORY_CHANGE_MANAGEMENT: b'Change manipulation', 103 registrar.command.CATEGORY_CHANGE_MANAGEMENT: b'Change manipulation',
100 121
101 # The order in which topic categories will be displayed. 122 # The order in which topic categories will be displayed.
102 # Extensions with custom categories should insert them into this list 123 # Extensions with custom categories should insert them into this list
103 # after/before the appropriate item, rather than replacing the list or 124 # after/before the appropriate item, rather than replacing the list or
104 # assuming absolute positions. 125 # assuming absolute positions.
105 TOPIC_CATEGORY_ORDER = [ 126 TOPIC_CATEGORY_ORDER: List[bytes] = [
106 TOPIC_CATEGORY_IDS, 127 TOPIC_CATEGORY_IDS,
107 TOPIC_CATEGORY_OUTPUT, 128 TOPIC_CATEGORY_OUTPUT,
108 TOPIC_CATEGORY_CONFIG, 129 TOPIC_CATEGORY_CONFIG,
109 TOPIC_CATEGORY_CONCEPTS, 130 TOPIC_CATEGORY_CONCEPTS,
110 TOPIC_CATEGORY_MISC, 131 TOPIC_CATEGORY_MISC,
111 TOPIC_CATEGORY_NONE, 132 TOPIC_CATEGORY_NONE,
112 ] 133 ]
113 134
114 # Human-readable topic category names. These are translated. 135 # Human-readable topic category names. These are translated.
115 TOPIC_CATEGORY_NAMES = { 136 TOPIC_CATEGORY_NAMES: Dict[bytes, bytes] = {
116 TOPIC_CATEGORY_IDS: b'Mercurial identifiers', 137 TOPIC_CATEGORY_IDS: b'Mercurial identifiers',
117 TOPIC_CATEGORY_OUTPUT: b'Mercurial output', 138 TOPIC_CATEGORY_OUTPUT: b'Mercurial output',
118 TOPIC_CATEGORY_CONFIG: b'Mercurial configuration', 139 TOPIC_CATEGORY_CONFIG: b'Mercurial configuration',
119 TOPIC_CATEGORY_CONCEPTS: b'Concepts', 140 TOPIC_CATEGORY_CONCEPTS: b'Concepts',
120 TOPIC_CATEGORY_MISC: b'Miscellaneous', 141 TOPIC_CATEGORY_MISC: b'Miscellaneous',
121 TOPIC_CATEGORY_NONE: b'Uncategorized topics', 142 TOPIC_CATEGORY_NONE: b'Uncategorized topics',
122 } 143 }
123 144
124 145
125 def listexts(header, exts, indent=1, showdeprecated=False): 146 def listexts(
147 header: bytes,
148 exts: Dict[bytes, bytes],
149 indent: int = 1,
150 showdeprecated: bool = False,
151 ) -> List[bytes]:
126 '''return a text listing of the given extensions''' 152 '''return a text listing of the given extensions'''
127 rst = [] 153 rst = []
128 if exts: 154 if exts:
129 for name, desc in sorted(exts.items()): 155 for name, desc in sorted(exts.items()):
130 if not showdeprecated and any(w in desc for w in _exclkeywords): 156 if not showdeprecated and any(w in desc for w in _exclkeywords):
133 if rst: 159 if rst:
134 rst.insert(0, b'\n%s\n\n' % header) 160 rst.insert(0, b'\n%s\n\n' % header)
135 return rst 161 return rst
136 162
137 163
138 def extshelp(ui): 164 def extshelp(ui: uimod.ui) -> bytes:
139 rst = loaddoc(b'extensions')(ui).splitlines(True) 165 rst = loaddoc(b'extensions')(ui).splitlines(True)
140 rst.extend( 166 rst.extend(
141 listexts( 167 listexts(
142 _(b'enabled extensions:'), extensions.enabled(), showdeprecated=True 168 _(b'enabled extensions:'), extensions.enabled(), showdeprecated=True
143 ) 169 )
151 ) 177 )
152 doc = b''.join(rst) 178 doc = b''.join(rst)
153 return doc 179 return doc
154 180
155 181
156 def parsedefaultmarker(text): 182 def parsedefaultmarker(text: bytes) -> Optional[Tuple[bytes, List[bytes]]]:
157 """given a text 'abc (DEFAULT: def.ghi)', 183 """given a text 'abc (DEFAULT: def.ghi)',
158 returns (b'abc', (b'def', b'ghi')). Otherwise return None""" 184 returns (b'abc', (b'def', b'ghi')). Otherwise return None"""
159 if text[-1:] == b')': 185 if text[-1:] == b')':
160 marker = b' (DEFAULT: ' 186 marker = b' (DEFAULT: '
161 pos = text.find(marker) 187 pos = text.find(marker)
162 if pos >= 0: 188 if pos >= 0:
163 item = text[pos + len(marker) : -1] 189 item = text[pos + len(marker) : -1]
164 return text[:pos], item.split(b'.', 2) 190 return text[:pos], item.split(b'.', 2)
165 191
166 192
167 def optrst(header, options, verbose, ui): 193 def optrst(header: bytes, options, verbose: bool, ui: uimod.ui) -> bytes:
168 data = [] 194 data = []
169 multioccur = False 195 multioccur = False
170 for option in options: 196 for option in options:
171 if len(option) == 5: 197 if len(option) == 5:
172 shortopt, longopt, default, desc, optlabel = option 198 shortopt, longopt, default, desc, optlabel = option
218 rst.extend(minirst.maketable(data, 1)) 244 rst.extend(minirst.maketable(data, 1))
219 245
220 return b''.join(rst) 246 return b''.join(rst)
221 247
222 248
223 def indicateomitted(rst, omitted, notomitted=None): 249 def indicateomitted(
250 rst: List[bytes], omitted: bytes, notomitted: Optional[bytes] = None
251 ) -> None:
224 rst.append(b'\n\n.. container:: omitted\n\n %s\n\n' % omitted) 252 rst.append(b'\n\n.. container:: omitted\n\n %s\n\n' % omitted)
225 if notomitted: 253 if notomitted:
226 rst.append(b'\n\n.. container:: notomitted\n\n %s\n\n' % notomitted) 254 rst.append(b'\n\n.. container:: notomitted\n\n %s\n\n' % notomitted)
227 255
228 256
229 def filtercmd(ui, cmd, func, kw, doc): 257 def filtercmd(ui: uimod.ui, cmd: bytes, func, kw: bytes, doc: bytes) -> bool:
230 if not ui.debugflag and cmd.startswith(b"debug") and kw != b"debug": 258 if not ui.debugflag and cmd.startswith(b"debug") and kw != b"debug":
231 # Debug command, and user is not looking for those. 259 # Debug command, and user is not looking for those.
232 return True 260 return True
233 if not ui.verbose: 261 if not ui.verbose:
234 if not kw and not doc: 262 if not kw and not doc:
247 # Configuration explicitly hides the command. 275 # Configuration explicitly hides the command.
248 return True 276 return True
249 return False 277 return False
250 278
251 279
252 def filtertopic(ui, topic): 280 def filtertopic(ui: uimod.ui, topic: bytes) -> bool:
253 return ui.configbool(b'help', b'hidden-topic.%s' % topic, False) 281 return ui.configbool(b'help', b'hidden-topic.%s' % topic, False)
254 282
255 283
256 def topicmatch(ui, commands, kw): 284 def topicmatch(
285 ui: uimod.ui, commands, kw: bytes
286 ) -> Dict[bytes, List[Tuple[bytes, bytes]]]:
257 """Return help topics matching kw. 287 """Return help topics matching kw.
258 288
259 Returns {'section': [(name, summary), ...], ...} where section is 289 Returns {'section': [(name, summary), ...], ...} where section is
260 one of topics, commands, extensions, or extensioncommands. 290 one of topics, commands, extensions, or extensioncommands.
261 """ 291 """
324 continue 354 continue
325 results[b'extensioncommands'].append((cmdname, cmddoc)) 355 results[b'extensioncommands'].append((cmdname, cmddoc))
326 return results 356 return results
327 357
328 358
329 def loaddoc(topic, subdir=None): 359 def loaddoc(topic: bytes, subdir: Optional[bytes] = None) -> _DocLoader:
330 """Return a delayed loader for help/topic.txt.""" 360 """Return a delayed loader for help/topic.txt."""
331 361
332 def loader(ui): 362 def loader(ui: uimod.ui) -> bytes:
333 package = b'mercurial.helptext' 363 package = b'mercurial.helptext'
334 if subdir: 364 if subdir:
335 package += b'.' + subdir 365 package += b'.' + subdir
336 with resourceutil.open_resource(package, topic + b'.txt') as fp: 366 with resourceutil.open_resource(package, topic + b'.txt') as fp:
337 doc = gettext(fp.read()) 367 doc = gettext(fp.read())
340 return doc 370 return doc
341 371
342 return loader 372 return loader
343 373
344 374
345 internalstable = sorted( 375 internalstable: List[_HelpEntryNoCategory] = sorted(
346 [ 376 [
347 ( 377 (
348 [b'bid-merge'], 378 [b'bid-merge'],
349 _(b'Bid Merge Algorithm'), 379 _(b'Bid Merge Algorithm'),
350 loaddoc(b'bid-merge', subdir=b'internals'), 380 loaddoc(b'bid-merge', subdir=b'internals'),
405 ), 435 ),
406 ] 436 ]
407 ) 437 )
408 438
409 439
410 def internalshelp(ui): 440 def internalshelp(ui: uimod.ui) -> bytes:
411 """Generate the index for the "internals" topic.""" 441 """Generate the index for the "internals" topic."""
412 lines = [ 442 lines = [
413 b'To access a subtopic, use "hg help internals.{subtopic-name}"\n', 443 b'To access a subtopic, use "hg help internals.{subtopic-name}"\n',
414 b'\n', 444 b'\n',
415 ] 445 ]
417 lines.append(b' :%s: %s\n' % (names[0], header)) 447 lines.append(b' :%s: %s\n' % (names[0], header))
418 448
419 return b''.join(lines) 449 return b''.join(lines)
420 450
421 451
422 helptable = sorted( 452 helptable: List[_HelpEntryWithCategory] = sorted(
423 [ 453 [
424 ( 454 (
425 [b'bundlespec'], 455 [b'bundlespec'],
426 _(b"Bundle File Formats"), 456 _(b"Bundle File Formats"),
427 loaddoc(b'bundlespec'), 457 loaddoc(b'bundlespec'),
579 ), 609 ),
580 ] 610 ]
581 ) 611 )
582 612
583 # Maps topics with sub-topics to a list of their sub-topics. 613 # Maps topics with sub-topics to a list of their sub-topics.
584 subtopics = { 614 subtopics: Dict[bytes, List[_HelpEntryNoCategory]] = {
585 b'internals': internalstable, 615 b'internals': internalstable,
586 } 616 }
587 617
588 # Map topics to lists of callable taking the current topic help and 618 # Map topics to lists of callable taking the current topic help and
589 # returning the updated version 619 # returning the updated version
590 helphooks = {} 620 helphooks: Dict[bytes, List[_TopicHook]] = {}
591 621
592 622
593 def addtopichook(topic, rewriter): 623 def addtopichook(topic: bytes, rewriter: _TopicHook) -> None:
594 helphooks.setdefault(topic, []).append(rewriter) 624 helphooks.setdefault(topic, []).append(rewriter)
595 625
596 626
597 def makeitemsdoc(ui, topic, doc, marker, items, dedent=False): 627 def makeitemsdoc(
628 ui: uimod.ui,
629 topic: bytes,
630 doc: bytes,
631 marker: bytes,
632 items: Dict[bytes, bytes],
633 dedent: bool = False,
634 ) -> bytes:
598 """Extract docstring from the items key to function mapping, build a 635 """Extract docstring from the items key to function mapping, build a
599 single documentation block and use it to overwrite the marker in doc. 636 single documentation block and use it to overwrite the marker in doc.
600 """ 637 """
601 entries = [] 638 entries = []
602 for name in sorted(items): 639 for name in sorted(items):
620 entries.append(b'\n'.join(doclines)) 657 entries.append(b'\n'.join(doclines))
621 entries = b'\n\n'.join(entries) 658 entries = b'\n\n'.join(entries)
622 return doc.replace(marker, entries) 659 return doc.replace(marker, entries)
623 660
624 661
625 def addtopicsymbols(topic, marker, symbols, dedent=False): 662 def addtopicsymbols(
626 def add(ui, topic, doc): 663 topic: bytes, marker: bytes, symbols, dedent: bool = False
664 ) -> None:
665 def add(ui: uimod.ui, topic: bytes, doc: bytes):
627 return makeitemsdoc(ui, topic, doc, marker, symbols, dedent=dedent) 666 return makeitemsdoc(ui, topic, doc, marker, symbols, dedent=dedent)
628 667
629 addtopichook(topic, add) 668 addtopichook(topic, add)
630 669
631 670
645 addtopicsymbols( 684 addtopicsymbols(
646 b'hgweb', b'.. webcommandsmarker', webcommands.commands, dedent=True 685 b'hgweb', b'.. webcommandsmarker', webcommands.commands, dedent=True
647 ) 686 )
648 687
649 688
650 def inserttweakrc(ui, topic, doc): 689 def inserttweakrc(ui: uimod.ui, topic: bytes, doc: bytes) -> bytes:
651 marker = b'.. tweakdefaultsmarker' 690 marker = b'.. tweakdefaultsmarker'
652 repl = uimod.tweakrc 691 repl = uimod.tweakrc
653 692
654 def sub(m): 693 def sub(m):
655 lines = [m.group(1) + s for s in repl.splitlines()] 694 lines = [m.group(1) + s for s in repl.splitlines()]
656 return b'\n'.join(lines) 695 return b'\n'.join(lines)
657 696
658 return re.sub(br'( *)%s' % re.escape(marker), sub, doc) 697 return re.sub(br'( *)%s' % re.escape(marker), sub, doc)
659 698
660 699
661 def _getcategorizedhelpcmds(ui, cmdtable, name, select=None): 700 def _getcategorizedhelpcmds(
701 ui: uimod.ui, cmdtable, name: bytes, select: Optional[_SelectFn] = None
702 ) -> Tuple[Dict[bytes, List[bytes]], Dict[bytes, bytes], _SynonymTable]:
662 # Category -> list of commands 703 # Category -> list of commands
663 cats = {} 704 cats = {}
664 # Command -> short description 705 # Command -> short description
665 h = {} 706 h = {}
666 # Command -> string showing synonyms 707 # Command -> string showing synonyms
685 ) 726 )
686 cats.setdefault(cat, []).append(f) 727 cats.setdefault(cat, []).append(f)
687 return cats, h, syns 728 return cats, h, syns
688 729
689 730
690 def _getcategorizedhelptopics(ui, topictable): 731 def _getcategorizedhelptopics(
732 ui: uimod.ui, topictable: List[_HelpEntry]
733 ) -> Tuple[Dict[bytes, List[Tuple[bytes, bytes]]], Dict[bytes, List[bytes]]]:
691 # Group commands by category. 734 # Group commands by category.
692 topiccats = {} 735 topiccats = {}
693 syns = {} 736 syns = {}
694 for topic in topictable: 737 for topic in topictable:
695 names, header, doc = topic[0:3] 738 names, header, doc = topic[0:3]
696 if len(topic) > 3 and topic[3]: 739 if len(topic) > 3 and topic[3]:
697 category = topic[3] 740 category: bytes = cast(bytes, topic[3]) # help pytype
698 else: 741 else:
699 category = TOPIC_CATEGORY_NONE 742 category: bytes = TOPIC_CATEGORY_NONE
700 743
701 topicname = names[0] 744 topicname = names[0]
702 syns[topicname] = list(names) 745 syns[topicname] = list(names)
703 if not filtertopic(ui, topicname): 746 if not filtertopic(ui, topicname):
704 topiccats.setdefault(category, []).append((topicname, header)) 747 topiccats.setdefault(category, []).append((topicname, header))
707 750
708 addtopichook(b'config', inserttweakrc) 751 addtopichook(b'config', inserttweakrc)
709 752
710 753
711 def help_( 754 def help_(
712 ui, 755 ui: uimod.ui,
713 commands, 756 commands,
714 name, 757 name: bytes,
715 unknowncmd=False, 758 unknowncmd: bool = False,
716 full=True, 759 full: bool = True,
717 subtopic=None, 760 subtopic: Optional[bytes] = None,
718 fullname=None, 761 fullname: Optional[bytes] = None,
719 **opts 762 **opts
720 ): 763 ) -> bytes:
721 """ 764 """
722 Generate the help for 'name' as unformatted restructured text. If 765 Generate the help for 'name' as unformatted restructured text. If
723 'name' is None, describe the commands available. 766 'name' is None, describe the commands available.
724 """ 767 """
725 768
726 opts = pycompat.byteskwargs(opts) 769 opts = pycompat.byteskwargs(opts)
727 770
728 def helpcmd(name, subtopic=None): 771 def helpcmd(name: bytes, subtopic: Optional[bytes]) -> List[bytes]:
729 try: 772 try:
730 aliases, entry = cmdutil.findcmd( 773 aliases, entry = cmdutil.findcmd(
731 name, commands.table, strict=unknowncmd 774 name, commands.table, strict=unknowncmd
732 ) 775 )
733 except error.AmbiguousCommand as inst: 776 except error.AmbiguousCommand as inst:
824 ) 867 )
825 ) 868 )
826 869
827 return rst 870 return rst
828 871
829 def helplist(select=None, **opts): 872 def helplist(select: Optional[_SelectFn] = None, **opts) -> List[bytes]:
830 cats, h, syns = _getcategorizedhelpcmds( 873 cats, h, syns = _getcategorizedhelpcmds(
831 ui, commands.table, name, select 874 ui, commands.table, name, select
832 ) 875 )
833 876
834 rst = [] 877 rst = []
844 elif name == b"debug": 887 elif name == b"debug":
845 rst.append(_(b'debug commands (internal and unsupported):\n\n')) 888 rst.append(_(b'debug commands (internal and unsupported):\n\n'))
846 else: 889 else:
847 rst.append(_(b'list of commands:\n')) 890 rst.append(_(b'list of commands:\n'))
848 891
849 def appendcmds(cmds): 892 def appendcmds(cmds: Iterable[bytes]) -> None:
850 cmds = sorted(cmds) 893 cmds = sorted(cmds)
851 for c in cmds: 894 for c in cmds:
852 display_cmd = c 895 display_cmd = c
853 if ui.verbose: 896 if ui.verbose:
854 display_cmd = b', '.join(syns[c]) 897 display_cmd = b', '.join(syns[c])
953 ) 996 )
954 % (name and b" " + name or b"") 997 % (name and b" " + name or b"")
955 ) 998 )
956 return rst 999 return rst
957 1000
958 def helptopic(name, subtopic=None): 1001 def helptopic(name: bytes, subtopic: Optional[bytes] = None) -> List[bytes]:
959 # Look for sub-topic entry first. 1002 # Look for sub-topic entry first.
960 header, doc = None, None 1003 header, doc = None, None
961 if subtopic and name in subtopics: 1004 if subtopic and name in subtopics:
962 for names, header, doc in subtopics[name]: 1005 for names, header, doc in subtopics[name]:
963 if subtopic in names: 1006 if subtopic in names:
996 ) 1039 )
997 except error.UnknownCommand: 1040 except error.UnknownCommand:
998 pass 1041 pass
999 return rst 1042 return rst
1000 1043
1001 def helpext(name, subtopic=None): 1044 def helpext(name: bytes, subtopic: Optional[bytes] = None) -> List[bytes]:
1002 try: 1045 try:
1003 mod = extensions.find(name) 1046 mod = extensions.find(name)
1004 doc = gettext(pycompat.getdoc(mod)) or _(b'no help text available') 1047 doc = gettext(pycompat.getdoc(mod)) or _(b'no help text available')
1005 except KeyError: 1048 except KeyError:
1006 mod = None 1049 mod = None
1038 b" extensions)\n" 1081 b" extensions)\n"
1039 ) 1082 )
1040 ) 1083 )
1041 return rst 1084 return rst
1042 1085
1043 def helpextcmd(name, subtopic=None): 1086 def helpextcmd(
1087 name: bytes, subtopic: Optional[bytes] = None
1088 ) -> List[bytes]:
1044 cmd, ext, doc = extensions.disabledcmd( 1089 cmd, ext, doc = extensions.disabledcmd(
1045 ui, name, ui.configbool(b'ui', b'strict') 1090 ui, name, ui.configbool(b'ui', b'strict')
1046 ) 1091 )
1047 doc = stringutil.firstline(doc) 1092 doc = stringutil.firstline(doc)
1048 1093
1125 1170
1126 return b''.join(rst) 1171 return b''.join(rst)
1127 1172
1128 1173
1129 def formattedhelp( 1174 def formattedhelp(
1130 ui, commands, fullname, keep=None, unknowncmd=False, full=True, **opts 1175 ui: uimod.ui,
1131 ): 1176 commands,
1177 fullname: Optional[bytes],
1178 keep: Optional[Iterable[bytes]] = None,
1179 unknowncmd: bool = False,
1180 full: bool = True,
1181 **opts
1182 ) -> bytes:
1132 """get help for a given topic (as a dotted name) as rendered rst 1183 """get help for a given topic (as a dotted name) as rendered rst
1133 1184
1134 Either returns the rendered help text or raises an exception. 1185 Either returns the rendered help text or raises an exception.
1135 """ 1186 """
1136 if keep is None: 1187 if keep is None: