Skip to content

Commit

Permalink
Merge 52df57e into b454f14
Browse files Browse the repository at this point in the history
  • Loading branch information
avanov committed Sep 16, 2022
2 parents b454f14 + 52df57e commit f8f53b5
Show file tree
Hide file tree
Showing 11 changed files with 83 additions and 71 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Expand Up @@ -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:
Expand Down
2 changes: 1 addition & 1 deletion Makefile
Expand Up @@ -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
Expand Down
6 changes: 3 additions & 3 deletions 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,
}
Expand Down
2 changes: 1 addition & 1 deletion plim/console.py
Expand Up @@ -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
# ----------------------------
Expand Down
7 changes: 3 additions & 4 deletions plim/errors.py
Expand Up @@ -4,8 +4,7 @@


class PlimError(Exception):
def __str__(self):
return self.__unicode__().encode('utf-8')
pass


class PlimSyntaxError(PlimError):
Expand All @@ -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)


Expand All @@ -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)
110 changes: 54 additions & 56 deletions plim/lexer.py
Expand Up @@ -2,8 +2,10 @@
"""Plim lexer"""
import functools
import re
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
Expand Down Expand Up @@ -61,35 +63,35 @@

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,
LITERAL_CONTENT_PREFIX,
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,
Expand All @@ -98,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,
Expand Down Expand Up @@ -173,11 +175,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)
Expand All @@ -198,7 +198,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:
Expand All @@ -214,7 +214,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
Expand All @@ -239,28 +239,28 @@ def extract_embedding_quotes(content):
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, source, starting_braces_re, open_braces_re, closing_braces_re):
def _extract_braces_expression(
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
:type line: str
:param source:
:type source: str
:param starting_braces_re:
:param open_braces_re:
:param closing_braces_re:
Expand Down Expand Up @@ -353,10 +353,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
Expand Down Expand Up @@ -420,7 +420,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:
Expand Down Expand Up @@ -463,8 +463,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:
Expand Down Expand Up @@ -676,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:
Expand Down Expand Up @@ -1287,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
Expand Down Expand Up @@ -1333,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
Expand Down Expand Up @@ -1375,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):
Expand Down Expand Up @@ -1556,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):
Expand Down Expand Up @@ -1595,18 +1593,18 @@ 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
# ============================================================================================

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,
Expand All @@ -1617,8 +1615,8 @@ def compile_plim_source(source, syntax, strip=True):
'stylus': stylus_to_css
}

DOCTYPES = {
'html':'<!DOCTYPE html>',
DOCTYPES: Mapping[str, str] = {
'html': '<!DOCTYPE html>',
'5': '<!DOCTYPE html>',
'1.1': '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">',
'strict': '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">',
Expand Down
4 changes: 2 additions & 2 deletions plim/syntax.py
Expand Up @@ -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'
15 changes: 13 additions & 2 deletions plim/util.py
@@ -1,10 +1,21 @@
import sys
from typing import Sequence, Iterable

PY3K = sys.version_info >= (3, 0)

from io import StringIO

joined = lambda buf: ''.join(buf)
space_separated = lambda buf: ' '.join(buf)

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)


def space_separated(buf: Sequence[str]) -> str:
return joined(buf, ' ')


u = str
MAXSIZE = sys.maxsize

0 comments on commit f8f53b5

Please sign in to comment.