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