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): |
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()) |
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: |
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: |
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: |