mirror of
https://github.com/kovidgoyal/kitty.git
synced 2024-11-11 19:34:36 +03:00
521 lines
18 KiB
Python
521 lines
18 KiB
Python
#!/usr/bin/env python3
|
|
# vim:fileencoding=utf-8
|
|
#
|
|
# Configuration file for the Sphinx documentation builder.
|
|
#
|
|
# This file does only contain a selection of the most common options. For a
|
|
# full list see the documentation:
|
|
# https://www.sphinx-doc.org/en/master/config
|
|
|
|
import os
|
|
import re
|
|
import subprocess
|
|
import sys
|
|
import time
|
|
from functools import partial
|
|
from typing import Any, Callable, Dict, Iterable, List, Match, Optional, Tuple
|
|
|
|
from docutils import nodes
|
|
from docutils.parsers.rst.roles import set_classes
|
|
from pygments.lexer import RegexLexer, bygroups # type: ignore
|
|
from pygments.token import ( # type: ignore
|
|
Comment, Keyword, Literal, Name, Number, String, Whitespace
|
|
)
|
|
from sphinx import addnodes, version_info # type: ignore
|
|
from sphinx.util.logging import getLogger # type: ignore
|
|
|
|
kitty_src = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
|
if kitty_src not in sys.path:
|
|
sys.path.insert(0, kitty_src)
|
|
|
|
from kitty.conf.types import Definition # noqa
|
|
from kitty.constants import str_version, website_url # noqa
|
|
|
|
# config {{{
|
|
# -- Project information -----------------------------------------------------
|
|
|
|
project = 'kitty'
|
|
copyright = time.strftime('%Y, Kovid Goyal')
|
|
author = 'Kovid Goyal'
|
|
building_man_pages = 'man' in sys.argv
|
|
|
|
# The short X.Y version
|
|
version = str_version
|
|
# The full version, including alpha/beta/rc tags
|
|
release = str_version
|
|
logger = getLogger(__name__)
|
|
|
|
|
|
# -- General configuration ---------------------------------------------------
|
|
|
|
# If your documentation needs a minimal Sphinx version, state it here.
|
|
#
|
|
needs_sphinx = '1.7'
|
|
|
|
# Add any Sphinx extension module names here, as strings. They can be
|
|
# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
|
|
# ones.
|
|
extensions = [
|
|
'sphinx.ext.ifconfig',
|
|
'sphinx.ext.viewcode',
|
|
'sphinx.ext.githubpages',
|
|
'sphinx_copybutton',
|
|
'sphinx_inline_tabs',
|
|
"sphinxext.opengraph",
|
|
]
|
|
|
|
# URL for OpenGraph tags
|
|
ogp_site_url = website_url()
|
|
|
|
# Add any paths that contain templates here, relative to this directory.
|
|
templates_path = ['_templates']
|
|
|
|
# The suffix(es) of source filenames.
|
|
# You can specify multiple suffix as a list of string:
|
|
#
|
|
# source_suffix = ['.rst', '.md']
|
|
source_suffix = '.rst'
|
|
|
|
# The master toctree document.
|
|
master_doc = 'index'
|
|
|
|
# The language for content autogenerated by Sphinx. Refer to documentation
|
|
# for a list of supported languages.
|
|
#
|
|
# This is also used if you do content translation via gettext catalogs.
|
|
# Usually you set "language" from the command line for these cases.
|
|
language: Optional[str] = None
|
|
|
|
# List of patterns, relative to source directory, that match files and
|
|
# directories to ignore when looking for source files.
|
|
# This pattern also affects html_static_path and html_extra_path .
|
|
exclude_patterns = [
|
|
'_build', 'Thumbs.db', '.DS_Store', 'basic.rst',
|
|
'generated/cli-*.rst', 'generated/conf-*.rst', 'generated/actions.rst'
|
|
]
|
|
|
|
rst_prolog = '''
|
|
.. |kitty| replace:: *kitty*
|
|
.. |version| replace:: VERSION
|
|
.. _tarball: https://github.com/kovidgoyal/kitty/releases/download/vVERSION/kitty-VERSION.tar.xz
|
|
.. role:: green
|
|
.. role:: italic
|
|
.. role:: bold
|
|
.. role:: cyan
|
|
.. role:: title
|
|
|
|
'''.replace('VERSION', str_version)
|
|
|
|
|
|
# -- Options for HTML output -------------------------------------------------
|
|
|
|
# The theme to use for HTML and HTML Help pages. See the documentation for
|
|
# a list of builtin themes.
|
|
#
|
|
html_theme = 'furo'
|
|
html_title = 'kitty'
|
|
|
|
# Theme options are theme-specific and customize the look and feel of a theme
|
|
# further. For a list of options available for each theme, see the
|
|
# documentation.
|
|
#
|
|
html_theme_options: Dict[str, Any] = {
|
|
'sidebar_hide_name': True,
|
|
'navigation_with_keys': True,
|
|
}
|
|
|
|
|
|
# Add any paths that contain custom static files (such as style sheets) here,
|
|
# relative to this directory. They are copied after the builtin static files,
|
|
# so a file named "default.css" will overwrite the builtin "default.css".
|
|
html_static_path = ['_static']
|
|
html_favicon = html_logo = '../logo/kitty.svg'
|
|
html_css_files = ['custom.css']
|
|
html_js_files = ['custom.js']
|
|
|
|
# Custom sidebar templates, must be a dictionary that maps document names
|
|
# to template names.
|
|
#
|
|
# The default sidebars (for documents that don't match any pattern) are
|
|
# defined by theme itself. Builtin themes are using these templates by
|
|
# default: ``['localtoc.html', 'relations.html', 'sourcelink.html',
|
|
# 'searchbox.html']``.
|
|
#
|
|
html_show_sourcelink = False
|
|
html_show_sphinx = False
|
|
manpages_url = 'https://man7.org/linux/man-pages/man{section}/{page}.{section}.html'
|
|
|
|
# -- Options for manual page output ------------------------------------------
|
|
|
|
# One entry per manual page. List of tuples
|
|
# (source start file, name, description, authors, manual section).
|
|
man_pages = [
|
|
('invocation', 'kitty', 'kitty Documentation', [author], 1),
|
|
('conf', 'kitty.conf', 'kitty.conf Documentation', [author], 5)
|
|
]
|
|
|
|
|
|
# -- Options for Texinfo output ----------------------------------------------
|
|
|
|
# Grouping the document tree into Texinfo files. List of tuples
|
|
# (source start file, target name, title, author,
|
|
# dir menu entry, description, category)
|
|
texinfo_documents = [
|
|
(master_doc, 'kitty', 'kitty Documentation',
|
|
author, 'kitty', 'Cross-platform, fast, feature-rich, GPU based terminal',
|
|
'Miscellaneous'),
|
|
]
|
|
# }}}
|
|
|
|
|
|
# GitHub linking inline roles {{{
|
|
|
|
def num_role(which: str, name: str, rawtext: str, text: str, lineno: int, inliner: Any, options: Any = {}, content: Any = []) -> Tuple[List, List]:
|
|
' Link to a github issue '
|
|
try:
|
|
issue_num = int(text)
|
|
if issue_num <= 0:
|
|
raise ValueError
|
|
except ValueError:
|
|
msg = inliner.reporter.error(
|
|
'GitHub issue number must be a number greater than or equal to 1; '
|
|
'"%s" is invalid.' % text, line=lineno)
|
|
prb = inliner.problematic(rawtext, rawtext, msg)
|
|
return [prb], [msg]
|
|
url = f'https://github.com/kovidgoyal/kitty/{which}/{issue_num}'
|
|
set_classes(options)
|
|
node = nodes.reference(rawtext, f'#{issue_num}', refuri=url, **options)
|
|
return [node], []
|
|
|
|
|
|
def commit_role(name: str, rawtext: str, text: str, lineno: int, inliner: Any, options: Any = {}, content: Any = []) -> Tuple[List, List]:
|
|
' Link to a github commit '
|
|
try:
|
|
commit_id = subprocess.check_output(
|
|
f'git rev-list --max-count=1 --skip=# {text}'.split()).decode('utf-8').strip()
|
|
except Exception:
|
|
msg = inliner.reporter.error(
|
|
f'GitHub commit id "{text}" not recognized.', line=lineno)
|
|
prb = inliner.problematic(rawtext, rawtext, msg)
|
|
return [prb], [msg]
|
|
url = f'https://github.com/kovidgoyal/kitty/commit/{commit_id}'
|
|
set_classes(options)
|
|
short_id = subprocess.check_output(
|
|
f'git rev-list --max-count=1 --abbrev-commit --skip=# {commit_id}'.split()).decode('utf-8').strip()
|
|
node = nodes.reference(rawtext, f'commit: {short_id}', refuri=url, **options)
|
|
return [node], []
|
|
# }}}
|
|
|
|
|
|
# CLI docs {{{
|
|
def write_cli_docs(all_kitten_names: Iterable[str]) -> None:
|
|
from kitty.cli import option_spec_as_rst
|
|
from kitty.launch import options_spec as launch_options_spec
|
|
with open('generated/launch.rst', 'w') as f:
|
|
f.write(option_spec_as_rst(
|
|
appname='launch', ospec=launch_options_spec, heading_char='_',
|
|
message='''\
|
|
Launch an arbitrary program in a new kitty window/tab. Note that
|
|
if you specify a program-to-run you can use the special placeholder
|
|
:code:`@selection` which will be replaced by the current selection.
|
|
'''
|
|
))
|
|
with open('generated/cli-kitty.rst', 'w') as f:
|
|
f.write(option_spec_as_rst(appname='kitty').replace(
|
|
'kitty --to', 'kitty @ --to'))
|
|
as_rst = partial(option_spec_as_rst, heading_char='_')
|
|
from kitty.rc.base import all_command_names, command_for_name
|
|
from kitty.remote_control import cli_msg, global_options_spec
|
|
with open('generated/cli-kitty-at.rst', 'w') as f:
|
|
p = partial(print, file=f)
|
|
p('kitty @\n' + '-' * 80)
|
|
p('.. program::', 'kitty @')
|
|
p('\n\n' + as_rst(
|
|
global_options_spec, message=cli_msg, usage='command ...', appname='kitty @'))
|
|
from kitty.rc.base import cli_params_for
|
|
for cmd_name in sorted(all_command_names()):
|
|
func = command_for_name(cmd_name)
|
|
p(f'.. _at_{func.name}:\n')
|
|
p('kitty @', func.name + '\n' + '-' * 120)
|
|
p('.. program::', 'kitty @', func.name)
|
|
p('\n\n' + as_rst(*cli_params_for(func)))
|
|
from kittens.runner import get_kitten_cli_docs
|
|
for kitten in all_kitten_names:
|
|
data = get_kitten_cli_docs(kitten)
|
|
if data:
|
|
with open(f'generated/cli-kitten-{kitten}.rst', 'w') as f:
|
|
p = partial(print, file=f)
|
|
p('.. program::', f'kitty +kitten {kitten}')
|
|
p(f'\nSource code for {kitten}')
|
|
p('-' * 72)
|
|
p(f'\nThe source code for this kitten is `available on GitHub <https://github.com/kovidgoyal/kitty/tree/master/kittens/{kitten}>`_.')
|
|
p('\nCommand Line Interface')
|
|
p('-' * 72, file=f)
|
|
p('\n\n' + option_spec_as_rst(
|
|
data['options'], message=data['help_text'], usage=data['usage'], appname=f'kitty +kitten {kitten}',
|
|
heading_char='^'))
|
|
|
|
# }}}
|
|
|
|
|
|
def write_remote_control_protocol_docs() -> None: # {{{
|
|
from kitty.rc.base import (
|
|
RemoteCommand, all_command_names, command_for_name
|
|
)
|
|
field_pat = re.compile(r'\s*([a-zA-Z0-9_+]+)\s*:\s*(.+)')
|
|
|
|
def format_cmd(p: Callable, name: str, cmd: RemoteCommand) -> None:
|
|
p(name)
|
|
p('-' * 80)
|
|
lines = (cmd.__doc__ or '').strip().splitlines()
|
|
fields = []
|
|
for line in lines:
|
|
m = field_pat.match(line)
|
|
if m is None:
|
|
p(line)
|
|
else:
|
|
fields.append((m.group(1), m.group(2)))
|
|
if fields:
|
|
p('\nFields are:\n')
|
|
for (name, desc) in fields:
|
|
if '+' in name:
|
|
title = name.replace('+', ' (required)')
|
|
else:
|
|
title = name
|
|
defval = cmd.get_default(name.replace('-', '_'), cmd)
|
|
if defval is not cmd:
|
|
title = f'{title} (default: {defval})'
|
|
else:
|
|
title = f'{title} (optional)'
|
|
p(f':code:`{title}`')
|
|
p(' ', desc), p()
|
|
p(), p()
|
|
|
|
with open('generated/rc.rst', 'w') as f:
|
|
p = partial(print, file=f)
|
|
for name in sorted(all_command_names()):
|
|
cmd = command_for_name(name)
|
|
if not cmd.__doc__:
|
|
continue
|
|
name = name.replace('_', '-')
|
|
format_cmd(p, name, cmd)
|
|
# }}}
|
|
|
|
|
|
# config file docs {{{
|
|
|
|
class ConfLexer(RegexLexer):
|
|
name = 'Conf'
|
|
aliases = ['conf']
|
|
filenames = ['*.conf']
|
|
|
|
tokens = {
|
|
'root': [
|
|
(r'#.*?$', Comment.Single),
|
|
(r'\s+$', Whitespace),
|
|
(r'\s+', Whitespace),
|
|
(r'(include)(\s+)(.+?)$', bygroups(Comment.Preproc, Whitespace, Name.Namespace)),
|
|
(r'(map)(\s+)(\S+)(\s+)', bygroups(
|
|
Keyword.Declaration, Whitespace, String, Whitespace), 'action'),
|
|
(r'(mouse_map)(\s+)(\S+)(\s+)(\S+)(\s+)(\S+)(\s+)', bygroups(
|
|
Keyword.Declaration, Whitespace, String, Whitespace, Name.Variable, Whitespace, String, Whitespace), 'action'),
|
|
(r'(symbol_map)(\s+)(\S+)(\s+)(.+?)$', bygroups(
|
|
Keyword.Declaration, Whitespace, String, Whitespace, Literal)),
|
|
(r'([a-zA-Z_0-9]+)(\s+)', bygroups(
|
|
Name.Variable, Whitespace), 'args'),
|
|
],
|
|
'action': [
|
|
(r'[a-z_0-9]+$', Name.Function, 'root'),
|
|
(r'[a-z_0-9]+', Name.Function, 'args'),
|
|
],
|
|
'args': [
|
|
(r'\s+', Whitespace, 'args'),
|
|
(r'\b(yes|no)\b$', Number.Bin, 'root'),
|
|
(r'\b(yes|no)\b', Number.Bin, 'args'),
|
|
(r'[+-]?[0-9]+\s*$', Number.Integer, 'root'),
|
|
(r'[+-]?[0-9.]+\s*$', Number.Float, 'root'),
|
|
(r'[+-]?[0-9]+', Number.Integer, 'args'),
|
|
(r'[+-]?[0-9.]+', Number.Float, 'args'),
|
|
(r'#[a-fA-F0-9]{3,6}\s*$', String, 'root'),
|
|
(r'#[a-fA-F0-9]{3,6}\s*', String, 'args'),
|
|
(r'.+', String, 'root'),
|
|
],
|
|
}
|
|
|
|
|
|
class SessionLexer(RegexLexer):
|
|
name = 'Session'
|
|
aliases = ['session']
|
|
filenames = ['*.session']
|
|
|
|
tokens = {
|
|
'root': [
|
|
(r'#.*?$', Comment.Single),
|
|
(r'[a-z][a-z0-9_]+', Name.Function, 'args'),
|
|
],
|
|
'args': [
|
|
(r'.*?$', Literal, 'root'),
|
|
]
|
|
}
|
|
|
|
|
|
def link_role(name: str, rawtext: str, text: str, lineno: int, inliner: Any, options: Any = {}, content: Any = []) -> Tuple[List, List]:
|
|
text = text.replace('\n', ' ')
|
|
m = re.match(r'(.+)\s+<(.+?)>', text)
|
|
if m is None:
|
|
msg = inliner.reporter.error(f'link "{text}" not recognized', line=lineno)
|
|
prb = inliner.problematic(rawtext, rawtext, msg)
|
|
return [prb], [msg]
|
|
text, url = m.group(1, 2)
|
|
url = url.replace(' ', '')
|
|
set_classes(options)
|
|
node = nodes.reference(rawtext, text, refuri=url, **options)
|
|
return [node], []
|
|
|
|
|
|
def expand_opt_references(conf_name: str, text: str) -> str:
|
|
conf_name += '.'
|
|
|
|
def expand(m: Match) -> str:
|
|
ref = m.group(1)
|
|
if '<' not in ref and '.' not in ref:
|
|
full_ref = conf_name + ref
|
|
return ':opt:`{} <{}>`'.format(ref, full_ref)
|
|
return str(m.group())
|
|
|
|
return re.sub(r':opt:`(.+?)`', expand, text)
|
|
|
|
|
|
opt_aliases: Dict[str, str] = {}
|
|
shortcut_slugs: Dict[str, Tuple[str, str]] = {}
|
|
|
|
|
|
def parse_opt_node(env: Any, sig: str, signode: Any) -> str:
|
|
"""Transform an option description into RST nodes."""
|
|
count = 0
|
|
firstname = ''
|
|
for potential_option in sig.split(', '):
|
|
optname = potential_option.strip()
|
|
if count:
|
|
signode += addnodes.desc_addname(', ', ', ')
|
|
text = optname.split('.', 1)[-1]
|
|
signode += addnodes.desc_name(text, text)
|
|
if not count:
|
|
firstname = optname
|
|
signode['allnames'] = [optname]
|
|
else:
|
|
signode['allnames'].append(optname)
|
|
opt_aliases[optname] = firstname
|
|
count += 1
|
|
if not firstname:
|
|
raise ValueError('{} is not a valid opt'.format(sig))
|
|
return firstname
|
|
|
|
|
|
def parse_shortcut_node(env: Any, sig: str, signode: Any) -> str:
|
|
"""Transform a shortcut description into RST nodes."""
|
|
conf_name, text = sig.split('.', 1)
|
|
signode += addnodes.desc_name(text, text)
|
|
return sig
|
|
|
|
|
|
def process_opt_link(env: Any, refnode: Any, has_explicit_title: bool, title: str, target: str) -> Tuple[str, str]:
|
|
conf_name, opt = target.partition('.')[::2]
|
|
if not opt:
|
|
conf_name, opt = 'kitty', conf_name
|
|
full_name = conf_name + '.' + opt
|
|
return title, opt_aliases.get(full_name, full_name)
|
|
|
|
|
|
def process_shortcut_link(env: Any, refnode: Any, has_explicit_title: bool, title: str, target: str) -> Tuple[str, str]:
|
|
conf_name, slug = target.partition('.')[::2]
|
|
if not slug:
|
|
conf_name, slug = 'kitty', conf_name
|
|
full_name = conf_name + '.' + slug
|
|
try:
|
|
target, stitle = shortcut_slugs[full_name]
|
|
except KeyError:
|
|
logger.warning('Unknown shortcut: {}'.format(target), location=refnode)
|
|
else:
|
|
if not has_explicit_title:
|
|
title = stitle
|
|
return title, target
|
|
|
|
|
|
def write_conf_docs(app: Any, all_kitten_names: Iterable[str]) -> None:
|
|
app.add_lexer('conf', ConfLexer() if version_info[0] < 3 else ConfLexer)
|
|
app.add_object_type(
|
|
'opt', 'opt',
|
|
indextemplate="pair: %s; Config Setting",
|
|
parse_node=parse_opt_node,
|
|
)
|
|
# Warn about opt references that could not be resolved
|
|
opt_role = app.registry.domain_roles['std']['opt']
|
|
opt_role.warn_dangling = True
|
|
opt_role.process_link = process_opt_link
|
|
|
|
app.add_object_type(
|
|
'shortcut', 'sc',
|
|
indextemplate="pair: %s; Keyboard Shortcut",
|
|
parse_node=parse_shortcut_node,
|
|
)
|
|
sc_role = app.registry.domain_roles['std']['sc']
|
|
sc_role.warn_dangling = True
|
|
sc_role.process_link = process_shortcut_link
|
|
shortcut_slugs.clear()
|
|
|
|
def generate_default_config(definition: Definition, name: str) -> None:
|
|
with open(f'generated/conf-{name}.rst', 'w', encoding='utf-8') as f:
|
|
print('.. highlight:: conf\n', file=f)
|
|
f.write('\n'.join(definition.as_rst(name, shortcut_slugs)))
|
|
|
|
conf_name = re.sub(r'^kitten-', '', name) + '.conf'
|
|
with open(f'generated/conf/{conf_name}', 'w', encoding='utf-8') as f:
|
|
text = '\n'.join(definition.as_conf())
|
|
print(text, file=f)
|
|
|
|
from kitty.options.definition import definition
|
|
generate_default_config(definition, 'kitty')
|
|
|
|
from kittens.runner import get_kitten_conf_docs
|
|
for kitten in all_kitten_names:
|
|
definition = get_kitten_conf_docs(kitten)
|
|
if definition:
|
|
generate_default_config(definition, f'kitten-{kitten}')
|
|
|
|
from kitty.actions import as_rst
|
|
with open('generated/actions.rst', 'w', encoding='utf-8') as f:
|
|
f.write(as_rst())
|
|
# }}}
|
|
|
|
|
|
def add_html_context(app: Any, pagename: str, templatename: str, context: Any, doctree: Any, *args: Any) -> None:
|
|
context['analytics_id'] = app.config.analytics_id
|
|
if 'toctree' in context:
|
|
# this is needed with furo to use all titles from pages
|
|
# in the sidebar (global) toc
|
|
original_toctee_function = context['toctree']
|
|
|
|
def include_sub_headings(**kwargs: Any) -> Any:
|
|
kwargs['titles_only'] = False
|
|
return original_toctee_function(**kwargs)
|
|
|
|
context['toctree'] = include_sub_headings
|
|
|
|
|
|
def setup(app: Any) -> None:
|
|
os.makedirs('generated/conf', exist_ok=True)
|
|
from kittens.runner import all_kitten_names
|
|
kn = all_kitten_names()
|
|
write_cli_docs(kn)
|
|
write_remote_control_protocol_docs()
|
|
write_conf_docs(app, kn)
|
|
app.add_config_value('analytics_id', '', 'env')
|
|
app.connect('html-page-context', add_html_context)
|
|
app.add_lexer('session', SessionLexer() if version_info[0] < 3 else SessionLexer)
|
|
app.add_role('link', link_role)
|
|
app.add_role('iss', partial(num_role, 'issues'))
|
|
app.add_role('pull', partial(num_role, 'pull'))
|
|
app.add_role('disc', partial(num_role, 'discussions'))
|
|
app.add_role('commit', commit_role)
|