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