From 5deed81737a79490b8ed9c4c16d33d14fa737ba7 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Fri, 19 Aug 2022 11:20:50 +0530 Subject: [PATCH] Dont maintain ref_map manually --- .gitignore | 1 + docs/extract-rst-targets.py | 35 +++++++++++++++++++++++++++ kitty/cli.py | 19 +++++++++------ kitty/conf/types.py | 48 +++++++++++++++++++++---------------- kitty/data-types.c | 6 +++++ kitty/fast_data_types.pyi | 1 + setup.py | 18 +++++++++++++- 7 files changed, 100 insertions(+), 28 deletions(-) create mode 100755 docs/extract-rst-targets.py diff --git a/.gitignore b/.gitignore index f3f45276f..bd174ba27 100644 --- a/.gitignore +++ b/.gitignore @@ -3,6 +3,7 @@ *.pyo *_stub.pyi *_generated.go +*_generated.h /.dmypy.json /tags /build/ diff --git a/docs/extract-rst-targets.py b/docs/extract-rst-targets.py new file mode 100755 index 000000000..e92583d8b --- /dev/null +++ b/docs/extract-rst-targets.py @@ -0,0 +1,35 @@ +#!/usr/bin/env python +# License: GPLv3 Copyright: 2022, Kovid Goyal + +import os +import re +from typing import Dict, Iterator + +tgt_pat = re.compile(r'^.. _(\S+?):$', re.MULTILINE) + + +def find_explicit_targets(text: str) -> Iterator[str]: + for m in tgt_pat.finditer(text): + yield m.group(1) + + +def main() -> Dict[str, str]: + refs = {'github_discussions': 'https://github.com/kovidgoyal/kitty/discussions'} + base = os.path.dirname(os.path.abspath(__file__)) + for dirpath, dirnames, filenames in os.walk(base): + if 'generated' in dirnames: + dirnames.remove('generated') + for f in filenames: + if f.endswith('.rst'): + with open(os.path.join(dirpath, f)) as stream: + raw = stream.read() + href = os.path.relpath(stream.name, base).replace(os.sep, '/') + href = href.rpartition('.')[0] + '/' + for explicit_target in find_explicit_targets(raw): + refs[explicit_target] = href + f'#{explicit_target}' + return {'ref': refs} + + +if __name__ == '__main__': + import json + print(json.dumps(main(), indent=2)) diff --git a/kitty/cli.py b/kitty/cli.py index 536ba5bff..e68e22d4d 100644 --- a/kitty/cli.py +++ b/kitty/cli.py @@ -11,7 +11,7 @@ from typing import ( ) from .cli_stub import CLIOptions -from .conf.types import ref_map +from .conf.types import resolve_ref from .conf.utils import resolve_config from .constants import ( appname, clear_handled_signals, defconf, is_macos, str_version, website_url @@ -131,9 +131,15 @@ def code(x: str) -> str: return cyan(x) +def text_and_target(x: str) -> Tuple[str, str]: + parts = x.split('<') + return parts[0].strip(), parts[-1].rstrip('>') + + @role def term(x: str) -> str: - return italic(x.split('<', 1)[0]) + t, q = text_and_target(x) + return italic(t) @role @@ -165,11 +171,10 @@ def doc(x: str) -> str: @role def ref(x: str) -> str: - parts = x.split('<') - t = parts[0].strip() - q = parts[-1].rstrip('>') - if q in ref_map(): - return hyperlink_for_url(ref_map()[q], t) + t, q = text_and_target(x) + url = resolve_ref(q) + if url: + return hyperlink_for_url(url, t) return t diff --git a/kitty/conf/types.py b/kitty/conf/types.py index 96686bf7d..f35a74602 100644 --- a/kitty/conf/types.py +++ b/kitty/conf/types.py @@ -45,24 +45,29 @@ def expand_opt_references(conf_name: str, text: str) -> str: @run_once -def ref_map() -> Dict[str, str]: - from kitty.actions import get_all_actions - ref_map = { - 'layouts': f'{website_url("overview")}#layouts', - 'include': f'{website_url("conf")}#include', - 'watchers': f'{website_url("launch")}#watchers', - 'sessions': f'{website_url("overview")}#startup-sessions', - 'functional': f'{website_url("keyboard-protocol")}#functional-key-definitions', - 'ssh_copy_command': f'{website_url("kittens/ssh")}#ssh-copy-command', - 'shell_integration': website_url("shell-integration"), - 'rc_custom_auth': f'{website_url("remote-control")}#rc-custom-auth', - 'clone_shell': f'{website_url("shell-integration")}#clone-shell', - 'github_discussions': 'https://github.com/kovidgoyal/kitty/discussions', - } - for actions in get_all_actions().values(): - for ac in actions: - ref_map[f'action-{ac.name}'] = f'{website_url("actions")}#' + ac.name.replace('_', '-') - return ref_map +def ref_map() -> Dict[str, Dict[str, str]]: + import json + from ..fast_data_types import get_docs_ref_map + ans: Dict[str, Dict[str, str]] = json.loads(get_docs_ref_map()) + return ans + + +def resolve_ref(ref: str) -> str: + m = ref_map() + href = m['ref'].get(ref, '') + if href: + return href + if ref.startswith('conf-'): + base = 'generated/' + ref.rpartition('-')[0] + href = f'{website_url(base)}#{ref}' + elif ref.startswith('at_'): + href = f'{website_url("generated/cli-kitty-at")}#{ref}' + elif ref.startswith('action-group-'): + href = f'{website_url("generated/actions")}#{ref}' + elif ref.startswith('action-'): + frag = ref.partition('-')[-1] + href = f'{website_url("generated/actions")}#{frag}' + return href def remove_markup(text: str) -> str: @@ -76,7 +81,10 @@ def remove_markup(text: str) -> str: def sub(m: 'Match[str]') -> str: if m.group(1) == 'ref': t, q = extract(m) - return f'{t} <{ref_map()[q]}>' + url = resolve_ref(q) + if not url: + raise KeyError(f'Failed to resolve :ref: {q}') + return f'{t} <{url}>' if m.group(1) == 'doc': t, q = extract(m) return f'{t} <{website_url(q.lstrip("/"))}>' @@ -91,7 +99,7 @@ def remove_markup(text: str) -> str: return t if m.group(1) == 'disc': t, q = extract(m) - return f'{t} {ref_map()["github_discussions"]}/{q}' + return f'{t} {resolve_ref("github_discussions")}/{q}' return str(m.group(2)) return re.sub(r':([a-zA-Z0-9]+):`(.+?)`', sub, text, flags=re.DOTALL) diff --git a/kitty/data-types.c b/kitty/data-types.c index 2e93bfb70..4f4fc79e4 100644 --- a/kitty/data-types.c +++ b/kitty/data-types.c @@ -209,10 +209,16 @@ py_getpeereid(PyObject *self UNUSED, PyObject *args) { return Py_BuildValue("ii", u, g); } +#include "docs_ref_map_generated.h" +static PyObject* +get_docs_ref_map(PyObject *self UNUSED, PyObject *args UNUSED) { + return PyBytes_FromStringAndSize(docs_ref_map, sizeof(docs_ref_map)); +} static PyMethodDef module_methods[] = { {"wcwidth", (PyCFunction)wcwidth_wrap, METH_O, ""}, + {"get_docs_ref_map", (PyCFunction)get_docs_ref_map, METH_NOARGS, ""}, {"getpeereid", (PyCFunction)py_getpeereid, METH_VARARGS, ""}, {"wcswidth", (PyCFunction)wcswidth_std, METH_O, ""}, {"open_tty", open_tty, METH_VARARGS, ""}, diff --git a/kitty/fast_data_types.pyi b/kitty/fast_data_types.pyi index 95feff2c9..35e4fb932 100644 --- a/kitty/fast_data_types.pyi +++ b/kitty/fast_data_types.pyi @@ -1486,3 +1486,4 @@ class SingleKey: def set_use_os_log(yes: bool) -> None: ... +def get_docs_ref_map() -> bytes: ... diff --git a/setup.py b/setup.py index 06f11fe4a..291f43a8a 100755 --- a/setup.py +++ b/setup.py @@ -14,6 +14,7 @@ import subprocess import sys import sysconfig import tempfile +import textwrap import time from contextlib import suppress from functools import lru_cache, partial @@ -839,10 +840,25 @@ def init_env_from_args(args: Options, native_optimizations: bool = False) -> Non ) +def build_ref_map() -> str: + m = runpy.run_path('docs/extract-rst-targets.py') + d = m['main']() + h = 'static const char docs_ref_map[] = {\n' + textwrap.fill(', '.join(map(str, bytearray(json.dumps(d).encode('utf-8'))))) + '\n};' + dest = 'kitty/docs_ref_map_generated.h' + q = '' + with suppress(FileNotFoundError), open(dest) as f: + q = f.read() + if q != h: + with open(dest, 'w') as f: + f.write(h) + return dest + + def build(args: Options, native_optimizations: bool = True, call_init: bool = True) -> None: if call_init: init_env_from_args(args, native_optimizations) sources, headers = find_c_files() + headers.append(build_ref_map()) compile_c_extension( kitty_env(), 'kitty/fast_data_types', args.compilation_database, sources, headers ) @@ -1375,7 +1391,7 @@ def clean() -> None: safe_remove( 'build', 'compile_commands.json', 'link_commands.json', 'linux-package', 'kitty.app', 'asan-launcher', - 'kitty-profile') + 'kitty-profile', 'docs/generated') clean_launcher_dir('kitty/launcher') def excluded(root: str, d: str) -> bool: