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