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