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