PageRenderTime 57ms CodeModel.GetById 25ms RepoModel.GetById 1ms app.codeStats 0ms

/mercurial/help.py

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