Skip to content

Commit

Permalink
translate encodeHTMLSource (#11)
Browse files Browse the repository at this point in the history
* translate encodeHTMLSource

* porting 1.1.1

* update test case

* comment print

* use Union
  • Loading branch information
David Chen authored May 3, 2023
1 parent 1eae420 commit 6d3bfe4
Show file tree
Hide file tree
Showing 14 changed files with 125 additions and 126 deletions.
3 changes: 1 addition & 2 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,7 @@ python = ">=3.8.1,<4.0.0"

[tool.poetry.group.test.dependencies]
pytest = "^7.3.1"
syrupy = "^4.0.2"

syrupy = "<4.0.0"

[tool.poetry.group.dev.dependencies]
pre-commit = "^3.3.1"
Expand Down
159 changes: 97 additions & 62 deletions src/doT/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@
# translate doT.js

import re
from typing import NamedTuple
from typing import Callable, NamedTuple, Union

version = "1.0.0"
version = "1.1.1"


class TemplateSettings(NamedTuple):
Expand All @@ -23,31 +23,63 @@ class TemplateSettings(NamedTuple):
strip: bool = True
append: bool = True
selfcontained: bool = False
doNotSkipEncoded: bool = False


template_settings: TemplateSettings = TemplateSettings()


def replace(
original_str: str, pattern: str, repl_func: Union[Callable[[re.Match], str], str]
) -> str:
return re.sub(pattern, repl_func, original_str)


def encodeHTMLsource(do_not_skip_encoded: bool) -> Callable[[str], str]:
encode_HTML_rules = {
"&": "&#38;",
"<": "&#60;",
">": "&#62;",
'"': "&#34;",
"'": "&#39;",
"/": "&#47;",
}
match_HTML = (
re.compile(r'[&<>"\'\/]')
if do_not_skip_encoded
else re.compile(r'&(?!#?\w+;)|<|>|"|\'|\/')
)

def encode(code: str) -> str:
return (
match_HTML.sub(
lambda m: encode_HTML_rules.get(m.group(0), m.group(0)), str(code)
)
if code
else ""
)

return encode


class Symbol(NamedTuple):
start: str
end: str
endencode: str
startencode: str


class StartEnd(NamedTuple):
append: Symbol = Symbol(
start="'+(", end=")+'", endencode="||'').toString().encodeHTML()+'"
)
append: Symbol = Symbol(start="'+(", end=")+'", startencode="'+encodeHTML(")
split: Symbol = Symbol(
start="';out+=(",
end=");out+='",
endencode="||'').toString().encodeHTML();out+='",
startencode="';out+=encodeHTML(",
)


startend: StartEnd = StartEnd()

skip = "$^"
skip = r"$^"


def resolveDefs(c, tmpl: str, _def) -> str:
Expand All @@ -64,19 +96,44 @@ def unescape(code: str) -> str:

def template(tmpl: str, c: TemplateSettings = None, _def=None):
c = c or template_settings
# needhtmlencode = None
cse = startend.append if c.append else startend.split
needhtmlencode = None
sid = 0
# indv = None
indv = None

cse = startend.append if c.append else startend.split
_str = resolveDefs(c, tmpl, _def or {}) if (c.use or c.define) else tmpl
# print(f"resolveDefs: {_str}")

if c.strip:
# remove white space
_str = re.sub(r"(^|\r|\n)\t* +| +\t*(\r|\n|$)", " ", _str)
_str = re.sub(r"\r|\n|\t|\/\*[\s\S]*?\*\/", "", _str)

# print(f"strip: {_str}")
_str = replace(_str, r"['\\]", r"\\\g<0>")

# print(f"replace: {_str}")

def _interpolate(match: re.Match) -> str:
code = match.groups()[0]
return cse.start + unescape(code) + cse.end

_str = replace(_str, c.interpolate or skip, _interpolate)
# print(f"interpolate: {_str}")

def _interpolate(code: str) -> str:
def _encode(match: re.Match) -> str:
nonlocal needhtmlencode
needhtmlencode = True
code = match.groups()[0]
return cse.start + unescape(code) + cse.end

def _encode(code: str) -> str:
return cse.start + unescape(code) + cse.endencode
_str = replace(_str, c.encode or skip, _encode)
# print(f"encode: {_str}")

def _conditional(match: re.Match) -> str:
elsecode = match.groups()[0]
code = match.groups()[1]

def _conditional(elsecode: str, code: str) -> str:
if elsecode:
if code:
return "';}else if(" + unescape(code) + "){out+='"
Expand All @@ -88,17 +145,23 @@ def _conditional(elsecode: str, code: str) -> str:
else:
return "';}out+='"

def _iterate(iterate, vname, iname, sid=sid):
if not iterate or not vname:
_str = replace(_str, c.conditional or skip, _conditional)
# print(f"conditional: {_str}")

def _iterate(match: re.Match) -> str:
iterate = match.groups()[0]
vname = match.groups()[1]
iname = match.groups()[2]
nonlocal sid, indv

if not iterate:
return "';} } out+='"

sid += 1
indv = iname or "i" + str(sid)
iterate = unescape(iterate)

_sid = str(sid)
# print iterate, vname, iname, _sid

return (
"';var arr"
+ _sid
Expand Down Expand Up @@ -127,52 +190,24 @@ def _iterate(iterate, vname, iname, sid=sid):
+ "+=1];out+='"
)

def _evalute(code: str) -> str:
return "';" + unescape(code) + "out+='"
_str = replace(_str, c.iterate or skip, _iterate)
# print(f"iterate: {_str}")

_str = resolveDefs(c, tmpl, _def or {}) if (c.use or c.define) else tmpl

if c.strip:
# remove white space
_str = re.sub(r"(^|\r|\n)\t* +| +\t*(\r|\n|$)", " ", _str)
_str = re.sub(r"\r|\n|\t|\/\*[\s\S]*?\*\/", "", _str)

# _str = re.sub(r"|\\", '\\$&', _str)

if c.interpolate:
_str = re.sub(c.interpolate, lambda i: _interpolate(i.groups()[0]), _str)

if c.encode:
_str = re.sub(c.encode, lambda i: _encode(i.groups()[0]), _str)
def _evalute(match: re.Match) -> str:
code = match.groups()[0]
return "';" + unescape(code) + "out+='"

if c.conditional:
_str = re.sub(
c.conditional, lambda i: _conditional(i.groups()[0], i.groups()[1]), _str
)
_str = replace(_str, c.evaluate or skip, _evalute)
# print(f"evaluate: {_str}")

if c.iterate:
_str = re.sub(
c.iterate,
lambda i: _iterate(i.groups()[0], i.groups()[1], i.groups()[2]),
_str,
)
_str = replace(_str, r"\n", "\\n")
_str = replace(_str, r"\t", "\\t")
_str = replace(_str, r"\r", "\\r")
_str = replace(_str, r"(\s|;|\}|^|\{)out\+='';", r"\1")
_str = replace(_str, r"\+''", "")

if c.evaluate:
_str = re.sub(c.evaluate, lambda i: _evalute(i.groups()[0]), _str)

# HINT:
""".replace(/\n/g, '\\n').replace(/\t/g, '\\t').replace(/\r/g, '\\r')
.replace(/(\s|;|\}|^|\{)out\+='';/g, '$1').replace(/\+''/g, '')
.replace(/(\s|;|\}|^|\{)out\+=''\+/g,'$1out+=');
if (needhtmlencode && c.selfcontained) {
str = "String.prototype.encodeHTML=(" + encodeHTMLSource.toString() + "());" + str;
}
try {
return new Function(c.varname, str);
} catch (e) {
if (typeof console !== 'undefined') console.log("Could not create a template function: " + str);
throw e;
}"""
# print(f"final: {_str}")
# if (needhtmlencode):
# if (c.selfcontained or c.doNotSkipEncoded):

return "function anonymous(" + c.varname + ") {var out='" + _str + "';return out;}"
52 changes: 0 additions & 52 deletions src/doT/tests/__snapshots__/test_dot.ambr

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
"function anonymous(it) {var out='';var arr1=it.array;if(arr1){var value,index=-1,l1=arr1.length-1;while(index<l1){value=arr1[index+=1];out+='\n<div>'+(value)+'!</div>\n';} } out+='';return out;}"
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
"function anonymous(it) {var out='';if(it.name){out+='\n<div>Oh, I love your name, '+(it.name)+'!</div>\n';}else if(it.age === 0){out+='\n<div>Guess nobody named you yet!</div>\n';}else{out+='\nYou are '+(it.age)+' and still don\\'t have a name?\n';}out+='';return out;}"
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
"function anonymous(it) {var out='Visit '+(it.uri)+'';return out;}"
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
"function anonymous(it) {var out=''; for(var prop in it) { out+='\n<div>'+(prop)+'</div>\n'; } out+='';return out;}"
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
"function anonymous(it) {var out='<div>Hi '+(it.name)+'!</div>\n<div>'+(it.age || '')+'</div>';return out;}"
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
"function anonymous(it) {var out='';if(it.options == 1){out+='Option 1';}else if(it.options == 2){out+='Option 2';}else{out+='Other option';}out+=':\n\nMore text';return out;}"
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
"function anonymous(it) {var out='';##def.snippet: <div>'+(it.name)+'</div>{{#def.jokeout+='\n#}}\n\n';#def.snippetout+='';return out;}"
7 changes: 7 additions & 0 deletions src/doT/tests/conftest.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import pytest
from syrupy.extensions.json import JSONSnapshotExtension


@pytest.fixture
def snapshot_json(snapshot):
return snapshot.use_extension(JSONSnapshotExtension)
10 changes: 8 additions & 2 deletions src/doT/tests/test_dot.py
Original file line number Diff line number Diff line change
@@ -1,16 +1,22 @@
from pathlib import Path

import pytest
from syrupy.extensions.json import JSONSnapshotExtension

from .. import template, template_settings


@pytest.fixture
def snapshot_json(snapshot):
return snapshot.use_extension(JSONSnapshotExtension)


@pytest.mark.parametrize(
"case", (Path(__file__).parent / "test_dot").glob("*.tmpl"), ids=lambda x: x.name
)
def test_template(snapshot, case: Path):
def test_template(snapshot_json, case: Path):
setting = template_settings._replace(strip=False)

with case.open("r", encoding="utf-8") as ifile:
_template = ifile.read()
assert snapshot == template(_template, setting)
assert snapshot_json == template(_template, setting)
3 changes: 3 additions & 0 deletions src/doT/tests/test_dot/evaluation.tmpl
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{{ for(var prop in it) { }}
<div>{{=prop}}</div>
{{ } }}
10 changes: 2 additions & 8 deletions src/doT/tests/test_dot/interpolation.tmpl
Original file line number Diff line number Diff line change
@@ -1,8 +1,2 @@
<div>Hi {
{=it.name
}
}!</div>
<div>{
{=it.age || ''
}
}</div>
<div>Hi {{=it.name}}!</div>
<div>{{=it.age || ''}}</div>

0 comments on commit 6d3bfe4

Please sign in to comment.