diff --git a/docs/conf.py b/docs/conf.py index 479b678a8..4084ceaaf 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -13,7 +13,9 @@ import subprocess import sys import time from functools import partial -from typing import Optional, Dict, Tuple +from typing import ( + Any, Callable, Dict, Iterable, List, Match, Optional, Tuple, Union +) from docutils import nodes from docutils.parsers.rst.roles import set_classes @@ -28,7 +30,10 @@ 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.constants import str_version # noqa +from kitty.conf.definition import Option, Sequence, Shortcut # noqa + # config {{{ # -- Project information ----------------------------------------------------- @@ -184,7 +189,7 @@ texinfo_documents = [ # GitHub linking inline roles {{{ -def num_role(which, name, rawtext, text, lineno, inliner, options={}, content=[]): +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) @@ -202,7 +207,7 @@ def num_role(which, name, rawtext, text, lineno, inliner, options={}, content=[] return [node], [] -def commit_role(name, rawtext, text, lineno, inliner, options={}, content=[]): +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( @@ -222,14 +227,14 @@ def commit_role(name, rawtext, text, lineno, inliner, options={}, content=[]): # Sidebar ToC {{{ -def create_toc(app, pagename): +def create_toc(app: Any, pagename: str) -> Optional[Any]: tt = TocTree(app.env) toctree = tt.get_toc_for(pagename, app.builder) if toctree is not None: subtree = toctree[toctree.first_child_matching_class(nodes.list_item)] bl = subtree.first_child_matching_class(nodes.bullet_list) if bl is None: - return # Empty ToC + return None # Empty ToC subtree = subtree[bl] # for li in subtree.traverse(nodes.list_item): # modify_li(li) @@ -237,14 +242,14 @@ def create_toc(app, pagename): return app.builder.render_partial(subtree)['fragment'] -def add_html_context(app, pagename, templatename, context, *args): +def add_html_context(app: Any, pagename: str, templatename: str, context: Any, *args: Any) -> None: if 'toc' in context: context['toc'] = create_toc(app, pagename) or context['toc'] # }}} # CLI docs {{{ -def write_cli_docs(all_kitten_names): +def write_cli_docs(all_kitten_names: Iterable[str]) -> None: from kitty.launch import options_spec as launch_options_spec from kitty.cli import option_spec_as_rst with open('generated/launch.rst', 'w') as f: @@ -290,13 +295,13 @@ if you specify a program-to-run you can use the special placeholder def write_remote_control_protocol_docs() -> None: # {{{ - from kitty.rc.base import all_command_names, command_for_name + from kitty.rc.base import all_command_names, command_for_name, RemoteCommand field_pat = re.compile(r'\s*([a-zA-Z0-9_+]+)\s*:\s*(.+)') - def format_cmd(p, name, cmd): + def format_cmd(p: Callable, name: str, cmd: RemoteCommand) -> None: p(name) p('-' * 80) - lines = cmd.__doc__.strip().splitlines() + lines = (cmd.__doc__ or '').strip().splitlines() fields = [] for line in lines: m = field_pat.match(line) @@ -386,7 +391,7 @@ class SessionLexer(RegexLexer): } -def link_role(name, rawtext, text, lineno, inliner, options={}, content=[]): +def link_role(name: str, rawtext: str, text: str, lineno: int, inliner: Any, options: Any = {}, content: Any = []) -> Tuple[List, List]: m = re.match(r'(.+)\s+<(.+?)>', text) if m is None: msg = inliner.reporter.error(f'link "{text}" not recognized', line=lineno) @@ -398,15 +403,15 @@ def link_role(name, rawtext, text, lineno, inliner, options={}, content=[]): return [node], [] -def expand_opt_references(conf_name, text): +def expand_opt_references(conf_name: str, text: str) -> str: conf_name += '.' - def expand(m): + 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 m.group() + return str(m.group()) return re.sub(r':opt:`(.+?)`', expand, text) @@ -415,7 +420,7 @@ opt_aliases: Dict[str, str] = {} shortcut_slugs: Dict[str, Tuple[str, str]] = {} -def parse_opt_node(env, sig, signode): +def parse_opt_node(env: Any, sig: str, signode: Any) -> str: """Transform an option description into RST nodes.""" count = 0 firstname = '' @@ -437,22 +442,22 @@ def parse_opt_node(env, sig, signode): return firstname -def parse_shortcut_node(env, sig, signode): +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 render_conf(conf_name, all_options): +def render_conf(conf_name: str, all_options: Iterable[Union['Option', Sequence['Shortcut']]]) -> str: from kitty.conf.definition import merged_opts, Option, Group ans = ['.. default-domain:: conf', ''] a = ans.append current_group: Optional[Group] = None - all_options = list(all_options) + all_options_ = list(all_options) kitty_mod = 'kitty_mod' - def render_group(group): + def render_group(group: Group) -> None: a('') a(f'.. _conf-{conf_name}-{group.name}:') a('') @@ -464,12 +469,12 @@ def render_conf(conf_name, all_options): a(group.start_text) a('') - def handle_group_end(group): + def handle_group_end(group: Group) -> None: if group.end_text: assert current_group is not None a(''), a(current_group.end_text) - def handle_group(new_group, new_group_is_shortcut=False): + def handle_group(new_group: Group, new_group_is_shortcut: bool = False) -> None: nonlocal current_group if new_group is not current_group: if current_group: @@ -477,14 +482,14 @@ def render_conf(conf_name, all_options): current_group = new_group render_group(current_group) - def handle_option(i, opt): + def handle_option(i: int, opt: Option) -> None: nonlocal kitty_mod if not opt.long_text or not opt.add_to_docs: return handle_group(opt.group) if opt.name == 'kitty_mod': kitty_mod = opt.defval_as_string - mopts = list(merged_opts(all_options, opt, i)) + mopts = list(merged_opts(all_options_, opt, i)) a('.. opt:: ' + ', '.join(conf_name + '.' + mo.name for mo in mopts)) a('.. code-block:: conf') a('') @@ -496,7 +501,7 @@ def render_conf(conf_name, all_options): a(expand_opt_references(conf_name, opt.long_text)) a('') - def handle_shortcuts(shortcuts): + def handle_shortcuts(shortcuts: Sequence[Shortcut]) -> None: sc = shortcuts[0] handle_group(sc.group, True) sc_text = f'{conf_name}.{sc.short_text}' @@ -514,7 +519,7 @@ def render_conf(conf_name, all_options): a(expand_opt_references(conf_name, sc.long_text)) a('') - for i, opt in enumerate(all_options): + for i, opt in enumerate(all_options_): if isinstance(opt, Option): handle_option(i, opt) else: @@ -525,7 +530,7 @@ def render_conf(conf_name, all_options): return '\n'.join(ans) -def process_opt_link(env, refnode, has_explicit_title, title, target): +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 @@ -533,7 +538,7 @@ def process_opt_link(env, refnode, has_explicit_title, title, target): return title, opt_aliases.get(full_name, full_name) -def process_shortcut_link(env, refnode, has_explicit_title, 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 @@ -548,7 +553,7 @@ def process_shortcut_link(env, refnode, has_explicit_title, title, target): return title, target -def write_conf_docs(app, all_kitten_names): +def write_conf_docs(app: Any, all_kitten_names: Iterable[str]) -> None: app.add_lexer('conf', ConfLexer()) app.add_object_type( 'opt', 'opt', @@ -569,7 +574,7 @@ def write_conf_docs(app, all_kitten_names): sc_role.warn_dangling = True sc_role.process_link = process_shortcut_link - def generate_default_config(all_options, name): + def generate_default_config(all_options: Dict[str, Union[Option, Sequence[Shortcut]]], name: str) -> None: from kitty.conf.definition import as_conf_file with open(f'generated/conf-{name}.rst', 'w', encoding='utf-8') as f: print('.. highlight:: conf\n', file=f) @@ -591,7 +596,7 @@ def write_conf_docs(app, all_kitten_names): # }}} -def setup(app): +def setup(app: Any) -> None: os.makedirs('generated/conf', exist_ok=True) from kittens.runner import all_kitten_names kn = all_kitten_names() diff --git a/test.py b/test.py index ad8c6e2fe..51bbe0207 100755 --- a/test.py +++ b/test.py @@ -6,7 +6,7 @@ import importlib import os import sys import unittest -from typing import Callable, NoReturn, Set +from typing import Callable, Generator, NoReturn, Sequence, Set base = os.path.dirname(os.path.abspath(__file__)) @@ -15,7 +15,7 @@ def init_env() -> None: sys.path.insert(0, base) -def itertests(suite): +def itertests(suite: unittest.TestSuite) -> Generator[unittest.TestCase, None, None]: stack = [suite] while stack: suite = stack.pop() @@ -28,7 +28,7 @@ def itertests(suite): yield test -def find_tests_in_dir(path, excludes=('main.py',)): +def find_tests_in_dir(path: str, excludes: Sequence[str] = ('main.py',)) -> unittest.TestSuite: package = os.path.relpath(path, base).replace(os.sep, '/').replace('/', '.') items = os.listdir(path) suits = [] @@ -52,7 +52,7 @@ def filter_tests(suite: unittest.TestSuite, test_ok: Callable[[unittest.TestCase def filter_tests_by_name(suite: unittest.TestSuite, *names: str) -> unittest.TestSuite: names_ = {x if x.startswith('test_') else 'test_' + x for x in names} - def q(test): + def q(test: unittest.TestCase) -> bool: return test._testMethodName in names_ return filter_tests(suite, q) @@ -60,7 +60,7 @@ def filter_tests_by_name(suite: unittest.TestSuite, *names: str) -> unittest.Tes def filter_tests_by_module(suite: unittest.TestSuite, *names: str) -> unittest.TestSuite: names_ = frozenset(names) - def q(test): + def q(test: unittest.TestCase) -> bool: m = test.__class__.__module__.rpartition('.')[-1] return m in names_ return filter_tests(suite, q) @@ -77,7 +77,7 @@ def type_check() -> NoReturn: os.execlp(sys.executable, 'python', '-m', 'mypy', '--pretty') -def run_tests(): +def run_tests() -> None: import argparse parser = argparse.ArgumentParser() parser.add_argument( @@ -86,7 +86,7 @@ def run_tests(): parser.add_argument('--verbosity', default=4, type=int, help='Test verbosity') args = parser.parse_args() if args.name and args.name[0] in ('type-check', 'type_check', 'mypy'): - return type_check() + type_check() tests = find_tests_in_dir(os.path.join(base, 'kitty_tests')) if args.name: tests = filter_tests_by_name(tests, *args.name) @@ -106,7 +106,7 @@ def run_cli(suite: unittest.TestSuite, verbosity: int = 4) -> None: raise SystemExit(1) -def main(): +def main() -> None: run_tests()