123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779 |
- #!/usr/bin/env python
- # 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 glob
- import os
- import re
- import subprocess
- import sys
- import time
- from functools import lru_cache, partial
- from typing import Any, Callable, Dict, Iterable, Iterator, List, 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 Comment, Error, Keyword, Literal, Name, Number, String, Whitespace # type: ignore
- from sphinx import addnodes, version_info
- from sphinx.util.logging import getLogger
- 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, expand_opt_references # noqa
- from kitty.constants import str_version, website_url # noqa
- from kitty.fast_data_types import Shlex # 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.ext.extlinks',
- 'sphinx_copybutton',
- 'sphinx_inline_tabs',
- "sphinxext.opengraph",
- ]
- # URL for OpenGraph tags
- ogp_site_url = website_url()
- # OGP needs a PNG image because of: https://github.com/wpilibsuite/sphinxext-opengraph/issues/96
- ogp_social_cards = {
- 'image': '../logo/kitty.png'
- }
- # 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: str = 'en'
- # 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:: italic
- '''.replace('VERSION', str_version)
- smartquotes_action = 'qe' # educate quotes and ellipses but not dashes
- def go_version(go_mod_path: str) -> str: # {{{
- with open(go_mod_path) as f:
- for line in f:
- if line.startswith('go '):
- return line.strip().split()[1]
- raise SystemExit(f'No Go version in {go_mod_path}')
- # }}}
- string_replacements = {
- '_kitty_install_cmd': 'curl -L https://sw.kovidgoyal.net/kitty/installer.sh | sh /dev/stdin',
- '_build_go_version': go_version('../go.mod'),
- }
- # -- 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.
- github_icon_path = 'M8 0C3.58 0 0 3.58 0 8c0 3.54 2.29 6.53 5.47 7.59.4.07.55-.17.55-.38 0-.19-.01-.82-.01-1.49-2.01.37-2.53-.49-2.69-.94-.09-.23-.48-.94-.82-1.13-.28-.15-.68-.52-.01-.53.63-.01 1.08.58 1.23.82.72 1.21 1.87.87 2.33.66.07-.52.28-.87.51-1.07-1.78-.2-3.64-.89-3.64-3.95 0-.87.31-1.59.82-2.15-.08-.2-.36-1.02.08-2.12 0 0 .67-.21 2.2.82.64-.18 1.32-.27 2-.27.68 0 1.36.09 2 .27 1.53-1.04 2.2-.82 2.2-.82.44 1.1.16 1.92.08 2.12.51.56.82 1.27.82 2.15 0 3.07-1.87 3.75-3.65 3.95.29.25.54.73.54 1.48 0 1.07-.01 1.93-.01 2.2 0 .21.15.46.55.38A8.013 8.013 0 0 0 16 8c0-4.42-3.58-8-8-8z' # noqa
- html_theme_options: Dict[str, Any] = {
- 'sidebar_hide_name': True,
- 'navigation_with_keys': True,
- 'footer_icons': [
- {
- "name": "GitHub",
- "url": "https://github.com/kovidgoyal/kitty",
- "html": f"""
- <svg stroke="currentColor" fill="currentColor" stroke-width="0" viewBox="0 0 16 16">
- <path fill-rule="evenodd" d="{github_icon_path}"></path>
- </svg>
- """,
- "class": "",
- },
- ],
- }
- # 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', 'timestamps.css']
- html_js_files = ['custom.js', 'timestamps.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', 'The fast, feature rich terminal emulator', [author], 1),
- ('conf', 'kitty.conf', 'Configuration file for kitty', [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 {{{
- extlinks = {
- 'iss': ('https://github.com/kovidgoyal/kitty/issues/%s', '#%s'),
- 'pull': ('https://github.com/kovidgoyal/kitty/pull/%s', '#%s'),
- 'disc': ('https://github.com/kovidgoyal/kitty/discussions/%s', '#%s'),
- }
- def commit_role(
- name: str, rawtext: str, text: str, lineno: int, inliner: Any, options: Any = {}, content: Any = []
- ) -> Tuple[List[nodes.reference], List[nodes.problematic]]:
- ' Link to a github commit '
- try:
- commit_id = subprocess.check_output(
- f'git rev-list --max-count=1 {text}'.split()).decode('utf-8').strip()
- except Exception:
- msg = inliner.reporter.error(
- f'git 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 {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 kittens.ssh.main import copy_message, option_text
- from kitty.cli import option_spec_as_rst
- with open('generated/ssh-copy.rst', 'w') as f:
- f.write(option_spec_as_rst(
- appname='copy', ospec=option_text, heading_char='^',
- usage='file-or-dir-to-copy ...', message=copy_message
- ))
- del sys.modules['kittens.ssh.main']
- 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-kitten-at.rst', 'w') as f:
- p = partial(print, file=f)
- p('kitten @')
- p('-' * 80)
- p('.. program::', 'kitten @')
- p('\n\n' + as_rst(
- global_options_spec, message=cli_msg, usage='command ...', appname='kitten @'))
- 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('kitten @', func.name)
- p('-' * 120)
- p('.. program::', 'kitten @', 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::', 'kitty +kitten', kitten)
- p('\nSource code for', kitten)
- p('-' * 72)
- scurl = f'https://github.com/kovidgoyal/kitty/tree/master/kittens/{kitten}'
- p(f'\nThe source code for this kitten is `available on GitHub <{scurl}>`_.')
- p('\nCommand Line Interface')
- p('-' * 72)
- appname = f'kitten {kitten}'
- if kitten in ('panel', 'broadcast', 'remote_file'):
- appname = 'kitty +' + appname
- p('\n\n' + option_spec_as_rst(
- data['options'], message=data['help_text'], usage=data['usage'], appname=appname, heading_char='^'))
- # }}}
- def write_color_names_table() -> None: # {{{
- from kitty.rgb import color_names
- def s(c: Any) -> str:
- return f'{c.red:02x}/{c.green:02x}/{c.blue:02x}'
- with open('generated/color-names.rst', 'w') as f:
- p = partial(print, file=f)
- p('=' * 50, '=' * 20)
- p('Name'.ljust(50), 'RGB value')
- p('=' * 50, '=' * 20)
- for name, col in color_names.items():
- p(name.ljust(50), s(col))
- p('=' * 50, '=' * 20)
- # }}}
- 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*([^:]+?)\s*:\s*(.+)')
- def format_cmd(p: Callable[..., None], 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).split('/')[0], 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)
- # }}}
- def replace_string(app: Any, docname: str, source: List[str]) -> None: # {{{
- src = source[0]
- for k, v in app.config.string_replacements.items():
- src = src.replace(k, v)
- source[0] = src
- # }}}
- # config file docs {{{
- class ConfLexer(RegexLexer): # type: ignore
- name = 'Conf'
- aliases = ['conf']
- filenames = ['*.conf']
- def map_flags(self: RegexLexer, val: str, start_pos: int) -> Iterator[Tuple[int, Any, str]]:
- expecting_arg = ''
- s = Shlex(val)
- from kitty.options.utils import allowed_key_map_options
- last_pos = 0
- while (tok := s.next_word())[0] > -1:
- x = tok[1]
- if tok[0] > last_pos:
- yield start_pos + last_pos, Whitespace, ' ' * (tok[0] - last_pos)
- last_pos = tok[0] + len(x)
- tok_start = start_pos + tok[0]
- if expecting_arg:
- yield tok_start, String, x
- expecting_arg = ''
- elif x.startswith('--'):
- expecting_arg = x[2:]
- k, sep, v = expecting_arg.partition('=')
- k = k.replace('-', '_')
- expecting_arg = k
- if expecting_arg not in allowed_key_map_options:
- yield tok_start, Error, x
- elif sep == '=':
- expecting_arg = ''
- yield tok_start, Name, x
- else:
- yield tok_start, Name, x
- else:
- break
- def mapargs(self: RegexLexer, match: 're.Match[str]') -> Iterator[Tuple[int, Any, str]]:
- start_pos = match.start()
- val = match.group()
- parts = val.split(maxsplit=1)
- if parts[0].startswith('--'):
- seen = 0
- for (pos, token, text) in self.map_flags(val, start_pos):
- yield pos, token, text
- seen += len(text)
- start_pos += seen
- val = val[seen:]
- parts = val.split(maxsplit=1)
- if not val:
- return
- yield start_pos, Literal, parts[0] # key spec
- if len(parts) == 1:
- return
- start_pos += len(parts[0])
- val = val[len(parts[0]):]
- m = re.match(r'(\s+)(\S+)', val)
- if m is None:
- return
- yield start_pos, Whitespace, m.group(1)
- yield start_pos + m.start(2), Name.Function, m.group(2) # action function
- yield start_pos + m.end(2), String, val[m.end(2):]
- tokens = {
- 'root': [
- (r'#.*?$', Comment.Single),
- (r'\s+$', Whitespace),
- (r'\s+', Whitespace),
- (r'(include)(\s+)(.+?)$', bygroups(Comment.Preproc, Whitespace, Name.Namespace)),
- (r'(map)(\s+)', bygroups(
- Keyword.Declaration, Whitespace), 'mapargs'),
- (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'),
- ],
- 'mapargs': [
- (r'.+$', mapargs, 'root'),
- ],
- '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): # type: ignore
- 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[nodes.reference], List[nodes.problematic]]:
- 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], []
- 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(f'{sig} is not a valid opt')
- 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 parse_action_node(env: Any, sig: str, signode: Any) -> str:
- """Transform an action description into RST nodes."""
- signode += addnodes.desc_name(sig, sig)
- 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 = f'{conf_name}.{opt}'
- return title, opt_aliases.get(full_name, full_name)
- def process_action_link(env: Any, refnode: Any, has_explicit_title: bool, title: str, target: str) -> Tuple[str, str]:
- return title, target
- 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 = f'{conf_name}.{slug}'
- try:
- target, stitle = shortcut_slugs[full_name]
- except KeyError:
- logger.warning(f'Unknown shortcut: {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()
- app.add_object_type(
- 'action', 'ac',
- indextemplate="pair: %s; Action",
- parse_node=parse_action_node,
- )
- ac_role = app.registry.domain_roles['std']['ac']
- ac_role.warn_dangling = True
- ac_role.process_link = process_action_link
- 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(commented=True))
- 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:
- defn = get_kitten_conf_docs(kitten)
- if defn is not None:
- generate_default_config(defn, f'kitten-{kitten}')
- from kitty.actions import as_rst
- with open('generated/actions.rst', 'w', encoding='utf-8') as f:
- f.write(as_rst())
- from kitty.rc.base import MATCH_TAB_OPTION, MATCH_WINDOW_OPTION
- with open('generated/matching.rst', 'w') as f:
- print('Matching windows', file=f)
- print('______________________________', file=f)
- w = 'm' + MATCH_WINDOW_OPTION[MATCH_WINDOW_OPTION.find('Match') + 1:]
- print('When matching windows,', w, file=f)
- print('Matching tabs', file=f)
- print('______________________________', file=f)
- w = 'm' + MATCH_TAB_OPTION[MATCH_TAB_OPTION.find('Match') + 1:]
- print('When matching tabs,', w, file=f)
- # }}}
- 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
- @lru_cache
- def monkeypatch_man_writer() -> None:
- '''
- Monkeypatch the docutils man translator to be nicer
- '''
- from docutils.nodes import Element
- from docutils.writers.manpage import Table, Translator
- from sphinx.writers.manpage import ManualPageTranslator
- # Generate nicer tables https://sourceforge.net/p/docutils/bugs/475/
- class PatchedTable(Table): # type: ignore
- _options: list[str]
- def __init__(self) -> None:
- super().__init__()
- self.needs_border_removal = self._options == ['center']
- if self.needs_border_removal:
- self._options = ['box', 'center']
- def as_list(self) -> list[str]:
- ans: list[str] = super().as_list()
- if self.needs_border_removal:
- # remove side and top borders as we use box in self._options
- ans[2] = ans[2][1:]
- a, b = ans[2].rpartition('|')[::2]
- ans[2] = a + b
- if ans[3] == '_\n':
- del ans[3] # top border
- del ans[-2] # bottom border
- return ans
- def visit_table(self: ManualPageTranslator, node: object) -> None:
- setattr(self, '_active_table', PatchedTable())
- setattr(ManualPageTranslator, 'visit_table', visit_table)
- # Improve header generation
- def header(self: ManualPageTranslator) -> str:
- di = getattr(self, '_docinfo')
- di['ktitle'] = di['title'].replace('_', '-')
- th = (".TH \"%(ktitle)s\" %(manual_section)s"
- " \"%(date)s\" \"%(version)s\"") % di
- if di["manual_group"]:
- th += " \"%(manual_group)s\"" % di
- th += "\n"
- sh_tmpl: str = (".SH Name\n"
- "%(ktitle)s \\- %(subtitle)s\n")
- return th + sh_tmpl % di # type: ignore
- setattr(ManualPageTranslator, 'header', header)
- def visit_image(self: ManualPageTranslator, node: Element) -> None:
- pass
- def depart_image(self: ManualPageTranslator, node: Element) -> None:
- pass
- def depart_figure(self: ManualPageTranslator, node: Element) -> None:
- self.body.append(' (images not supported)\n')
- Translator.depart_figure(self, node)
- setattr(ManualPageTranslator, 'visit_image', visit_image)
- setattr(ManualPageTranslator, 'depart_image', depart_image)
- setattr(ManualPageTranslator, 'depart_figure', depart_figure)
- orig_astext = Translator.astext
- def astext(self: Translator) -> Any:
- b = []
- for line in self.body:
- if line.startswith('.SH'):
- x, y = line.split(' ', 1)
- parts = y.splitlines(keepends=True)
- parts[0] = parts[0].capitalize()
- line = x + ' ' + '\n'.join(parts)
- b.append(line)
- self.body = b
- return orig_astext(self)
- setattr(Translator, 'astext', astext)
- def setup_man_pages() -> None:
- from kittens.runner import get_kitten_cli_docs
- base = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
- for x in glob.glob(os.path.join(base, 'docs/kittens/*.rst')):
- kn = os.path.basename(x).rpartition('.')[0]
- if kn in ('custom', 'developing-builtin-kittens'):
- continue
- cd = get_kitten_cli_docs(kn) or {}
- khn = kn.replace('_', '-')
- man_pages.append((f'kittens/{kn}', 'kitten-' + khn, cd.get('short_desc', 'kitten Documentation'), [author], 1))
- monkeypatch_man_writer()
- def build_extra_man_pages() -> None:
- base = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
- kitten = os.environ.get('KITTEN_EXE_FOR_DOCS', os.path.join(base, 'kitty/launcher/kitten'))
- if not os.path.exists(kitten):
- kitten = os.path.join(base, 'kitty/launcher/kitty.app/Contents/MacOS/kitten')
- if not os.path.exists(kitten):
- subprocess.call(['find', os.path.join(base, 'kitty/launcher')])
- raise Exception(f'The kitten binary {kitten} is not built cannot generate man pages')
- raw = subprocess.check_output([kitten, '-h']).decode()
- started = 0
- names = set()
- for line in raw.splitlines():
- if line.strip() == '@':
- started = len(line.rstrip()[:-1])
- q = line.strip()
- if started and len(q.split()) == 1 and not q.startswith('-') and ':' not in q:
- if len(line) - len(line.lstrip()) == started:
- if not os.path.exists(os.path.join(base, f'docs/kittens/{q}.rst')):
- names.add(q)
- cwd = os.path.join(base, 'docs/_build/man')
- subprocess.check_call([kitten, '__generate_man_pages__'], cwd=cwd)
- subprocess.check_call([kitten, '__generate_man_pages__'] + list(names), cwd=cwd)
- if building_man_pages:
- setup_man_pages()
- def build_finished(*a: Any, **kw: Any) -> None:
- if building_man_pages:
- build_extra_man_pages()
- 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_color_names_table()
- write_conf_docs(app, kn)
- app.add_config_value('string_replacements', {}, True)
- app.connect('source-read', replace_string)
- app.add_config_value('analytics_id', '', 'env')
- app.connect('html-page-context', add_html_context)
- app.connect('build-finished', build_finished)
- app.add_lexer('session', SessionLexer() if version_info[0] < 3 else SessionLexer)
- app.add_role('link', link_role)
- app.add_role('commit', commit_role)
|