help.py
861 lines
| 31.3 KiB
| text/x-python
|
PythonLexer
/ mercurial / help.py
|
r3795 | # help.py - help data for mercurial | ||
# | ||||
# Copyright 2006 Matt Mackall <mpm@selenic.com> | ||||
# | ||||
|
r8225 | # This software may be used and distributed according to the terms of the | ||
|
r10263 | # GNU General Public License version 2 or any later version. | ||
|
r3795 | |||
|
r27479 | from __future__ import absolute_import | ||
import itertools | ||||
import os | ||||
|
r40472 | import re | ||
|
r27479 | import textwrap | ||
from .i18n import ( | ||||
_, | ||||
gettext, | ||||
) | ||||
from . import ( | ||||
cmdutil, | ||||
encoding, | ||||
error, | ||||
extensions, | ||||
|
r37109 | fancyopts, | ||
|
r27479 | filemerge, | ||
fileset, | ||||
minirst, | ||||
|
r32143 | pycompat, | ||
|
r40327 | registrar, | ||
|
r27479 | revset, | ||
templatefilters, | ||||
|
r36940 | templatefuncs, | ||
|
r27479 | templatekw, | ||
|
r40472 | ui as uimod, | ||
|
r27479 | util, | ||
) | ||||
from .hgweb import ( | ||||
webcommands, | ||||
) | ||||
|
r8863 | |||
|
r32291 | _exclkeywords = { | ||
|
r31080 | "(ADVANCED)", | ||
|
r26370 | "(DEPRECATED)", | ||
"(EXPERIMENTAL)", | ||||
|
r31080 | # i18n: "(ADVANCED)" is a keyword, must be translated consistently | ||
_("(ADVANCED)"), | ||||
|
r26370 | # i18n: "(DEPRECATED)" is a keyword, must be translated consistently | ||
_("(DEPRECATED)"), | ||||
# i18n: "(EXPERIMENTAL)" is a keyword, must be translated consistently | ||||
_("(EXPERIMENTAL)"), | ||||
|
r32291 | } | ||
|
r26369 | |||
|
r40327 | # The order in which command categories will be displayed. | ||
# Extensions with custom categories should insert them into this list | ||||
# after/before the appropriate item, rather than replacing the list or | ||||
# assuming absolute positions. | ||||
CATEGORY_ORDER = [ | ||||
|
r40329 | registrar.command.CATEGORY_REPO_CREATION, | ||
registrar.command.CATEGORY_REMOTE_REPO_MANAGEMENT, | ||||
registrar.command.CATEGORY_COMMITTING, | ||||
registrar.command.CATEGORY_CHANGE_MANAGEMENT, | ||||
registrar.command.CATEGORY_CHANGE_ORGANIZATION, | ||||
registrar.command.CATEGORY_FILE_CONTENTS, | ||||
registrar.command.CATEGORY_CHANGE_NAVIGATION , | ||||
registrar.command.CATEGORY_WORKING_DIRECTORY, | ||||
registrar.command.CATEGORY_IMPORT_EXPORT, | ||||
registrar.command.CATEGORY_MAINTENANCE, | ||||
registrar.command.CATEGORY_HELP, | ||||
registrar.command.CATEGORY_MISC, | ||||
|
r40327 | registrar.command.CATEGORY_NONE, | ||
] | ||||
# Human-readable category names. These are translated. | ||||
# Extensions with custom categories should add their names here. | ||||
CATEGORY_NAMES = { | ||||
|
r40329 | registrar.command.CATEGORY_REPO_CREATION: 'Repository creation', | ||
registrar.command.CATEGORY_REMOTE_REPO_MANAGEMENT: | ||||
'Remote repository management', | ||||
registrar.command.CATEGORY_COMMITTING: 'Change creation', | ||||
registrar.command.CATEGORY_CHANGE_NAVIGATION: 'Change navigation', | ||||
registrar.command.CATEGORY_CHANGE_MANAGEMENT: 'Change manipulation', | ||||
registrar.command.CATEGORY_CHANGE_ORGANIZATION: 'Change organization', | ||||
registrar.command.CATEGORY_WORKING_DIRECTORY: | ||||
'Working directory management', | ||||
registrar.command.CATEGORY_FILE_CONTENTS: 'File content management', | ||||
registrar.command.CATEGORY_IMPORT_EXPORT: 'Change import/export', | ||||
registrar.command.CATEGORY_MAINTENANCE: 'Repository maintenance', | ||||
registrar.command.CATEGORY_HELP: 'Help', | ||||
registrar.command.CATEGORY_MISC: 'Miscellaneous commands', | ||||
|
r40327 | registrar.command.CATEGORY_NONE: 'Uncategorized commands', | ||
} | ||||
|
r40328 | # Topic categories. | ||
|
r40330 | TOPIC_CATEGORY_IDS = 'ids' | ||
TOPIC_CATEGORY_OUTPUT = 'output' | ||||
TOPIC_CATEGORY_CONFIG = 'config' | ||||
TOPIC_CATEGORY_CONCEPTS = 'concepts' | ||||
TOPIC_CATEGORY_MISC = 'misc' | ||||
|
r40328 | TOPIC_CATEGORY_NONE = 'none' | ||
# The order in which topic categories will be displayed. | ||||
# Extensions with custom categories should insert them into this list | ||||
# after/before the appropriate item, rather than replacing the list or | ||||
# assuming absolute positions. | ||||
TOPIC_CATEGORY_ORDER = [ | ||||
|
r40330 | TOPIC_CATEGORY_IDS, | ||
TOPIC_CATEGORY_OUTPUT, | ||||
TOPIC_CATEGORY_CONFIG, | ||||
TOPIC_CATEGORY_CONCEPTS, | ||||
TOPIC_CATEGORY_MISC, | ||||
|
r40328 | TOPIC_CATEGORY_NONE, | ||
] | ||||
# Human-readable topic category names. These are translated. | ||||
TOPIC_CATEGORY_NAMES = { | ||||
|
r40330 | TOPIC_CATEGORY_IDS: 'Mercurial identifiers', | ||
TOPIC_CATEGORY_OUTPUT: 'Mercurial output', | ||||
TOPIC_CATEGORY_CONFIG: 'Mercurial configuration', | ||||
TOPIC_CATEGORY_CONCEPTS: 'Concepts', | ||||
TOPIC_CATEGORY_MISC: 'Miscellaneous', | ||||
TOPIC_CATEGORY_NONE: 'Uncategorized topics', | ||||
|
r40328 | TOPIC_CATEGORY_NONE: 'Uncategorized topics', | ||
} | ||||
|
r20582 | def listexts(header, exts, indent=1, showdeprecated=False): | ||
|
r8879 | '''return a text listing of the given extensions''' | ||
|
r16852 | rst = [] | ||
if exts: | ||||
for name, desc in sorted(exts.iteritems()): | ||||
|
r26371 | if not showdeprecated and any(w in desc for w in _exclkeywords): | ||
|
r20582 | continue | ||
|
r16852 | rst.append('%s:%s: %s\n' % (' ' * indent, name, desc)) | ||
|
r27151 | if rst: | ||
rst.insert(0, '\n%s\n\n' % header) | ||||
|
r16852 | return rst | ||
|
r8864 | |||
|
r26413 | def extshelp(ui): | ||
rst = loaddoc('extensions')(ui).splitlines(True) | ||||
|
r20582 | rst.extend(listexts( | ||
_('enabled extensions:'), extensions.enabled(), showdeprecated=True)) | ||||
|
r36263 | rst.extend(listexts(_('disabled extensions:'), extensions.disabled(), | ||
showdeprecated=ui.verbose)) | ||||
|
r16852 | doc = ''.join(rst) | ||
|
r8863 | return doc | ||
|
r7013 | |||
|
r22116 | def optrst(header, options, verbose): | ||
|
r16781 | 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 | ||||
|
r26369 | if not verbose and any(w in desc for w in _exclkeywords): | ||
|
r16781 | continue | ||
so = '' | ||||
if shortopt: | ||||
so = '-' + shortopt | ||||
lo = '--' + longopt | ||||
|
r37109 | |||
if isinstance(default, fancyopts.customopt): | ||||
|
r37110 | default = default.getdefaultvalue() | ||
|
r37109 | if default and not callable(default): | ||
|
r32619 | # default is of unknown type, and in Python 2 we abused | ||
# the %s-shows-repr property to handle integers etc. To | ||||
# match that behavior on Python 3, we do str(default) and | ||||
# then convert it to bytes. | ||||
desc += _(" (default: %s)") % pycompat.bytestr(default) | ||||
|
r16781 | |||
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)) | ||||
|
r22117 | if multioccur: | ||
header += (_(" ([+] can be repeated)")) | ||||
|
r16781 | |||
|
r22116 | rst = ['\n%s:\n\n' % header] | ||
rst.extend(minirst.maketable(data, 1)) | ||||
|
r16781 | |||
|
r16815 | return ''.join(rst) | ||
|
r16781 | |||
|
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) | ||||
|
r40450 | def filtercmd(ui, cmd, func, kw, doc): | ||
|
r27323 | if not ui.debugflag and cmd.startswith("debug") and kw != "debug": | ||
|
r40450 | # Debug command, and user is not looking for those. | ||
|
r27323 | return True | ||
|
r40450 | if not ui.verbose: | ||
if not kw and not doc: | ||||
# Command had no documentation, no point in showing it by default. | ||||
return True | ||||
if getattr(func, 'alias', False) and not getattr(func, 'owndoc', False): | ||||
# Alias didn't have its own documentation. | ||||
return True | ||||
if doc and any(w in doc for w in _exclkeywords): | ||||
# Documentation has excluded keywords. | ||||
return True | ||||
if kw == "shortlist" and not getattr(func, 'helpbasic', False): | ||||
# We're presenting the short list but the command is not basic. | ||||
|
r27323 | return True | ||
|
r40448 | if ui.configbool('help', 'hidden-command.%s' % cmd): | ||
|
r40450 | # Configuration explicitly hides the command. | ||
|
r27323 | return True | ||
return False | ||||
|
r40449 | def filtertopic(ui, topic): | ||
return ui.configbool('help', 'hidden-topic.%s' % topic, False) | ||||
|
r32567 | def topicmatch(ui, commands, kw): | ||
|
r16710 | """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): | ||||
|
r16845 | return kw in encoding.lower(container) # translated in helptable | ||
|
r16710 | results = {'topics': [], | ||
'commands': [], | ||||
'extensions': [], | ||||
'extensioncommands': [], | ||||
} | ||||
|
r40328 | for topic in helptable: | ||
names, header, doc = topic[0:3] | ||||
|
r22322 | # Old extensions may use a str as doc. | ||
|
r16710 | if (sum(map(lowercontains, names)) | ||
or lowercontains(header) | ||||
|
r26413 | or (callable(doc) and lowercontains(doc(ui)))): | ||
|
r40449 | name = names[0] | ||
if not filtertopic(ui, name): | ||||
results['topics'].append((names[0], header)) | ||||
|
r16710 | for cmd, entry in commands.table.iteritems(): | ||
if len(entry) == 3: | ||||
summary = entry[2] | ||||
else: | ||||
summary = '' | ||||
|
r16845 | # translate docs *before* searching there | ||
|
r40450 | func = entry[0] | ||
docs = _(pycompat.getdoc(func)) or '' | ||||
|
r16710 | if kw in cmd or lowercontains(summary) or lowercontains(docs): | ||
|
r16845 | doclines = docs.splitlines() | ||
|
r16710 | if doclines: | ||
summary = doclines[0] | ||||
|
r36264 | cmdname = cmdutil.parsealiases(cmd)[0] | ||
|
r40450 | if filtercmd(ui, cmdname, func, kw, docs): | ||
|
r27324 | continue | ||
|
r16710 | results['commands'].append((cmdname, summary)) | ||
for name, docs in itertools.chain( | ||||
|
r19769 | extensions.enabled(False).iteritems(), | ||
|
r16710 | extensions.disabled().iteritems()): | ||
|
r28058 | if not docs: | ||
continue | ||||
r26845 | name = name.rpartition('.')[-1] | |||
|
r16710 | if lowercontains(name) or lowercontains(docs): | ||
|
r16845 | # extension docs are already translated | ||
results['extensions'].append((name, docs.splitlines()[0])) | ||||
|
r34913 | try: | ||
mod = extensions.load(ui, name, '') | ||||
except ImportError: | ||||
# debug message would be printed in extensions.load() | ||||
continue | ||||
|
r16710 | for cmd, entry in getattr(mod, 'cmdtable', {}).iteritems(): | ||
|
r16711 | if kw in cmd or (len(entry) > 2 and lowercontains(entry[2])): | ||
|
r36264 | cmdname = cmdutil.parsealiases(cmd)[0] | ||
|
r40450 | func = entry[0] | ||
cmddoc = pycompat.getdoc(func) | ||||
|
r32615 | if cmddoc: | ||
cmddoc = gettext(cmddoc).splitlines()[0] | ||||
|
r16884 | else: | ||
cmddoc = _('(no help text available)') | ||||
|
r40450 | if filtercmd(ui, cmdname, func, kw, cmddoc): | ||
|
r27387 | continue | ||
|
r16884 | results['extensioncommands'].append((cmdname, cmddoc)) | ||
|
r16710 | return results | ||
|
r27375 | def loaddoc(topic, subdir=None): | ||
|
r9539 | """Return a delayed loader for help/topic.txt.""" | ||
|
r3798 | |||
|
r26413 | def loader(ui): | ||
|
r22637 | docdir = os.path.join(util.datapath, 'help') | ||
|
r27375 | if subdir: | ||
docdir = os.path.join(docdir, subdir) | ||||
|
r9539 | path = os.path.join(docdir, topic + ".txt") | ||
|
r14168 | doc = gettext(util.readfile(path)) | ||
|
r12820 | for rewriter in helphooks.get(topic, []): | ||
|
r26414 | doc = rewriter(ui, topic, doc) | ||
|
r12820 | return doc | ||
|
r9539 | return loader | ||
|
r7677 | |||
|
r27376 | internalstable = sorted([ | ||
|
r36469 | (['bundle2'], _('Bundle2'), | ||
loaddoc('bundle2', subdir='internals')), | ||||
|
r29747 | (['bundles'], _('Bundles'), | ||
|
r27376 | loaddoc('bundles', subdir='internals')), | ||
|
r39446 | (['cbor'], _('CBOR'), | ||
loaddoc('cbor', subdir='internals')), | ||||
|
r31293 | (['censor'], _('Censor'), | ||
loaddoc('censor', subdir='internals')), | ||||
|
r29747 | (['changegroups'], _('Changegroups'), | ||
|
r27376 | loaddoc('changegroups', subdir='internals')), | ||
|
r34952 | (['config'], _('Config Registrar'), | ||
|
r34933 | loaddoc('config', subdir='internals')), | ||
|
r40631 | (['extensions', 'extension'], _('Extension API'), | ||
loaddoc('extensions', subdir='internals')), | ||||
|
r29747 | (['requirements'], _('Repository Requirements'), | ||
|
r28523 | loaddoc('requirements', subdir='internals')), | ||
|
r29747 | (['revlogs'], _('Revision Logs'), | ||
|
r27631 | loaddoc('revlogs', subdir='internals')), | ||
|
r29859 | (['wireprotocol'], _('Wire Protocol'), | ||
loaddoc('wireprotocol', subdir='internals')), | ||||
|
r39650 | (['wireprotocolrpc'], _('Wire Protocol RPC'), | ||
loaddoc('wireprotocolrpc', subdir='internals')), | ||||
|
r39497 | (['wireprotocolv2'], _('Wire Protocol Version 2'), | ||
loaddoc('wireprotocolv2', subdir='internals')), | ||||
|
r27376 | ]) | ||
def internalshelp(ui): | ||||
"""Generate the index for the "internals" topic.""" | ||||
|
r32076 | lines = ['To access a subtopic, use "hg help internals.{subtopic-name}"\n', | ||
'\n'] | ||||
|
r27376 | for names, header, doc in internalstable: | ||
|
r27400 | lines.append(' :%s: %s\n' % (names[0], header)) | ||
|
r27376 | |||
|
r27400 | return ''.join(lines) | ||
|
r27376 | |||
|
r13888 | helptable = sorted([ | ||
|
r40330 | (['bundlespec'], _("Bundle File Formats"), loaddoc('bundlespec'), | ||
TOPIC_CATEGORY_CONCEPTS), | ||||
(['color'], _("Colorizing Outputs"), loaddoc('color'), | ||||
TOPIC_CATEGORY_OUTPUT), | ||||
(["config", "hgrc"], _("Configuration Files"), loaddoc('config'), | ||||
TOPIC_CATEGORY_CONFIG), | ||||
(['deprecated'], _("Deprecated Features"), loaddoc('deprecated'), | ||||
TOPIC_CATEGORY_MISC), | ||||
(["dates"], _("Date Formats"), loaddoc('dates'), TOPIC_CATEGORY_OUTPUT), | ||||
(["flags"], _("Command-line flags"), loaddoc('flags'), | ||||
TOPIC_CATEGORY_CONFIG), | ||||
(["patterns"], _("File Name Patterns"), loaddoc('patterns'), | ||||
TOPIC_CATEGORY_IDS), | ||||
|
r10282 | (['environment', 'env'], _('Environment Variables'), | ||
|
r40330 | loaddoc('environment'), TOPIC_CATEGORY_CONFIG), | ||
|
r30769 | (['revisions', 'revs', 'revsets', 'revset', 'multirevs', 'mrevs'], | ||
|
r40330 | _('Specifying Revisions'), loaddoc('revisions'), TOPIC_CATEGORY_IDS), | ||
(['filesets', 'fileset'], _("Specifying File Sets"), loaddoc('filesets'), | ||||
TOPIC_CATEGORY_IDS), | ||||
(['diffs'], _('Diff Formats'), loaddoc('diffs'), TOPIC_CATEGORY_OUTPUT), | ||||
|
r30812 | (['merge-tools', 'mergetools', 'mergetool'], _('Merge Tools'), | ||
|
r40330 | loaddoc('merge-tools'), TOPIC_CATEGORY_CONFIG), | ||
|
r16568 | (['templating', 'templates', 'template', 'style'], _('Template Usage'), | ||
|
r40330 | loaddoc('templates'), TOPIC_CATEGORY_OUTPUT), | ||
(['urls'], _('URL Paths'), loaddoc('urls'), TOPIC_CATEGORY_IDS), | ||||
(["extensions"], _("Using Additional Features"), extshelp, | ||||
TOPIC_CATEGORY_CONFIG), | ||||
(["subrepos", "subrepo"], _("Subrepositories"), loaddoc('subrepos'), | ||||
TOPIC_CATEGORY_CONCEPTS), | ||||
(["hgweb"], _("Configuring hgweb"), loaddoc('hgweb'), | ||||
TOPIC_CATEGORY_CONFIG), | ||||
(["glossary"], _("Glossary"), loaddoc('glossary'), TOPIC_CATEGORY_CONCEPTS), | ||||
|
r17321 | (["hgignore", "ignore"], _("Syntax for Mercurial Ignore Files"), | ||
|
r40330 | loaddoc('hgignore'), TOPIC_CATEGORY_IDS), | ||
(["phases"], _("Working with Phases"), loaddoc('phases'), | ||||
TOPIC_CATEGORY_CONCEPTS), | ||||
|
r25881 | (['scripting'], _('Using Mercurial from scripts and automation'), | ||
|
r40330 | loaddoc('scripting'), TOPIC_CATEGORY_MISC), | ||
(['internals'], _("Technical implementation topics"), internalshelp, | ||||
TOPIC_CATEGORY_MISC), | ||||
(['pager'], _("Pager Support"), loaddoc('pager'), TOPIC_CATEGORY_CONFIG), | ||||
|
r13888 | ]) | ||
|
r12820 | |||
|
r27379 | # Maps topics with sub-topics to a list of their sub-topics. | ||
subtopics = { | ||||
'internals': internalstable, | ||||
} | ||||
|
r12820 | # Map topics to lists of callable taking the current topic help and | ||
# returning the updated version | ||||
|
r14318 | helphooks = {} | ||
|
r12820 | |||
def addtopichook(topic, rewriter): | ||||
helphooks.setdefault(topic, []).append(rewriter) | ||||
|
r13593 | |||
|
r26414 | def makeitemsdoc(ui, topic, doc, marker, items, dedent=False): | ||
|
r13593 | """Extract docstring from the items key to function mapping, build a | ||
|
r26196 | single documentation block and use it to overwrite the marker in doc. | ||
|
r13593 | """ | ||
entries = [] | ||||
for name in sorted(items): | ||||
|
r32615 | text = (pycompat.getdoc(items[name]) or '').rstrip() | ||
|
r26415 | if (not text | ||
or not ui.verbose and any(w in text for w in _exclkeywords)): | ||||
|
r13593 | continue | ||
text = gettext(text) | ||||
|
r24098 | if dedent: | ||
|
r32549 | # Abuse latin1 to use textwrap.dedent() on bytes. | ||
text = textwrap.dedent(text.decode('latin1')).encode('latin1') | ||||
|
r13593 | lines = text.splitlines() | ||
|
r16250 | doclines = [(lines[0])] | ||
for l in lines[1:]: | ||||
# Stop once we find some Python doctest | ||||
if l.strip().startswith('>>>'): | ||||
break | ||||
|
r24098 | if dedent: | ||
doclines.append(l.rstrip()) | ||||
else: | ||||
doclines.append(' ' + l.strip()) | ||||
|
r16250 | entries.append('\n'.join(doclines)) | ||
|
r13593 | entries = '\n\n'.join(entries) | ||
return doc.replace(marker, entries) | ||||
|
r14318 | |||
|
r24098 | def addtopicsymbols(topic, marker, symbols, dedent=False): | ||
|
r26414 | def add(ui, topic, doc): | ||
return makeitemsdoc(ui, topic, doc, marker, symbols, dedent=dedent) | ||||
|
r14318 | addtopichook(topic, add) | ||
|
r31793 | addtopicsymbols('bundlespec', '.. bundlecompressionmarker', | ||
util.bundlecompressiontopics()) | ||||
|
r14686 | addtopicsymbols('filesets', '.. predicatesmarker', fileset.symbols) | ||
|
r24099 | addtopicsymbols('merge-tools', '.. internaltoolsmarker', | ||
filemerge.internalsdoc) | ||||
|
r30769 | addtopicsymbols('revisions', '.. predicatesmarker', revset.symbols) | ||
|
r26436 | addtopicsymbols('templates', '.. keywordsmarker', templatekw.keywords) | ||
|
r14318 | addtopicsymbols('templates', '.. filtersmarker', templatefilters.filters) | ||
|
r36940 | addtopicsymbols('templates', '.. functionsmarker', templatefuncs.funcs) | ||
|
r24098 | addtopicsymbols('hgweb', '.. webcommandsmarker', webcommands.commands, | ||
dedent=True) | ||||
|
r18746 | |||
|
r40472 | def inserttweakrc(ui, topic, doc): | ||
marker = '.. tweakdefaultsmarker' | ||||
repl = uimod.tweakrc | ||||
def sub(m): | ||||
lines = [m.group(1) + s for s in repl.splitlines()] | ||||
return '\n'.join(lines) | ||||
return re.sub(br'( *)%s' % re.escape(marker), sub, doc) | ||||
addtopichook('config', inserttweakrc) | ||||
|
r32567 | def help_(ui, commands, name, unknowncmd=False, full=True, subtopic=None, | ||
**opts): | ||||
|
r18746 | ''' | ||
Generate the help for 'name' as unformatted restructured text. If | ||||
'name' is None, describe the commands available. | ||||
''' | ||||
|
r32143 | opts = pycompat.byteskwargs(opts) | ||
|
r18746 | |||
|
r27378 | def helpcmd(name, subtopic=None): | ||
|
r18746 | try: | ||
aliases, entry = cmdutil.findcmd(name, commands.table, | ||||
strict=unknowncmd) | ||||
|
r25660 | except error.AmbiguousCommand as inst: | ||
|
r40299 | # py3 fix: except vars can't be used outside the scope of the | ||
|
r18746 | # except block, nor can be used inside a lambda. python issue4617 | ||
prefix = inst.args[0] | ||||
|
r36264 | select = lambda c: cmdutil.parsealiases(c)[0].startswith(prefix) | ||
|
r18746 | rst = helplist(select) | ||
return rst | ||||
rst = [] | ||||
# check if it's an invalid alias and display its error if it is | ||||
|
r22160 | if getattr(entry[0], 'badalias', None): | ||
|
r22162 | rst.append(entry[0].badalias + '\n') | ||
if entry[0].unknowncmd: | ||||
try: | ||||
rst.extend(helpextcmd(entry[0].cmdname)) | ||||
except error.UnknownCommand: | ||||
pass | ||||
|
r18746 | 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 | ||||
|
r32615 | doc = gettext(pycompat.getdoc(entry[0])) | ||
|
r18746 | if not doc: | ||
doc = _("(no help text available)") | ||||
if util.safehasattr(entry[0], 'definition'): # aliased command | ||||
|
r28828 | source = entry[0].source | ||
|
r18746 | if entry[0].definition.startswith('!'): # shell alias | ||
|
r37152 | doc = (_('shell alias for: %s\n\n%s\n\ndefined by: %s\n') % | ||
(entry[0].definition[1:], doc, source)) | ||||
|
r18746 | else: | ||
|
r28828 | doc = (_('alias for: hg %s\n\n%s\n\ndefined by: %s\n') % | ||
(entry[0].definition, doc, source)) | ||||
|
r18746 | 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) | ||||
|
r32615 | doc = gettext(pycompat.getdoc(mod)) or '' | ||
|
r18746 | if '\n' in doc.strip(): | ||
|
r29974 | msg = _("(use 'hg help -e %s' to show help for " | ||
"the %s extension)") % (name, name) | ||||
|
r18746 | rst.append('\n%s\n' % msg) | ||
except KeyError: | ||||
pass | ||||
# options | ||||
if not ui.quiet and entry[1]: | ||||
|
r22116 | rst.append(optrst(_("options"), entry[1], ui.verbose)) | ||
|
r18746 | |||
if ui.verbose: | ||||
|
r22116 | rst.append(optrst(_("global options"), | ||
commands.globalopts, ui.verbose)) | ||||
|
r18746 | |||
if not ui.verbose: | ||||
if not full: | ||||
|
r29974 | rst.append(_("\n(use 'hg %s -h' to show more help)\n") | ||
|
r18746 | % name) | ||
elif not ui.quiet: | ||||
|
r22110 | rst.append(_('\n(some details hidden, use --verbose ' | ||
'to show complete help)')) | ||||
|
r18746 | |||
return rst | ||||
|
r27325 | def helplist(select=None, **opts): | ||
|
r40327 | # Category -> list of commands | ||
cats = {} | ||||
# Command -> short description | ||||
|
r18746 | h = {} | ||
|
r40327 | # Command -> string showing synonyms | ||
syns = {} | ||||
|
r18746 | for c, e in commands.table.iteritems(): | ||
|
r36264 | fs = cmdutil.parsealiases(c) | ||
f = fs[0] | ||||
|
r40327 | syns[f] = ', '.join(fs) | ||
func = e[0] | ||||
|
r40331 | if select and not select(f): | ||
|
r18746 | continue | ||
|
r40327 | doc = pycompat.getdoc(func) | ||
|
r40450 | if filtercmd(ui, f, func, name, doc): | ||
|
r18746 | continue | ||
doc = gettext(doc) | ||||
if not doc: | ||||
doc = _("(no help text available)") | ||||
h[f] = doc.splitlines()[0].rstrip() | ||||
|
r40327 | |||
cat = getattr(func, 'helpcategory', None) or ( | ||||
registrar.command.CATEGORY_NONE) | ||||
cats.setdefault(cat, []).append(f) | ||||
|
r18746 | |||
rst = [] | ||||
if not h: | ||||
if not ui.quiet: | ||||
rst.append(_('no commands defined\n')) | ||||
return rst | ||||
|
r40327 | # Output top header. | ||
|
r18746 | if not ui.quiet: | ||
|
r40327 | if name == "shortlist": | ||
rst.append(_('basic commands:\n\n')) | ||||
elif name == "debug": | ||||
rst.append(_('debug commands (internal and unsupported):\n\n')) | ||||
|
r18746 | else: | ||
|
r40327 | rst.append(_('list of commands:\n')) | ||
def appendcmds(cmds): | ||||
cmds = sorted(cmds) | ||||
for c in cmds: | ||||
if ui.verbose: | ||||
rst.append(" :%s: %s\n" % (syns[c], h[c])) | ||||
else: | ||||
rst.append(' :%s: %s\n' % (c, h[c])) | ||||
if name in ('shortlist', 'debug'): | ||||
# List without categories. | ||||
appendcmds(h) | ||||
else: | ||||
# Check that all categories have an order. | ||||
missing_order = set(cats.keys()) - set(CATEGORY_ORDER) | ||||
if missing_order: | ||||
ui.develwarn('help categories missing from CATEGORY_ORDER: %s' % | ||||
missing_order) | ||||
# List per category. | ||||
for cat in CATEGORY_ORDER: | ||||
catfns = cats.get(cat, []) | ||||
if catfns: | ||||
if len(cats) > 1: | ||||
catname = gettext(CATEGORY_NAMES[cat]) | ||||
rst.append("\n%s:\n" % catname) | ||||
rst.append("\n") | ||||
appendcmds(catfns) | ||||
|
r18746 | |||
|
r27325 | ex = opts.get | ||
|
r35357 | anyopts = (ex(r'keyword') or not (ex(r'command') or ex(r'extension'))) | ||
|
r27325 | if not name and anyopts: | ||
|
r18746 | exts = listexts(_('enabled extensions:'), extensions.enabled()) | ||
if exts: | ||||
rst.append('\n') | ||||
rst.extend(exts) | ||||
|
r40328 | rst.append(_("\nadditional help topics:\n")) | ||
# Group commands by category. | ||||
topiccats = {} | ||||
for topic in helptable: | ||||
names, header, doc = topic[0:3] | ||||
if len(topic) > 3 and topic[3]: | ||||
category = topic[3] | ||||
else: | ||||
category = TOPIC_CATEGORY_NONE | ||||
|
r40449 | topicname = names[0] | ||
if not filtertopic(ui, topicname): | ||||
topiccats.setdefault(category, []).append( | ||||
(topicname, header)) | ||||
|
r40328 | |||
# Check that all categories have an order. | ||||
missing_order = set(topiccats.keys()) - set(TOPIC_CATEGORY_ORDER) | ||||
if missing_order: | ||||
ui.develwarn( | ||||
'help categories missing from TOPIC_CATEGORY_ORDER: %s' % | ||||
missing_order) | ||||
# Output topics per category. | ||||
for cat in TOPIC_CATEGORY_ORDER: | ||||
topics = topiccats.get(cat, []) | ||||
if topics: | ||||
if len(topiccats) > 1: | ||||
catname = gettext(TOPIC_CATEGORY_NAMES[cat]) | ||||
rst.append("\n%s:\n" % catname) | ||||
rst.append("\n") | ||||
for t, desc in topics: | ||||
rst.append(" :%s: %s\n" % (t, desc)) | ||||
|
r18746 | |||
|
r22115 | if ui.quiet: | ||
pass | ||||
elif ui.verbose: | ||||
|
r22116 | rst.append('\n%s\n' % optrst(_("global options"), | ||
commands.globalopts, ui.verbose)) | ||||
|
r22115 | if name == 'shortlist': | ||
|
r29974 | rst.append(_("\n(use 'hg help' for the full list " | ||
"of commands)\n")) | ||||
|
r22115 | else: | ||
if name == 'shortlist': | ||||
|
r29974 | rst.append(_("\n(use 'hg help' for the full list of commands " | ||
"or 'hg -v' for details)\n")) | ||||
|
r22115 | elif name and not full: | ||
|
r29974 | rst.append(_("\n(use 'hg help %s' to show the full help " | ||
"text)\n") % name) | ||||
|
r40327 | elif name and syns and name in syns.keys(): | ||
|
r29974 | rst.append(_("\n(use 'hg help -v -e %s' to show built-in " | ||
"aliases and global options)\n") % name) | ||||
|
r18746 | else: | ||
|
r29974 | rst.append(_("\n(use 'hg help -v%s' to show built-in aliases " | ||
"and global options)\n") | ||||
|
r22118 | % (name and " " + name or "")) | ||
|
r18746 | return rst | ||
|
r27378 | def helptopic(name, subtopic=None): | ||
|
r27379 | # Look for sub-topic entry first. | ||
header, doc = None, None | ||||
if subtopic and name in subtopics: | ||||
for names, header, doc in subtopics[name]: | ||||
if subtopic in names: | ||||
break | ||||
if not header: | ||||
|
r40328 | for topic in helptable: | ||
names, header, doc = topic[0:3] | ||||
|
r27379 | if name in names: | ||
break | ||||
else: | ||||
raise error.UnknownCommand(name) | ||||
|
r18746 | |||
|
r18748 | rst = [minirst.section(header)] | ||
|
r18746 | # description | ||
if not doc: | ||||
rst.append(" %s\n" % _("(no help text available)")) | ||||
|
r21796 | if callable(doc): | ||
|
r26413 | rst += [" %s\n" % l for l in doc(ui).splitlines()] | ||
|
r18746 | |||
if not ui.verbose: | ||||
|
r22114 | omitted = _('(some details hidden, use --verbose' | ||
' to show complete help)') | ||||
|
r18746 | indicateomitted(rst, omitted) | ||
try: | ||||
cmdutil.findcmd(name, commands.table) | ||||
|
r29974 | rst.append(_("\nuse 'hg help -c %s' to see help for " | ||
"the %s command\n") % (name, name)) | ||||
|
r18746 | except error.UnknownCommand: | ||
pass | ||||
return rst | ||||
|
r27378 | def helpext(name, subtopic=None): | ||
|
r18746 | try: | ||
mod = extensions.find(name) | ||||
|
r32615 | doc = gettext(pycompat.getdoc(mod)) or _('no help text available') | ||
|
r18746 | 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) | ||||
r26845 | rst = [_('%s extension - %s\n\n') % (name.rpartition('.')[-1], head)] | |||
|
r18746 | if tail: | ||
rst.extend(tail.splitlines(True)) | ||||
rst.append('\n') | ||||
if not ui.verbose: | ||||
|
r22114 | omitted = _('(some details hidden, use --verbose' | ||
' to show complete help)') | ||||
|
r18746 | indicateomitted(rst, omitted) | ||
if mod: | ||||
try: | ||||
ct = mod.cmdtable | ||||
except AttributeError: | ||||
ct = {} | ||||
r26845 | modcmds = set([c.partition('|')[0] for c in ct]) | |||
|
r18746 | rst.extend(helplist(modcmds.__contains__)) | ||
else: | ||||
|
r29974 | rst.append(_("(use 'hg help extensions' for information on enabling" | ||
" extensions)\n")) | ||||
|
r18746 | return rst | ||
|
r27378 | def helpextcmd(name, subtopic=None): | ||
|
r37996 | cmd, ext, doc = extensions.disabledcmd(ui, name, | ||
|
r18746 | ui.configbool('ui', 'strict')) | ||
|
r37996 | doc = doc.splitlines()[0] | ||
|
r18746 | |||
rst = listexts(_("'%s' is provided by the following " | ||||
|
r27152 | "extension:") % cmd, {ext: doc}, indent=4, | ||
showdeprecated=True) | ||||
|
r18746 | rst.append('\n') | ||
|
r29974 | rst.append(_("(use 'hg help extensions' for information on enabling " | ||
"extensions)\n")) | ||||
|
r18746 | return rst | ||
rst = [] | ||||
kw = opts.get('keyword') | ||||
|
r27325 | if kw or name is None and any(opts[o] for o in opts): | ||
|
r32567 | matches = topicmatch(ui, commands, name or '') | ||
|
r26238 | helpareas = [] | ||
if opts.get('extension'): | ||||
helpareas += [('extensions', _('Extensions'))] | ||||
if opts.get('command'): | ||||
helpareas += [('commands', _('Commands'))] | ||||
if not helpareas: | ||||
helpareas = [('topics', _('Topics')), | ||||
|
r18746 | ('commands', _('Commands')), | ||
('extensions', _('Extensions')), | ||||
|
r26238 | ('extensioncommands', _('Extension Commands'))] | ||
for t, title in helpareas: | ||||
|
r18746 | if matches[t]: | ||
rst.append('%s:\n\n' % title) | ||||
rst.extend(minirst.maketable(sorted(matches[t]), 1)) | ||||
rst.append('\n') | ||||
|
r21288 | if not rst: | ||
msg = _('no matches') | ||||
|
r29974 | hint = _("try 'hg help' for a list of topics") | ||
|
r26587 | raise error.Abort(msg, hint=hint) | ||
|
r18746 | elif name and name != 'shortlist': | ||
|
r26238 | queries = [] | ||
|
r18746 | if unknowncmd: | ||
|
r26238 | queries += [helpextcmd] | ||
if opts.get('extension'): | ||||
queries += [helpext] | ||||
if opts.get('command'): | ||||
queries += [helpcmd] | ||||
if not queries: | ||||
|
r18746 | queries = (helptopic, helpcmd, helpext, helpextcmd) | ||
for f in queries: | ||||
try: | ||||
|
r27378 | rst = f(name, subtopic) | ||
|
r18746 | break | ||
|
r21289 | except error.UnknownCommand: | ||
pass | ||||
else: | ||||
if unknowncmd: | ||||
raise error.UnknownCommand(name) | ||||
else: | ||||
msg = _('no such help topic: %s') % name | ||||
|
r29974 | hint = _("try 'hg help --keyword %s'") % name | ||
|
r26587 | raise error.Abort(msg, hint=hint) | ||
|
r18746 | else: | ||
# program name | ||||
if not ui.quiet: | ||||
rst = [_("Mercurial Distributed SCM\n"), '\n'] | ||||
|
r32547 | rst.extend(helplist(None, **pycompat.strkwargs(opts))) | ||
|
r18746 | |||
return ''.join(rst) | ||||
|
r31059 | |||
|
r39375 | def formattedhelp(ui, commands, fullname, keep=None, unknowncmd=False, | ||
full=True, **opts): | ||||
|
r31059 | """get help for a given topic (as a dotted name) as rendered rst | ||
Either returns the rendered help text or raises an exception. | ||||
""" | ||||
if keep is None: | ||||
keep = [] | ||||
|
r31265 | else: | ||
keep = list(keep) # make a copy so we can mutate this later | ||||
|
r39375 | |||
# <fullname> := <name>[.<subtopic][.<section>] | ||||
name = subtopic = section = None | ||||
if fullname is not None: | ||||
nameparts = fullname.split('.') | ||||
name = nameparts.pop(0) | ||||
if nameparts and name in subtopics: | ||||
subtopic = nameparts.pop(0) | ||||
if nameparts: | ||||
section = encoding.lower('.'.join(nameparts)) | ||||
|
r33499 | textwidth = ui.configint('ui', 'textwidth') | ||
|
r31059 | termwidth = ui.termwidth() - 2 | ||
if textwidth <= 0 or termwidth < textwidth: | ||||
textwidth = termwidth | ||||
|
r32567 | text = help_(ui, commands, name, | ||
|
r31059 | subtopic=subtopic, unknowncmd=unknowncmd, full=full, **opts) | ||
|
r39344 | blocks, pruned = minirst.parse(text, keep=keep) | ||
|
r31059 | if 'verbose' in pruned: | ||
keep.append('omitted') | ||||
else: | ||||
keep.append('notomitted') | ||||
|
r39344 | blocks, pruned = minirst.parse(text, keep=keep) | ||
if section: | ||||
blocks = minirst.filtersections(blocks, section) | ||||
|
r39345 | |||
# We could have been given a weird ".foo" section without a name | ||||
# to look for, or we could have simply failed to found "foo.bar" | ||||
# because bar isn't a section of foo | ||||
if section and not (blocks and name): | ||||
raise error.Abort(_("help section not found: %s") % fullname) | ||||
|
r39344 | return minirst.formatplain(blocks, textwidth) | ||