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