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