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