help.py
1180 lines
| 36.6 KiB
| text/x-python
|
PythonLexer
/ mercurial / help.py
|
r3795 | # help.py - help data for mercurial | ||
# | ||||
|
r47575 | # Copyright 2006 Olivia Mackall <olivia@selenic.com> | ||
|
r3795 | # | ||
|
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 | |||
import itertools | ||||
|
r40472 | import re | ||
|
r27479 | import textwrap | ||
from .i18n import ( | ||||
_, | ||||
gettext, | ||||
) | ||||
|
r43359 | from .pycompat import getattr | ||
|
r27479 | 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, | ||
) | ||||
|
r43346 | from .hgweb import webcommands | ||
|
r44070 | from .utils import ( | ||
compression, | ||||
resourceutil, | ||||
|
r49888 | stringutil, | ||
|
r44070 | ) | ||
|
r8863 | |||
|
r32291 | _exclkeywords = { | ||
|
r43347 | b"(ADVANCED)", | ||
b"(DEPRECATED)", | ||||
b"(EXPERIMENTAL)", | ||||
|
r31080 | # i18n: "(ADVANCED)" is a keyword, must be translated consistently | ||
|
r43347 | _(b"(ADVANCED)"), | ||
|
r26370 | # i18n: "(DEPRECATED)" is a keyword, must be translated consistently | ||
|
r43347 | _(b"(DEPRECATED)"), | ||
|
r26370 | # i18n: "(EXPERIMENTAL)" is a keyword, must be translated consistently | ||
|
r43347 | _(b"(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, | ||||
|
r43346 | registrar.command.CATEGORY_CHANGE_NAVIGATION, | ||
|
r40329 | 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 = { | ||||
|
r43347 | registrar.command.CATEGORY_REPO_CREATION: b'Repository creation', | ||
registrar.command.CATEGORY_REMOTE_REPO_MANAGEMENT: b'Remote repository management', | ||||
registrar.command.CATEGORY_COMMITTING: b'Change creation', | ||||
registrar.command.CATEGORY_CHANGE_NAVIGATION: b'Change navigation', | ||||
registrar.command.CATEGORY_CHANGE_MANAGEMENT: b'Change manipulation', | ||||
registrar.command.CATEGORY_CHANGE_ORGANIZATION: b'Change organization', | ||||
registrar.command.CATEGORY_WORKING_DIRECTORY: b'Working directory management', | ||||
registrar.command.CATEGORY_FILE_CONTENTS: b'File content management', | ||||
registrar.command.CATEGORY_IMPORT_EXPORT: b'Change import/export', | ||||
registrar.command.CATEGORY_MAINTENANCE: b'Repository maintenance', | ||||
registrar.command.CATEGORY_HELP: b'Help', | ||||
registrar.command.CATEGORY_MISC: b'Miscellaneous commands', | ||||
registrar.command.CATEGORY_NONE: b'Uncategorized commands', | ||||
|
r40327 | } | ||
|
r40328 | # Topic categories. | ||
|
r43347 | TOPIC_CATEGORY_IDS = b'ids' | ||
TOPIC_CATEGORY_OUTPUT = b'output' | ||||
TOPIC_CATEGORY_CONFIG = b'config' | ||||
TOPIC_CATEGORY_CONCEPTS = b'concepts' | ||||
TOPIC_CATEGORY_MISC = b'misc' | ||||
TOPIC_CATEGORY_NONE = b'none' | ||||
|
r40328 | |||
# 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 = { | ||||
|
r43347 | TOPIC_CATEGORY_IDS: b'Mercurial identifiers', | ||
TOPIC_CATEGORY_OUTPUT: b'Mercurial output', | ||||
TOPIC_CATEGORY_CONFIG: b'Mercurial configuration', | ||||
TOPIC_CATEGORY_CONCEPTS: b'Concepts', | ||||
TOPIC_CATEGORY_MISC: b'Miscellaneous', | ||||
TOPIC_CATEGORY_NONE: b'Uncategorized topics', | ||||
|
r40328 | } | ||
|
r43346 | |||
|
r20582 | def listexts(header, exts, indent=1, showdeprecated=False): | ||
|
r8879 | '''return a text listing of the given extensions''' | ||
|
r16852 | rst = [] | ||
if exts: | ||||
|
r49768 | for name, desc in sorted(exts.items()): | ||
|
r26371 | if not showdeprecated and any(w in desc for w in _exclkeywords): | ||
|
r20582 | continue | ||
|
r43347 | rst.append(b'%s:%s: %s\n' % (b' ' * indent, name, desc)) | ||
|
r27151 | if rst: | ||
|
r43347 | rst.insert(0, b'\n%s\n\n' % header) | ||
|
r16852 | return rst | ||
|
r8864 | |||
|
r43346 | |||
|
r26413 | def extshelp(ui): | ||
|
r43347 | rst = loaddoc(b'extensions')(ui).splitlines(True) | ||
|
r43346 | rst.extend( | ||
listexts( | ||||
|
r43347 | _(b'enabled extensions:'), extensions.enabled(), showdeprecated=True | ||
|
r43346 | ) | ||
) | ||||
rst.extend( | ||||
listexts( | ||||
|
r43347 | _(b'disabled extensions:'), | ||
|
r43346 | extensions.disabled(), | ||
showdeprecated=ui.verbose, | ||||
) | ||||
) | ||||
|
r43347 | doc = b''.join(rst) | ||
|
r8863 | return doc | ||
|
r7013 | |||
|
r44787 | |||
|
r44777 | def parsedefaultmarker(text): | ||
"""given a text 'abc (DEFAULT: def.ghi)', | ||||
returns (b'abc', (b'def', b'ghi')). Otherwise return None""" | ||||
if text[-1:] == b')': | ||||
marker = b' (DEFAULT: ' | ||||
pos = text.find(marker) | ||||
if pos >= 0: | ||||
|
r44787 | item = text[pos + len(marker) : -1] | ||
|
r44777 | return text[:pos], item.split(b'.', 2) | ||
|
r43346 | |||
|
r44787 | |||
|
r44777 | def optrst(header, options, verbose, ui): | ||
|
r16781 | data = [] | ||
multioccur = False | ||||
for option in options: | ||||
if len(option) == 5: | ||||
shortopt, longopt, default, desc, optlabel = option | ||||
else: | ||||
shortopt, longopt, default, desc = option | ||||
|
r43347 | optlabel = _(b"VALUE") # default label | ||
|
r16781 | |||
|
r26369 | if not verbose and any(w in desc for w in _exclkeywords): | ||
|
r16781 | continue | ||
|
r44777 | defaultstrsuffix = b'' | ||
if default is None: | ||||
parseresult = parsedefaultmarker(desc) | ||||
if parseresult is not None: | ||||
(desc, (section, name)) = parseresult | ||||
if ui.configbool(section, name): | ||||
default = True | ||||
defaultstrsuffix = _(b' from config') | ||||
|
r43347 | so = b'' | ||
|
r16781 | if shortopt: | ||
|
r43347 | so = b'-' + shortopt | ||
lo = b'--' + longopt | ||||
|
r41045 | if default is True: | ||
|
r43347 | lo = b'--[no-]' + longopt | ||
|
r37109 | |||
if isinstance(default, fancyopts.customopt): | ||||
|
r37110 | default = default.getdefaultvalue() | ||
|
r41046 | 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. | ||||
|
r40989 | defaultstr = pycompat.bytestr(default) | ||
|
r41046 | if default is True: | ||
|
r43347 | defaultstr = _(b"on") | ||
|
r44777 | desc += _(b" (default: %s)") % (defaultstr + defaultstrsuffix) | ||
|
r16781 | |||
if isinstance(default, list): | ||||
|
r43347 | lo += b" %s [+]" % optlabel | ||
|
r16781 | multioccur = True | ||
elif (default is not None) and not isinstance(default, bool): | ||||
|
r43347 | lo += b" %s" % optlabel | ||
|
r16781 | |||
data.append((so, lo, desc)) | ||||
|
r22117 | if multioccur: | ||
|
r43347 | header += _(b" ([+] can be repeated)") | ||
|
r16781 | |||
|
r43347 | rst = [b'\n%s:\n\n' % header] | ||
|
r22116 | rst.extend(minirst.maketable(data, 1)) | ||
|
r16781 | |||
|
r43347 | return b''.join(rst) | ||
|
r16781 | |||
|
r43346 | |||
|
r17837 | def indicateomitted(rst, omitted, notomitted=None): | ||
|
r43347 | rst.append(b'\n\n.. container:: omitted\n\n %s\n\n' % omitted) | ||
|
r17837 | if notomitted: | ||
|
r43347 | rst.append(b'\n\n.. container:: notomitted\n\n %s\n\n' % notomitted) | ||
|
r17837 | |||
|
r43346 | |||
|
r40450 | def filtercmd(ui, cmd, func, kw, doc): | ||
|
r43347 | if not ui.debugflag and cmd.startswith(b"debug") and kw != b"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 | ||||
|
r43347 | if kw == b"shortlist" and not getattr(func, 'helpbasic', False): | ||
|
r40450 | # We're presenting the short list but the command is not basic. | ||
|
r27323 | return True | ||
|
r43347 | if ui.configbool(b'help', b'hidden-command.%s' % cmd): | ||
|
r40450 | # Configuration explicitly hides the command. | ||
|
r27323 | return True | ||
return False | ||||
|
r43346 | |||
|
r40449 | def filtertopic(ui, topic): | ||
|
r43347 | return ui.configbool(b'help', b'hidden-topic.%s' % topic, False) | ||
|
r40449 | |||
|
r43346 | |||
|
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) | ||||
|
r43346 | |||
|
r16710 | def lowercontains(container): | ||
|
r16845 | return kw in encoding.lower(container) # translated in helptable | ||
|
r43346 | |||
results = { | ||||
|
r43347 | b'topics': [], | ||
b'commands': [], | ||||
b'extensions': [], | ||||
b'extensioncommands': [], | ||||
|
r43346 | } | ||
|
r40328 | for topic in helptable: | ||
names, header, doc = topic[0:3] | ||||
|
r22322 | # Old extensions may use a str as doc. | ||
|
r43346 | if ( | ||
sum(map(lowercontains, names)) | ||||
|
r16710 | or lowercontains(header) | ||
|
r43346 | or (callable(doc) and lowercontains(doc(ui))) | ||
): | ||||
|
r40449 | name = names[0] | ||
if not filtertopic(ui, name): | ||||
|
r43347 | results[b'topics'].append((names[0], header)) | ||
|
r49768 | for cmd, entry in commands.table.items(): | ||
|
r16710 | if len(entry) == 3: | ||
summary = entry[2] | ||||
else: | ||||
|
r43347 | summary = b'' | ||
|
r16845 | # translate docs *before* searching there | ||
|
r40450 | func = entry[0] | ||
|
r43347 | docs = _(pycompat.getdoc(func)) or b'' | ||
|
r16710 | if kw in cmd or lowercontains(summary) or lowercontains(docs): | ||
|
r49888 | if docs: | ||
summary = stringutil.firstline(docs) | ||||
|
r36264 | cmdname = cmdutil.parsealiases(cmd)[0] | ||
|
r40450 | if filtercmd(ui, cmdname, func, kw, docs): | ||
|
r27324 | continue | ||
|
r43347 | results[b'commands'].append((cmdname, summary)) | ||
|
r16710 | for name, docs in itertools.chain( | ||
|
r49778 | extensions.enabled(False).items(), | ||
extensions.disabled().items(), | ||||
|
r43346 | ): | ||
|
r28058 | if not docs: | ||
continue | ||||
|
r43347 | name = name.rpartition(b'.')[-1] | ||
|
r16710 | if lowercontains(name) or lowercontains(docs): | ||
|
r16845 | # extension docs are already translated | ||
|
r49888 | results[b'extensions'].append((name, stringutil.firstline(docs))) | ||
|
r34913 | try: | ||
|
r43347 | mod = extensions.load(ui, name, b'') | ||
|
r34913 | except ImportError: | ||
# debug message would be printed in extensions.load() | ||||
continue | ||||
|
r49778 | for cmd, entry in getattr(mod, 'cmdtable', {}).items(): | ||
|
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: | ||
|
r49888 | cmddoc = stringutil.firstline(gettext(cmddoc)) | ||
|
r16884 | else: | ||
|
r43347 | cmddoc = _(b'(no help text available)') | ||
|
r40450 | if filtercmd(ui, cmdname, func, kw, cmddoc): | ||
|
r27387 | continue | ||
|
r43347 | results[b'extensioncommands'].append((cmdname, cmddoc)) | ||
|
r16710 | return results | ||
|
r43346 | |||
|
r27375 | def loaddoc(topic, subdir=None): | ||
|
r9539 | """Return a delayed loader for help/topic.txt.""" | ||
|
r3798 | |||
|
r26413 | def loader(ui): | ||
|
r44479 | package = b'mercurial.helptext' | ||
|
r27375 | if subdir: | ||
|
r44479 | package += b'.' + subdir | ||
|
r44323 | with resourceutil.open_resource(package, topic + b'.txt') as fp: | ||
doc = gettext(fp.read()) | ||||
|
r12820 | for rewriter in helphooks.get(topic, []): | ||
|
r26414 | doc = rewriter(ui, topic, doc) | ||
|
r12820 | return doc | ||
|
r9539 | return loader | ||
|
r7677 | |||
|
r43346 | |||
internalstable = sorted( | ||||
[ | ||||
r45619 | ( | |||
[b'bid-merge'], | ||||
_(b'Bid Merge Algorithm'), | ||||
loaddoc(b'bid-merge', subdir=b'internals'), | ||||
), | ||||
|
r43347 | ([b'bundle2'], _(b'Bundle2'), loaddoc(b'bundle2', subdir=b'internals')), | ||
([b'bundles'], _(b'Bundles'), loaddoc(b'bundles', subdir=b'internals')), | ||||
([b'cbor'], _(b'CBOR'), loaddoc(b'cbor', subdir=b'internals')), | ||||
([b'censor'], _(b'Censor'), loaddoc(b'censor', subdir=b'internals')), | ||||
|
r43346 | ( | ||
|
r43347 | [b'changegroups'], | ||
_(b'Changegroups'), | ||||
loaddoc(b'changegroups', subdir=b'internals'), | ||||
|
r43346 | ), | ||
( | ||||
|
r43347 | [b'config'], | ||
_(b'Config Registrar'), | ||||
loaddoc(b'config', subdir=b'internals'), | ||||
|
r43346 | ), | ||
( | ||||
|
r48978 | [b'dirstate-v2'], | ||
_(b'dirstate-v2 file format'), | ||||
loaddoc(b'dirstate-v2', subdir=b'internals'), | ||||
), | ||||
( | ||||
|
r43347 | [b'extensions', b'extension'], | ||
_(b'Extension API'), | ||||
loaddoc(b'extensions', subdir=b'internals'), | ||||
|
r43346 | ), | ||
( | ||||
|
r43347 | [b'mergestate'], | ||
_(b'Mergestate'), | ||||
loaddoc(b'mergestate', subdir=b'internals'), | ||||
|
r43346 | ), | ||
( | ||||
|
r43347 | [b'requirements'], | ||
_(b'Repository Requirements'), | ||||
loaddoc(b'requirements', subdir=b'internals'), | ||||
|
r43346 | ), | ||
( | ||||
|
r43347 | [b'revlogs'], | ||
_(b'Revision Logs'), | ||||
loaddoc(b'revlogs', subdir=b'internals'), | ||||
|
r43346 | ), | ||
( | ||||
|
r43347 | [b'wireprotocol'], | ||
_(b'Wire Protocol'), | ||||
loaddoc(b'wireprotocol', subdir=b'internals'), | ||||
|
r43346 | ), | ||
( | ||||
|
r43347 | [b'wireprotocolrpc'], | ||
_(b'Wire Protocol RPC'), | ||||
loaddoc(b'wireprotocolrpc', subdir=b'internals'), | ||||
|
r43346 | ), | ||
( | ||||
|
r43347 | [b'wireprotocolv2'], | ||
_(b'Wire Protocol Version 2'), | ||||
loaddoc(b'wireprotocolv2', subdir=b'internals'), | ||||
|
r43346 | ), | ||
] | ||||
) | ||||
|
r27376 | |||
def internalshelp(ui): | ||||
"""Generate the index for the "internals" topic.""" | ||||
|
r43346 | lines = [ | ||
|
r43347 | b'To access a subtopic, use "hg help internals.{subtopic-name}"\n', | ||
b'\n', | ||||
|
r43346 | ] | ||
|
r27376 | for names, header, doc in internalstable: | ||
|
r43347 | lines.append(b' :%s: %s\n' % (names[0], header)) | ||
|
r27376 | |||
|
r43347 | return b''.join(lines) | ||
|
r27376 | |||
|
r43346 | |||
helptable = sorted( | ||||
[ | ||||
( | ||||
|
r43347 | [b'bundlespec'], | ||
_(b"Bundle File Formats"), | ||||
loaddoc(b'bundlespec'), | ||||
|
r43346 | TOPIC_CATEGORY_CONCEPTS, | ||
), | ||||
( | ||||
|
r43347 | [b'color'], | ||
_(b"Colorizing Outputs"), | ||||
loaddoc(b'color'), | ||||
|
r43346 | TOPIC_CATEGORY_OUTPUT, | ||
), | ||||
( | ||||
|
r43347 | [b"config", b"hgrc"], | ||
_(b"Configuration Files"), | ||||
loaddoc(b'config'), | ||||
|
r43346 | TOPIC_CATEGORY_CONFIG, | ||
), | ||||
( | ||||
|
r43347 | [b'deprecated'], | ||
_(b"Deprecated Features"), | ||||
loaddoc(b'deprecated'), | ||||
|
r43346 | TOPIC_CATEGORY_MISC, | ||
), | ||||
( | ||||
|
r43347 | [b"dates"], | ||
_(b"Date Formats"), | ||||
loaddoc(b'dates'), | ||||
TOPIC_CATEGORY_OUTPUT, | ||||
), | ||||
( | ||||
[b"flags"], | ||||
_(b"Command-line flags"), | ||||
loaddoc(b'flags'), | ||||
|
r43346 | TOPIC_CATEGORY_CONFIG, | ||
), | ||||
( | ||||
|
r43347 | [b"patterns"], | ||
_(b"File Name Patterns"), | ||||
loaddoc(b'patterns'), | ||||
|
r43346 | TOPIC_CATEGORY_IDS, | ||
), | ||||
( | ||||
|
r43347 | [b'environment', b'env'], | ||
_(b'Environment Variables'), | ||||
loaddoc(b'environment'), | ||||
|
r43346 | TOPIC_CATEGORY_CONFIG, | ||
), | ||||
( | ||||
|
r43347 | [ | ||
b'revisions', | ||||
b'revs', | ||||
b'revsets', | ||||
b'revset', | ||||
b'multirevs', | ||||
b'mrevs', | ||||
], | ||||
_(b'Specifying Revisions'), | ||||
loaddoc(b'revisions'), | ||||
|
r43346 | TOPIC_CATEGORY_IDS, | ||
), | ||||
( | ||||
|
r49364 | [ | ||
b'rust', | ||||
b'rustext', | ||||
], | ||||
_(b'Rust in Mercurial'), | ||||
loaddoc(b'rust'), | ||||
TOPIC_CATEGORY_CONFIG, | ||||
), | ||||
( | ||||
|
r43347 | [b'filesets', b'fileset'], | ||
_(b"Specifying File Sets"), | ||||
loaddoc(b'filesets'), | ||||
|
r43346 | TOPIC_CATEGORY_IDS, | ||
), | ||||
( | ||||
|
r43347 | [b'diffs'], | ||
_(b'Diff Formats'), | ||||
loaddoc(b'diffs'), | ||||
TOPIC_CATEGORY_OUTPUT, | ||||
), | ||||
( | ||||
[b'merge-tools', b'mergetools', b'mergetool'], | ||||
_(b'Merge Tools'), | ||||
loaddoc(b'merge-tools'), | ||||
|
r43346 | TOPIC_CATEGORY_CONFIG, | ||
), | ||||
( | ||||
|
r43347 | [b'templating', b'templates', b'template', b'style'], | ||
_(b'Template Usage'), | ||||
loaddoc(b'templates'), | ||||
|
r43346 | TOPIC_CATEGORY_OUTPUT, | ||
), | ||||
|
r43347 | ([b'urls'], _(b'URL Paths'), loaddoc(b'urls'), TOPIC_CATEGORY_IDS), | ||
|
r43346 | ( | ||
|
r43347 | [b"extensions"], | ||
_(b"Using Additional Features"), | ||||
|
r43346 | extshelp, | ||
TOPIC_CATEGORY_CONFIG, | ||||
), | ||||
( | ||||
|
r43347 | [b"subrepos", b"subrepo"], | ||
_(b"Subrepositories"), | ||||
loaddoc(b'subrepos'), | ||||
|
r43346 | TOPIC_CATEGORY_CONCEPTS, | ||
), | ||||
( | ||||
|
r43347 | [b"hgweb"], | ||
_(b"Configuring hgweb"), | ||||
loaddoc(b'hgweb'), | ||||
|
r43346 | TOPIC_CATEGORY_CONFIG, | ||
), | ||||
( | ||||
|
r43347 | [b"glossary"], | ||
_(b"Glossary"), | ||||
loaddoc(b'glossary'), | ||||
|
r43346 | TOPIC_CATEGORY_CONCEPTS, | ||
), | ||||
( | ||||
|
r43347 | [b"hgignore", b"ignore"], | ||
_(b"Syntax for Mercurial Ignore Files"), | ||||
loaddoc(b'hgignore'), | ||||
|
r43346 | TOPIC_CATEGORY_IDS, | ||
), | ||||
( | ||||
|
r43347 | [b"phases"], | ||
_(b"Working with Phases"), | ||||
loaddoc(b'phases'), | ||||
|
r43346 | TOPIC_CATEGORY_CONCEPTS, | ||
), | ||||
( | ||||
|
r47781 | [b"evolution"], | ||
_(b"Safely rewriting history (EXPERIMENTAL)"), | ||||
loaddoc(b'evolution'), | ||||
TOPIC_CATEGORY_CONCEPTS, | ||||
), | ||||
( | ||||
|
r43347 | [b'scripting'], | ||
_(b'Using Mercurial from scripts and automation'), | ||||
loaddoc(b'scripting'), | ||||
|
r43346 | TOPIC_CATEGORY_MISC, | ||
), | ||||
( | ||||
|
r43347 | [b'internals'], | ||
_(b"Technical implementation topics"), | ||||
|
r43346 | internalshelp, | ||
TOPIC_CATEGORY_MISC, | ||||
), | ||||
( | ||||
|
r43347 | [b'pager'], | ||
_(b"Pager Support"), | ||||
loaddoc(b'pager'), | ||||
|
r43346 | TOPIC_CATEGORY_CONFIG, | ||
), | ||||
] | ||||
) | ||||
|
r12820 | |||
|
r27379 | # Maps topics with sub-topics to a list of their sub-topics. | ||
subtopics = { | ||||
|
r43347 | b'internals': internalstable, | ||
|
r27379 | } | ||
|
r12820 | # Map topics to lists of callable taking the current topic help and | ||
# returning the updated version | ||||
|
r14318 | helphooks = {} | ||
|
r12820 | |||
|
r43346 | |||
|
r12820 | def addtopichook(topic, rewriter): | ||
helphooks.setdefault(topic, []).append(rewriter) | ||||
|
r13593 | |||
|
r43346 | |||
|
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): | ||||
|
r43347 | text = (pycompat.getdoc(items[name]) or b'').rstrip() | ||
|
r43346 | 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() | ||
|
r49888 | doclines = [lines[0]] | ||
|
r16250 | for l in lines[1:]: | ||
# Stop once we find some Python doctest | ||||
|
r43347 | if l.strip().startswith(b'>>>'): | ||
|
r16250 | break | ||
|
r24098 | if dedent: | ||
doclines.append(l.rstrip()) | ||||
else: | ||||
|
r43347 | doclines.append(b' ' + l.strip()) | ||
entries.append(b'\n'.join(doclines)) | ||||
entries = b'\n\n'.join(entries) | ||||
|
r13593 | return doc.replace(marker, entries) | ||
|
r14318 | |||
|
r43346 | |||
|
r24098 | def addtopicsymbols(topic, marker, symbols, dedent=False): | ||
|
r26414 | def add(ui, topic, doc): | ||
return makeitemsdoc(ui, topic, doc, marker, symbols, dedent=dedent) | ||||
|
r43346 | |||
|
r14318 | addtopichook(topic, add) | ||
|
r43346 | |||
addtopicsymbols( | ||||
|
r43347 | b'bundlespec', | ||
b'.. bundlecompressionmarker', | ||||
|
r43346 | compression.bundlecompressiontopics(), | ||
) | ||||
|
r43347 | addtopicsymbols(b'filesets', b'.. predicatesmarker', fileset.symbols) | ||
|
r43346 | addtopicsymbols( | ||
|
r43347 | b'merge-tools', b'.. internaltoolsmarker', filemerge.internalsdoc | ||
) | ||||
addtopicsymbols(b'revisions', b'.. predicatesmarker', revset.symbols) | ||||
addtopicsymbols(b'templates', b'.. keywordsmarker', templatekw.keywords) | ||||
addtopicsymbols(b'templates', b'.. filtersmarker', templatefilters.filters) | ||||
addtopicsymbols(b'templates', b'.. functionsmarker', templatefuncs.funcs) | ||||
addtopicsymbols( | ||||
b'hgweb', b'.. webcommandsmarker', webcommands.commands, dedent=True | ||||
|
r43346 | ) | ||
|
r18746 | |||
|
r40472 | def inserttweakrc(ui, topic, doc): | ||
|
r43347 | marker = b'.. tweakdefaultsmarker' | ||
|
r40472 | repl = uimod.tweakrc | ||
|
r43346 | |||
|
r40472 | def sub(m): | ||
lines = [m.group(1) + s for s in repl.splitlines()] | ||||
|
r43347 | return b'\n'.join(lines) | ||
|
r43346 | |||
|
r40472 | return re.sub(br'( *)%s' % re.escape(marker), sub, doc) | ||
|
r43346 | |||
|
r46218 | def _getcategorizedhelpcmds(ui, cmdtable, name, select=None): | ||
# Category -> list of commands | ||||
cats = {} | ||||
# Command -> short description | ||||
h = {} | ||||
# Command -> string showing synonyms | ||||
syns = {} | ||||
|
r49768 | for c, e in cmdtable.items(): | ||
|
r46218 | fs = cmdutil.parsealiases(c) | ||
f = fs[0] | ||||
syns[f] = fs | ||||
func = e[0] | ||||
if select and not select(f): | ||||
continue | ||||
doc = pycompat.getdoc(func) | ||||
if filtercmd(ui, f, func, name, doc): | ||||
continue | ||||
doc = gettext(doc) | ||||
if not doc: | ||||
doc = _(b"(no help text available)") | ||||
|
r49888 | h[f] = stringutil.firstline(doc).rstrip() | ||
|
r46218 | |||
cat = getattr(func, 'helpcategory', None) or ( | ||||
registrar.command.CATEGORY_NONE | ||||
) | ||||
cats.setdefault(cat, []).append(f) | ||||
return cats, h, syns | ||||
def _getcategorizedhelptopics(ui, topictable): | ||||
# Group commands by category. | ||||
topiccats = {} | ||||
syns = {} | ||||
for topic in topictable: | ||||
names, header, doc = topic[0:3] | ||||
if len(topic) > 3 and topic[3]: | ||||
category = topic[3] | ||||
else: | ||||
category = TOPIC_CATEGORY_NONE | ||||
topicname = names[0] | ||||
syns[topicname] = list(names) | ||||
if not filtertopic(ui, topicname): | ||||
topiccats.setdefault(category, []).append((topicname, header)) | ||||
return topiccats, syns | ||||
|
r43347 | addtopichook(b'config', inserttweakrc) | ||
|
r40472 | |||
|
r43346 | |||
def help_( | ||||
ui, | ||||
commands, | ||||
name, | ||||
unknowncmd=False, | ||||
full=True, | ||||
subtopic=None, | ||||
fullname=None, | ||||
**opts | ||||
): | ||||
|
r46554 | """ | ||
|
r18746 | Generate the help for 'name' as unformatted restructured text. If | ||
'name' is None, describe the commands available. | ||||
|
r46554 | """ | ||
|
r18746 | |||
|
r32143 | opts = pycompat.byteskwargs(opts) | ||
|
r18746 | |||
|
r27378 | def helpcmd(name, subtopic=None): | ||
|
r18746 | try: | ||
|
r43346 | 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 | ||
|
r46271 | prefix = inst.prefix | ||
|
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): | ||
|
r43347 | rst.append(entry[0].badalias + b'\n') | ||
|
r22162 | if entry[0].unknowncmd: | ||
try: | ||||
rst.extend(helpextcmd(entry[0].cmdname)) | ||||
except error.UnknownCommand: | ||||
pass | ||||
|
r18746 | return rst | ||
# synopsis | ||||
if len(entry) > 2: | ||||
|
r43347 | if entry[2].startswith(b'hg'): | ||
rst.append(b"%s\n" % entry[2]) | ||||
|
r18746 | else: | ||
|
r43347 | rst.append(b'hg %s %s\n' % (aliases[0], entry[2])) | ||
|
r18746 | else: | ||
|
r43347 | rst.append(b'hg %s\n' % aliases[0]) | ||
|
r18746 | # aliases | ||
if full and not ui.quiet and len(aliases) > 1: | ||||
|
r43347 | rst.append(_(b"\naliases: %s\n") % b', '.join(aliases[1:])) | ||
rst.append(b'\n') | ||||
|
r18746 | |||
# description | ||||
|
r32615 | doc = gettext(pycompat.getdoc(entry[0])) | ||
|
r18746 | if not doc: | ||
|
r43347 | doc = _(b"(no help text available)") | ||
if util.safehasattr(entry[0], b'definition'): # aliased command | ||||
|
r28828 | source = entry[0].source | ||
|
r43347 | if entry[0].definition.startswith(b'!'): # shell alias | ||
doc = _(b'shell alias for: %s\n\n%s\n\ndefined by: %s\n') % ( | ||||
|
r43346 | entry[0].definition[1:], | ||
doc, | ||||
source, | ||||
) | ||||
|
r18746 | else: | ||
|
r43347 | doc = _(b'alias for: hg %s\n\n%s\n\ndefined by: %s\n') % ( | ||
|
r43346 | entry[0].definition, | ||
doc, | ||||
source, | ||||
) | ||||
|
r18746 | doc = doc.splitlines(True) | ||
if ui.quiet or not full: | ||||
rst.append(doc[0]) | ||||
else: | ||||
rst.extend(doc) | ||||
|
r43347 | rst.append(b'\n') | ||
|
r18746 | |||
# check if this command shadows a non-trivial (multi-line) | ||||
# extension help text | ||||
try: | ||||
mod = extensions.find(name) | ||||
|
r43347 | doc = gettext(pycompat.getdoc(mod)) or b'' | ||
if b'\n' in doc.strip(): | ||||
|
r43346 | msg = _( | ||
|
r43347 | b"(use 'hg help -e %s' to show help for " | ||
b"the %s extension)" | ||||
|
r43346 | ) % (name, name) | ||
|
r43347 | rst.append(b'\n%s\n' % msg) | ||
|
r18746 | except KeyError: | ||
pass | ||||
# options | ||||
if not ui.quiet and entry[1]: | ||||
|
r44777 | rst.append(optrst(_(b"options"), entry[1], ui.verbose, ui)) | ||
|
r18746 | |||
if ui.verbose: | ||||
|
r43346 | rst.append( | ||
|
r44787 | optrst( | ||
_(b"global options"), commands.globalopts, ui.verbose, ui | ||||
) | ||||
|
r43346 | ) | ||
|
r18746 | |||
if not ui.verbose: | ||||
if not full: | ||||
|
r43347 | rst.append(_(b"\n(use 'hg %s -h' to show more help)\n") % name) | ||
|
r18746 | elif not ui.quiet: | ||
|
r43346 | rst.append( | ||
_( | ||||
|
r43347 | b'\n(some details hidden, use --verbose ' | ||
b'to show complete help)' | ||||
|
r43346 | ) | ||
) | ||||
|
r18746 | |||
return rst | ||||
|
r27325 | def helplist(select=None, **opts): | ||
|
r46218 | cats, h, syns = _getcategorizedhelpcmds( | ||
ui, commands.table, name, select | ||||
) | ||||
|
r18746 | |||
rst = [] | ||||
if not h: | ||||
if not ui.quiet: | ||||
|
r43347 | rst.append(_(b'no commands defined\n')) | ||
|
r18746 | return rst | ||
|
r40327 | # Output top header. | ||
|
r18746 | if not ui.quiet: | ||
|
r43347 | if name == b"shortlist": | ||
rst.append(_(b'basic commands:\n\n')) | ||||
elif name == b"debug": | ||||
rst.append(_(b'debug commands (internal and unsupported):\n\n')) | ||||
|
r18746 | else: | ||
|
r43347 | rst.append(_(b'list of commands:\n')) | ||
|
r40327 | |||
def appendcmds(cmds): | ||||
cmds = sorted(cmds) | ||||
for c in cmds: | ||||
r47115 | display_cmd = c | |||
|
r40327 | if ui.verbose: | ||
r47115 | display_cmd = b', '.join(syns[c]) | |||
display_cmd = display_cmd.replace(b':', br'\:') | ||||
rst.append(b' :%s: %s\n' % (display_cmd, h[c])) | ||||
|
r40327 | |||
|
r43347 | if name in (b'shortlist', b'debug'): | ||
|
r40327 | # List without categories. | ||
appendcmds(h) | ||||
else: | ||||
# Check that all categories have an order. | ||||
missing_order = set(cats.keys()) - set(CATEGORY_ORDER) | ||||
if missing_order: | ||||
|
r43346 | ui.develwarn( | ||
|
r43347 | b'help categories missing from CATEGORY_ORDER: %s' | ||
|
r43346 | % missing_order | ||
) | ||||
|
r40327 | |||
# List per category. | ||||
for cat in CATEGORY_ORDER: | ||||
catfns = cats.get(cat, []) | ||||
if catfns: | ||||
if len(cats) > 1: | ||||
catname = gettext(CATEGORY_NAMES[cat]) | ||||
|
r43347 | rst.append(b"\n%s:\n" % catname) | ||
rst.append(b"\n") | ||||
|
r40327 | appendcmds(catfns) | ||
|
r18746 | |||
|
r27325 | ex = opts.get | ||
|
r43906 | anyopts = ex('keyword') or not (ex('command') or ex('extension')) | ||
|
r27325 | if not name and anyopts: | ||
|
r43346 | exts = listexts( | ||
|
r43347 | _(b'enabled extensions:'), | ||
|
r43346 | extensions.enabled(), | ||
showdeprecated=ui.verbose, | ||||
) | ||||
|
r18746 | if exts: | ||
|
r43347 | rst.append(b'\n') | ||
|
r18746 | rst.extend(exts) | ||
|
r43347 | rst.append(_(b"\nadditional help topics:\n")) | ||
|
r46218 | topiccats, topicsyns = _getcategorizedhelptopics(ui, helptable) | ||
|
r40328 | |||
# Check that all categories have an order. | ||||
missing_order = set(topiccats.keys()) - set(TOPIC_CATEGORY_ORDER) | ||||
if missing_order: | ||||
ui.develwarn( | ||||
|
r43347 | b'help categories missing from TOPIC_CATEGORY_ORDER: %s' | ||
|
r43346 | % missing_order | ||
) | ||||
|
r40328 | |||
# 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]) | ||||
|
r43347 | rst.append(b"\n%s:\n" % catname) | ||
rst.append(b"\n") | ||||
|
r40328 | for t, desc in topics: | ||
|
r43347 | rst.append(b" :%s: %s\n" % (t, desc)) | ||
|
r18746 | |||
|
r22115 | if ui.quiet: | ||
pass | ||||
elif ui.verbose: | ||||
|
r43346 | rst.append( | ||
|
r43347 | b'\n%s\n' | ||
|
r44787 | % optrst( | ||
_(b"global options"), commands.globalopts, ui.verbose, ui | ||||
) | ||||
|
r43346 | ) | ||
|
r43347 | if name == b'shortlist': | ||
|
r43346 | rst.append( | ||
|
r43387 | _(b"\n(use 'hg help' for the full list of commands)\n") | ||
|
r43346 | ) | ||
|
r22115 | else: | ||
|
r43347 | if name == b'shortlist': | ||
|
r43346 | rst.append( | ||
_( | ||||
|
r43347 | b"\n(use 'hg help' for the full list of commands " | ||
b"or 'hg -v' for details)\n" | ||||
|
r43346 | ) | ||
) | ||||
|
r22115 | elif name and not full: | ||
|
r43346 | rst.append( | ||
|
r43387 | _(b"\n(use 'hg help %s' to show the full help text)\n") | ||
|
r43346 | % name | ||
) | ||||
|
r40327 | elif name and syns and name in syns.keys(): | ||
|
r43346 | rst.append( | ||
_( | ||||
|
r43347 | b"\n(use 'hg help -v -e %s' to show built-in " | ||
b"aliases and global options)\n" | ||||
|
r43346 | ) | ||
% name | ||||
) | ||||
|
r18746 | else: | ||
|
r43346 | rst.append( | ||
_( | ||||
|
r43347 | b"\n(use 'hg help -v%s' to show built-in aliases " | ||
b"and global options)\n" | ||||
|
r43346 | ) | ||
|
r43347 | % (name and b" " + name or b"") | ||
|
r43346 | ) | ||
|
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 | ||||
|
r42585 | if not any(subtopic in s[0] for s in subtopics[name]): | ||
raise error.UnknownCommand(name) | ||||
|
r27379 | |||
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: | ||||
|
r43347 | rst.append(b" %s\n" % _(b"(no help text available)")) | ||
|
r21796 | if callable(doc): | ||
|
r43347 | rst += [b" %s\n" % l for l in doc(ui).splitlines()] | ||
|
r18746 | |||
if not ui.verbose: | ||||
|
r43346 | omitted = _( | ||
|
r43347 | b'(some details hidden, use --verbose' | ||
b' to show complete help)' | ||||
|
r43346 | ) | ||
|
r18746 | indicateomitted(rst, omitted) | ||
try: | ||||
cmdutil.findcmd(name, commands.table) | ||||
|
r43346 | rst.append( | ||
|
r43387 | _(b"\nuse 'hg help -c %s' to see help for the %s command\n") | ||
|
r43346 | % (name, name) | ||
) | ||||
|
r18746 | except error.UnknownCommand: | ||
pass | ||||
return rst | ||||
|
r27378 | def helpext(name, subtopic=None): | ||
|
r18746 | try: | ||
mod = extensions.find(name) | ||||
|
r43347 | doc = gettext(pycompat.getdoc(mod)) or _(b'no help text available') | ||
|
r18746 | except KeyError: | ||
mod = None | ||||
|
r45145 | doc = extensions.disabled_help(name) | ||
|
r18746 | if not doc: | ||
raise error.UnknownCommand(name) | ||||
|
r43347 | if b'\n' not in doc: | ||
head, tail = doc, b"" | ||||
|
r18746 | else: | ||
|
r43347 | head, tail = doc.split(b'\n', 1) | ||
rst = [_(b'%s extension - %s\n\n') % (name.rpartition(b'.')[-1], head)] | ||||
|
r18746 | if tail: | ||
rst.extend(tail.splitlines(True)) | ||||
|
r43347 | rst.append(b'\n') | ||
|
r18746 | |||
if not ui.verbose: | ||||
|
r43346 | omitted = _( | ||
|
r43347 | b'(some details hidden, use --verbose' | ||
b' to show complete help)' | ||||
|
r43346 | ) | ||
|
r18746 | indicateomitted(rst, omitted) | ||
if mod: | ||||
try: | ||||
ct = mod.cmdtable | ||||
except AttributeError: | ||||
ct = {} | ||||
|
r43347 | modcmds = {c.partition(b'|')[0] for c in ct} | ||
|
r18746 | rst.extend(helplist(modcmds.__contains__)) | ||
else: | ||||
|
r43346 | rst.append( | ||
_( | ||||
|
r43347 | b"(use 'hg help extensions' for information on enabling" | ||
b" extensions)\n" | ||||
|
r43346 | ) | ||
) | ||||
|
r18746 | return rst | ||
|
r27378 | def helpextcmd(name, subtopic=None): | ||
|
r43346 | cmd, ext, doc = extensions.disabledcmd( | ||
|
r43347 | ui, name, ui.configbool(b'ui', b'strict') | ||
|
r43346 | ) | ||
|
r49888 | doc = stringutil.firstline(doc) | ||
|
r18746 | |||
|
r43346 | rst = listexts( | ||
|
r43387 | _(b"'%s' is provided by the following extension:") % cmd, | ||
|
r43346 | {ext: doc}, | ||
indent=4, | ||||
showdeprecated=True, | ||||
) | ||||
|
r43347 | rst.append(b'\n') | ||
|
r43346 | rst.append( | ||
_( | ||||
|
r43347 | b"(use 'hg help extensions' for information on enabling " | ||
b"extensions)\n" | ||||
|
r43346 | ) | ||
) | ||||
|
r18746 | return rst | ||
rst = [] | ||||
|
r43347 | kw = opts.get(b'keyword') | ||
|
r27325 | if kw or name is None and any(opts[o] for o in opts): | ||
|
r43347 | matches = topicmatch(ui, commands, name or b'') | ||
|
r26238 | helpareas = [] | ||
|
r43347 | if opts.get(b'extension'): | ||
helpareas += [(b'extensions', _(b'Extensions'))] | ||||
if opts.get(b'command'): | ||||
helpareas += [(b'commands', _(b'Commands'))] | ||||
|
r26238 | if not helpareas: | ||
|
r43346 | helpareas = [ | ||
|
r43347 | (b'topics', _(b'Topics')), | ||
(b'commands', _(b'Commands')), | ||||
(b'extensions', _(b'Extensions')), | ||||
(b'extensioncommands', _(b'Extension Commands')), | ||||
|
r43346 | ] | ||
|
r26238 | for t, title in helpareas: | ||
|
r18746 | if matches[t]: | ||
|
r43347 | rst.append(b'%s:\n\n' % title) | ||
|
r18746 | rst.extend(minirst.maketable(sorted(matches[t]), 1)) | ||
|
r43347 | rst.append(b'\n') | ||
|
r21288 | if not rst: | ||
|
r43347 | msg = _(b'no matches') | ||
hint = _(b"try 'hg help' for a list of topics") | ||||
|
r46887 | raise error.InputError(msg, hint=hint) | ||
|
r43347 | elif name and name != b'shortlist': | ||
|
r26238 | queries = [] | ||
|
r18746 | if unknowncmd: | ||
|
r26238 | queries += [helpextcmd] | ||
|
r43347 | if opts.get(b'extension'): | ||
|
r26238 | queries += [helpext] | ||
|
r43347 | if opts.get(b'command'): | ||
|
r26238 | 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: | ||||
|
r42586 | if fullname: | ||
formatname = fullname | ||||
else: | ||||
formatname = name | ||||
if subtopic: | ||||
hintname = subtopic | ||||
else: | ||||
hintname = name | ||||
|
r43347 | msg = _(b'no such help topic: %s') % formatname | ||
hint = _(b"try 'hg help --keyword %s'") % hintname | ||||
|
r46887 | raise error.InputError(msg, hint=hint) | ||
|
r18746 | else: | ||
# program name | ||||
if not ui.quiet: | ||||
|
r43347 | rst = [_(b"Mercurial Distributed SCM\n"), b'\n'] | ||
|
r32547 | rst.extend(helplist(None, **pycompat.strkwargs(opts))) | ||
|
r18746 | |||
|
r43347 | return b''.join(rst) | ||
|
r31059 | |||
|
r43346 | |||
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: | ||
|
r43346 | 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: | ||||
|
r43347 | nameparts = fullname.split(b'.') | ||
|
r39375 | name = nameparts.pop(0) | ||
if nameparts and name in subtopics: | ||||
subtopic = nameparts.pop(0) | ||||
if nameparts: | ||||
|
r43347 | section = encoding.lower(b'.'.join(nameparts)) | ||
|
r39375 | |||
|
r43347 | textwidth = ui.configint(b'ui', b'textwidth') | ||
|
r31059 | termwidth = ui.termwidth() - 2 | ||
if textwidth <= 0 or termwidth < textwidth: | ||||
textwidth = termwidth | ||||
|
r43346 | text = help_( | ||
ui, | ||||
commands, | ||||
name, | ||||
fullname=fullname, | ||||
subtopic=subtopic, | ||||
unknowncmd=unknowncmd, | ||||
full=full, | ||||
**opts | ||||
) | ||||
|
r31059 | |||
|
r39344 | blocks, pruned = minirst.parse(text, keep=keep) | ||
|
r43347 | if b'verbose' in pruned: | ||
keep.append(b'omitted') | ||||
|
r31059 | else: | ||
|
r43347 | keep.append(b'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): | ||||
|
r46887 | raise error.InputError(_(b"help section not found: %s") % fullname) | ||
|
r39345 | |||
|
r39344 | return minirst.formatplain(blocks, textwidth) | ||