From 8ae110691eb885651e7d41a40725bc8ddc186609 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Mon, 9 Mar 2020 20:56:06 +0530 Subject: [PATCH] More typing work --- kittens/diff/config_data.py | 4 +- kitty/conf/definition.py | 97 +++++++++++++++++++------------------ kitty/conf/utils.py | 37 +++++++------- kitty/config.py | 4 +- kitty/config_data.py | 5 +- setup.cfg | 2 +- 6 files changed, 77 insertions(+), 72 deletions(-) diff --git a/kittens/diff/config_data.py b/kittens/diff/config_data.py index 01460c39f..389ad97e5 100644 --- a/kittens/diff/config_data.py +++ b/kittens/diff/config_data.py @@ -6,14 +6,14 @@ # Utils {{{ from functools import partial from gettext import gettext as _ -from typing import Any, Dict, Union +from typing import Any, Dict, Sequence, Union from kitty.conf.definition import Option, Shortcut, option_func from kitty.conf.utils import positive_int, python_string, to_color # }}} -all_options: Dict[str, Union[Option, Shortcut]] = {} +all_options: Dict[str, Union[Option, Sequence[Shortcut]]] = {} o, k, g, all_groups = option_func(all_options, { 'colors': [_('Colors')], 'diff': [_('Diffing'), ], diff --git a/kitty/conf/definition.py b/kitty/conf/definition.py index 7b2b1b52a..47c690992 100644 --- a/kitty/conf/definition.py +++ b/kitty/conf/definition.py @@ -5,7 +5,8 @@ import re from functools import partial from typing import ( - Any, Dict, Iterable, List, Optional, Set, Tuple, Union, get_type_hints + Any, Callable, Dict, Generator, Iterable, List, Match, Optional, Sequence, + Set, Tuple, Union, cast, get_type_hints ) from .utils import to_bool @@ -19,7 +20,7 @@ class Group: __slots__ = 'name', 'short_text', 'start_text', 'end_text' - def __init__(self, name, short_text, start_text='', end_text=''): + def __init__(self, name: str, short_text: str, start_text: str = '', end_text: str = '') -> None: self.name, self.short_text = name, short_text.strip() self.start_text, self.end_text = start_text.strip(), end_text.strip() @@ -28,7 +29,7 @@ class Option: __slots__ = 'name', 'group', 'long_text', 'option_type', 'defval_as_string', 'add_to_default', 'add_to_docs', 'line' - def __init__(self, name: str, group: str, defval: str, option_type: Any, long_text: str, add_to_default: bool, add_to_docs: bool): + def __init__(self, name: str, group: Group, defval: str, option_type: Any, long_text: str, add_to_default: bool, add_to_docs: bool): self.name, self.group = name, group self.long_text, self.option_type = long_text.strip(), option_type self.defval_as_string = defval @@ -67,7 +68,7 @@ class Shortcut: __slots__ = 'name', 'group', 'key', 'action_def', 'short_text', 'long_text', 'add_to_default', 'add_to_docs', 'line' - def __init__(self, name, group, key, action_def, short_text, long_text, add_to_default, add_to_docs): + def __init__(self, name: str, group: Group, key: str, action_def: str, short_text: str, long_text: str, add_to_default: bool, add_to_docs: bool): self.name, self.group, self.key, self.action_def = name, group, key, action_def self.short_text, self.long_text = short_text, long_text self.add_to_default = add_to_default @@ -76,15 +77,15 @@ class Shortcut: def option( - all_options, - group, - name, - defval, - long_text='', - option_type=to_string, - add_to_default=True, - add_to_docs=True -): + all_options: Dict[str, Option], + group: Sequence[Group], + name: str, + defval: Any, + long_text: str = '', + option_type: Callable[[str], Any] = to_string, + add_to_default: bool = True, + add_to_docs: bool = True +) -> Option: is_multiple = name.startswith('+') if is_multiple: name = name[1:] @@ -109,33 +110,33 @@ def option( def shortcut( - all_options, - group, - action_name, - key, - action_def, - short_text='', - long_text='', - add_to_default=True, - add_to_docs=True -): + all_options: Dict[str, List[Shortcut]], + group: Sequence[Group], + action_name: str, + key: str, + action_def: str, + short_text: str = '', + long_text: str = '', + add_to_default: bool = True, + add_to_docs: bool = True +) -> Shortcut: ans = Shortcut(action_name, group[0], key, action_def, short_text, long_text, add_to_default, add_to_docs) key = 'sc-' + action_name all_options.setdefault(key, []).append(ans) return ans -def option_func(all_options, all_groups): - all_groups = {k: Group(k, *v) for k, v in all_groups.items()} - group = [None] +def option_func(all_options: Dict[str, Any], all_groups: Dict[str, Sequence[str]]) -> Tuple[Callable, Callable, Callable[[str], None], Dict[str, Group]]: + all_groups_ = {k: Group(k, *v) for k, v in all_groups.items()} + group: List[Optional[Group]] = [None] - def change_group(name): - group[0] = all_groups[name] + def change_group(name: str) -> None: + group[0] = all_groups_[name] - return partial(option, all_options, group), partial(shortcut, all_options, group), change_group, all_groups + return partial(option, all_options, group), partial(shortcut, all_options, group), change_group, all_groups_ -def merged_opts(all_options, opt, i): +def merged_opts(all_options: Sequence[Union[Option, Sequence[Shortcut]]], opt: Option, i: int) -> Generator[Option, None, None]: yield opt for k in range(i + 1, len(all_options)): q = all_options[k] @@ -147,20 +148,20 @@ def merged_opts(all_options, opt, i): break -def remove_markup(text): +def remove_markup(text: str) -> str: - def sub(m): + def sub(m: Match) -> str: if m.group(1) == 'ref': return { 'layouts': 'https://sw.kovidgoyal.net/kitty/index.html#layouts', 'sessions': 'https://sw.kovidgoyal.net/kitty/index.html#sessions', }[m.group(2)] - return m.group(2) + return cast(str, m.group(2)) return re.sub(r':([a-zA-Z0-9]+):`(.+?)`', sub, text, flags=re.DOTALL) -def iter_blocks(lines: Iterable[str]): +def iter_blocks(lines: Iterable[str]) -> Generator[Tuple[List[str], int], None, None]: current_block: List[str] = [] prev_indent = 0 for line in lines: @@ -178,7 +179,7 @@ def iter_blocks(lines: Iterable[str]): yield current_block, indent_size -def wrapped_block(lines): +def wrapped_block(lines: Iterable[str]) -> Generator[str, None, None]: wrapper = getattr(wrapped_block, 'wrapper', None) if wrapper is None: import textwrap @@ -198,20 +199,20 @@ def wrapped_block(lines): yield line -def render_block(text): +def render_block(text: str) -> str: text = remove_markup(text) lines = text.splitlines() return '\n'.join(wrapped_block(lines)) -def as_conf_file(all_options: Iterable[Union[Option, Shortcut]]) -> List[str]: +def as_conf_file(all_options: Iterable[Union[Option, Sequence[Shortcut]]]) -> List[str]: ans = ['# vim:fileencoding=utf-8:ft=conf:foldmethod=marker', ''] a = ans.append current_group: Optional[Group] = None num_open_folds = 0 - all_options = list(all_options) + all_options_ = list(all_options) - def render_group(group, is_shortcut): + def render_group(group: Group, is_shortcut: bool) -> None: nonlocal num_open_folds if is_shortcut or '.' not in group.name: a('#: ' + group.short_text + ' {{''{') @@ -221,7 +222,7 @@ def as_conf_file(all_options: Iterable[Union[Option, Shortcut]]) -> List[str]: a(render_block(group.start_text)) a('') - def handle_group_end(group, new_group_name='', new_group_is_shortcut=False): + def handle_group_end(group: Group, new_group_name: str = '', new_group_is_shortcut: bool = False) -> None: nonlocal num_open_folds if group.end_text: a(''), a(render_block(group.end_text)) @@ -230,7 +231,7 @@ def as_conf_file(all_options: Iterable[Union[Option, Shortcut]]) -> List[str]: a('#: }}''}'), a('') num_open_folds -= 1 - def handle_group(new_group, is_shortcut=False): + def handle_group(new_group: Group, is_shortcut: bool = False) -> None: nonlocal current_group if new_group is not current_group: if current_group: @@ -238,7 +239,7 @@ def as_conf_file(all_options: Iterable[Union[Option, Shortcut]]) -> List[str]: current_group = new_group render_group(current_group, is_shortcut) - def handle_shortcut(shortcuts): + def handle_shortcut(shortcuts: Sequence[Shortcut]) -> None: handle_group(shortcuts[0].group, True) for sc in shortcuts: if sc.add_to_default: @@ -246,11 +247,11 @@ def as_conf_file(all_options: Iterable[Union[Option, Shortcut]]) -> List[str]: if sc.long_text: a(''), a(render_block(sc.long_text.strip())), a('') - def handle_option(opt): + def handle_option(opt: Option) -> None: if not opt.long_text or not opt.add_to_docs: return handle_group(opt.group) - mopts = list(merged_opts(all_options, opt, i)) + mopts = list(merged_opts(all_options_, opt, i)) sz = max(len(x.name) for x in mopts) for mo in mopts: prefix = '' if mo.add_to_default else '# ' @@ -259,7 +260,7 @@ def as_conf_file(all_options: Iterable[Union[Option, Shortcut]]) -> List[str]: a(render_block(opt.long_text)) a('') - for i, opt in enumerate(all_options): + for i, opt in enumerate(all_options_): if isinstance(opt, Option): handle_option(opt) else: @@ -298,7 +299,9 @@ def as_conf_file(all_options: Iterable[Union[Option, Shortcut]]) -> List[str]: return ans -def config_lines(all_options): +def config_lines( + all_options: Dict[str, Union[Option, Sequence[Shortcut]]], +) -> Generator[str, None, None]: for opt in all_options.values(): if isinstance(opt, Option): if opt.add_to_default: @@ -310,7 +313,7 @@ def config_lines(all_options): def as_type_stub( - all_options: Dict[str, Union[Option, List[Shortcut]]], + all_options: Dict[str, Union[Option, Sequence[Shortcut]]], special_types: Optional[Dict[str, str]] = None, preamble_lines: Union[Tuple[str, ...], List[str], Iterable[str]] = (), extra_fields: Union[Tuple[Tuple[str, str], ...], List[Tuple[str, str]], Iterable[Tuple[str, str]]] = (), diff --git a/kitty/conf/utils.py b/kitty/conf/utils.py index 2e28696d2..97a085cd1 100644 --- a/kitty/conf/utils.py +++ b/kitty/conf/utils.py @@ -6,14 +6,15 @@ import os import re import shlex from typing import ( - Any, Callable, Dict, FrozenSet, Iterable, List, NamedTuple, Optional, - Sequence, Tuple, Type, Union + Any, Callable, Dict, FrozenSet, Generator, Iterable, Iterator, List, + NamedTuple, Optional, Sequence, Tuple, Type, TypeVar, Union ) from ..rgb import Color, to_color as as_color from ..utils import log_error key_pat = re.compile(r'([a-zA-Z][a-zA-Z0-9_-]*)\s+(.+)$') +T = TypeVar('T') class BadLine(NamedTuple): @@ -153,9 +154,9 @@ def parse_config_base( type_convert: Callable[[str, Any], Any], special_handling: Callable, ans: Dict[str, Any], - check_keys=True, + check_keys: bool = True, accumulate_bad_lines: Optional[List[BadLine]] = None -): +) -> None: all_keys: Optional[FrozenSet[str]] = defaults._asdict() if check_keys else None _parse( lines, type_convert, special_handling, ans, all_keys, accumulate_bad_lines @@ -166,17 +167,17 @@ def create_options_class(all_keys: Iterable[str]) -> Type: keys = tuple(sorted(all_keys)) slots = keys + ('_fields', ) - def __init__(self, kw): + def __init__(self: Any, kw: Dict[str, Any]) -> None: for k, v in kw.items(): setattr(self, k, v) - def __iter__(self): + def __iter__(self: Any) -> Iterator[str]: return iter(keys) - def __len__(self): + def __len__(self: Any) -> int: return len(keys) - def __getitem__(self, i): + def __getitem__(self: Any, i: Union[int, str]) -> Any: if isinstance(i, int): i = keys[i] try: @@ -184,10 +185,10 @@ def create_options_class(all_keys: Iterable[str]) -> Type: except AttributeError: raise KeyError('No option named: {}'.format(i)) - def _asdict(self): + def _asdict(self: Any) -> Dict[str, Any]: return {k: getattr(self, k) for k in self._fields} - def _replace(self, **kw): + def _replace(self: Any, **kw: Dict) -> Any: ans = self._asdict() ans.update(kw) return self.__class__(ans) @@ -213,7 +214,7 @@ def merge_dicts(defaults: Dict, newvals: Dict) -> Dict: return ans -def resolve_config(SYSTEM_CONF: str, defconf: str, config_files_on_cmd_line: Sequence[str]): +def resolve_config(SYSTEM_CONF: str, defconf: str, config_files_on_cmd_line: Sequence[str]) -> Generator[str, None, None]: if config_files_on_cmd_line: if 'NONE' not in config_files_on_cmd_line: yield SYSTEM_CONF @@ -225,13 +226,13 @@ def resolve_config(SYSTEM_CONF: str, defconf: str, config_files_on_cmd_line: Seq def load_config( - Options: Type, + Options: Type[T], defaults: Any, parse_config: Callable[[Iterable[str]], Dict[str, Any]], merge_configs: Callable[[Dict, Dict], Dict], *paths: str, overrides: Optional[Iterable[str]] = None -): +) -> T: ans: Dict = defaults._asdict() for path in paths: if not path: @@ -245,22 +246,22 @@ def load_config( if overrides is not None: vals = parse_config(overrides) ans = merge_configs(ans, vals) - return Options(ans) + return Options(ans) # type: ignore -def init_config(default_config_lines: Iterable[str], parse_config: Callable): +def init_config(default_config_lines: Iterable[str], parse_config: Callable) -> Tuple[Type, Any]: defaults = parse_config(default_config_lines, check_keys=False) Options = create_options_class(defaults.keys()) defaults = Options(defaults) return Options, defaults -def key_func(): +def key_func() -> Tuple[Callable, Dict[str, Callable]]: ans: Dict[str, Callable] = {} - def func_with_args(*names): + def func_with_args(*names: str) -> Callable: - def w(f): + def w(f: Callable) -> Callable: for name in names: if ans.setdefault(name, f) is not f: raise ValueError( diff --git a/kitty/config.py b/kitty/config.py index 70f7d3c69..4b09a55aa 100644 --- a/kitty/config.py +++ b/kitty/config.py @@ -11,7 +11,7 @@ from contextlib import contextmanager, suppress from functools import partial from typing import ( Any, Callable, Dict, Iterable, List, Optional, - Sequence, Set, Tuple, Type, cast + Sequence, Set, Tuple, Type ) from . import fast_data_types as defines @@ -796,7 +796,7 @@ def load_config(*paths: str, overrides: Optional[Iterable[str]] = None, accumula parser = parse_config if accumulate_bad_lines is not None: parser = partial(parse_config, accumulate_bad_lines=accumulate_bad_lines) - opts = cast(OptionsStub, _load_config(Options, defaults, parser, merge_configs, *paths, overrides=overrides)) + opts = _load_config(Options, defaults, parser, merge_configs, *paths, overrides=overrides) finalize_keys(opts) if opts.background_opacity < 1.0 and opts.macos_titlebar_color: log_error('Cannot use both macos_titlebar_color and background_opacity') diff --git a/kitty/config_data.py b/kitty/config_data.py index cc90e5ba9..fc05a8c00 100644 --- a/kitty/config_data.py +++ b/kitty/config_data.py @@ -6,7 +6,8 @@ import os from gettext import gettext as _ from typing import ( - Any, Dict, FrozenSet, Iterable, List, Optional, Set, Tuple, TypeVar, Union + Any, Dict, FrozenSet, Iterable, List, Optional, Sequence, Set, Tuple, + TypeVar, Union ) from . import fast_data_types as defines @@ -60,7 +61,7 @@ def uniq(vals: Iterable[T]) -> List[T]: # Groups {{{ -all_options: Dict[str, Union[Option, Shortcut]] = {} +all_options: Dict[str, Union[Option, Sequence[Shortcut]]] = {} o, k, g, all_groups = option_func(all_options, { diff --git a/setup.cfg b/setup.cfg index 884eb70ae..1f376e6a0 100644 --- a/setup.cfg +++ b/setup.cfg @@ -27,7 +27,7 @@ warn_unused_configs = True check_untyped_defs = True # disallow_untyped_defs = True -[mypy-kitty.rc.*] +[mypy-kitty.rc.*,kitty.conf.*] disallow_untyped_defs = True [mypy-conf]