help.py
505 lines
| 17.6 KiB
| text/x-python
|
PythonLexer
/ mercurial / help.py
Matt Mackall
|
r3795 | # help.py - help data for mercurial | ||
# | ||||
# Copyright 2006 Matt Mackall <mpm@selenic.com> | ||||
# | ||||
Martin Geisler
|
r8225 | # This software may be used and distributed according to the terms of the | ||
Matt Mackall
|
r10263 | # GNU General Public License version 2 or any later version. | ||
Matt Mackall
|
r3795 | |||
Martin Geisler
|
r9539 | from i18n import gettext, _ | ||
Dan Villiom Podlaski Christiansen
|
r18746 | import itertools, sys, os, error | ||
FUJIWARA Katsunori
|
r16126 | import extensions, revset, fileset, templatekw, templatefilters, filemerge | ||
Olav Reinert
|
r16781 | import encoding, util, minirst | ||
Dan Villiom Podlaski Christiansen
|
r18746 | import cmdutil | ||
Cédric Duval
|
r8863 | |||
Matt Mackall
|
r14316 | def listexts(header, exts, indent=1): | ||
Cédric Duval
|
r8879 | '''return a text listing of the given extensions''' | ||
Olav Reinert
|
r16852 | rst = [] | ||
if exts: | ||||
rst.append('\n%s\n\n' % header) | ||||
for name, desc in sorted(exts.iteritems()): | ||||
rst.append('%s:%s: %s\n' % (' ' * indent, name, desc)) | ||||
return rst | ||||
Cédric Duval
|
r8864 | |||
Cédric Duval
|
r8879 | def extshelp(): | ||
Olav Reinert
|
r16852 | rst = loaddoc('extensions')().splitlines(True) | ||
rst.extend(listexts(_('enabled extensions:'), extensions.enabled())) | ||||
rst.extend(listexts(_('disabled extensions:'), extensions.disabled())) | ||||
doc = ''.join(rst) | ||||
Cédric Duval
|
r8863 | return doc | ||
Martin Geisler
|
r7013 | |||
Olav Reinert
|
r16781 | 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 _("DEPRECATED") in desc and not verbose: | ||||
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: | ||||
Olav Reinert
|
r16815 | rst.append(_("\n[+] marked option can be specified multiple times\n")) | ||
Olav Reinert
|
r16781 | |||
Olav Reinert
|
r16815 | return ''.join(rst) | ||
Olav Reinert
|
r16781 | |||
FUJIWARA Katsunori
|
r17837 | 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) | ||||
Augie Fackler
|
r16710 | 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): | ||||
Nikolaj Sjujskij
|
r16845 | return kw in encoding.lower(container) # translated in helptable | ||
Augie Fackler
|
r16710 | 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 cmd.startswith('debug'): | ||||
continue | ||||
if len(entry) == 3: | ||||
summary = entry[2] | ||||
else: | ||||
summary = '' | ||||
Nikolaj Sjujskij
|
r16845 | # translate docs *before* searching there | ||
docs = _(getattr(entry[0], '__doc__', None)) or '' | ||||
Augie Fackler
|
r16710 | if kw in cmd or lowercontains(summary) or lowercontains(docs): | ||
Nikolaj Sjujskij
|
r16845 | doclines = docs.splitlines() | ||
Augie Fackler
|
r16710 | if doclines: | ||
summary = doclines[0] | ||||
cmdname = cmd.split('|')[0].lstrip('^') | ||||
results['commands'].append((cmdname, summary)) | ||||
for name, docs in itertools.chain( | ||||
FUJIWARA Katsunori
|
r19769 | extensions.enabled(False).iteritems(), | ||
Augie Fackler
|
r16710 | extensions.disabled().iteritems()): | ||
# extensions.load ignores the UI argument | ||||
mod = extensions.load(None, name, '') | ||||
FUJIWARA Katsunori
|
r19769 | name = name.split('.')[-1] | ||
Augie Fackler
|
r16710 | if lowercontains(name) or lowercontains(docs): | ||
Nikolaj Sjujskij
|
r16845 | # extension docs are already translated | ||
results['extensions'].append((name, docs.splitlines()[0])) | ||||
Augie Fackler
|
r16710 | for cmd, entry in getattr(mod, 'cmdtable', {}).iteritems(): | ||
Augie Fackler
|
r16711 | if kw in cmd or (len(entry) > 2 and lowercontains(entry[2])): | ||
Augie Fackler
|
r16710 | cmdname = cmd.split('|')[0].lstrip('^') | ||
Olav Reinert
|
r16942 | if entry[0].__doc__: | ||
cmddoc = gettext(entry[0].__doc__).splitlines()[0] | ||||
Thomas Arendsen Hein
|
r16884 | else: | ||
cmddoc = _('(no help text available)') | ||||
results['extensioncommands'].append((cmdname, cmddoc)) | ||||
Augie Fackler
|
r16710 | return results | ||
Martin Geisler
|
r9539 | def loaddoc(topic): | ||
"""Return a delayed loader for help/topic.txt.""" | ||||
Matt Mackall
|
r3798 | |||
Martin Geisler
|
r9539 | def loader(): | ||
Augie Fackler
|
r14941 | if util.mainfrozen(): | ||
Martin Geisler
|
r9539 | module = sys.executable | ||
else: | ||||
module = __file__ | ||||
base = os.path.dirname(module) | ||||
Dirkjan Ochtman
|
r7293 | |||
Martin Geisler
|
r9539 | for dir in ('.', '..'): | ||
docdir = os.path.join(base, dir, 'help') | ||||
if os.path.isdir(docdir): | ||||
break | ||||
Alexander Solovyov
|
r7677 | |||
Martin Geisler
|
r9539 | path = os.path.join(docdir, topic + ".txt") | ||
Dan Villiom Podlaski Christiansen
|
r14168 | doc = gettext(util.readfile(path)) | ||
Patrick Mezard
|
r12820 | for rewriter in helphooks.get(topic, []): | ||
doc = rewriter(topic, doc) | ||||
return doc | ||||
Martin Geisler
|
r9539 | return loader | ||
Alexander Solovyov
|
r7677 | |||
Yun Lee
|
r13888 | helptable = sorted([ | ||
Martin Geisler
|
r12145 | (["config", "hgrc"], _("Configuration Files"), loaddoc('config')), | ||
Martin Geisler
|
r9539 | (["dates"], _("Date Formats"), loaddoc('dates')), | ||
(["patterns"], _("File Name Patterns"), loaddoc('patterns')), | ||||
Matt Mackall
|
r10282 | (['environment', 'env'], _('Environment Variables'), | ||
loaddoc('environment')), | ||||
Mads Kiilerich
|
r17322 | (['revisions', 'revs'], _('Specifying Single Revisions'), | ||
Matt Mackall
|
r10282 | loaddoc('revisions')), | ||
Mads Kiilerich
|
r17322 | (['multirevs', 'mrevs'], _('Specifying Multiple Revisions'), | ||
Matt Mackall
|
r10282 | loaddoc('multirevs')), | ||
Mads Kiilerich
|
r17322 | (['revsets', 'revset'], _("Specifying Revision Sets"), loaddoc('revsets')), | ||
(['filesets', 'fileset'], _("Specifying File Sets"), loaddoc('filesets')), | ||||
Martin Geisler
|
r9539 | (['diffs'], _('Diff Formats'), loaddoc('diffs')), | ||
Mads Kiilerich
|
r17323 | (['merge-tools', 'mergetools'], _('Merge Tools'), loaddoc('merge-tools')), | ||
A. S. Budden
|
r16568 | (['templating', 'templates', 'template', 'style'], _('Template Usage'), | ||
Matt Mackall
|
r10282 | loaddoc('templates')), | ||
Martin Geisler
|
r9539 | (['urls'], _('URL Paths'), loaddoc('urls')), | ||
Martin Geisler
|
r16547 | (["extensions"], _("Using Additional Features"), extshelp), | ||
Mads Kiilerich
|
r17322 | (["subrepos", "subrepo"], _("Subrepositories"), loaddoc('subrepos')), | ||
Mads Kiilerich
|
r17321 | (["hgweb"], _("Configuring hgweb"), loaddoc('hgweb')), | ||
(["glossary"], _("Glossary"), loaddoc('glossary')), | ||||
(["hgignore", "ignore"], _("Syntax for Mercurial Ignore Files"), | ||||
loaddoc('hgignore')), | ||||
(["phases"], _("Working with Phases"), loaddoc('phases')), | ||||
Yun Lee
|
r13888 | ]) | ||
Patrick Mezard
|
r12820 | |||
# Map topics to lists of callable taking the current topic help and | ||||
# returning the updated version | ||||
Matt Mackall
|
r14318 | helphooks = {} | ||
Patrick Mezard
|
r12820 | |||
def addtopichook(topic, rewriter): | ||||
helphooks.setdefault(topic, []).append(rewriter) | ||||
Patrick Mezard
|
r13593 | |||
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() | ||||
"Yann E. MORIN"
|
r16250 | 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)) | ||||
Patrick Mezard
|
r13593 | entries = '\n\n'.join(entries) | ||
return doc.replace(marker, entries) | ||||
Matt Mackall
|
r14318 | |||
def addtopicsymbols(topic, marker, symbols): | ||||
def add(topic, doc): | ||||
return makeitemsdoc(topic, doc, marker, symbols) | ||||
addtopichook(topic, add) | ||||
Matt Mackall
|
r14686 | addtopicsymbols('filesets', '.. predicatesmarker', fileset.symbols) | ||
FUJIWARA Katsunori
|
r16126 | addtopicsymbols('merge-tools', '.. internaltoolsmarker', filemerge.internals) | ||
Matt Mackall
|
r14318 | addtopicsymbols('revsets', '.. predicatesmarker', revset.symbols) | ||
epriestley
|
r17187 | addtopicsymbols('templates', '.. keywordsmarker', templatekw.dockeywords) | ||
Matt Mackall
|
r14318 | addtopicsymbols('templates', '.. filtersmarker', templatefilters.filters) | ||
Dan Villiom Podlaski Christiansen
|
r18746 | |||
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') | ||||
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"): | ||||
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) | ||||
Dan Villiom Podlaski Christiansen
|
r18748 | rst = [minirst.section(header)] | ||
Dan Villiom Podlaski Christiansen
|
r18746 | # description | ||
if not doc: | ||||
rst.append(" %s\n" % _("(no help text available)")) | ||||
if util.safehasattr(doc, '__call__'): | ||||
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') | ||||
elif name and name != 'shortlist': | ||||
i = None | ||||
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) | ||||
i = None | ||||
break | ||||
except error.UnknownCommand, inst: | ||||
i = inst | ||||
if i: | ||||
raise i | ||||
else: | ||||
# program name | ||||
if not ui.quiet: | ||||
rst = [_("Mercurial Distributed SCM\n"), '\n'] | ||||
rst.extend(helplist()) | ||||
return ''.join(rst) | ||||