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