/mercurial/help.py
Python | 516 lines | 494 code | 8 blank | 14 comment | 33 complexity | b7430c08827051079e58d8e4f01c70dc MD5 | raw file
Possible License(s): GPL-2.0
- # help.py - help data for mercurial
- #
- # Copyright 2006 Matt Mackall <mpm@selenic.com>
- #
- # This software may be used and distributed according to the terms of the
- # GNU General Public License version 2 or any later version.
- from i18n import gettext, _
- import itertools, sys, os
- import error
- import extensions, revset, fileset, templatekw, templatefilters, filemerge
- import encoding, util, minirst
- import cmdutil
- def listexts(header, exts, indent=1, showdeprecated=False):
- '''return a text listing of the given extensions'''
- rst = []
- if exts:
- rst.append('\n%s\n\n' % header)
- for name, desc in sorted(exts.iteritems()):
- if '(DEPRECATED)' in desc and not showdeprecated:
- continue
- rst.append('%s:%s: %s\n' % (' ' * indent, name, desc))
- return rst
- def extshelp():
- rst = loaddoc('extensions')().splitlines(True)
- rst.extend(listexts(
- _('enabled extensions:'), extensions.enabled(), showdeprecated=True))
- rst.extend(listexts(_('disabled extensions:'), extensions.disabled()))
- doc = ''.join(rst)
- return doc
- def optrst(options, verbose):
- data = []
- multioccur = False
- for option in options:
- if len(option) == 5:
- shortopt, longopt, default, desc, optlabel = option
- else:
- shortopt, longopt, default, desc = option
- optlabel = _("VALUE") # default label
- if not verbose and ("DEPRECATED" in desc or _("DEPRECATED") in desc):
- continue
- so = ''
- if shortopt:
- so = '-' + shortopt
- lo = '--' + longopt
- if default:
- desc += _(" (default: %s)") % default
- if isinstance(default, list):
- lo += " %s [+]" % optlabel
- multioccur = True
- elif (default is not None) and not isinstance(default, bool):
- lo += " %s" % optlabel
- data.append((so, lo, desc))
- rst = minirst.maketable(data, 1)
- if multioccur:
- rst.append(_("\n[+] marked option can be specified multiple times\n"))
- return ''.join(rst)
- def indicateomitted(rst, omitted, notomitted=None):
- rst.append('\n\n.. container:: omitted\n\n %s\n\n' % omitted)
- if notomitted:
- rst.append('\n\n.. container:: notomitted\n\n %s\n\n' % notomitted)
- def topicmatch(kw):
- """Return help topics matching kw.
- Returns {'section': [(name, summary), ...], ...} where section is
- one of topics, commands, extensions, or extensioncommands.
- """
- kw = encoding.lower(kw)
- def lowercontains(container):
- return kw in encoding.lower(container) # translated in helptable
- results = {'topics': [],
- 'commands': [],
- 'extensions': [],
- 'extensioncommands': [],
- }
- for names, header, doc in helptable:
- if (sum(map(lowercontains, names))
- or lowercontains(header)
- or lowercontains(doc())):
- results['topics'].append((names[0], header))
- import commands # avoid cycle
- for cmd, entry in commands.table.iteritems():
- if len(entry) == 3:
- summary = entry[2]
- else:
- summary = ''
- # translate docs *before* searching there
- docs = _(getattr(entry[0], '__doc__', None)) or ''
- if kw in cmd or lowercontains(summary) or lowercontains(docs):
- doclines = docs.splitlines()
- if doclines:
- summary = doclines[0]
- cmdname = cmd.split('|')[0].lstrip('^')
- results['commands'].append((cmdname, summary))
- for name, docs in itertools.chain(
- extensions.enabled(False).iteritems(),
- extensions.disabled().iteritems()):
- # extensions.load ignores the UI argument
- mod = extensions.load(None, name, '')
- name = name.split('.')[-1]
- if lowercontains(name) or lowercontains(docs):
- # extension docs are already translated
- results['extensions'].append((name, docs.splitlines()[0]))
- for cmd, entry in getattr(mod, 'cmdtable', {}).iteritems():
- if kw in cmd or (len(entry) > 2 and lowercontains(entry[2])):
- cmdname = cmd.split('|')[0].lstrip('^')
- if entry[0].__doc__:
- cmddoc = gettext(entry[0].__doc__).splitlines()[0]
- else:
- cmddoc = _('(no help text available)')
- results['extensioncommands'].append((cmdname, cmddoc))
- return results
- def loaddoc(topic):
- """Return a delayed loader for help/topic.txt."""
- def loader():
- if util.mainfrozen():
- module = sys.executable
- else:
- module = __file__
- base = os.path.dirname(module)
- for dir in ('.', '..'):
- docdir = os.path.join(base, dir, 'help')
- if os.path.isdir(docdir):
- break
- path = os.path.join(docdir, topic + ".txt")
- doc = gettext(util.readfile(path))
- for rewriter in helphooks.get(topic, []):
- doc = rewriter(topic, doc)
- return doc
- return loader
- helptable = sorted([
- (["config", "hgrc"], _("Configuration Files"), loaddoc('config')),
- (["dates"], _("Date Formats"), loaddoc('dates')),
- (["patterns"], _("File Name Patterns"), loaddoc('patterns')),
- (['environment', 'env'], _('Environment Variables'),
- loaddoc('environment')),
- (['revisions', 'revs'], _('Specifying Single Revisions'),
- loaddoc('revisions')),
- (['multirevs', 'mrevs'], _('Specifying Multiple Revisions'),
- loaddoc('multirevs')),
- (['revsets', 'revset'], _("Specifying Revision Sets"), loaddoc('revsets')),
- (['filesets', 'fileset'], _("Specifying File Sets"), loaddoc('filesets')),
- (['diffs'], _('Diff Formats'), loaddoc('diffs')),
- (['merge-tools', 'mergetools'], _('Merge Tools'), loaddoc('merge-tools')),
- (['templating', 'templates', 'template', 'style'], _('Template Usage'),
- loaddoc('templates')),
- (['urls'], _('URL Paths'), loaddoc('urls')),
- (["extensions"], _("Using Additional Features"), extshelp),
- (["subrepos", "subrepo"], _("Subrepositories"), loaddoc('subrepos')),
- (["hgweb"], _("Configuring hgweb"), loaddoc('hgweb')),
- (["glossary"], _("Glossary"), loaddoc('glossary')),
- (["hgignore", "ignore"], _("Syntax for Mercurial Ignore Files"),
- loaddoc('hgignore')),
- (["phases"], _("Working with Phases"), loaddoc('phases')),
- ])
- # Map topics to lists of callable taking the current topic help and
- # returning the updated version
- helphooks = {}
- def addtopichook(topic, rewriter):
- helphooks.setdefault(topic, []).append(rewriter)
- def makeitemsdoc(topic, doc, marker, items):
- """Extract docstring from the items key to function mapping, build a
- .single documentation block and use it to overwrite the marker in doc
- """
- entries = []
- for name in sorted(items):
- text = (items[name].__doc__ or '').rstrip()
- if not text:
- continue
- text = gettext(text)
- lines = text.splitlines()
- doclines = [(lines[0])]
- for l in lines[1:]:
- # Stop once we find some Python doctest
- if l.strip().startswith('>>>'):
- break
- doclines.append(' ' + l.strip())
- entries.append('\n'.join(doclines))
- entries = '\n\n'.join(entries)
- return doc.replace(marker, entries)
- def addtopicsymbols(topic, marker, symbols):
- def add(topic, doc):
- return makeitemsdoc(topic, doc, marker, symbols)
- addtopichook(topic, add)
- addtopicsymbols('filesets', '.. predicatesmarker', fileset.symbols)
- addtopicsymbols('merge-tools', '.. internaltoolsmarker', filemerge.internals)
- addtopicsymbols('revsets', '.. predicatesmarker', revset.symbols)
- addtopicsymbols('templates', '.. keywordsmarker', templatekw.dockeywords)
- addtopicsymbols('templates', '.. filtersmarker', templatefilters.filters)
- def help_(ui, name, unknowncmd=False, full=True, **opts):
- '''
- Generate the help for 'name' as unformatted restructured text. If
- 'name' is None, describe the commands available.
- '''
- import commands # avoid cycle
- def helpcmd(name):
- try:
- aliases, entry = cmdutil.findcmd(name, commands.table,
- strict=unknowncmd)
- except error.AmbiguousCommand, inst:
- # py3k fix: except vars can't be used outside the scope of the
- # except block, nor can be used inside a lambda. python issue4617
- prefix = inst.args[0]
- select = lambda c: c.lstrip('^').startswith(prefix)
- rst = helplist(select)
- return rst
- rst = []
- # check if it's an invalid alias and display its error if it is
- if getattr(entry[0], 'badalias', False):
- if not unknowncmd:
- ui.pushbuffer()
- entry[0](ui)
- rst.append(ui.popbuffer())
- return rst
- # synopsis
- if len(entry) > 2:
- if entry[2].startswith('hg'):
- rst.append("%s\n" % entry[2])
- else:
- rst.append('hg %s %s\n' % (aliases[0], entry[2]))
- else:
- rst.append('hg %s\n' % aliases[0])
- # aliases
- if full and not ui.quiet and len(aliases) > 1:
- rst.append(_("\naliases: %s\n") % ', '.join(aliases[1:]))
- rst.append('\n')
- # description
- doc = gettext(entry[0].__doc__)
- if not doc:
- doc = _("(no help text available)")
- if util.safehasattr(entry[0], 'definition'): # aliased command
- if entry[0].definition.startswith('!'): # shell alias
- doc = _('shell alias for::\n\n %s') % entry[0].definition[1:]
- else:
- doc = _('alias for: hg %s\n\n%s') % (entry[0].definition, doc)
- doc = doc.splitlines(True)
- if ui.quiet or not full:
- rst.append(doc[0])
- else:
- rst.extend(doc)
- rst.append('\n')
- # check if this command shadows a non-trivial (multi-line)
- # extension help text
- try:
- mod = extensions.find(name)
- doc = gettext(mod.__doc__) or ''
- if '\n' in doc.strip():
- msg = _('use "hg help -e %s" to show help for '
- 'the %s extension') % (name, name)
- rst.append('\n%s\n' % msg)
- except KeyError:
- pass
- # options
- if not ui.quiet and entry[1]:
- rst.append('\n%s\n\n' % _("options:"))
- rst.append(optrst(entry[1], ui.verbose))
- if ui.verbose:
- rst.append('\n%s\n\n' % _("global options:"))
- rst.append(optrst(commands.globalopts, ui.verbose))
- if not ui.verbose:
- if not full:
- rst.append(_('\nuse "hg help %s" to show the full help text\n')
- % name)
- elif not ui.quiet:
- omitted = _('use "hg -v help %s" to show more complete'
- ' help and the global options') % name
- notomitted = _('use "hg -v help %s" to show'
- ' the global options') % name
- indicateomitted(rst, omitted, notomitted)
- return rst
- def helplist(select=None):
- # list of commands
- if name == "shortlist":
- header = _('basic commands:\n\n')
- elif name == "debug":
- header = _('debug commands (internal and unsupported):\n\n')
- else:
- header = _('list of commands:\n\n')
- h = {}
- cmds = {}
- for c, e in commands.table.iteritems():
- f = c.split("|", 1)[0]
- if select and not select(f):
- continue
- if (not select and name != 'shortlist' and
- e[0].__module__ != commands.__name__):
- continue
- if name == "shortlist" and not f.startswith("^"):
- continue
- f = f.lstrip("^")
- if not ui.debugflag and f.startswith("debug") and name != "debug":
- continue
- doc = e[0].__doc__
- if doc and 'DEPRECATED' in doc and not ui.verbose:
- continue
- doc = gettext(doc)
- if not doc:
- doc = _("(no help text available)")
- h[f] = doc.splitlines()[0].rstrip()
- cmds[f] = c.lstrip("^")
- rst = []
- if not h:
- if not ui.quiet:
- rst.append(_('no commands defined\n'))
- return rst
- if not ui.quiet:
- rst.append(header)
- fns = sorted(h)
- for f in fns:
- if ui.verbose:
- commacmds = cmds[f].replace("|",", ")
- rst.append(" :%s: %s\n" % (commacmds, h[f]))
- else:
- rst.append(' :%s: %s\n' % (f, h[f]))
- if not name:
- exts = listexts(_('enabled extensions:'), extensions.enabled())
- if exts:
- rst.append('\n')
- rst.extend(exts)
- rst.append(_("\nadditional help topics:\n\n"))
- topics = []
- for names, header, doc in helptable:
- topics.append((names[0], header))
- for t, desc in topics:
- rst.append(" :%s: %s\n" % (t, desc))
- optlist = []
- if not ui.quiet:
- if ui.verbose:
- optlist.append((_("global options:"), commands.globalopts))
- if name == 'shortlist':
- optlist.append((_('use "hg help" for the full list '
- 'of commands'), ()))
- else:
- if name == 'shortlist':
- msg = _('use "hg help" for the full list of commands '
- 'or "hg -v" for details')
- elif name and not full:
- msg = _('use "hg help %s" to show the full help '
- 'text') % name
- else:
- msg = _('use "hg -v help%s" to show builtin aliases and '
- 'global options') % (name and " " + name or "")
- optlist.append((msg, ()))
- if optlist:
- for title, options in optlist:
- rst.append('\n%s\n' % title)
- if options:
- rst.append('\n%s\n' % optrst(options, ui.verbose))
- return rst
- def helptopic(name):
- for names, header, doc in helptable:
- if name in names:
- break
- else:
- raise error.UnknownCommand(name)
- rst = [minirst.section(header)]
- # description
- if not doc:
- rst.append(" %s\n" % _("(no help text available)"))
- if callable(doc):
- rst += [" %s\n" % l for l in doc().splitlines()]
- if not ui.verbose:
- omitted = (_('use "hg help -v %s" to show more complete help') %
- name)
- indicateomitted(rst, omitted)
- try:
- cmdutil.findcmd(name, commands.table)
- rst.append(_('\nuse "hg help -c %s" to see help for '
- 'the %s command\n') % (name, name))
- except error.UnknownCommand:
- pass
- return rst
- def helpext(name):
- try:
- mod = extensions.find(name)
- doc = gettext(mod.__doc__) or _('no help text available')
- except KeyError:
- mod = None
- doc = extensions.disabledext(name)
- if not doc:
- raise error.UnknownCommand(name)
- if '\n' not in doc:
- head, tail = doc, ""
- else:
- head, tail = doc.split('\n', 1)
- rst = [_('%s extension - %s\n\n') % (name.split('.')[-1], head)]
- if tail:
- rst.extend(tail.splitlines(True))
- rst.append('\n')
- if not ui.verbose:
- omitted = (_('use "hg help -v %s" to show more complete help') %
- name)
- indicateomitted(rst, omitted)
- if mod:
- try:
- ct = mod.cmdtable
- except AttributeError:
- ct = {}
- modcmds = set([c.split('|', 1)[0] for c in ct])
- rst.extend(helplist(modcmds.__contains__))
- else:
- rst.append(_('use "hg help extensions" for information on enabling '
- 'extensions\n'))
- return rst
- def helpextcmd(name):
- cmd, ext, mod = extensions.disabledcmd(ui, name,
- ui.configbool('ui', 'strict'))
- doc = gettext(mod.__doc__).splitlines()[0]
- rst = listexts(_("'%s' is provided by the following "
- "extension:") % cmd, {ext: doc}, indent=4)
- rst.append('\n')
- rst.append(_('use "hg help extensions" for information on enabling '
- 'extensions\n'))
- return rst
- rst = []
- kw = opts.get('keyword')
- if kw:
- matches = topicmatch(kw)
- for t, title in (('topics', _('Topics')),
- ('commands', _('Commands')),
- ('extensions', _('Extensions')),
- ('extensioncommands', _('Extension Commands'))):
- if matches[t]:
- rst.append('%s:\n\n' % title)
- rst.extend(minirst.maketable(sorted(matches[t]), 1))
- rst.append('\n')
- if not rst:
- msg = _('no matches')
- hint = _('try "hg help" for a list of topics')
- raise util.Abort(msg, hint=hint)
- elif name and name != 'shortlist':
- if unknowncmd:
- queries = (helpextcmd,)
- elif opts.get('extension'):
- queries = (helpext,)
- elif opts.get('command'):
- queries = (helpcmd,)
- else:
- queries = (helptopic, helpcmd, helpext, helpextcmd)
- for f in queries:
- try:
- rst = f(name)
- break
- except error.UnknownCommand:
- pass
- else:
- if unknowncmd:
- raise error.UnknownCommand(name)
- else:
- msg = _('no such help topic: %s') % name
- hint = _('try "hg help --keyword %s"') % name
- raise util.Abort(msg, hint=hint)
- else:
- # program name
- if not ui.quiet:
- rst = [_("Mercurial Distributed SCM\n"), '\n']
- rst.extend(helplist())
- return ''.join(rst)