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