From 77d9de7b843173c4349dd3f75c2d067f61dc4c4b Mon Sep 17 00:00:00 2001 From: Max Date: Sat, 17 Sep 2022 01:24:23 +0300 Subject: [PATCH 1/6] Add a few types to begin mypy story --- .github/workflows/ci.yml | 2 +- plim/__init__.py | 6 +++--- plim/errors.py | 7 +++---- plim/lexer.py | 26 ++++++++++++-------------- plim/util.py | 12 ++++++++++-- requirements-test.txt | 3 +++ 6 files changed, 32 insertions(+), 24 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 2503d65..d350820 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -22,7 +22,7 @@ jobs: - name: Run tests on Python${{ matrix.python-version }} run: | nix-shell --argstr pyVersion ${{ matrix.python-version }} --run \ - "pip install -e . && pip install -r requirements.txt && pip install -r requirements-test.txt && python -m pytest" + "pip install -e . && pip install -r requirements.txt && pip install -r requirements-test.txt && make test" - name: Coveralls env: diff --git a/plim/__init__.py b/plim/__init__.py index 9f5119d..bb56872 100644 --- a/plim/__init__.py +++ b/plim/__init__.py @@ -1,19 +1,19 @@ import functools +from typing import Mapping, Type from .lexer import compile_plim_source from . import syntax as available_syntax -def preprocessor_factory(custom_parsers=None, syntax='mako'): +def preprocessor_factory(custom_parsers=None, syntax: str = 'mako'): """ :param custom_parsers: a list of 2-tuples of (parser_regex, parser_callable) or None :type custom_parsers: list or None :param syntax: name of the target template engine ('mako' by default) - :type syntax: str or None :return: preprocessor instance """ - syntax_choices = { + syntax_choices: Mapping[str, Type[available_syntax.BaseSyntax]] = { 'mako': available_syntax.Mako, 'django': available_syntax.Django, } diff --git a/plim/errors.py b/plim/errors.py index d828624..7ca5d9b 100644 --- a/plim/errors.py +++ b/plim/errors.py @@ -4,8 +4,7 @@ class PlimError(Exception): - def __str__(self): - return self.__unicode__().encode('utf-8') + pass class PlimSyntaxError(PlimError): @@ -14,7 +13,7 @@ def __init__(self, msg, line): self.msg = msg self.line = line - def __unicode__(self): + def __str__(self): return u('{msg} | at line(pos) "{line}"').format(msg=self.msg, line=self.line) @@ -24,6 +23,6 @@ def __init__(self, lineno, line): self.lineno = lineno self.line = line - def __unicode__(self): + def __str__(self): return u("Invalid syntax at line {lineno}: {line}").format( lineno=self.lineno, line=self.line) diff --git a/plim/lexer.py b/plim/lexer.py index 36045d9..b3b629f 100644 --- a/plim/lexer.py +++ b/plim/lexer.py @@ -2,6 +2,7 @@ """Plim lexer""" import functools import re +from typing import Optional, Tuple, Any, Mapping, Callable import markdown2 @@ -173,11 +174,9 @@ # Searchers # ================================================================================== -def search_quotes(line, escape_char='\\', quotes_re=QUOTES_RE): +def search_quotes(line: str, escape_char: str = '\\', quotes_re = QUOTES_RE) -> Optional[int]: """ - :param line: may be empty - :type line: str :param escape_char: """ match = quotes_re.match(line) @@ -198,7 +197,7 @@ def search_quotes(line, escape_char='\\', quotes_re=QUOTES_RE): return None -def search_parser(lineno, line, syntax): +def search_parser(lineno, line, syntax) -> Tuple[Any, Any]: """Finds a proper parser function for a given line or raises an error :param lineno: @@ -214,7 +213,7 @@ def search_parser(lineno, line, syntax): # Extractors # ================================================================================== -def extract_embedding_quotes(content): +def extract_embedding_quotes(content) -> Optional[Tuple[Any, Any, Any]]: """ ``content`` may be empty @@ -254,13 +253,13 @@ def extract_embedding_quotes(content): raise errors.PlimSyntaxError(u('Embedding quote is not closed: "{}"').format(original_string), pos) -def _extract_braces_expression(line, source, starting_braces_re, open_braces_re, closing_braces_re): +def _extract_braces_expression( + line: str, source: str, starting_braces_re, open_braces_re, closing_braces_re +) -> Tuple[Any, Any, Any]: """ :param line: may be empty - :type line: str :param source: - :type source: str :param starting_braces_re: :param open_braces_re: :param closing_braces_re: @@ -420,7 +419,7 @@ def extract_dynamic_attr_value(line, source, terminators, syntax): return value, tail, source -def extract_dynamic_tag_attributes(line, source, syntax, inside_parentheses=False): +def extract_dynamic_tag_attributes(line: str, source: str, syntax, inside_parentheses=False) -> Optional[Tuple[Any, Any, Any]]: """ Extract one occurrence of ``**dynamic_attributes`` :param line: @@ -463,8 +462,7 @@ def extract_dynamic_tag_attributes(line, source, syntax, inside_parentheses=Fals return attributes, tail, source - -def extract_tag_attribute(line, source, syntax, inside_parentheses=False): +def extract_tag_attribute(line: str, source: str, syntax, inside_parentheses=False): """ :param line: @@ -1606,7 +1604,7 @@ def compile_plim_source(source, syntax, strip=True): EMPTY_TAGS = {'meta', 'img', 'link', 'input', 'area', 'base', 'col', 'br', 'hr'} -MARKUP_LANGUAGES = { +MARKUP_LANGUAGES: Mapping[str, Callable[[Any], str]] = { 'md': markdown2.markdown, 'markdown': markdown2.markdown, 'rst': rst_to_html, @@ -1617,8 +1615,8 @@ def compile_plim_source(source, syntax, strip=True): 'stylus': stylus_to_css } -DOCTYPES = { - 'html':'', +DOCTYPES: Mapping[str, str] = { + 'html': '', '5': '', '1.1': '', 'strict': '', diff --git a/plim/util.py b/plim/util.py index 0f8c845..5f4b0e2 100644 --- a/plim/util.py +++ b/plim/util.py @@ -1,10 +1,18 @@ import sys +from typing import Sequence PY3K = sys.version_info >= (3, 0) from io import StringIO -joined = lambda buf: ''.join(buf) -space_separated = lambda buf: ' '.join(buf) + +def joined(buf: Sequence[str], sep: str = '') -> str: + return sep.join(buf) + + +def space_separated(buf: Sequence[str]) -> str: + return joined(buf, ' ') + + u = str MAXSIZE = sys.maxsize diff --git a/requirements-test.txt b/requirements-test.txt index 78fc9ce..b6c48c9 100644 --- a/requirements-test.txt +++ b/requirements-test.txt @@ -1,4 +1,7 @@ pytest coverage pytest-cov + mypy +types-docutils +types-setuptools From eea579c9f7bf3e6f4c6a3f5cfffa3d4ca3f65171 Mon Sep 17 00:00:00 2001 From: Max Date: Sat, 17 Sep 2022 01:33:00 +0300 Subject: [PATCH 2/6] more typing --- plim/lexer.py | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/plim/lexer.py b/plim/lexer.py index b3b629f..6bc00f8 100644 --- a/plim/lexer.py +++ b/plim/lexer.py @@ -2,7 +2,7 @@ """Plim lexer""" import functools import re -from typing import Optional, Tuple, Any, Mapping, Callable +from typing import Optional, Tuple, Any, Mapping, Callable, Iterator import markdown2 @@ -238,24 +238,24 @@ def extract_embedding_quotes(content) -> Optional[Tuple[Any, Any, Any]]: if tail.startswith(EMBEDDING_QUOTE): append_seq = EMBEDDING_QUOTE_END if tail.startswith(EMBEDDING_QUOTE_END) else EMBEDDING_QUOTE original_string.append(append_seq) - original_string = joined(original_string) - content = content[len(original_string):] - embedded_string = joined(embedded_string) - return embedded_string, original_string, content + original_string_str = joined(original_string) + content = content[len(original_string_str):] + embedded_string_str = joined(embedded_string) + return embedded_string_str, original_string_str, content current_char = tail[0] original_string.append(current_char) embedded_string.append(current_char) tail = tail[1:] - original_string = joined(original_string) - pos = len(original_string) - raise errors.PlimSyntaxError(u('Embedding quote is not closed: "{}"').format(original_string), pos) + original_string_str = joined(original_string) + pos = len(original_string_str) + raise errors.PlimSyntaxError(u('Embedding quote is not closed: "{}"').format(original_string_str), pos) def _extract_braces_expression( - line: str, source: str, starting_braces_re, open_braces_re, closing_braces_re -) -> Tuple[Any, Any, Any]: + line: str, source: Iterator[Tuple[Any, str]], starting_braces_re, open_braces_re, closing_braces_re +) -> Optional[Tuple[Any, Any, Any]]: """ :param line: may be empty @@ -352,10 +352,10 @@ def extract_identifier(line, source, identifier_start='#', terminators=('.', ' ' continue # Check for a string object - result = search_quotes(tail) - if result is not None: - buf.append(tail[:result]) - tail = tail[result:] + result_pos = search_quotes(tail) + if result_pos is not None: + buf.append(tail[:result_pos]) + tail = tail[result_pos:] continue # Try to search braces of function calls etc From 52df57e957b7ebbf0eec50b43943a867812f3d80 Mon Sep 17 00:00:00 2001 From: Max Date: Sat, 17 Sep 2022 01:55:22 +0300 Subject: [PATCH 3/6] resolve one deprecation warning --- Makefile | 2 +- plim/console.py | 2 +- plim/lexer.py | 62 ++++++++++++++++++++++++------------------------ plim/syntax.py | 4 ++-- plim/util.py | 7 ++++-- requirements.txt | 1 + setup.py | 2 +- 7 files changed, 42 insertions(+), 38 deletions(-) diff --git a/Makefile b/Makefile index 9a1ee0e..d72f49c 100644 --- a/Makefile +++ b/Makefile @@ -12,7 +12,7 @@ DIST_DIR := $(PROJECT_ROOT)/dist PROJECT=plim .PHONY: test -test: +test: typecheck pytest -s --cov=plim --cov-report xml $(PROJECT_ROOT)/tests .PHONY: typecheck diff --git a/plim/console.py b/plim/console.py index 69bf82c..736d05f 100644 --- a/plim/console.py +++ b/plim/console.py @@ -43,7 +43,7 @@ def plimc(args=None, stdout=None): # Add an empty string path, so modules located at the current working dir # are reachable and considered in the first place (see issue #32). sys.path.insert(0, '') - preprocessor = EntryPoint.parse('x={}'.format(preprocessor_path)).load(False) + preprocessor = EntryPoint.parse('x={}'.format(preprocessor_path)).resolve() # Render to html, if requested # ---------------------------- diff --git a/plim/lexer.py b/plim/lexer.py index 6bc00f8..7cf7c71 100644 --- a/plim/lexer.py +++ b/plim/lexer.py @@ -5,6 +5,7 @@ from typing import Optional, Tuple, Any, Mapping, Callable, Iterator import markdown2 +from pyrsistent import v from . import errors from .util import StringIO, MAXSIZE, joined, space_separated, u @@ -62,21 +63,21 @@ INLINE_PYTHON_TERMINATOR = '---' -CSS_ID_SHORTCUT_TERMINATORS = ( +CSS_ID_SHORTCUT_TERMINATORS = v( CSS_CLASS_SHORTCUT_DELIMITER, WHITESPACE, OPEN_BRACE, INLINE_TAG_SEPARATOR ) -CSS_CLASS_SHORTCUT_TERMINATORS = ( +CSS_CLASS_SHORTCUT_TERMINATORS = v( CSS_CLASS_SHORTCUT_DELIMITER, WHITESPACE, OPEN_BRACE, INLINE_TAG_SEPARATOR ) -ATTRIBUTE_TERMINATORS = ( +ATTRIBUTE_TERMINATORS = v( ATTRIBUTE_VALUE_DELIMITER, ATTRIBUTES_DELIMITER, INLINE_TAG_SEPARATOR, @@ -84,13 +85,13 @@ LITERAL_CONTENT_SPACE_PREFIX ) -ATTRIBUTE_TERMINATORS_WITH_PARENTHESES = ( +ATTRIBUTE_TERMINATORS_WITH_PARENTHESES = v( ATTRIBUTE_VALUE_DELIMITER, ATTRIBUTES_DELIMITER, CLOSE_BRACE ) -ATTRIBUTE_VALUE_TERMINATORS = ( +ATTRIBUTE_VALUE_TERMINATORS = v( ATTRIBUTES_DELIMITER, INLINE_TAG_SEPARATOR, LITERAL_CONTENT_PREFIX, @@ -99,7 +100,7 @@ BOOLEAN_ATTRIBUTE_MARKER ) -ATTRIBUTE_VALUE_TERMINATORS_WITH_PARENTHESES = ( +ATTRIBUTE_VALUE_TERMINATORS_WITH_PARENTHESES = v( ATTRIBUTES_DELIMITER, INLINE_TAG_SEPARATOR, LITERAL_CONTENT_PREFIX, @@ -674,13 +675,13 @@ def extract_tag_line(line, source, syntax): if css_id: attributes.append(u('id="{ids}"').format(ids=css_id)) if class_identifiers: - class_identifiers = space_separated(class_identifiers) - attributes.append(u('class="{classes}"').format(classes=class_identifiers)) + class_identifiers_str = space_separated(class_identifiers) + attributes.append(u('class="{classes}"').format(classes=class_identifiers_str)) break - attributes = space_separated(attributes) - components['attributes'] = attributes - if attributes: - tag_composer.extend([' ', attributes]) + attributes_str = space_separated(attributes) + components['attributes'] = attributes_str + if attributes_str: + tag_composer.extend([' ', attributes_str]) # 3.2 syntax check if inside_parentheses: @@ -1285,8 +1286,8 @@ def prepare_result(buf): if align > new_align: align = new_align # remove preceding spaces - line = current_line[align:].rstrip() - buf.extend([line.rstrip(), "\n"]) + ne_line = current_line[align:].rstrip() + buf.extend([ne_line.rstrip(), "\n"]) result = prepare_result(buf) return result, 0, '', source @@ -1331,7 +1332,7 @@ def _parse_embedded_markup(content, syntax): return joined(buf) -def _inject_n_filter(line): +def _inject_n_filter(line: str) -> str: """ This is a helper function for :func:parse_variable @@ -1373,19 +1374,19 @@ def parse_variable(indent_level, __, matched, source, syntax): if not line: continue if indent <= indent_level: - buf = joined(buf) + buf_str = joined(buf) if prevent_escape: - buf = _inject_n_filter(buf) + buf_str = _inject_n_filter(buf_str) # add a closing brace to complete variable expression syntax ("${}" in case of mako). - buf += syntax.VARIABLE_PLACEHOLDER_END_SEQUENCE + explicit_space - return buf, indent, line, source + buf_str += syntax.VARIABLE_PLACEHOLDER_END_SEQUENCE + explicit_space + return buf_str, indent, line, source buf.append(line.strip()) - buf = joined(buf) + buf_str = joined(buf) if prevent_escape: - buf = _inject_n_filter(buf) - buf += syntax.VARIABLE_PLACEHOLDER_END_SEQUENCE + explicit_space - return buf, 0, '', source + buf_str = _inject_n_filter(buf_str) + buf_str += syntax.VARIABLE_PLACEHOLDER_END_SEQUENCE + explicit_space + return buf_str, 0, '', source def parse_early_return(indent_level, __, matched, source, syntax): @@ -1554,14 +1555,13 @@ def enumerate_source(source): return enumerate(StringIO(source), start=1) -def scan_line(line): +def scan_line(line: str) -> Tuple[Optional[int], Optional[str]]: """ Returns a 2-tuple of (length_of_the_indentation, line_without_preceding_indentation) - - :param line: - :type line: str """ match = LINE_PARTS_RE.match(line) - return len(match.group('indent')), match.group('line') + if match: + return len(match.group('indent')), match.group('line') + return None, None def compile_plim_source(source, syntax, strip=True): @@ -1593,10 +1593,10 @@ def compile_plim_source(source, syntax, strip=True): parsed_data, tail_indent, tail_line, source = parse(tail_indent, tail_line, matched_obj, source, syntax) result.append(parsed_data) - result = joined(result) + result_str = joined(result) if strip: - result = result.strip() - return result + result_str = result_str.strip() + return result_str # Acknowledgements diff --git a/plim/syntax.py b/plim/syntax.py index 32539ab..3ff1b08 100644 --- a/plim/syntax.py +++ b/plim/syntax.py @@ -104,8 +104,8 @@ class Django(BaseSyntax): STATEMENT_END_START_SEQUENCE = STATEMENT_START_START_SEQUENCE STATEMENT_END_END_SEQUENCE = STATEMENT_START_END_SEQUENCE - PARSE_MAKO_ONE_LINERS_RE = None - PARSE_MAKO_TEXT_RE = None + PARSE_MAKO_ONE_LINERS_RE = None # type: ignore + PARSE_MAKO_TEXT_RE = None # type: ignore def __str__(self): return 'Django Syntax' diff --git a/plim/util.py b/plim/util.py index 5f4b0e2..6746da6 100644 --- a/plim/util.py +++ b/plim/util.py @@ -1,12 +1,15 @@ import sys -from typing import Sequence +from typing import Sequence, Iterable PY3K = sys.version_info >= (3, 0) from io import StringIO -def joined(buf: Sequence[str], sep: str = '') -> str: +def joined(buf: Iterable[str], sep: str = '') -> str: + """ note: `buf` iterable will be fully consumed, so if you are passing a stream make sure you tee it + if you need to use the `buf` again later + """ return sep.join(buf) diff --git a/requirements.txt b/requirements.txt index bfec571..2ac49ff 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,5 @@ Mako>=0.9.0 +pyrsistent>=0.18.1 babel>=1.3 # We use reStructuredText (docutils component) for both supporting # the "-rest" extension and project documenting. So, ensure that the docutils diff --git a/setup.py b/setup.py index 47043cc..45b269c 100644 --- a/setup.py +++ b/setup.py @@ -37,7 +37,7 @@ def read(*filenames, **kwargs): setup( name='Plim', - version='1.0.0', + version='1.1.0', packages=find_packages(exclude=['tests', 'nixpkgs', 'node_modules']), install_requires=requires, setup_requires=[], From 88c87f041b2f7e95bbedc277c97197dd534d5d6c Mon Sep 17 00:00:00 2001 From: Max Date: Sat, 17 Sep 2022 01:57:34 +0300 Subject: [PATCH 4/6] f --- setup.cfg | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.cfg b/setup.cfg index 27a5a42..0781b1b 100644 --- a/setup.cfg +++ b/setup.cfg @@ -5,7 +5,7 @@ show_error_context = true warn_incomplete_stub = true ignore_missing_imports = true check_untyped_defs = true -cache_dir = ./local/mypy-cache +cache_dir = ./.local/mypy-cache warn_redundant_casts = true warn_unused_configs = true strict_optional = true From bb8122e685333e523effdc5554ee935db3b667a2 Mon Sep 17 00:00:00 2001 From: Max Date: Sat, 17 Sep 2022 02:30:36 +0300 Subject: [PATCH 5/6] more typing --- Makefile | 4 ++-- plim/__init__.py | 8 +++++--- plim/adapters/pyramid_renderer.py | 2 +- plim/errors.py | 17 ++++++----------- plim/extensions.py | 20 ++++++++++---------- plim/lexer.py | 28 ++++++++++++++-------------- plim/syntax.py | 17 +++++++++++------ 7 files changed, 49 insertions(+), 47 deletions(-) diff --git a/Makefile b/Makefile index d72f49c..7f796e3 100644 --- a/Makefile +++ b/Makefile @@ -12,12 +12,12 @@ DIST_DIR := $(PROJECT_ROOT)/dist PROJECT=plim .PHONY: test -test: typecheck +test: pytest -s --cov=plim --cov-report xml $(PROJECT_ROOT)/tests .PHONY: typecheck typecheck: - mypy --config-file setup.cfg --package $(PROJECT_NAME) + mypy --config-file setup.cfg --strict --package $(PROJECT_NAME) .PHONY: prepare-dist prepare-dist: diff --git a/plim/__init__.py b/plim/__init__.py index bb56872..763cc5d 100644 --- a/plim/__init__.py +++ b/plim/__init__.py @@ -1,11 +1,13 @@ import functools -from typing import Mapping, Type +from typing import Mapping, Type, Sequence, Any, Callable + +from pyrsistent import v from .lexer import compile_plim_source from . import syntax as available_syntax -def preprocessor_factory(custom_parsers=None, syntax: str = 'mako'): +def preprocessor_factory(custom_parsers: Sequence[Any] = v(), syntax: str = 'mako') -> Callable[[str, bool], str]: """ :param custom_parsers: a list of 2-tuples of (parser_regex, parser_callable) or None @@ -17,7 +19,7 @@ def preprocessor_factory(custom_parsers=None, syntax: str = 'mako'): 'mako': available_syntax.Mako, 'django': available_syntax.Django, } - selected_syntax = syntax_choices[syntax](custom_parsers) + selected_syntax = syntax_choices[syntax](custom_parsers or v()) return functools.partial(compile_plim_source, syntax=selected_syntax) diff --git a/plim/adapters/pyramid_renderer.py b/plim/adapters/pyramid_renderer.py index 15e99c8..43ffb3f 100644 --- a/plim/adapters/pyramid_renderer.py +++ b/plim/adapters/pyramid_renderer.py @@ -33,7 +33,7 @@ def add_plim_renderer(config, extension, mako_settings_prefix='mako.', preproces renderer_factory = MakoRendererFactory() config.add_renderer(extension, renderer_factory) - def register(): + def register() -> None: settings = copy.copy(config.registry.settings) settings['{prefix}preprocessor'.format(prefix=mako_settings_prefix)] = preprocessor diff --git a/plim/errors.py b/plim/errors.py index 7ca5d9b..5b81c74 100644 --- a/plim/errors.py +++ b/plim/errors.py @@ -1,28 +1,23 @@ -# -*- coding: utf-8 -*- -from .util import u - - - class PlimError(Exception): pass class PlimSyntaxError(PlimError): - def __init__(self, msg, line): + def __init__(self, msg: str, line: str): super(PlimSyntaxError, self).__init__() self.msg = msg self.line = line - def __str__(self): - return u('{msg} | at line(pos) "{line}"').format(msg=self.msg, line=self.line) + def __str__(self) -> str: + return '{msg} | at line(pos) "{line}"'.format(msg=self.msg, line=self.line) class ParserNotFound(PlimError): - def __init__(self, lineno, line): + def __init__(self, lineno: int, line: str): super(ParserNotFound, self).__init__() self.lineno = lineno self.line = line - def __str__(self): - return u("Invalid syntax at line {lineno}: {line}").format( + def __str__(self) -> str: + return "Invalid syntax at line {lineno}: {line}".format( lineno=self.lineno, line=self.line) diff --git a/plim/extensions.py b/plim/extensions.py index 530ad7d..920e3d9 100644 --- a/plim/extensions.py +++ b/plim/extensions.py @@ -1,28 +1,28 @@ +from typing import Mapping + from docutils.core import publish_parts import coffeescript from scss import Scss from stylus import Stylus -from .util import u - -def rst_to_html(source): +def rst_to_html(source: str) -> str: # This code was taken from http://wiki.python.org/moin/ReStructuredText # You may also be interested in http://www.tele3.cz/jbar/rest/about.html - html = publish_parts(source=source, writer_name='html') + html: Mapping[str, str] = publish_parts(source=source, writer_name='html') return html['html_body'] -def coffee_to_js(source): - return u('').format(js=coffeescript.compile(source)) +def coffee_to_js(source: str) -> str: + return ''.format(js=coffeescript.compile(source)) -def scss_to_css(source): +def scss_to_css(source: str) -> str: css = Scss().compile(source).strip() - return u('').format(css=css) + return ''.format(css=css) -def stylus_to_css(source): +def stylus_to_css(source: str) -> str: compiler = Stylus() - return u('').format(css=compiler.compile(source).strip()) + return ''.format(css=compiler.compile(source).strip()) diff --git a/plim/lexer.py b/plim/lexer.py index 7cf7c71..b05e28b 100644 --- a/plim/lexer.py +++ b/plim/lexer.py @@ -558,7 +558,7 @@ def extract_line_break(tail, source): return found, tail, source -def extract_statement_expression(tail, source): +def extract_statement_expression(tail: str, source: str) -> Tuple[str, str]: """ :param tail: @@ -961,7 +961,7 @@ def parse_python_new_style(indent_level, __, matched, source, syntax): -def parse_mako_text(indent, __, matched, source, syntax): +def parse_mako_text(indent, __, matched, source, syntax) -> Tuple[str, int, str, str]: """ :param indent: @@ -1060,7 +1060,7 @@ def parse_comment(indent_level, __, ___, source, syntax): return '', 0, '', source -def parse_statements(indent_level, __, matched, source, syntax): +def parse_statements(indent_level, __, matched, source, syntax) -> Tuple[str, int, str, str]: """ :param indent_level: @@ -1237,7 +1237,7 @@ def parse_foreign_statements(indent_level, __, matched, source, syntax): return parse_statements(indent_level, __, matched, source, syntax) -def parse_explicit_literal(indent_level, current_line, ___, source, syntax, parse_embedded): +def parse_explicit_literal(indent_level, current_line, ___, source, syntax, parse_embedded) -> Tuple[str, int, str, str]: """ Parses lines and blocks started with the "|" (pipe) or "," (comma) character. @@ -1275,7 +1275,7 @@ def prepare_result(buf): except StopIteration: break indent, line = scan_line(current_line) - if not line: + if not line or indent is None: buf.append('\n') continue if indent <= indent_level: @@ -1351,7 +1351,7 @@ def _inject_n_filter(line: str) -> str: return line -def parse_variable(indent_level, __, matched, source, syntax): +def parse_variable(indent_level, __, matched, source, syntax) -> Tuple[str, int, str, str]: """ = variable or == variable :param indent_level: @@ -1389,7 +1389,7 @@ def parse_variable(indent_level, __, matched, source, syntax): return buf_str, 0, '', source -def parse_early_return(indent_level, __, matched, source, syntax): +def parse_early_return(indent_level, __, matched, source, syntax) -> Tuple[str, int, str, str]: """ :param indent_level: @@ -1403,7 +1403,7 @@ def parse_early_return(indent_level, __, matched, source, syntax): return u('\n<% {keyword} %>\n').format(keyword=matched.group('keyword')), indent_level, '', source -def parse_implicit_literal(indent_level, __, matched, source, syntax): +def parse_implicit_literal(indent_level, __, matched, source, syntax) -> Tuple[str, int, str, str]: """ :param indent_level: @@ -1423,7 +1423,7 @@ def parse_implicit_literal(indent_level, __, matched, source, syntax): ) -def parse_raw_html(indent_level, current_line, ___, source, syntax): +def parse_raw_html(indent_level, current_line, ___, source, syntax) -> Tuple[str, int, str, str]: """ :param indent_level: @@ -1457,7 +1457,7 @@ def parse_raw_html(indent_level, current_line, ___, source, syntax): return joined(buf), 0, '', source -def parse_mako_one_liners(indent_level, __, matched, source, syntax): +def parse_mako_one_liners(indent_level, __, matched, source, syntax) -> Tuple[str, int, str, str]: """ :param indent_level: @@ -1478,7 +1478,7 @@ def parse_mako_one_liners(indent_level, __, matched, source, syntax): return joined(buf), indent_level, '', source -def parse_def_block(indent_level, __, matched, source, syntax): +def parse_def_block(indent_level: int, __, matched, source: str, syntax) -> Tuple[str, int, str, str]: """ :param indent_level: @@ -1504,7 +1504,7 @@ def parse_def_block(indent_level, __, matched, source, syntax): except StopIteration: break tail_indent, tail_line = scan_line(tail_line) - if not tail_line: + if not tail_line or tail_indent is None: continue # Parse a tree # -------------------------------------------------------- @@ -1546,7 +1546,7 @@ def parse_plim_tail(lineno, indent_level, tail_line, source, syntax): # Miscellaneous utilities # ================================================================================== -def enumerate_source(source): +def enumerate_source(source: str) -> Iterator[Tuple[int, str]]: """ :param source: @@ -1564,7 +1564,7 @@ def scan_line(line: str) -> Tuple[Optional[int], Optional[str]]: return None, None -def compile_plim_source(source, syntax, strip=True): +def compile_plim_source(source: str, syntax: Any, strip=True) -> str: """ :param source: diff --git a/plim/syntax.py b/plim/syntax.py index 3ff1b08..4307f5a 100644 --- a/plim/syntax.py +++ b/plim/syntax.py @@ -1,4 +1,7 @@ import re +from typing import Sequence, Any + +from pyrsistent import v, pvector from . import lexer as l @@ -51,13 +54,15 @@ class BaseSyntax(object): PARSE_ELIF_ELSE_RE = re.compile('-\s*(?Pelif|else)(?P.*)') PARSE_EXCEPT_ELSE_FINALLY_RE = re.compile('-\s*(?Pexcept|else|finally)(?P.*)') - def __init__(self, custom_parsers=None): + def __init__(self, custom_parsers: Sequence[Any] = v()): """ :param custom_parsers: a list of 2-tuples of (parser_regex, parser_callable) or None :type custom_parsers: list or None """ - if custom_parsers is None: - custom_parsers = [] + if not custom_parsers: + custom_parsers = v() + else: + custom_parsers = pvector(custom_parsers) # We initialize standard parsers here rather than in a class' scope, because # we would like to be able to discard parsers in some syntax implementations by @@ -83,16 +88,16 @@ def __init__(self, custom_parsers=None): (self.PARSE_EARLY_RETURN_RE, l.parse_early_return), (self.PARSE_EXTENSION_LANGUAGES_RE, l.parse_markup_languages) ) - custom_parsers.extend(standard_parsers) + custom_parsers = custom_parsers.extend(standard_parsers) # discard parsers with None pattern self.parsers = tuple([p for p in custom_parsers if p[0]]) - def __str__(self): + def __str__(self) -> str: return 'Base Syntax' class Mako(BaseSyntax): - def __str__(self): + def __str__(self) -> str: return 'Mako Syntax' From f67be69a2ce4403e8609f0665536d70e2f88c66d Mon Sep 17 00:00:00 2001 From: Max Date: Sat, 17 Sep 2022 02:45:14 +0300 Subject: [PATCH 6/6] more hints --- plim/lexer.py | 54 +++++++++++++++++++++++++++------------------------ 1 file changed, 29 insertions(+), 25 deletions(-) diff --git a/plim/lexer.py b/plim/lexer.py index b05e28b..406d0ad 100644 --- a/plim/lexer.py +++ b/plim/lexer.py @@ -2,7 +2,7 @@ """Plim lexer""" import functools import re -from typing import Optional, Tuple, Any, Mapping, Callable, Iterator +from typing import Optional, Tuple, Any, Mapping, Callable, Iterator, Sequence import markdown2 from pyrsistent import v @@ -175,6 +175,9 @@ # Searchers # ================================================================================== +SourceIter = Iterator[tuple[int, str]] +Parsed = Tuple[str, int, str, str] + def search_quotes(line: str, escape_char: str = '\\', quotes_re = QUOTES_RE) -> Optional[int]: """ :param line: may be empty @@ -255,7 +258,7 @@ def extract_embedding_quotes(content) -> Optional[Tuple[Any, Any, Any]]: def _extract_braces_expression( - line: str, source: Iterator[Tuple[Any, str]], starting_braces_re, open_braces_re, closing_braces_re + line: str, source: SourceIter, starting_braces_re, open_braces_re, closing_braces_re ) -> Optional[Tuple[Any, Any, Any]]: """ @@ -323,16 +326,16 @@ def _extract_braces_expression( ) -def extract_identifier(line, source, identifier_start='#', terminators=('.', ' ', CLOSE_BRACE, INLINE_TAG_SEPARATOR)): +def extract_identifier(line: str, source, identifier_start: str = '#', + terminators: Sequence[str] = v('.', ' ', CLOSE_BRACE, INLINE_TAG_SEPARATOR) + ) -> Optional[Tuple[str, str, Any]]: """ :param line: Current line. It may be empty. :type line: str or unicode :param source: - :type source: str :param identifier_start: :param terminators: - :type terminators: tuple or set """ if not line or not line.startswith(identifier_start): return None @@ -536,7 +539,7 @@ def extract_tag_attribute(line: str, source: str, syntax, inside_parentheses=Fal return None -def extract_line_break(tail, source): +def extract_line_break(tail, source: SourceIter): """ Checks the first character of the tail. @@ -558,7 +561,7 @@ def extract_line_break(tail, source): return found, tail, source -def extract_statement_expression(tail: str, source: str) -> Tuple[str, str]: +def extract_statement_expression(tail: str, source: SourceIter) -> Tuple[str, str]: """ :param tail: @@ -806,7 +809,7 @@ def parse_doctype(indent_level, current_line, ___, source, syntax): return DOCTYPES.get(doctype, DOCTYPES['5']), indent_level, '', source -def parse_handlebars(indent_level, current_line, ___, source, syntax): +def parse_handlebars(indent_level: int, current_line, ___, source: SourceIter, syntax) -> Parsed: """ :param indent_level: @@ -829,7 +832,7 @@ def parse_handlebars(indent_level, current_line, ___, source, syntax): return processed_tag, tail_indent, tail_line, source -def parse_tag_tree(indent_level, current_line, ___, source, syntax): +def parse_tag_tree(indent_level: int, current_line: str, ___: Any, source: SourceIter, syntax) -> Parsed: """ :param indent_level: @@ -961,7 +964,7 @@ def parse_python_new_style(indent_level, __, matched, source, syntax): -def parse_mako_text(indent, __, matched, source, syntax) -> Tuple[str, int, str, str]: +def parse_mako_text(indent, __, matched, source, syntax) -> Parsed: """ :param indent: @@ -1060,7 +1063,7 @@ def parse_comment(indent_level, __, ___, source, syntax): return '', 0, '', source -def parse_statements(indent_level, __, matched, source, syntax) -> Tuple[str, int, str, str]: +def parse_statements(indent_level: int, __: Any, matched, source: SourceIter, syntax) -> Parsed: """ :param indent_level: @@ -1095,7 +1098,9 @@ def parse_statements(indent_level, __, matched, source, syntax) -> Tuple[str, in else: tail_indent, tail_line = scan_line(tail_line) - def complete_statement(buf, tail_indent, tail_line, source, statement, syntax): + def complete_statement( + buf: list[str], tail_indent: int, tail_line: str, source, statement: str, syntax + ) -> Parsed: buf.extend([ '\n', syntax.STATEMENT_END_START_SEQUENCE, @@ -1216,7 +1221,7 @@ def complete_statement(buf, tail_indent, tail_line, source, statement, syntax): -def parse_foreign_statements(indent_level, __, matched, source, syntax): +def parse_foreign_statements(indent_level: int, __: Any, matched, source: SourceIter, syntax): """ :param indent_level: @@ -1237,7 +1242,7 @@ def parse_foreign_statements(indent_level, __, matched, source, syntax): return parse_statements(indent_level, __, matched, source, syntax) -def parse_explicit_literal(indent_level, current_line, ___, source, syntax, parse_embedded) -> Tuple[str, int, str, str]: +def parse_explicit_literal(indent_level, current_line, ___, source, syntax, parse_embedded) -> Parsed: """ Parses lines and blocks started with the "|" (pipe) or "," (comma) character. @@ -1254,7 +1259,7 @@ def parse_explicit_literal(indent_level, current_line, ___, source, syntax, pars trailing_space_required = current_line[0] == LITERAL_CONTENT_SPACE_PREFIX # --------------------------------- - def prepare_result(buf): + def prepare_result(buf: Sequence[str]) -> str: result = joined(buf).rstrip() if trailing_space_required: result = u("{} ").format(result) @@ -1296,14 +1301,13 @@ def prepare_result(buf): parse_explicit_literal_no_embedded = functools.partial(parse_explicit_literal, parse_embedded=False) -def _parse_embedded_markup(content, syntax): +def _parse_embedded_markup(content: str, syntax) -> str: """ :param content: :param syntax: an instance of one of :class:`plim.syntax.BaseSyntax` children. :type syntax: :class:`plim.syntax.BaseSyntax` :return: - :rtype: str """ buf = [] tail = content @@ -1351,7 +1355,7 @@ def _inject_n_filter(line: str) -> str: return line -def parse_variable(indent_level, __, matched, source, syntax) -> Tuple[str, int, str, str]: +def parse_variable(indent_level: int, __, matched, source: SourceIter, syntax) -> Parsed: """ = variable or == variable :param indent_level: @@ -1389,7 +1393,7 @@ def parse_variable(indent_level, __, matched, source, syntax) -> Tuple[str, int, return buf_str, 0, '', source -def parse_early_return(indent_level, __, matched, source, syntax) -> Tuple[str, int, str, str]: +def parse_early_return(indent_level, __, matched, source, syntax) -> Parsed: """ :param indent_level: @@ -1403,7 +1407,7 @@ def parse_early_return(indent_level, __, matched, source, syntax) -> Tuple[str, return u('\n<% {keyword} %>\n').format(keyword=matched.group('keyword')), indent_level, '', source -def parse_implicit_literal(indent_level, __, matched, source, syntax) -> Tuple[str, int, str, str]: +def parse_implicit_literal(indent_level, __, matched, source: SourceIter, syntax) -> Parsed: """ :param indent_level: @@ -1423,7 +1427,7 @@ def parse_implicit_literal(indent_level, __, matched, source, syntax) -> Tuple[s ) -def parse_raw_html(indent_level, current_line, ___, source, syntax) -> Tuple[str, int, str, str]: +def parse_raw_html(indent_level, current_line, ___, source, syntax) -> Parsed: """ :param indent_level: @@ -1457,7 +1461,7 @@ def parse_raw_html(indent_level, current_line, ___, source, syntax) -> Tuple[str return joined(buf), 0, '', source -def parse_mako_one_liners(indent_level, __, matched, source, syntax) -> Tuple[str, int, str, str]: +def parse_mako_one_liners(indent_level, __, matched, source, syntax) -> Parsed: """ :param indent_level: @@ -1478,7 +1482,7 @@ def parse_mako_one_liners(indent_level, __, matched, source, syntax) -> Tuple[st return joined(buf), indent_level, '', source -def parse_def_block(indent_level: int, __, matched, source: str, syntax) -> Tuple[str, int, str, str]: +def parse_def_block(indent_level: int, __, matched, source: str, syntax) -> Parsed: """ :param indent_level: @@ -1522,7 +1526,7 @@ def parse_def_block(indent_level: int, __, matched, source: str, syntax) -> Tupl return joined(buf), 0, '', source -def parse_plim_tail(lineno, indent_level, tail_line, source, syntax): +def parse_plim_tail(lineno: int, indent_level, tail_line, source: SourceIter, syntax) -> Parsed: """ :param lineno: @@ -1546,7 +1550,7 @@ def parse_plim_tail(lineno, indent_level, tail_line, source, syntax): # Miscellaneous utilities # ================================================================================== -def enumerate_source(source: str) -> Iterator[Tuple[int, str]]: +def enumerate_source(source: str) -> SourceIter: """ :param source: