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