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