##// END OF EJS Templates
help: use cmdutil.parsealiases() to resolve command name...
Yuya Nishihara -
r36264:4174970c default
parent child Browse files
Show More
@@ -1,680 +1,683 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 textwrap
12 import textwrap
13
13
14 from .i18n import (
14 from .i18n import (
15 _,
15 _,
16 gettext,
16 gettext,
17 )
17 )
18 from . import (
18 from . import (
19 cmdutil,
19 cmdutil,
20 encoding,
20 encoding,
21 error,
21 error,
22 extensions,
22 extensions,
23 filemerge,
23 filemerge,
24 fileset,
24 fileset,
25 minirst,
25 minirst,
26 pycompat,
26 pycompat,
27 revset,
27 revset,
28 templatefilters,
28 templatefilters,
29 templatekw,
29 templatekw,
30 templater,
30 templater,
31 util,
31 util,
32 )
32 )
33 from .hgweb import (
33 from .hgweb import (
34 webcommands,
34 webcommands,
35 )
35 )
36
36
37 _exclkeywords = {
37 _exclkeywords = {
38 "(ADVANCED)",
38 "(ADVANCED)",
39 "(DEPRECATED)",
39 "(DEPRECATED)",
40 "(EXPERIMENTAL)",
40 "(EXPERIMENTAL)",
41 # i18n: "(ADVANCED)" is a keyword, must be translated consistently
41 # i18n: "(ADVANCED)" is a keyword, must be translated consistently
42 _("(ADVANCED)"),
42 _("(ADVANCED)"),
43 # i18n: "(DEPRECATED)" is a keyword, must be translated consistently
43 # i18n: "(DEPRECATED)" is a keyword, must be translated consistently
44 _("(DEPRECATED)"),
44 _("(DEPRECATED)"),
45 # i18n: "(EXPERIMENTAL)" is a keyword, must be translated consistently
45 # i18n: "(EXPERIMENTAL)" is a keyword, must be translated consistently
46 _("(EXPERIMENTAL)"),
46 _("(EXPERIMENTAL)"),
47 }
47 }
48
48
49 def listexts(header, exts, indent=1, showdeprecated=False):
49 def listexts(header, exts, indent=1, showdeprecated=False):
50 '''return a text listing of the given extensions'''
50 '''return a text listing of the given extensions'''
51 rst = []
51 rst = []
52 if exts:
52 if exts:
53 for name, desc in sorted(exts.iteritems()):
53 for name, desc in sorted(exts.iteritems()):
54 if not showdeprecated and any(w in desc for w in _exclkeywords):
54 if not showdeprecated and any(w in desc for w in _exclkeywords):
55 continue
55 continue
56 rst.append('%s:%s: %s\n' % (' ' * indent, name, desc))
56 rst.append('%s:%s: %s\n' % (' ' * indent, name, desc))
57 if rst:
57 if rst:
58 rst.insert(0, '\n%s\n\n' % header)
58 rst.insert(0, '\n%s\n\n' % header)
59 return rst
59 return rst
60
60
61 def extshelp(ui):
61 def extshelp(ui):
62 rst = loaddoc('extensions')(ui).splitlines(True)
62 rst = loaddoc('extensions')(ui).splitlines(True)
63 rst.extend(listexts(
63 rst.extend(listexts(
64 _('enabled extensions:'), extensions.enabled(), showdeprecated=True))
64 _('enabled extensions:'), extensions.enabled(), showdeprecated=True))
65 rst.extend(listexts(_('disabled extensions:'), extensions.disabled(),
65 rst.extend(listexts(_('disabled extensions:'), extensions.disabled(),
66 showdeprecated=ui.verbose))
66 showdeprecated=ui.verbose))
67 doc = ''.join(rst)
67 doc = ''.join(rst)
68 return doc
68 return doc
69
69
70 def optrst(header, options, verbose):
70 def optrst(header, options, verbose):
71 data = []
71 data = []
72 multioccur = False
72 multioccur = False
73 for option in options:
73 for option in options:
74 if len(option) == 5:
74 if len(option) == 5:
75 shortopt, longopt, default, desc, optlabel = option
75 shortopt, longopt, default, desc, optlabel = option
76 else:
76 else:
77 shortopt, longopt, default, desc = option
77 shortopt, longopt, default, desc = option
78 optlabel = _("VALUE") # default label
78 optlabel = _("VALUE") # default label
79
79
80 if not verbose and any(w in desc for w in _exclkeywords):
80 if not verbose and any(w in desc for w in _exclkeywords):
81 continue
81 continue
82
82
83 so = ''
83 so = ''
84 if shortopt:
84 if shortopt:
85 so = '-' + shortopt
85 so = '-' + shortopt
86 lo = '--' + longopt
86 lo = '--' + longopt
87 if default:
87 if default:
88 # default is of unknown type, and in Python 2 we abused
88 # default is of unknown type, and in Python 2 we abused
89 # the %s-shows-repr property to handle integers etc. To
89 # the %s-shows-repr property to handle integers etc. To
90 # match that behavior on Python 3, we do str(default) and
90 # match that behavior on Python 3, we do str(default) and
91 # then convert it to bytes.
91 # then convert it to bytes.
92 desc += _(" (default: %s)") % pycompat.bytestr(default)
92 desc += _(" (default: %s)") % pycompat.bytestr(default)
93
93
94 if isinstance(default, list):
94 if isinstance(default, list):
95 lo += " %s [+]" % optlabel
95 lo += " %s [+]" % optlabel
96 multioccur = True
96 multioccur = True
97 elif (default is not None) and not isinstance(default, bool):
97 elif (default is not None) and not isinstance(default, bool):
98 lo += " %s" % optlabel
98 lo += " %s" % optlabel
99
99
100 data.append((so, lo, desc))
100 data.append((so, lo, desc))
101
101
102 if multioccur:
102 if multioccur:
103 header += (_(" ([+] can be repeated)"))
103 header += (_(" ([+] can be repeated)"))
104
104
105 rst = ['\n%s:\n\n' % header]
105 rst = ['\n%s:\n\n' % header]
106 rst.extend(minirst.maketable(data, 1))
106 rst.extend(minirst.maketable(data, 1))
107
107
108 return ''.join(rst)
108 return ''.join(rst)
109
109
110 def indicateomitted(rst, omitted, notomitted=None):
110 def indicateomitted(rst, omitted, notomitted=None):
111 rst.append('\n\n.. container:: omitted\n\n %s\n\n' % omitted)
111 rst.append('\n\n.. container:: omitted\n\n %s\n\n' % omitted)
112 if notomitted:
112 if notomitted:
113 rst.append('\n\n.. container:: notomitted\n\n %s\n\n' % notomitted)
113 rst.append('\n\n.. container:: notomitted\n\n %s\n\n' % notomitted)
114
114
115 def filtercmd(ui, cmd, kw, doc):
115 def filtercmd(ui, cmd, kw, doc):
116 if not ui.debugflag and cmd.startswith("debug") and kw != "debug":
116 if not ui.debugflag and cmd.startswith("debug") and kw != "debug":
117 return True
117 return True
118 if not ui.verbose and doc and any(w in doc for w in _exclkeywords):
118 if not ui.verbose and doc and any(w in doc for w in _exclkeywords):
119 return True
119 return True
120 return False
120 return False
121
121
122 def topicmatch(ui, commands, kw):
122 def topicmatch(ui, commands, kw):
123 """Return help topics matching kw.
123 """Return help topics matching kw.
124
124
125 Returns {'section': [(name, summary), ...], ...} where section is
125 Returns {'section': [(name, summary), ...], ...} where section is
126 one of topics, commands, extensions, or extensioncommands.
126 one of topics, commands, extensions, or extensioncommands.
127 """
127 """
128 kw = encoding.lower(kw)
128 kw = encoding.lower(kw)
129 def lowercontains(container):
129 def lowercontains(container):
130 return kw in encoding.lower(container) # translated in helptable
130 return kw in encoding.lower(container) # translated in helptable
131 results = {'topics': [],
131 results = {'topics': [],
132 'commands': [],
132 'commands': [],
133 'extensions': [],
133 'extensions': [],
134 'extensioncommands': [],
134 'extensioncommands': [],
135 }
135 }
136 for names, header, doc in helptable:
136 for names, header, doc in helptable:
137 # Old extensions may use a str as doc.
137 # Old extensions may use a str as doc.
138 if (sum(map(lowercontains, names))
138 if (sum(map(lowercontains, names))
139 or lowercontains(header)
139 or lowercontains(header)
140 or (callable(doc) and lowercontains(doc(ui)))):
140 or (callable(doc) and lowercontains(doc(ui)))):
141 results['topics'].append((names[0], header))
141 results['topics'].append((names[0], header))
142 for cmd, entry in commands.table.iteritems():
142 for cmd, entry in commands.table.iteritems():
143 if len(entry) == 3:
143 if len(entry) == 3:
144 summary = entry[2]
144 summary = entry[2]
145 else:
145 else:
146 summary = ''
146 summary = ''
147 # translate docs *before* searching there
147 # translate docs *before* searching there
148 docs = _(pycompat.getdoc(entry[0])) or ''
148 docs = _(pycompat.getdoc(entry[0])) or ''
149 if kw in cmd or lowercontains(summary) or lowercontains(docs):
149 if kw in cmd or lowercontains(summary) or lowercontains(docs):
150 doclines = docs.splitlines()
150 doclines = docs.splitlines()
151 if doclines:
151 if doclines:
152 summary = doclines[0]
152 summary = doclines[0]
153 cmdname = cmd.partition('|')[0].lstrip('^')
153 cmdname = cmdutil.parsealiases(cmd)[0]
154 if filtercmd(ui, cmdname, kw, docs):
154 if filtercmd(ui, cmdname, kw, docs):
155 continue
155 continue
156 results['commands'].append((cmdname, summary))
156 results['commands'].append((cmdname, summary))
157 for name, docs in itertools.chain(
157 for name, docs in itertools.chain(
158 extensions.enabled(False).iteritems(),
158 extensions.enabled(False).iteritems(),
159 extensions.disabled().iteritems()):
159 extensions.disabled().iteritems()):
160 if not docs:
160 if not docs:
161 continue
161 continue
162 name = name.rpartition('.')[-1]
162 name = name.rpartition('.')[-1]
163 if lowercontains(name) or lowercontains(docs):
163 if lowercontains(name) or lowercontains(docs):
164 # extension docs are already translated
164 # extension docs are already translated
165 results['extensions'].append((name, docs.splitlines()[0]))
165 results['extensions'].append((name, docs.splitlines()[0]))
166 try:
166 try:
167 mod = extensions.load(ui, name, '')
167 mod = extensions.load(ui, name, '')
168 except ImportError:
168 except ImportError:
169 # debug message would be printed in extensions.load()
169 # debug message would be printed in extensions.load()
170 continue
170 continue
171 for cmd, entry in getattr(mod, 'cmdtable', {}).iteritems():
171 for cmd, entry in getattr(mod, 'cmdtable', {}).iteritems():
172 if kw in cmd or (len(entry) > 2 and lowercontains(entry[2])):
172 if kw in cmd or (len(entry) > 2 and lowercontains(entry[2])):
173 cmdname = cmd.partition('|')[0].lstrip('^')
173 cmdname = cmdutil.parsealiases(cmd)[0]
174 cmddoc = pycompat.getdoc(entry[0])
174 cmddoc = pycompat.getdoc(entry[0])
175 if cmddoc:
175 if cmddoc:
176 cmddoc = gettext(cmddoc).splitlines()[0]
176 cmddoc = gettext(cmddoc).splitlines()[0]
177 else:
177 else:
178 cmddoc = _('(no help text available)')
178 cmddoc = _('(no help text available)')
179 if filtercmd(ui, cmdname, kw, cmddoc):
179 if filtercmd(ui, cmdname, kw, cmddoc):
180 continue
180 continue
181 results['extensioncommands'].append((cmdname, cmddoc))
181 results['extensioncommands'].append((cmdname, cmddoc))
182 return results
182 return results
183
183
184 def loaddoc(topic, subdir=None):
184 def loaddoc(topic, subdir=None):
185 """Return a delayed loader for help/topic.txt."""
185 """Return a delayed loader for help/topic.txt."""
186
186
187 def loader(ui):
187 def loader(ui):
188 docdir = os.path.join(util.datapath, 'help')
188 docdir = os.path.join(util.datapath, 'help')
189 if subdir:
189 if subdir:
190 docdir = os.path.join(docdir, subdir)
190 docdir = os.path.join(docdir, subdir)
191 path = os.path.join(docdir, topic + ".txt")
191 path = os.path.join(docdir, topic + ".txt")
192 doc = gettext(util.readfile(path))
192 doc = gettext(util.readfile(path))
193 for rewriter in helphooks.get(topic, []):
193 for rewriter in helphooks.get(topic, []):
194 doc = rewriter(ui, topic, doc)
194 doc = rewriter(ui, topic, doc)
195 return doc
195 return doc
196
196
197 return loader
197 return loader
198
198
199 internalstable = sorted([
199 internalstable = sorted([
200 (['bundles'], _('Bundles'),
200 (['bundles'], _('Bundles'),
201 loaddoc('bundles', subdir='internals')),
201 loaddoc('bundles', subdir='internals')),
202 (['censor'], _('Censor'),
202 (['censor'], _('Censor'),
203 loaddoc('censor', subdir='internals')),
203 loaddoc('censor', subdir='internals')),
204 (['changegroups'], _('Changegroups'),
204 (['changegroups'], _('Changegroups'),
205 loaddoc('changegroups', subdir='internals')),
205 loaddoc('changegroups', subdir='internals')),
206 (['config'], _('Config Registrar'),
206 (['config'], _('Config Registrar'),
207 loaddoc('config', subdir='internals')),
207 loaddoc('config', subdir='internals')),
208 (['requirements'], _('Repository Requirements'),
208 (['requirements'], _('Repository Requirements'),
209 loaddoc('requirements', subdir='internals')),
209 loaddoc('requirements', subdir='internals')),
210 (['revlogs'], _('Revision Logs'),
210 (['revlogs'], _('Revision Logs'),
211 loaddoc('revlogs', subdir='internals')),
211 loaddoc('revlogs', subdir='internals')),
212 (['wireprotocol'], _('Wire Protocol'),
212 (['wireprotocol'], _('Wire Protocol'),
213 loaddoc('wireprotocol', subdir='internals')),
213 loaddoc('wireprotocol', subdir='internals')),
214 ])
214 ])
215
215
216 def internalshelp(ui):
216 def internalshelp(ui):
217 """Generate the index for the "internals" topic."""
217 """Generate the index for the "internals" topic."""
218 lines = ['To access a subtopic, use "hg help internals.{subtopic-name}"\n',
218 lines = ['To access a subtopic, use "hg help internals.{subtopic-name}"\n',
219 '\n']
219 '\n']
220 for names, header, doc in internalstable:
220 for names, header, doc in internalstable:
221 lines.append(' :%s: %s\n' % (names[0], header))
221 lines.append(' :%s: %s\n' % (names[0], header))
222
222
223 return ''.join(lines)
223 return ''.join(lines)
224
224
225 helptable = sorted([
225 helptable = sorted([
226 (['bundlespec'], _("Bundle File Formats"), loaddoc('bundlespec')),
226 (['bundlespec'], _("Bundle File Formats"), loaddoc('bundlespec')),
227 (['color'], _("Colorizing Outputs"), loaddoc('color')),
227 (['color'], _("Colorizing Outputs"), loaddoc('color')),
228 (["config", "hgrc"], _("Configuration Files"), loaddoc('config')),
228 (["config", "hgrc"], _("Configuration Files"), loaddoc('config')),
229 (["dates"], _("Date Formats"), loaddoc('dates')),
229 (["dates"], _("Date Formats"), loaddoc('dates')),
230 (["flags"], _("Command-line flags"), loaddoc('flags')),
230 (["flags"], _("Command-line flags"), loaddoc('flags')),
231 (["patterns"], _("File Name Patterns"), loaddoc('patterns')),
231 (["patterns"], _("File Name Patterns"), loaddoc('patterns')),
232 (['environment', 'env'], _('Environment Variables'),
232 (['environment', 'env'], _('Environment Variables'),
233 loaddoc('environment')),
233 loaddoc('environment')),
234 (['revisions', 'revs', 'revsets', 'revset', 'multirevs', 'mrevs'],
234 (['revisions', 'revs', 'revsets', 'revset', 'multirevs', 'mrevs'],
235 _('Specifying Revisions'), loaddoc('revisions')),
235 _('Specifying Revisions'), loaddoc('revisions')),
236 (['filesets', 'fileset'], _("Specifying File Sets"), loaddoc('filesets')),
236 (['filesets', 'fileset'], _("Specifying File Sets"), loaddoc('filesets')),
237 (['diffs'], _('Diff Formats'), loaddoc('diffs')),
237 (['diffs'], _('Diff Formats'), loaddoc('diffs')),
238 (['merge-tools', 'mergetools', 'mergetool'], _('Merge Tools'),
238 (['merge-tools', 'mergetools', 'mergetool'], _('Merge Tools'),
239 loaddoc('merge-tools')),
239 loaddoc('merge-tools')),
240 (['templating', 'templates', 'template', 'style'], _('Template Usage'),
240 (['templating', 'templates', 'template', 'style'], _('Template Usage'),
241 loaddoc('templates')),
241 loaddoc('templates')),
242 (['urls'], _('URL Paths'), loaddoc('urls')),
242 (['urls'], _('URL Paths'), loaddoc('urls')),
243 (["extensions"], _("Using Additional Features"), extshelp),
243 (["extensions"], _("Using Additional Features"), extshelp),
244 (["subrepos", "subrepo"], _("Subrepositories"), loaddoc('subrepos')),
244 (["subrepos", "subrepo"], _("Subrepositories"), loaddoc('subrepos')),
245 (["hgweb"], _("Configuring hgweb"), loaddoc('hgweb')),
245 (["hgweb"], _("Configuring hgweb"), loaddoc('hgweb')),
246 (["glossary"], _("Glossary"), loaddoc('glossary')),
246 (["glossary"], _("Glossary"), loaddoc('glossary')),
247 (["hgignore", "ignore"], _("Syntax for Mercurial Ignore Files"),
247 (["hgignore", "ignore"], _("Syntax for Mercurial Ignore Files"),
248 loaddoc('hgignore')),
248 loaddoc('hgignore')),
249 (["phases"], _("Working with Phases"), loaddoc('phases')),
249 (["phases"], _("Working with Phases"), loaddoc('phases')),
250 (['scripting'], _('Using Mercurial from scripts and automation'),
250 (['scripting'], _('Using Mercurial from scripts and automation'),
251 loaddoc('scripting')),
251 loaddoc('scripting')),
252 (['internals'], _("Technical implementation topics"),
252 (['internals'], _("Technical implementation topics"),
253 internalshelp),
253 internalshelp),
254 (['pager'], _("Pager Support"), loaddoc('pager')),
254 (['pager'], _("Pager Support"), loaddoc('pager')),
255 ])
255 ])
256
256
257 # Maps topics with sub-topics to a list of their sub-topics.
257 # Maps topics with sub-topics to a list of their sub-topics.
258 subtopics = {
258 subtopics = {
259 'internals': internalstable,
259 'internals': internalstable,
260 }
260 }
261
261
262 # Map topics to lists of callable taking the current topic help and
262 # Map topics to lists of callable taking the current topic help and
263 # returning the updated version
263 # returning the updated version
264 helphooks = {}
264 helphooks = {}
265
265
266 def addtopichook(topic, rewriter):
266 def addtopichook(topic, rewriter):
267 helphooks.setdefault(topic, []).append(rewriter)
267 helphooks.setdefault(topic, []).append(rewriter)
268
268
269 def makeitemsdoc(ui, topic, doc, marker, items, dedent=False):
269 def makeitemsdoc(ui, topic, doc, marker, items, dedent=False):
270 """Extract docstring from the items key to function mapping, build a
270 """Extract docstring from the items key to function mapping, build a
271 single documentation block and use it to overwrite the marker in doc.
271 single documentation block and use it to overwrite the marker in doc.
272 """
272 """
273 entries = []
273 entries = []
274 for name in sorted(items):
274 for name in sorted(items):
275 text = (pycompat.getdoc(items[name]) or '').rstrip()
275 text = (pycompat.getdoc(items[name]) or '').rstrip()
276 if (not text
276 if (not text
277 or not ui.verbose and any(w in text for w in _exclkeywords)):
277 or not ui.verbose and any(w in text for w in _exclkeywords)):
278 continue
278 continue
279 text = gettext(text)
279 text = gettext(text)
280 if dedent:
280 if dedent:
281 # Abuse latin1 to use textwrap.dedent() on bytes.
281 # Abuse latin1 to use textwrap.dedent() on bytes.
282 text = textwrap.dedent(text.decode('latin1')).encode('latin1')
282 text = textwrap.dedent(text.decode('latin1')).encode('latin1')
283 lines = text.splitlines()
283 lines = text.splitlines()
284 doclines = [(lines[0])]
284 doclines = [(lines[0])]
285 for l in lines[1:]:
285 for l in lines[1:]:
286 # Stop once we find some Python doctest
286 # Stop once we find some Python doctest
287 if l.strip().startswith('>>>'):
287 if l.strip().startswith('>>>'):
288 break
288 break
289 if dedent:
289 if dedent:
290 doclines.append(l.rstrip())
290 doclines.append(l.rstrip())
291 else:
291 else:
292 doclines.append(' ' + l.strip())
292 doclines.append(' ' + l.strip())
293 entries.append('\n'.join(doclines))
293 entries.append('\n'.join(doclines))
294 entries = '\n\n'.join(entries)
294 entries = '\n\n'.join(entries)
295 return doc.replace(marker, entries)
295 return doc.replace(marker, entries)
296
296
297 def addtopicsymbols(topic, marker, symbols, dedent=False):
297 def addtopicsymbols(topic, marker, symbols, dedent=False):
298 def add(ui, topic, doc):
298 def add(ui, topic, doc):
299 return makeitemsdoc(ui, topic, doc, marker, symbols, dedent=dedent)
299 return makeitemsdoc(ui, topic, doc, marker, symbols, dedent=dedent)
300 addtopichook(topic, add)
300 addtopichook(topic, add)
301
301
302 addtopicsymbols('bundlespec', '.. bundlecompressionmarker',
302 addtopicsymbols('bundlespec', '.. bundlecompressionmarker',
303 util.bundlecompressiontopics())
303 util.bundlecompressiontopics())
304 addtopicsymbols('filesets', '.. predicatesmarker', fileset.symbols)
304 addtopicsymbols('filesets', '.. predicatesmarker', fileset.symbols)
305 addtopicsymbols('merge-tools', '.. internaltoolsmarker',
305 addtopicsymbols('merge-tools', '.. internaltoolsmarker',
306 filemerge.internalsdoc)
306 filemerge.internalsdoc)
307 addtopicsymbols('revisions', '.. predicatesmarker', revset.symbols)
307 addtopicsymbols('revisions', '.. predicatesmarker', revset.symbols)
308 addtopicsymbols('templates', '.. keywordsmarker', templatekw.keywords)
308 addtopicsymbols('templates', '.. keywordsmarker', templatekw.keywords)
309 addtopicsymbols('templates', '.. filtersmarker', templatefilters.filters)
309 addtopicsymbols('templates', '.. filtersmarker', templatefilters.filters)
310 addtopicsymbols('templates', '.. functionsmarker', templater.funcs)
310 addtopicsymbols('templates', '.. functionsmarker', templater.funcs)
311 addtopicsymbols('hgweb', '.. webcommandsmarker', webcommands.commands,
311 addtopicsymbols('hgweb', '.. webcommandsmarker', webcommands.commands,
312 dedent=True)
312 dedent=True)
313
313
314 def help_(ui, commands, name, unknowncmd=False, full=True, subtopic=None,
314 def help_(ui, commands, name, unknowncmd=False, full=True, subtopic=None,
315 **opts):
315 **opts):
316 '''
316 '''
317 Generate the help for 'name' as unformatted restructured text. If
317 Generate the help for 'name' as unformatted restructured text. If
318 'name' is None, describe the commands available.
318 'name' is None, describe the commands available.
319 '''
319 '''
320
320
321 opts = pycompat.byteskwargs(opts)
321 opts = pycompat.byteskwargs(opts)
322
322
323 def helpcmd(name, subtopic=None):
323 def helpcmd(name, subtopic=None):
324 try:
324 try:
325 aliases, entry = cmdutil.findcmd(name, commands.table,
325 aliases, entry = cmdutil.findcmd(name, commands.table,
326 strict=unknowncmd)
326 strict=unknowncmd)
327 except error.AmbiguousCommand as inst:
327 except error.AmbiguousCommand as inst:
328 # py3k fix: except vars can't be used outside the scope of the
328 # py3k fix: except vars can't be used outside the scope of the
329 # except block, nor can be used inside a lambda. python issue4617
329 # except block, nor can be used inside a lambda. python issue4617
330 prefix = inst.args[0]
330 prefix = inst.args[0]
331 select = lambda c: c.lstrip('^').startswith(prefix)
331 select = lambda c: cmdutil.parsealiases(c)[0].startswith(prefix)
332 rst = helplist(select)
332 rst = helplist(select)
333 return rst
333 return rst
334
334
335 rst = []
335 rst = []
336
336
337 # check if it's an invalid alias and display its error if it is
337 # check if it's an invalid alias and display its error if it is
338 if getattr(entry[0], 'badalias', None):
338 if getattr(entry[0], 'badalias', None):
339 rst.append(entry[0].badalias + '\n')
339 rst.append(entry[0].badalias + '\n')
340 if entry[0].unknowncmd:
340 if entry[0].unknowncmd:
341 try:
341 try:
342 rst.extend(helpextcmd(entry[0].cmdname))
342 rst.extend(helpextcmd(entry[0].cmdname))
343 except error.UnknownCommand:
343 except error.UnknownCommand:
344 pass
344 pass
345 return rst
345 return rst
346
346
347 # synopsis
347 # synopsis
348 if len(entry) > 2:
348 if len(entry) > 2:
349 if entry[2].startswith('hg'):
349 if entry[2].startswith('hg'):
350 rst.append("%s\n" % entry[2])
350 rst.append("%s\n" % entry[2])
351 else:
351 else:
352 rst.append('hg %s %s\n' % (aliases[0], entry[2]))
352 rst.append('hg %s %s\n' % (aliases[0], entry[2]))
353 else:
353 else:
354 rst.append('hg %s\n' % aliases[0])
354 rst.append('hg %s\n' % aliases[0])
355 # aliases
355 # aliases
356 if full and not ui.quiet and len(aliases) > 1:
356 if full and not ui.quiet and len(aliases) > 1:
357 rst.append(_("\naliases: %s\n") % ', '.join(aliases[1:]))
357 rst.append(_("\naliases: %s\n") % ', '.join(aliases[1:]))
358 rst.append('\n')
358 rst.append('\n')
359
359
360 # description
360 # description
361 doc = gettext(pycompat.getdoc(entry[0]))
361 doc = gettext(pycompat.getdoc(entry[0]))
362 if not doc:
362 if not doc:
363 doc = _("(no help text available)")
363 doc = _("(no help text available)")
364 if util.safehasattr(entry[0], 'definition'): # aliased command
364 if util.safehasattr(entry[0], 'definition'): # aliased command
365 source = entry[0].source
365 source = entry[0].source
366 if entry[0].definition.startswith('!'): # shell alias
366 if entry[0].definition.startswith('!'): # shell alias
367 doc = (_('shell alias for::\n\n %s\n\ndefined by: %s\n') %
367 doc = (_('shell alias for::\n\n %s\n\ndefined by: %s\n') %
368 (entry[0].definition[1:], source))
368 (entry[0].definition[1:], source))
369 else:
369 else:
370 doc = (_('alias for: hg %s\n\n%s\n\ndefined by: %s\n') %
370 doc = (_('alias for: hg %s\n\n%s\n\ndefined by: %s\n') %
371 (entry[0].definition, doc, source))
371 (entry[0].definition, doc, source))
372 doc = doc.splitlines(True)
372 doc = doc.splitlines(True)
373 if ui.quiet or not full:
373 if ui.quiet or not full:
374 rst.append(doc[0])
374 rst.append(doc[0])
375 else:
375 else:
376 rst.extend(doc)
376 rst.extend(doc)
377 rst.append('\n')
377 rst.append('\n')
378
378
379 # check if this command shadows a non-trivial (multi-line)
379 # check if this command shadows a non-trivial (multi-line)
380 # extension help text
380 # extension help text
381 try:
381 try:
382 mod = extensions.find(name)
382 mod = extensions.find(name)
383 doc = gettext(pycompat.getdoc(mod)) or ''
383 doc = gettext(pycompat.getdoc(mod)) or ''
384 if '\n' in doc.strip():
384 if '\n' in doc.strip():
385 msg = _("(use 'hg help -e %s' to show help for "
385 msg = _("(use 'hg help -e %s' to show help for "
386 "the %s extension)") % (name, name)
386 "the %s extension)") % (name, name)
387 rst.append('\n%s\n' % msg)
387 rst.append('\n%s\n' % msg)
388 except KeyError:
388 except KeyError:
389 pass
389 pass
390
390
391 # options
391 # options
392 if not ui.quiet and entry[1]:
392 if not ui.quiet and entry[1]:
393 rst.append(optrst(_("options"), entry[1], ui.verbose))
393 rst.append(optrst(_("options"), entry[1], ui.verbose))
394
394
395 if ui.verbose:
395 if ui.verbose:
396 rst.append(optrst(_("global options"),
396 rst.append(optrst(_("global options"),
397 commands.globalopts, ui.verbose))
397 commands.globalopts, ui.verbose))
398
398
399 if not ui.verbose:
399 if not ui.verbose:
400 if not full:
400 if not full:
401 rst.append(_("\n(use 'hg %s -h' to show more help)\n")
401 rst.append(_("\n(use 'hg %s -h' to show more help)\n")
402 % name)
402 % name)
403 elif not ui.quiet:
403 elif not ui.quiet:
404 rst.append(_('\n(some details hidden, use --verbose '
404 rst.append(_('\n(some details hidden, use --verbose '
405 'to show complete help)'))
405 'to show complete help)'))
406
406
407 return rst
407 return rst
408
408
409
409
410 def helplist(select=None, **opts):
410 def helplist(select=None, **opts):
411 # list of commands
411 # list of commands
412 if name == "shortlist":
412 if name == "shortlist":
413 header = _('basic commands:\n\n')
413 header = _('basic commands:\n\n')
414 elif name == "debug":
414 elif name == "debug":
415 header = _('debug commands (internal and unsupported):\n\n')
415 header = _('debug commands (internal and unsupported):\n\n')
416 else:
416 else:
417 header = _('list of commands:\n\n')
417 header = _('list of commands:\n\n')
418
418
419 h = {}
419 h = {}
420 cmds = {}
420 cmds = {}
421 for c, e in commands.table.iteritems():
421 for c, e in commands.table.iteritems():
422 f = c.partition("|")[0]
422 fs = cmdutil.parsealiases(c)
423 if select and not select(f):
423 f = fs[0]
424 p = ''
425 if c.startswith("^"):
426 p = '^'
427 if select and not select(p + f):
424 continue
428 continue
425 if (not select and name != 'shortlist' and
429 if (not select and name != 'shortlist' and
426 e[0].__module__ != commands.__name__):
430 e[0].__module__ != commands.__name__):
427 continue
431 continue
428 if name == "shortlist" and not f.startswith("^"):
432 if name == "shortlist" and not p:
429 continue
433 continue
430 f = f.lstrip("^")
431 doc = pycompat.getdoc(e[0])
434 doc = pycompat.getdoc(e[0])
432 if filtercmd(ui, f, name, doc):
435 if filtercmd(ui, f, name, doc):
433 continue
436 continue
434 doc = gettext(doc)
437 doc = gettext(doc)
435 if not doc:
438 if not doc:
436 doc = _("(no help text available)")
439 doc = _("(no help text available)")
437 h[f] = doc.splitlines()[0].rstrip()
440 h[f] = doc.splitlines()[0].rstrip()
438 cmds[f] = c.lstrip("^")
441 cmds[f] = '|'.join(fs)
439
442
440 rst = []
443 rst = []
441 if not h:
444 if not h:
442 if not ui.quiet:
445 if not ui.quiet:
443 rst.append(_('no commands defined\n'))
446 rst.append(_('no commands defined\n'))
444 return rst
447 return rst
445
448
446 if not ui.quiet:
449 if not ui.quiet:
447 rst.append(header)
450 rst.append(header)
448 fns = sorted(h)
451 fns = sorted(h)
449 for f in fns:
452 for f in fns:
450 if ui.verbose:
453 if ui.verbose:
451 commacmds = cmds[f].replace("|",", ")
454 commacmds = cmds[f].replace("|",", ")
452 rst.append(" :%s: %s\n" % (commacmds, h[f]))
455 rst.append(" :%s: %s\n" % (commacmds, h[f]))
453 else:
456 else:
454 rst.append(' :%s: %s\n' % (f, h[f]))
457 rst.append(' :%s: %s\n' % (f, h[f]))
455
458
456 ex = opts.get
459 ex = opts.get
457 anyopts = (ex(r'keyword') or not (ex(r'command') or ex(r'extension')))
460 anyopts = (ex(r'keyword') or not (ex(r'command') or ex(r'extension')))
458 if not name and anyopts:
461 if not name and anyopts:
459 exts = listexts(_('enabled extensions:'), extensions.enabled())
462 exts = listexts(_('enabled extensions:'), extensions.enabled())
460 if exts:
463 if exts:
461 rst.append('\n')
464 rst.append('\n')
462 rst.extend(exts)
465 rst.extend(exts)
463
466
464 rst.append(_("\nadditional help topics:\n\n"))
467 rst.append(_("\nadditional help topics:\n\n"))
465 topics = []
468 topics = []
466 for names, header, doc in helptable:
469 for names, header, doc in helptable:
467 topics.append((names[0], header))
470 topics.append((names[0], header))
468 for t, desc in topics:
471 for t, desc in topics:
469 rst.append(" :%s: %s\n" % (t, desc))
472 rst.append(" :%s: %s\n" % (t, desc))
470
473
471 if ui.quiet:
474 if ui.quiet:
472 pass
475 pass
473 elif ui.verbose:
476 elif ui.verbose:
474 rst.append('\n%s\n' % optrst(_("global options"),
477 rst.append('\n%s\n' % optrst(_("global options"),
475 commands.globalopts, ui.verbose))
478 commands.globalopts, ui.verbose))
476 if name == 'shortlist':
479 if name == 'shortlist':
477 rst.append(_("\n(use 'hg help' for the full list "
480 rst.append(_("\n(use 'hg help' for the full list "
478 "of commands)\n"))
481 "of commands)\n"))
479 else:
482 else:
480 if name == 'shortlist':
483 if name == 'shortlist':
481 rst.append(_("\n(use 'hg help' for the full list of commands "
484 rst.append(_("\n(use 'hg help' for the full list of commands "
482 "or 'hg -v' for details)\n"))
485 "or 'hg -v' for details)\n"))
483 elif name and not full:
486 elif name and not full:
484 rst.append(_("\n(use 'hg help %s' to show the full help "
487 rst.append(_("\n(use 'hg help %s' to show the full help "
485 "text)\n") % name)
488 "text)\n") % name)
486 elif name and cmds and name in cmds.keys():
489 elif name and cmds and name in cmds.keys():
487 rst.append(_("\n(use 'hg help -v -e %s' to show built-in "
490 rst.append(_("\n(use 'hg help -v -e %s' to show built-in "
488 "aliases and global options)\n") % name)
491 "aliases and global options)\n") % name)
489 else:
492 else:
490 rst.append(_("\n(use 'hg help -v%s' to show built-in aliases "
493 rst.append(_("\n(use 'hg help -v%s' to show built-in aliases "
491 "and global options)\n")
494 "and global options)\n")
492 % (name and " " + name or ""))
495 % (name and " " + name or ""))
493 return rst
496 return rst
494
497
495 def helptopic(name, subtopic=None):
498 def helptopic(name, subtopic=None):
496 # Look for sub-topic entry first.
499 # Look for sub-topic entry first.
497 header, doc = None, None
500 header, doc = None, None
498 if subtopic and name in subtopics:
501 if subtopic and name in subtopics:
499 for names, header, doc in subtopics[name]:
502 for names, header, doc in subtopics[name]:
500 if subtopic in names:
503 if subtopic in names:
501 break
504 break
502
505
503 if not header:
506 if not header:
504 for names, header, doc in helptable:
507 for names, header, doc in helptable:
505 if name in names:
508 if name in names:
506 break
509 break
507 else:
510 else:
508 raise error.UnknownCommand(name)
511 raise error.UnknownCommand(name)
509
512
510 rst = [minirst.section(header)]
513 rst = [minirst.section(header)]
511
514
512 # description
515 # description
513 if not doc:
516 if not doc:
514 rst.append(" %s\n" % _("(no help text available)"))
517 rst.append(" %s\n" % _("(no help text available)"))
515 if callable(doc):
518 if callable(doc):
516 rst += [" %s\n" % l for l in doc(ui).splitlines()]
519 rst += [" %s\n" % l for l in doc(ui).splitlines()]
517
520
518 if not ui.verbose:
521 if not ui.verbose:
519 omitted = _('(some details hidden, use --verbose'
522 omitted = _('(some details hidden, use --verbose'
520 ' to show complete help)')
523 ' to show complete help)')
521 indicateomitted(rst, omitted)
524 indicateomitted(rst, omitted)
522
525
523 try:
526 try:
524 cmdutil.findcmd(name, commands.table)
527 cmdutil.findcmd(name, commands.table)
525 rst.append(_("\nuse 'hg help -c %s' to see help for "
528 rst.append(_("\nuse 'hg help -c %s' to see help for "
526 "the %s command\n") % (name, name))
529 "the %s command\n") % (name, name))
527 except error.UnknownCommand:
530 except error.UnknownCommand:
528 pass
531 pass
529 return rst
532 return rst
530
533
531 def helpext(name, subtopic=None):
534 def helpext(name, subtopic=None):
532 try:
535 try:
533 mod = extensions.find(name)
536 mod = extensions.find(name)
534 doc = gettext(pycompat.getdoc(mod)) or _('no help text available')
537 doc = gettext(pycompat.getdoc(mod)) or _('no help text available')
535 except KeyError:
538 except KeyError:
536 mod = None
539 mod = None
537 doc = extensions.disabledext(name)
540 doc = extensions.disabledext(name)
538 if not doc:
541 if not doc:
539 raise error.UnknownCommand(name)
542 raise error.UnknownCommand(name)
540
543
541 if '\n' not in doc:
544 if '\n' not in doc:
542 head, tail = doc, ""
545 head, tail = doc, ""
543 else:
546 else:
544 head, tail = doc.split('\n', 1)
547 head, tail = doc.split('\n', 1)
545 rst = [_('%s extension - %s\n\n') % (name.rpartition('.')[-1], head)]
548 rst = [_('%s extension - %s\n\n') % (name.rpartition('.')[-1], head)]
546 if tail:
549 if tail:
547 rst.extend(tail.splitlines(True))
550 rst.extend(tail.splitlines(True))
548 rst.append('\n')
551 rst.append('\n')
549
552
550 if not ui.verbose:
553 if not ui.verbose:
551 omitted = _('(some details hidden, use --verbose'
554 omitted = _('(some details hidden, use --verbose'
552 ' to show complete help)')
555 ' to show complete help)')
553 indicateomitted(rst, omitted)
556 indicateomitted(rst, omitted)
554
557
555 if mod:
558 if mod:
556 try:
559 try:
557 ct = mod.cmdtable
560 ct = mod.cmdtable
558 except AttributeError:
561 except AttributeError:
559 ct = {}
562 ct = {}
560 modcmds = set([c.partition('|')[0] for c in ct])
563 modcmds = set([c.partition('|')[0] for c in ct])
561 rst.extend(helplist(modcmds.__contains__))
564 rst.extend(helplist(modcmds.__contains__))
562 else:
565 else:
563 rst.append(_("(use 'hg help extensions' for information on enabling"
566 rst.append(_("(use 'hg help extensions' for information on enabling"
564 " extensions)\n"))
567 " extensions)\n"))
565 return rst
568 return rst
566
569
567 def helpextcmd(name, subtopic=None):
570 def helpextcmd(name, subtopic=None):
568 cmd, ext, mod = extensions.disabledcmd(ui, name,
571 cmd, ext, mod = extensions.disabledcmd(ui, name,
569 ui.configbool('ui', 'strict'))
572 ui.configbool('ui', 'strict'))
570 doc = gettext(pycompat.getdoc(mod)).splitlines()[0]
573 doc = gettext(pycompat.getdoc(mod)).splitlines()[0]
571
574
572 rst = listexts(_("'%s' is provided by the following "
575 rst = listexts(_("'%s' is provided by the following "
573 "extension:") % cmd, {ext: doc}, indent=4,
576 "extension:") % cmd, {ext: doc}, indent=4,
574 showdeprecated=True)
577 showdeprecated=True)
575 rst.append('\n')
578 rst.append('\n')
576 rst.append(_("(use 'hg help extensions' for information on enabling "
579 rst.append(_("(use 'hg help extensions' for information on enabling "
577 "extensions)\n"))
580 "extensions)\n"))
578 return rst
581 return rst
579
582
580
583
581 rst = []
584 rst = []
582 kw = opts.get('keyword')
585 kw = opts.get('keyword')
583 if kw or name is None and any(opts[o] for o in opts):
586 if kw or name is None and any(opts[o] for o in opts):
584 matches = topicmatch(ui, commands, name or '')
587 matches = topicmatch(ui, commands, name or '')
585 helpareas = []
588 helpareas = []
586 if opts.get('extension'):
589 if opts.get('extension'):
587 helpareas += [('extensions', _('Extensions'))]
590 helpareas += [('extensions', _('Extensions'))]
588 if opts.get('command'):
591 if opts.get('command'):
589 helpareas += [('commands', _('Commands'))]
592 helpareas += [('commands', _('Commands'))]
590 if not helpareas:
593 if not helpareas:
591 helpareas = [('topics', _('Topics')),
594 helpareas = [('topics', _('Topics')),
592 ('commands', _('Commands')),
595 ('commands', _('Commands')),
593 ('extensions', _('Extensions')),
596 ('extensions', _('Extensions')),
594 ('extensioncommands', _('Extension Commands'))]
597 ('extensioncommands', _('Extension Commands'))]
595 for t, title in helpareas:
598 for t, title in helpareas:
596 if matches[t]:
599 if matches[t]:
597 rst.append('%s:\n\n' % title)
600 rst.append('%s:\n\n' % title)
598 rst.extend(minirst.maketable(sorted(matches[t]), 1))
601 rst.extend(minirst.maketable(sorted(matches[t]), 1))
599 rst.append('\n')
602 rst.append('\n')
600 if not rst:
603 if not rst:
601 msg = _('no matches')
604 msg = _('no matches')
602 hint = _("try 'hg help' for a list of topics")
605 hint = _("try 'hg help' for a list of topics")
603 raise error.Abort(msg, hint=hint)
606 raise error.Abort(msg, hint=hint)
604 elif name and name != 'shortlist':
607 elif name and name != 'shortlist':
605 queries = []
608 queries = []
606 if unknowncmd:
609 if unknowncmd:
607 queries += [helpextcmd]
610 queries += [helpextcmd]
608 if opts.get('extension'):
611 if opts.get('extension'):
609 queries += [helpext]
612 queries += [helpext]
610 if opts.get('command'):
613 if opts.get('command'):
611 queries += [helpcmd]
614 queries += [helpcmd]
612 if not queries:
615 if not queries:
613 queries = (helptopic, helpcmd, helpext, helpextcmd)
616 queries = (helptopic, helpcmd, helpext, helpextcmd)
614 for f in queries:
617 for f in queries:
615 try:
618 try:
616 rst = f(name, subtopic)
619 rst = f(name, subtopic)
617 break
620 break
618 except error.UnknownCommand:
621 except error.UnknownCommand:
619 pass
622 pass
620 else:
623 else:
621 if unknowncmd:
624 if unknowncmd:
622 raise error.UnknownCommand(name)
625 raise error.UnknownCommand(name)
623 else:
626 else:
624 msg = _('no such help topic: %s') % name
627 msg = _('no such help topic: %s') % name
625 hint = _("try 'hg help --keyword %s'") % name
628 hint = _("try 'hg help --keyword %s'") % name
626 raise error.Abort(msg, hint=hint)
629 raise error.Abort(msg, hint=hint)
627 else:
630 else:
628 # program name
631 # program name
629 if not ui.quiet:
632 if not ui.quiet:
630 rst = [_("Mercurial Distributed SCM\n"), '\n']
633 rst = [_("Mercurial Distributed SCM\n"), '\n']
631 rst.extend(helplist(None, **pycompat.strkwargs(opts)))
634 rst.extend(helplist(None, **pycompat.strkwargs(opts)))
632
635
633 return ''.join(rst)
636 return ''.join(rst)
634
637
635 def formattedhelp(ui, commands, name, keep=None, unknowncmd=False, full=True,
638 def formattedhelp(ui, commands, name, keep=None, unknowncmd=False, full=True,
636 **opts):
639 **opts):
637 """get help for a given topic (as a dotted name) as rendered rst
640 """get help for a given topic (as a dotted name) as rendered rst
638
641
639 Either returns the rendered help text or raises an exception.
642 Either returns the rendered help text or raises an exception.
640 """
643 """
641 if keep is None:
644 if keep is None:
642 keep = []
645 keep = []
643 else:
646 else:
644 keep = list(keep) # make a copy so we can mutate this later
647 keep = list(keep) # make a copy so we can mutate this later
645 fullname = name
648 fullname = name
646 section = None
649 section = None
647 subtopic = None
650 subtopic = None
648 if name and '.' in name:
651 if name and '.' in name:
649 name, remaining = name.split('.', 1)
652 name, remaining = name.split('.', 1)
650 remaining = encoding.lower(remaining)
653 remaining = encoding.lower(remaining)
651 if '.' in remaining:
654 if '.' in remaining:
652 subtopic, section = remaining.split('.', 1)
655 subtopic, section = remaining.split('.', 1)
653 else:
656 else:
654 if name in subtopics:
657 if name in subtopics:
655 subtopic = remaining
658 subtopic = remaining
656 else:
659 else:
657 section = remaining
660 section = remaining
658 textwidth = ui.configint('ui', 'textwidth')
661 textwidth = ui.configint('ui', 'textwidth')
659 termwidth = ui.termwidth() - 2
662 termwidth = ui.termwidth() - 2
660 if textwidth <= 0 or termwidth < textwidth:
663 if textwidth <= 0 or termwidth < textwidth:
661 textwidth = termwidth
664 textwidth = termwidth
662 text = help_(ui, commands, name,
665 text = help_(ui, commands, name,
663 subtopic=subtopic, unknowncmd=unknowncmd, full=full, **opts)
666 subtopic=subtopic, unknowncmd=unknowncmd, full=full, **opts)
664
667
665 formatted, pruned = minirst.format(text, textwidth, keep=keep,
668 formatted, pruned = minirst.format(text, textwidth, keep=keep,
666 section=section)
669 section=section)
667
670
668 # We could have been given a weird ".foo" section without a name
671 # We could have been given a weird ".foo" section without a name
669 # to look for, or we could have simply failed to found "foo.bar"
672 # to look for, or we could have simply failed to found "foo.bar"
670 # because bar isn't a section of foo
673 # because bar isn't a section of foo
671 if section and not (formatted and name):
674 if section and not (formatted and name):
672 raise error.Abort(_("help section not found: %s") % fullname)
675 raise error.Abort(_("help section not found: %s") % fullname)
673
676
674 if 'verbose' in pruned:
677 if 'verbose' in pruned:
675 keep.append('omitted')
678 keep.append('omitted')
676 else:
679 else:
677 keep.append('notomitted')
680 keep.append('notomitted')
678 formatted, pruned = minirst.format(text, textwidth, keep=keep,
681 formatted, pruned = minirst.format(text, textwidth, keep=keep,
679 section=section)
682 section=section)
680 return formatted
683 return formatted
General Comments 0
You need to be logged in to leave comments. Login now