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