From 5956277863787c9bc7e25f0d2680092451cacc0e Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Sat, 14 Mar 2020 14:37:11 +0530 Subject: [PATCH] All defs are now typed --- gen-apc-parsers.py | 30 +++--- gen-wcwidth.py | 68 +++++++------ glfw/glfw.py | 56 +++++++++-- publish.py | 77 ++++++++------- setup.py | 232 +++++++++++++++++++++++++-------------------- 5 files changed, 272 insertions(+), 191 deletions(-) diff --git a/gen-apc-parsers.py b/gen-apc-parsers.py index 48d62cdc7..3929f0d7b 100755 --- a/gen-apc-parsers.py +++ b/gen-apc-parsers.py @@ -4,12 +4,12 @@ import subprocess from collections import defaultdict -from typing import DefaultDict, Dict, FrozenSet, List, Tuple, Union +from typing import Any, DefaultDict, Dict, FrozenSet, List, Tuple, Union KeymapType = Dict[str, Tuple[str, Union[FrozenSet[str], str]]] -def resolve_keys(keymap: KeymapType): +def resolve_keys(keymap: KeymapType) -> DefaultDict[str, List[str]]: ans: DefaultDict[str, List[str]] = defaultdict(list) for ch, (attr, atype) in keymap.items(): if isinstance(atype, str) and atype in ('int', 'uint'): @@ -20,7 +20,7 @@ def resolve_keys(keymap: KeymapType): return ans -def enum(keymap: KeymapType): +def enum(keymap: KeymapType) -> str: lines = [] for ch, (attr, atype) in keymap.items(): lines.append(f"{attr}='{ch}'") @@ -31,7 +31,7 @@ def enum(keymap: KeymapType): '''.format(',\n'.join(lines)) -def parse_key(keymap: KeymapType): +def parse_key(keymap: KeymapType) -> str: lines = [] for attr, atype in keymap.values(): vs = atype.upper() if isinstance(atype, str) and atype in ('uint', 'int') else 'FLAG' @@ -39,7 +39,7 @@ def parse_key(keymap: KeymapType): return ' \n'.join(lines) -def parse_flag(keymap: KeymapType, type_map, command_class): +def parse_flag(keymap: KeymapType, type_map: Dict[str, Any], command_class: str) -> str: lines = [] for ch in type_map['flag']: attr, allowed_values = keymap[ch] @@ -57,14 +57,14 @@ def parse_flag(keymap: KeymapType, type_map, command_class): return ' \n'.join(lines) -def parse_number(keymap: KeymapType): +def parse_number(keymap: KeymapType) -> Tuple[str, str]: int_keys = [f'I({attr})' for attr, atype in keymap.values() if atype == 'int'] uint_keys = [f'U({attr})' for attr, atype in keymap.values() if atype == 'uint'] return '; '.join(int_keys), '; '.join(uint_keys) -def cmd_for_report(report_name, keymap: KeymapType, type_map, payload_allowed): - def group(atype, conv): +def cmd_for_report(report_name: str, keymap: KeymapType, type_map: Dict[str, Any], payload_allowed: bool) -> str: + def group(atype: str, conv: str) -> Tuple[str, str]: flag_fmt, flag_attrs = [], [] cv = {'flag': 'c', 'int': 'i', 'uint': 'I'}[atype] for ch in type_map[atype]: @@ -89,7 +89,15 @@ def cmd_for_report(report_name, keymap: KeymapType, type_map, payload_allowed): return '\n'.join(ans) -def generate(function_name, callback_name, report_name, keymap: KeymapType, command_class, initial_key='a', payload_allowed=True): +def generate( + function_name: str, + callback_name: str, + report_name: str, + keymap: KeymapType, + command_class: str, + initial_key: str = 'a', + payload_allowed: bool = True +) -> str: type_map = resolve_keys(keymap) keys_enum = enum(keymap) handle_key = parse_key(keymap) @@ -230,7 +238,7 @@ static inline void ''' -def write_header(text, path): +def write_header(text: str, path: str) -> None: with open(path, 'w') as f: print(f'// This file is generated by {__file__} do not edit!', file=f, end='\n\n') print('#pragma once', file=f) @@ -238,7 +246,7 @@ def write_header(text, path): subprocess.check_call(['clang-format', '-i', path]) -def graphics_parser(): +def graphics_parser() -> None: flag = frozenset keymap: KeymapType = { 'a': ('action', flag('tTqpd')), diff --git a/gen-wcwidth.py b/gen-wcwidth.py index fd2d5c3e4..cc0b968b5 100755 --- a/gen-wcwidth.py +++ b/gen-wcwidth.py @@ -13,7 +13,8 @@ from html.entities import html5 from itertools import groupby from operator import itemgetter from typing import ( - DefaultDict, Dict, Generator, Iterable, List, Optional, Set, Tuple, Union + Callable, DefaultDict, Dict, FrozenSet, Generator, Iterable, List, + Optional, Set, Tuple, Union ) from urllib.request import urlopen @@ -27,7 +28,7 @@ if len(non_characters) != 66: emoji_skin_tone_modifiers = frozenset(range(0x1f3fb, 0x1F3FF + 1)) -def get_data(fname, folder='UCD'): +def get_data(fname: str, folder: str = 'UCD') -> Iterable[str]: url = f'https://www.unicode.org/Public/{folder}/latest/{fname}' bn = os.path.basename(url) local = os.path.join('/tmp', bn) @@ -54,9 +55,9 @@ marks = set(emoji_skin_tone_modifiers) | {zwj} not_assigned = set(range(0, sys.maxunicode)) -def parse_ucd(): +def parse_ucd() -> None: - def add_word(w, c): + def add_word(w: str, c: int) -> None: if c <= 32 or c == 127 or 128 <= c <= 159: return if len(w) > 1: @@ -102,7 +103,7 @@ def parse_ucd(): word_search_map['lamda'] |= word_search_map['lambda'] -def split_two(line): +def split_two(line: str) -> Tuple[Set[int], str]: spec, rest = line.split(';', 1) spec, rest = spec.strip(), rest.strip().split(' ', 1)[0].strip() if '..' in spec: @@ -118,26 +119,27 @@ emoji_categories: Dict[str, Set[int]] = {} emoji_presentation_bases: Set[int] = set() -def parse_emoji(): +def parse_emoji() -> None: for line in get_data('emoji-data.txt', 'emoji'): chars, rest = split_two(line) s = emoji_categories.setdefault(rest, set()) s.update(chars) all_emoji.update(chars) for line in get_data('emoji-variation-sequences.txt', 'emoji'): - base, var, *rest = line.split() + parts = line.split() + base, var = parts[0], parts[1] if base.startswith('#'): continue - base = int(base, 16) if var.upper() == 'FE0F': - emoji_presentation_bases.add(base) + ibase = int(base, 16) + emoji_presentation_bases.add(ibase) doublewidth: Set[int] = set() ambiguous: Set[int] = set() -def parse_eaw(): +def parse_eaw() -> None: global doublewidth, ambiguous seen: Set[int] = set() for line in get_data('ucd/EastAsianWidth.txt'): @@ -166,7 +168,7 @@ def get_ranges(items: List[int]) -> Generator[Union[int, Tuple[int, int]], None, yield a, b -def write_case(spec, p): +def write_case(spec: Union[Tuple, int], p: Callable) -> None: if isinstance(spec, tuple): p('\t\tcase 0x{:x} ... 0x{:x}:'.format(*spec)) else: @@ -174,7 +176,7 @@ def write_case(spec, p): @contextmanager -def create_header(path, include_data_types=True): +def create_header(path: str, include_data_types: bool = True) -> Generator[Callable, None, None]: with open(path, 'w') as f: p = partial(print, file=f) p('// unicode data, built from the unicode standard on:', date.today()) @@ -191,7 +193,7 @@ def create_header(path, include_data_types=True): p('END_ALLOW_CASE_RANGE') -def gen_emoji(): +def gen_emoji() -> None: with create_header('kitty/emoji.h') as p: p('static inline bool\nis_emoji(char_type code) {') p('\tswitch(code) {') @@ -221,8 +223,16 @@ def gen_emoji(): p('\treturn false;\n}') -def category_test(name, p, classes, comment, static=False, extra_chars=frozenset(), exclude=frozenset()): - static = 'static inline ' if static else '' +def category_test( + name: str, + p: Callable, + classes: Iterable[str], + comment: str, + use_static: bool = False, + extra_chars: Union[FrozenSet[int], Set[int]] = frozenset(), + exclude: Union[Set[int], FrozenSet[int]] = frozenset() +) -> None: + static = 'static inline ' if use_static else '' chars: Set[int] = set() for c in classes: chars |= class_maps[c] @@ -238,7 +248,7 @@ def category_test(name, p, classes, comment, static=False, extra_chars=frozenset p('\treturn false;\n}\n') -def codepoint_to_mark_map(p, mark_map): +def codepoint_to_mark_map(p: Callable, mark_map: List[int]) -> Dict[int, int]: p('\tswitch(c) { // {{{') rmap = {c: m for m, c in enumerate(mark_map)} for spec in get_ranges(mark_map): @@ -253,14 +263,14 @@ def codepoint_to_mark_map(p, mark_map): return rmap -def classes_to_regex(classes, exclude=''): +def classes_to_regex(classes: Iterable[str], exclude: str = '') -> Iterable[str]: chars: Set[int] = set() for c in classes: chars |= class_maps[c] - for c in map(ord, exclude): - chars.discard(c) + for x in map(ord, exclude): + chars.discard(x) - def as_string(codepoint): + def as_string(codepoint: int) -> str: if codepoint < 256: return r'\x{:02x}'.format(codepoint) if codepoint <= 0xffff: @@ -274,7 +284,7 @@ def classes_to_regex(classes, exclude=''): yield as_string(spec) -def gen_ucd(): +def gen_ucd() -> None: cz = {c for c in class_maps if c[0] in 'CZ'} with create_header('kitty/unicode-data.c') as p: p('#include "unicode-data.h"') @@ -313,7 +323,7 @@ def gen_ucd(): f.write("url_delimiters = '{}' # noqa".format(''.join(classes_to_regex(cz, exclude='\n')))) -def gen_names(): +def gen_names() -> None: with create_header('kittens/unicode_input/names.h') as p: mark_to_cp = list(sorted(name_map)) cp_to_mark = {cp: m for m, cp in enumerate(mark_to_cp)} @@ -372,24 +382,24 @@ def gen_names(): class TrieNode: - def __init__(self): + def __init__(self) -> None: self.match_offset = 0 self.children_offset = 0 - self.children = {} + self.children: Dict[int, int] = {} - def add_letter(self, letter): + def add_letter(self, letter: int) -> int: if letter not in self.children: self.children[letter] = len(all_trie_nodes) all_trie_nodes.append(TrieNode()) return self.children[letter] - def __str__(self): + def __str__(self) -> str: return f'{{ .children_offset={self.children_offset}, .match_offset={self.match_offset} }}' root = TrieNode() all_trie_nodes.append(root) - def add_word(word_idx, word): + def add_word(word_idx: int, word: str) -> None: parent = root for letter in map(ord, word): idx = parent.add_letter(letter) @@ -414,10 +424,10 @@ def gen_names(): p('}; // }}}\n') -def gen_wcwidth(): +def gen_wcwidth() -> None: seen: Set[int] = set() - def add(p, comment, chars_, ret): + def add(p: Callable, comment: str, chars_: Set[int], ret: int) -> None: chars = chars_ - seen seen.update(chars) p(f'\t\t// {comment} ({len(chars)} codepoints)' + ' {{' '{') diff --git a/glfw/glfw.py b/glfw/glfw.py index d737b191b..77e2dee68 100755 --- a/glfw/glfw.py +++ b/glfw/glfw.py @@ -6,18 +6,54 @@ import json import os import re import sys +from typing import Callable, List, Optional, Tuple _plat = sys.platform.lower() is_linux = 'linux' in _plat base = os.path.dirname(os.path.abspath(__file__)) -def wayland_protocol_file_name(base, ext='c'): +class Env: + + cc: str = '' + cppflags: List[str] = [] + cflags: List[str] = [] + ldflags: List[str] = [] + ldpaths: List[str] = [] + ccver: Tuple[int, int] + + # glfw stuff + all_headers: List[str] = [] + sources: List[str] = [] + wayland_packagedir: str = '' + wayland_scanner: str = '' + wayland_scanner_code: str = '' + wayland_protocols: Tuple[str, ...] = () + + def __init__( + self, cc: str = '', cppflags: List[str] = [], cflags: List[str] = [], ldflags: List[str] = [], + ldpaths: Optional[List[str]] = None, ccver: Tuple[int, int] = (0, 0) + ): + self.cc, self.cppflags, self.cflags, self.ldflags, self.ldpaths = cc, cppflags, cflags, ldflags, [] if ldpaths is None else ldpaths + self.ccver = ccver + + def copy(self) -> 'Env': + ans = Env(self.cc, list(self.cppflags), list(self.cflags), list(self.ldflags), list(self.ldpaths), self.ccver) + ans.all_headers = list(self.all_headers) + ans.sources = list(self.sources) + ans.wayland_packagedir = self.wayland_packagedir + ans.wayland_scanner = self.wayland_scanner + ans.wayland_scanner_code = self.wayland_scanner_code + ans.wayland_protocols = self.wayland_protocols + return ans + + +def wayland_protocol_file_name(base: str, ext: str = 'c') -> str: base = os.path.basename(base).rpartition('.')[0] return 'wayland-{}-client-protocol.{}'.format(base, ext) -def init_env(env, pkg_config, at_least_version, test_compile, module='x11'): +def init_env(env: Env, pkg_config: Callable, at_least_version: Callable, test_compile: Callable, module: str = 'x11') -> Env: ans = env.copy() ans.cflags.append('-fpic') ans.cppflags.append('-D_GLFW_' + module.upper()) @@ -74,7 +110,7 @@ def init_env(env, pkg_config, at_least_version, test_compile, module='x11'): return ans -def build_wayland_protocols(env, Command, parallel_run, emphasis, newer, dest_dir): +def build_wayland_protocols(env: Env, Command: Callable, parallel_run: Callable, emphasis: Callable, newer: Callable, dest_dir: str) -> None: items = [] for protocol in env.wayland_protocols: src = os.path.join(env.wayland_packagedir, protocol) @@ -95,7 +131,7 @@ def build_wayland_protocols(env, Command, parallel_run, emphasis, newer, dest_di class Arg: - def __init__(self, decl): + def __init__(self, decl: str): self.type, self.name = decl.rsplit(' ', 1) self.type = self.type.strip() self.name = self.name.strip() @@ -103,13 +139,13 @@ class Arg: self.name = self.name[1:] self.type = self.type + '*' - def __repr__(self): + def __repr__(self) -> str: return 'Arg({}, {})'.format(self.type, self.name) class Function: - def __init__(self, declaration, check_fail=True): + def __init__(self, declaration: str, check_fail: bool = True): self.check_fail = check_fail m = re.match( r'(.+?)\s+(glfw[A-Z][a-zA-Z0-9]+)[(](.+)[)]$', declaration @@ -128,14 +164,14 @@ class Function: if not self.args: self.args = [Arg('void v')] - def declaration(self): + def declaration(self) -> str: return 'typedef {restype} (*{name}_func)({args});\n{name}_func {name}_impl;\n#define {name} {name}_impl'.format( restype=self.restype, name=self.name, args=', '.join(a.type for a in self.args) ) - def load(self): + def load(self) -> str: ans = '*(void **) (&{name}_impl) = dlsym(handle, "{name}");'.format( name=self.name ) @@ -146,7 +182,7 @@ class Function: return ans -def generate_wrappers(glfw_header): +def generate_wrappers(glfw_header: str) -> None: with open(glfw_header) as f: src = f.read() functions = [] @@ -244,7 +280,7 @@ unload_glfw(void) { f.write(code) -def main(): +def main() -> None: os.chdir(os.path.dirname(os.path.abspath(__file__))) generate_wrappers('glfw3.h') diff --git a/publish.py b/publish.py index 315bddedb..698ab1e38 100755 --- a/publish.py +++ b/publish.py @@ -16,7 +16,7 @@ import sys import tempfile import time from contextlib import suppress -from typing import IO, Optional, cast +from typing import IO, Any, Dict, Iterable, List, Optional, cast import requests @@ -44,7 +44,7 @@ def call(*cmd: str, cwd: Optional[str] = None) -> None: raise SystemExit(ret) -def run_build(args): +def run_build(args: Any) -> None: os.chdir(build_path) call('./linux 64 kitty') call('./osx kitty --sign-installers') @@ -52,21 +52,21 @@ def run_build(args): call('./linux 32 kitty') -def run_tag(args): +def run_tag(args: Any) -> None: call('git push') call('git tag -s v{0} -m version-{0}'.format(version)) call('git push origin v{0}'.format(version)) -def run_man(args): +def run_man(args: Any) -> None: call('make FAIL_WARN=-W man', cwd=docs_dir) -def run_html(args): +def run_html(args: Any) -> None: call('make FAIL_WARN=-W html', cwd=docs_dir) -def add_analytics(): +def add_analytics() -> None: analytics = '''