ssc_codegen 0.8.16__tar.gz → 0.8.17__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of ssc_codegen might be problematic. Click here for more details.
- {ssc_codegen-0.8.16 → ssc_codegen-0.8.17}/PKG-INFO +1 -1
- {ssc_codegen-0.8.16 → ssc_codegen-0.8.17}/pyproject.toml +1 -1
- {ssc_codegen-0.8.16 → ssc_codegen-0.8.17}/ssc_codegen/__init__.py +7 -2
- {ssc_codegen-0.8.16 → ssc_codegen-0.8.17}/ssc_codegen/ast_/__init__.py +2 -1
- ssc_codegen-0.8.17/ssc_codegen/ast_/nodes_filter.py +110 -0
- {ssc_codegen-0.8.16 → ssc_codegen-0.8.17}/ssc_codegen/ast_build/main.py +19 -1
- {ssc_codegen-0.8.16 → ssc_codegen-0.8.17}/ssc_codegen/converters/base.py +31 -6
- {ssc_codegen-0.8.16 → ssc_codegen-0.8.17}/ssc_codegen/converters/go_goquery.py +1 -0
- {ssc_codegen-0.8.16 → ssc_codegen-0.8.17}/ssc_codegen/converters/helpers.py +9 -0
- {ssc_codegen-0.8.16 → ssc_codegen-0.8.17}/ssc_codegen/converters/js_pure.py +124 -6
- {ssc_codegen-0.8.16 → ssc_codegen-0.8.17}/ssc_codegen/converters/py_base.py +121 -0
- {ssc_codegen-0.8.16 → ssc_codegen-0.8.17}/ssc_codegen/document.py +99 -0
- {ssc_codegen-0.8.16 → ssc_codegen-0.8.17}/ssc_codegen/str_utils.py +4 -1
- {ssc_codegen-0.8.16 → ssc_codegen-0.8.17}/ssc_codegen/tokens.py +15 -0
- {ssc_codegen-0.8.16 → ssc_codegen-0.8.17}/.gitignore +0 -0
- {ssc_codegen-0.8.16 → ssc_codegen-0.8.17}/LICENSE +0 -0
- {ssc_codegen-0.8.16 → ssc_codegen-0.8.17}/README.md +0 -0
- {ssc_codegen-0.8.16 → ssc_codegen-0.8.17}/ssc_codegen/_compat.py +0 -0
- {ssc_codegen-0.8.16 → ssc_codegen-0.8.17}/ssc_codegen/ast_/base.py +0 -0
- {ssc_codegen-0.8.16 → ssc_codegen-0.8.17}/ssc_codegen/ast_/nodes_array.py +0 -0
- {ssc_codegen-0.8.16 → ssc_codegen-0.8.17}/ssc_codegen/ast_/nodes_cast.py +0 -0
- {ssc_codegen-0.8.16 → ssc_codegen-0.8.17}/ssc_codegen/ast_/nodes_core.py +0 -0
- {ssc_codegen-0.8.16 → ssc_codegen-0.8.17}/ssc_codegen/ast_/nodes_selectors.py +0 -0
- {ssc_codegen-0.8.16 → ssc_codegen-0.8.17}/ssc_codegen/ast_/nodes_string.py +0 -0
- {ssc_codegen-0.8.16 → ssc_codegen-0.8.17}/ssc_codegen/ast_/nodes_validate.py +0 -0
- {ssc_codegen-0.8.16 → ssc_codegen-0.8.17}/ssc_codegen/ast_build/__init__.py +0 -0
- {ssc_codegen-0.8.16 → ssc_codegen-0.8.17}/ssc_codegen/ast_build/utils.py +0 -0
- {ssc_codegen-0.8.16 → ssc_codegen-0.8.17}/ssc_codegen/cli/__init__.py +0 -0
- {ssc_codegen-0.8.16 → ssc_codegen-0.8.17}/ssc_codegen/cli/cli_callbacks.py +0 -0
- {ssc_codegen-0.8.16 → ssc_codegen-0.8.17}/ssc_codegen/cli/cli_utils.py +0 -0
- {ssc_codegen-0.8.16 → ssc_codegen-0.8.17}/ssc_codegen/cli/code_callbacks.py +0 -0
- {ssc_codegen-0.8.16 → ssc_codegen-0.8.17}/ssc_codegen/cli/consts.py +0 -0
- {ssc_codegen-0.8.16 → ssc_codegen-0.8.17}/ssc_codegen/cli/main.py +0 -0
- {ssc_codegen-0.8.16 → ssc_codegen-0.8.17}/ssc_codegen/cli/runtime_parse_runners.py +0 -0
- {ssc_codegen-0.8.16 → ssc_codegen-0.8.17}/ssc_codegen/compiler.py +0 -0
- {ssc_codegen-0.8.16 → ssc_codegen-0.8.17}/ssc_codegen/consts.py +0 -0
- {ssc_codegen-0.8.16 → ssc_codegen-0.8.17}/ssc_codegen/converters/__init__.py +0 -0
- {ssc_codegen-0.8.16 → ssc_codegen-0.8.17}/ssc_codegen/converters/py_bs4.py +0 -0
- {ssc_codegen-0.8.16 → ssc_codegen-0.8.17}/ssc_codegen/converters/py_parsel.py +0 -0
- {ssc_codegen-0.8.16 → ssc_codegen-0.8.17}/ssc_codegen/converters/py_selectolax.py +0 -0
- {ssc_codegen-0.8.16 → ssc_codegen-0.8.17}/ssc_codegen/converters/templates/__init__.py +0 -0
- {ssc_codegen-0.8.16 → ssc_codegen-0.8.17}/ssc_codegen/converters/templates/go_goquery.py +0 -0
- {ssc_codegen-0.8.16 → ssc_codegen-0.8.17}/ssc_codegen/converters/templates/js_pure.py +0 -0
- {ssc_codegen-0.8.16 → ssc_codegen-0.8.17}/ssc_codegen/document_utlis.py +0 -0
- {ssc_codegen-0.8.16 → ssc_codegen-0.8.17}/ssc_codegen/json_struct.py +0 -0
- {ssc_codegen-0.8.16 → ssc_codegen-0.8.17}/ssc_codegen/json_to_scc.py +0 -0
- {ssc_codegen-0.8.16 → ssc_codegen-0.8.17}/ssc_codegen/logs.py +0 -0
- {ssc_codegen-0.8.16 → ssc_codegen-0.8.17}/ssc_codegen/pseudo_selectors.py +0 -0
- {ssc_codegen-0.8.16 → ssc_codegen-0.8.17}/ssc_codegen/schema.py +0 -0
- {ssc_codegen-0.8.16 → ssc_codegen-0.8.17}/ssc_codegen/selector_utils.py +0 -0
- {ssc_codegen-0.8.16 → ssc_codegen-0.8.17}/ssc_codegen/static_checker/__init__.py +0 -0
- {ssc_codegen-0.8.16 → ssc_codegen-0.8.17}/ssc_codegen/static_checker/base.py +0 -0
- {ssc_codegen-0.8.16 → ssc_codegen-0.8.17}/ssc_codegen/static_checker/callbacks.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: ssc_codegen
|
|
3
|
-
Version: 0.8.
|
|
3
|
+
Version: 0.8.17
|
|
4
4
|
Summary: Python-dsl code converter to html parser for web scraping
|
|
5
5
|
Project-URL: Documentation, https://github.com/vypivshiy/selector_schema_codegen#readme
|
|
6
6
|
Project-URL: Issues, https://github.com/vypivshiy/selector_schema_codegen/issues
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
from ssc_codegen.document import HTMLDocument, StringDocument, ArrayDocument, AssertDocument, NestedDocument, \
|
|
2
2
|
DefaultDocument, \
|
|
3
|
-
NumericDocument, JsonDocument, BooleanDocument
|
|
3
|
+
NumericDocument, JsonDocument, BooleanDocument, DocumentFilter
|
|
4
4
|
from ssc_codegen.json_struct import Json
|
|
5
5
|
from ssc_codegen.logs import setup_logger
|
|
6
6
|
from ssc_codegen.schema import ItemSchema, DictSchema, ListSchema, FlatListSchema
|
|
7
7
|
|
|
8
8
|
setup_logger()
|
|
9
9
|
|
|
10
|
-
VERSION = "0.8.
|
|
10
|
+
VERSION = "0.8.17"
|
|
11
11
|
|
|
12
12
|
|
|
13
13
|
class __MISSING(object):
|
|
@@ -59,3 +59,8 @@ def R(default: None | str | int | float | list | __MISSING = _NO_DEFAULT) -> Doc
|
|
|
59
59
|
if default == _NO_DEFAULT:
|
|
60
60
|
return Document().raw()
|
|
61
61
|
return Document().default(default).raw() # type: ignore
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def F() -> DocumentFilter:
|
|
65
|
+
"""Shortcut as a DocumentFilter() object"""
|
|
66
|
+
return DocumentFilter()
|
|
@@ -20,9 +20,10 @@ from .nodes_core import (ExprReturn,
|
|
|
20
20
|
TypeDef,
|
|
21
21
|
TypeDefField,
|
|
22
22
|
ExprCallStructMethod)
|
|
23
|
+
from .nodes_filter import Filter, FilterOr, FilterAnd, FilterNot, FilterNotEqual, FilterEqual, FilterStrIn, \
|
|
24
|
+
FilterStrStarts, FilterStrEnds, FilterStrRe, ExprFilter
|
|
23
25
|
from .nodes_selectors import ExprCss, ExprCssAll, ExprXpathAll, ExprXpath, ExprGetHtmlText, ExprGetHtmlRaw, \
|
|
24
26
|
ExprGetHtmlAttr, ExprGetHtmlAttrAll, ExprGetHtmlRawAll, ExprGetHtmlTextAll
|
|
25
|
-
|
|
26
27
|
from .nodes_string import ExprStringTrim, ExprStringRegex, ExprStringSplit, ExprStringReplace, ExprStringLeftTrim, \
|
|
27
28
|
ExprStringFormat, ExprListStringTrim, ExprStringRegexAll, ExprStringRegexSub, ExprStringRightTrim, \
|
|
28
29
|
ExprListStringFormat, ExprListStringReplace, ExprListStringRightTrim, ExprListStringLeftTrim, \
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
from dataclasses import dataclass
|
|
2
|
+
from typing import TypedDict, ClassVar
|
|
3
|
+
|
|
4
|
+
from ssc_codegen.ast_.base import BaseAstNode, T_EMPTY_KWARGS
|
|
5
|
+
from ssc_codegen.tokens import TokenType, VariableType
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
# TODO: static analyzer for filter operations
|
|
9
|
+
# TODO: implement operators for DOCUMENT, INT, FLOAT
|
|
10
|
+
@dataclass(kw_only=True)
|
|
11
|
+
class Filter(BaseAstNode):
|
|
12
|
+
kind: ClassVar[TokenType] = TokenType.FILTER
|
|
13
|
+
accept_type: VariableType = VariableType.STRING
|
|
14
|
+
ret_type: VariableType = VariableType.STRING
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
@dataclass(kw_only=True)
|
|
18
|
+
class FilterOr(BaseAstNode[T_EMPTY_KWARGS, tuple]):
|
|
19
|
+
kind: ClassVar[TokenType] = TokenType.FILTER_OR
|
|
20
|
+
accept_type: VariableType = VariableType.STRING
|
|
21
|
+
ret_type: VariableType = VariableType.STRING
|
|
22
|
+
# TODO: typing body accept Filter-like nodes
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
@dataclass(kw_only=True)
|
|
26
|
+
class FilterAnd(BaseAstNode[T_EMPTY_KWARGS, tuple]):
|
|
27
|
+
kind: ClassVar[TokenType] = TokenType.FILTER_AND
|
|
28
|
+
accept_type: VariableType = VariableType.STRING
|
|
29
|
+
ret_type: VariableType = VariableType.STRING
|
|
30
|
+
# TODO: typing body accept Filter-like nodes
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
@dataclass(kw_only=True)
|
|
34
|
+
class FilterNot(BaseAstNode[T_EMPTY_KWARGS, tuple]):
|
|
35
|
+
kind: ClassVar[TokenType] = TokenType.FILTER_NOT
|
|
36
|
+
accept_type: VariableType = VariableType.STRING
|
|
37
|
+
ret_type: VariableType = VariableType.STRING
|
|
38
|
+
# TODO: typing body accept Filter-like nodes
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
KW_STR_IN = TypedDict("KW_STR_IN", {"substr": str})
|
|
42
|
+
ARGS_STR_IN = tuple[str]
|
|
43
|
+
|
|
44
|
+
KW_STR_STARTS_OR_ENDS = TypedDict(
|
|
45
|
+
"KW_STR_STARTS_OR_ENDS", {"substr": tuple[str, ...]}
|
|
46
|
+
)
|
|
47
|
+
ARGS_STR_STARTS_OR_ENDS = tuple[tuple[str, ...]]
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
@dataclass(kw_only=True)
|
|
51
|
+
class FilterStrIn(BaseAstNode[KW_STR_IN, ARGS_STR_IN]):
|
|
52
|
+
kind: ClassVar[TokenType] = TokenType.FILTER_STR_IN
|
|
53
|
+
accept_type: VariableType = VariableType.STRING
|
|
54
|
+
ret_type: VariableType = VariableType.STRING
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
@dataclass(kw_only=True)
|
|
58
|
+
class FilterStrStarts(
|
|
59
|
+
BaseAstNode[KW_STR_STARTS_OR_ENDS, ARGS_STR_STARTS_OR_ENDS]
|
|
60
|
+
):
|
|
61
|
+
kind: ClassVar[TokenType] = TokenType.FILTER_STR_STARTS
|
|
62
|
+
accept_type: VariableType = VariableType.STRING
|
|
63
|
+
ret_type: VariableType = VariableType.STRING
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
@dataclass(kw_only=True)
|
|
67
|
+
class FilterStrEnds(
|
|
68
|
+
BaseAstNode[KW_STR_STARTS_OR_ENDS, ARGS_STR_STARTS_OR_ENDS]
|
|
69
|
+
):
|
|
70
|
+
kind: ClassVar[TokenType] = TokenType.FILTER_STR_ENDS
|
|
71
|
+
accept_type: VariableType = VariableType.STRING
|
|
72
|
+
ret_type: VariableType = VariableType.STRING
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
KW_STR_RE = TypedDict("KW_STR_RE", {"pattern": str, "ignore_case": bool})
|
|
76
|
+
ARGS_STR_RE = tuple[str, bool]
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
@dataclass(kw_only=True)
|
|
80
|
+
class FilterStrRe(BaseAstNode[KW_STR_RE, ARGS_STR_RE]):
|
|
81
|
+
kind: ClassVar[TokenType] = TokenType.FILTER_STR_RE
|
|
82
|
+
accept_type: VariableType = VariableType.STRING
|
|
83
|
+
ret_type: VariableType = VariableType.STRING
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
# TODO: provide API for int, float, etc
|
|
87
|
+
KW_STR_EQ_OR_NE = TypedDict("KW_STR_EQ_OR_NE", {"value": str})
|
|
88
|
+
ARGS_STR_EQ_OR_NE = tuple[str]
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
@dataclass(kw_only=True)
|
|
92
|
+
class FilterEqual(BaseAstNode[KW_STR_EQ_OR_NE, ARGS_STR_EQ_OR_NE]):
|
|
93
|
+
kind: ClassVar[TokenType] = TokenType.FILTER_EQ
|
|
94
|
+
accept_type: VariableType = VariableType.STRING
|
|
95
|
+
ret_type: VariableType = VariableType.STRING
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
@dataclass(kw_only=True)
|
|
99
|
+
class FilterNotEqual(BaseAstNode[KW_STR_EQ_OR_NE, ARGS_STR_EQ_OR_NE]):
|
|
100
|
+
kind: ClassVar[TokenType] = TokenType.FILTER_EQ
|
|
101
|
+
accept_type: VariableType = VariableType.STRING
|
|
102
|
+
ret_type: VariableType = VariableType.STRING
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
@dataclass(kw_only=True)
|
|
106
|
+
class ExprFilter(BaseAstNode[T_EMPTY_KWARGS, tuple]):
|
|
107
|
+
kind: ClassVar[TokenType] = TokenType.EXPR_FILTER
|
|
108
|
+
accept_type: VariableType = VariableType.LIST_STRING
|
|
109
|
+
ret_type: VariableType = VariableType.LIST_STRING
|
|
110
|
+
# TODO: typing body accept Filter-like nodes
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import warnings
|
|
2
2
|
from pathlib import Path
|
|
3
|
-
from typing import Type, cast
|
|
3
|
+
from typing import Type, cast, MutableSequence
|
|
4
4
|
|
|
5
5
|
from ssc_codegen import Json
|
|
6
6
|
from ssc_codegen.ast_ import (
|
|
@@ -230,6 +230,15 @@ def build_ast_struct_parser(
|
|
|
230
230
|
return st
|
|
231
231
|
|
|
232
232
|
|
|
233
|
+
def set_parent_for_expr_filter(
|
|
234
|
+
parent: BaseAstNode, parent_body: MutableSequence[BaseAstNode]
|
|
235
|
+
) -> None:
|
|
236
|
+
for j in parent_body:
|
|
237
|
+
j.parent = parent
|
|
238
|
+
if j.body:
|
|
239
|
+
set_parent_for_expr_filter(j, j.body)
|
|
240
|
+
|
|
241
|
+
|
|
233
242
|
def _fetch_field_nodes(
|
|
234
243
|
body: list[BaseAstNode],
|
|
235
244
|
body_parse_method_expr: list[BaseAstNode],
|
|
@@ -263,8 +272,12 @@ def _fetch_field_nodes(
|
|
|
263
272
|
)
|
|
264
273
|
|
|
265
274
|
_unwrap_default_node(document, ret_type)
|
|
275
|
+
|
|
266
276
|
for i in document.stack:
|
|
267
277
|
i.parent = method
|
|
278
|
+
if i.kind == TokenType.EXPR_FILTER:
|
|
279
|
+
set_parent_for_expr_filter(i, i.body)
|
|
280
|
+
|
|
268
281
|
method.body = document.stack
|
|
269
282
|
|
|
270
283
|
body.append(method)
|
|
@@ -361,6 +374,9 @@ def _try_fetch_split_doc_node(
|
|
|
361
374
|
)
|
|
362
375
|
for i in split_doc.stack:
|
|
363
376
|
i.parent = method
|
|
377
|
+
if i.kind == TokenType.EXPR_FILTER:
|
|
378
|
+
set_parent_for_expr_filter(i, i.body)
|
|
379
|
+
|
|
364
380
|
method.body = split_doc.stack
|
|
365
381
|
body.append(method)
|
|
366
382
|
|
|
@@ -388,6 +404,8 @@ def _try_fetch_pre_validate_node(
|
|
|
388
404
|
validate_field.stack.append(ExprNoReturn())
|
|
389
405
|
for i in validate_field.stack:
|
|
390
406
|
i.parent = method
|
|
407
|
+
if i.kind == TokenType.EXPR_FILTER:
|
|
408
|
+
set_parent_for_expr_filter(i, i.body)
|
|
391
409
|
|
|
392
410
|
body.append(method)
|
|
393
411
|
call_methods_expr.append(
|
|
@@ -14,6 +14,9 @@ from ssc_codegen.ast_ import (
|
|
|
14
14
|
StructInitMethod,
|
|
15
15
|
StructPartDocMethod,
|
|
16
16
|
StructPreValidateMethod,
|
|
17
|
+
FilterOr,
|
|
18
|
+
FilterAnd,
|
|
19
|
+
FilterNot,
|
|
17
20
|
)
|
|
18
21
|
from ssc_codegen.tokens import TokenType
|
|
19
22
|
|
|
@@ -168,12 +171,22 @@ class BaseCodeConverter:
|
|
|
168
171
|
case TokenType.JSON_FIELD:
|
|
169
172
|
ast_entry = cast(JsonStructField, ast_entry)
|
|
170
173
|
self._convert_json_struct_field(ast_entry, acc)
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
TokenType.
|
|
174
|
-
TokenType.
|
|
175
|
-
TokenType.
|
|
176
|
-
|
|
174
|
+
|
|
175
|
+
case (
|
|
176
|
+
TokenType.EXPR_FILTER
|
|
177
|
+
| TokenType.FILTER_OR
|
|
178
|
+
| TokenType.FILTER_AND
|
|
179
|
+
| TokenType.FILTER_NOT
|
|
180
|
+
):
|
|
181
|
+
ast_entry = cast(FilterOr | FilterAnd | FilterNot, ast_entry)
|
|
182
|
+
self._convert_logic_filter(ast_entry, acc)
|
|
183
|
+
|
|
184
|
+
case (
|
|
185
|
+
TokenType.STRUCT_PART_DOCUMENT
|
|
186
|
+
| TokenType.STRUCT_PRE_VALIDATE
|
|
187
|
+
| TokenType.STRUCT_PARSE_START
|
|
188
|
+
| TokenType.STRUCT_FIELD
|
|
189
|
+
):
|
|
177
190
|
ast_entry = cast(
|
|
178
191
|
StructFieldMethod
|
|
179
192
|
| StructInitMethod
|
|
@@ -186,6 +199,18 @@ class BaseCodeConverter:
|
|
|
186
199
|
self._convert_default(ast_entry, acc)
|
|
187
200
|
return acc
|
|
188
201
|
|
|
202
|
+
def _convert_logic_filter(
|
|
203
|
+
self, ast_entry: FilterOr | FilterAnd | FilterNot, acc: list[str]
|
|
204
|
+
) -> None:
|
|
205
|
+
# AND(body=[F1, F2]) ->
|
|
206
|
+
# AND ( (OPEN)
|
|
207
|
+
# F1 and F2
|
|
208
|
+
# ) (CLOSE)
|
|
209
|
+
acc.append(self._pre_convert_node(ast_entry))
|
|
210
|
+
for node in ast_entry.body:
|
|
211
|
+
self.convert(node, acc)
|
|
212
|
+
acc.append(self._post_convert_node(ast_entry))
|
|
213
|
+
|
|
189
214
|
def _convert_module(self, ast_entry: ModuleProgram, acc: list[str]) -> None:
|
|
190
215
|
"""Handle module conversion."""
|
|
191
216
|
for node in ast_entry.body:
|
|
@@ -47,3 +47,12 @@ def get_struct_field_method_by_name(
|
|
|
47
47
|
][0]
|
|
48
48
|
result = cast(StructFieldMethod, result)
|
|
49
49
|
return result
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def is_prev_node_atomic_cond(node: BaseAstNode) -> bool:
|
|
53
|
+
"""return true if node is atomic condition (exclude body), NOT FIRST"""
|
|
54
|
+
return node.index != 0 and not node.parent.body[node.index_prev].body
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def is_first_node_cond(node: BaseAstNode) -> bool:
|
|
58
|
+
return node.index == 0 and node.parent.kind == TokenType.EXPR_FILTER
|
|
@@ -85,12 +85,24 @@ from ssc_codegen.ast_ import (
|
|
|
85
85
|
ExprListStringAllRegex,
|
|
86
86
|
ExprListHasAttr,
|
|
87
87
|
ExprHasAttr,
|
|
88
|
+
FilterEqual,
|
|
89
|
+
FilterNotEqual,
|
|
90
|
+
FilterStrRe,
|
|
91
|
+
FilterStrEnds,
|
|
92
|
+
FilterStrStarts,
|
|
93
|
+
FilterStrIn,
|
|
94
|
+
FilterNot,
|
|
95
|
+
FilterAnd,
|
|
96
|
+
FilterOr,
|
|
97
|
+
ExprFilter,
|
|
88
98
|
)
|
|
89
99
|
from ssc_codegen.converters.base import BaseCodeConverter
|
|
90
100
|
from ssc_codegen.converters.helpers import (
|
|
91
101
|
prev_next_var,
|
|
92
102
|
is_last_var_no_ret,
|
|
93
103
|
have_pre_validate_call,
|
|
104
|
+
is_first_node_cond,
|
|
105
|
+
is_prev_node_atomic_cond,
|
|
94
106
|
)
|
|
95
107
|
from ssc_codegen.converters.templates.js_pure import (
|
|
96
108
|
J2_STRUCT_INIT,
|
|
@@ -130,11 +142,15 @@ CONVERTER = BaseCodeConverter(debug_comment_prefix="// ")
|
|
|
130
142
|
|
|
131
143
|
|
|
132
144
|
# TODO: move to string_utils
|
|
133
|
-
def to_js_regexp(
|
|
145
|
+
def to_js_regexp(
|
|
146
|
+
pattern: str, ignore_case: bool = False, is_global: bool = True
|
|
147
|
+
) -> str:
|
|
134
148
|
"""helper function for convert string pattern to js"""
|
|
135
149
|
pattern = pattern.replace("<\\/", "</")
|
|
136
150
|
pattern = pattern.replace("/", "\\/")
|
|
137
|
-
pattern = f"/{pattern}/
|
|
151
|
+
pattern = f"/{pattern}/"
|
|
152
|
+
if is_global:
|
|
153
|
+
pattern += "g"
|
|
138
154
|
if ignore_case:
|
|
139
155
|
pattern += "i"
|
|
140
156
|
return pattern
|
|
@@ -368,8 +384,8 @@ def pre_list_str_replace(node: ExprListStringReplace) -> str:
|
|
|
368
384
|
def pre_str_regex(node: ExprStringRegex) -> str:
|
|
369
385
|
prv, nxt = prev_next_var(node)
|
|
370
386
|
pattern, group, ignore_case = node.unpack_args()
|
|
371
|
-
pattern = to_js_regexp(pattern, ignore_case)
|
|
372
|
-
return f"let {nxt} =
|
|
387
|
+
pattern = to_js_regexp(pattern, ignore_case, is_global=False)
|
|
388
|
+
return f"let {nxt} = {prv}.match({pattern})[{group}];"
|
|
373
389
|
|
|
374
390
|
|
|
375
391
|
@CONVERTER(ExprStringRegexAll.kind)
|
|
@@ -377,8 +393,7 @@ def pre_str_regex_all(node: ExprStringRegexAll) -> str:
|
|
|
377
393
|
prv, nxt = prev_next_var(node)
|
|
378
394
|
pattern, ignore_case = node.unpack_args()
|
|
379
395
|
pattern = to_js_regexp(pattern, ignore_case)
|
|
380
|
-
|
|
381
|
-
return f"let {nxt} = (new RegExp({pattern})).exec({prv});"
|
|
396
|
+
return f"let {nxt} = Array.from({prv}.match({pattern}));"
|
|
382
397
|
|
|
383
398
|
|
|
384
399
|
@CONVERTER(ExprStringRegexSub.kind)
|
|
@@ -714,3 +729,106 @@ def pre_list_has_attr(node: ExprListHasAttr) -> str:
|
|
|
714
729
|
if is_last_var_no_ret(node):
|
|
715
730
|
return expr
|
|
716
731
|
return expr + f"let {nxt} = {prv};"
|
|
732
|
+
|
|
733
|
+
|
|
734
|
+
@CONVERTER(ExprFilter.kind)
|
|
735
|
+
def pre_expr_filter(node: ExprFilter) -> str:
|
|
736
|
+
prv, nxt = prev_next_var(node)
|
|
737
|
+
return f"let {nxt} = {prv}.filter(i => "
|
|
738
|
+
|
|
739
|
+
|
|
740
|
+
@CONVERTER.post(ExprFilter.kind)
|
|
741
|
+
def post_expr_filter(_node: ExprFilter) -> str:
|
|
742
|
+
return ");"
|
|
743
|
+
|
|
744
|
+
|
|
745
|
+
@CONVERTER(FilterOr.kind)
|
|
746
|
+
def pre_filter_or(_node: FilterOr) -> str:
|
|
747
|
+
return " || ("
|
|
748
|
+
|
|
749
|
+
|
|
750
|
+
@CONVERTER.post(FilterOr.kind)
|
|
751
|
+
def post_filter_or(_node: FilterOr) -> str:
|
|
752
|
+
return ")"
|
|
753
|
+
|
|
754
|
+
|
|
755
|
+
@CONVERTER(FilterAnd.kind)
|
|
756
|
+
def pre_filter_and(_node: FilterAnd) -> str:
|
|
757
|
+
return " && ("
|
|
758
|
+
|
|
759
|
+
|
|
760
|
+
@CONVERTER.post(FilterAnd.kind)
|
|
761
|
+
def post_filter_and(_node: FilterAnd) -> str:
|
|
762
|
+
return ")"
|
|
763
|
+
|
|
764
|
+
|
|
765
|
+
@CONVERTER(FilterNot.kind)
|
|
766
|
+
def pre_filter_not(_node: FilterNot) -> str: # type: ignore
|
|
767
|
+
return "!("
|
|
768
|
+
|
|
769
|
+
|
|
770
|
+
@CONVERTER.post(FilterNot.kind)
|
|
771
|
+
def post_filter_not(_node: FilterNot) -> str:
|
|
772
|
+
return ")"
|
|
773
|
+
|
|
774
|
+
|
|
775
|
+
@CONVERTER(FilterStrIn.kind)
|
|
776
|
+
def pre_filter_in(node: FilterStrIn) -> str:
|
|
777
|
+
substr, *_ = node.unpack_args()
|
|
778
|
+
expr = f"i.includes({substr!r})"
|
|
779
|
+
if not is_first_node_cond(node) and is_prev_node_atomic_cond(node):
|
|
780
|
+
return f" && {expr}"
|
|
781
|
+
return expr
|
|
782
|
+
|
|
783
|
+
|
|
784
|
+
@CONVERTER(FilterStrStarts.kind)
|
|
785
|
+
def pre_filter_starts_with(node: FilterStrStarts) -> str:
|
|
786
|
+
start_, *_ = node.unpack_args()
|
|
787
|
+
if len(start_) == 1:
|
|
788
|
+
expr = f"i.startsWith({start_[0]!r})"
|
|
789
|
+
else:
|
|
790
|
+
expr = "(" + " || ".join(f"i.startsWith({s!r})" for s in start_) + ")"
|
|
791
|
+
if not is_first_node_cond(node) and is_prev_node_atomic_cond(node):
|
|
792
|
+
return f" && {expr}"
|
|
793
|
+
return expr
|
|
794
|
+
|
|
795
|
+
|
|
796
|
+
@CONVERTER(FilterStrEnds.kind)
|
|
797
|
+
def pre_filter_ends_with(node: FilterStrEnds) -> str:
|
|
798
|
+
suffix_, *_ = node.unpack_args()
|
|
799
|
+
if len(suffix_) == 1:
|
|
800
|
+
expr = f"i.endsWith({suffix_[0]!r})"
|
|
801
|
+
else:
|
|
802
|
+
expr = "(" + " || ".join(f"i.endsWith({s!r})" for s in suffix_) + ")"
|
|
803
|
+
if not is_first_node_cond(node) and is_prev_node_atomic_cond(node):
|
|
804
|
+
return f" && {expr}"
|
|
805
|
+
return expr
|
|
806
|
+
|
|
807
|
+
|
|
808
|
+
@CONVERTER(FilterStrRe.kind)
|
|
809
|
+
def pre_filter_re(node: FilterStrRe) -> str:
|
|
810
|
+
pattern, ignore_case, *_ = node.unpack_args()
|
|
811
|
+
pattern = to_js_regexp(pattern, ignore_case, is_global=False)
|
|
812
|
+
|
|
813
|
+
expr = f"(new RegExp({pattern})).test(i)"
|
|
814
|
+
if not is_first_node_cond(node) and is_prev_node_atomic_cond(node):
|
|
815
|
+
return f" && {expr}"
|
|
816
|
+
return expr
|
|
817
|
+
|
|
818
|
+
|
|
819
|
+
@CONVERTER(FilterEqual.kind)
|
|
820
|
+
def pre_filter_eq(node: FilterEqual) -> str:
|
|
821
|
+
value, *_ = node.unpack_args()
|
|
822
|
+
expr = f"i === {value!r}"
|
|
823
|
+
if not is_first_node_cond(node) and is_prev_node_atomic_cond(node):
|
|
824
|
+
return f" && {expr}"
|
|
825
|
+
return expr
|
|
826
|
+
|
|
827
|
+
|
|
828
|
+
@CONVERTER(FilterNotEqual.kind)
|
|
829
|
+
def pre_filter_ne(node: FilterNotEqual) -> str:
|
|
830
|
+
value, *_ = node.unpack_args()
|
|
831
|
+
expr = f"i !== {value!r}"
|
|
832
|
+
if not is_first_node_cond(node) and is_prev_node_atomic_cond(node):
|
|
833
|
+
return f" && {expr}"
|
|
834
|
+
return expr
|
|
@@ -87,6 +87,16 @@ from ssc_codegen.ast_ import (
|
|
|
87
87
|
ExprListStringRmPrefixAndSuffix,
|
|
88
88
|
ExprListStringAnyRegex,
|
|
89
89
|
ExprListStringAllRegex,
|
|
90
|
+
FilterOr,
|
|
91
|
+
FilterAnd,
|
|
92
|
+
FilterNot,
|
|
93
|
+
ExprFilter,
|
|
94
|
+
FilterStrIn,
|
|
95
|
+
FilterStrStarts,
|
|
96
|
+
FilterStrEnds,
|
|
97
|
+
FilterStrRe,
|
|
98
|
+
FilterEqual,
|
|
99
|
+
FilterNotEqual,
|
|
90
100
|
)
|
|
91
101
|
from ssc_codegen.converters.base import (
|
|
92
102
|
BaseCodeConverter,
|
|
@@ -98,6 +108,8 @@ from ssc_codegen.converters.helpers import (
|
|
|
98
108
|
have_pre_validate_call,
|
|
99
109
|
prev_next_var,
|
|
100
110
|
is_last_var_no_ret,
|
|
111
|
+
is_prev_node_atomic_cond,
|
|
112
|
+
is_first_node_cond,
|
|
101
113
|
)
|
|
102
114
|
from ssc_codegen.tokens import StructType, VariableType, JsonVariableType
|
|
103
115
|
|
|
@@ -181,6 +193,8 @@ class BasePyCodeConverter(BaseCodeConverter):
|
|
|
181
193
|
)
|
|
182
194
|
from ssc_codegen.ast_ import ExprListStringRmPrefix
|
|
183
195
|
|
|
196
|
+
from ssc_codegen.ast_ import FilterNotEqual
|
|
197
|
+
|
|
184
198
|
self.pre_definitions = {
|
|
185
199
|
Docstring.kind: pre_docstring,
|
|
186
200
|
ModuleImports.kind: pre_imports,
|
|
@@ -234,12 +248,27 @@ class BasePyCodeConverter(BaseCodeConverter):
|
|
|
234
248
|
ExprToListLength.kind: pre_to_len,
|
|
235
249
|
ExprToBool.kind: pre_to_bool,
|
|
236
250
|
ExprJsonify.kind: pre_jsonify,
|
|
251
|
+
# FILTER,
|
|
252
|
+
ExprFilter.kind: pre_expr_filter,
|
|
253
|
+
FilterAnd.kind: pre_filter_and,
|
|
254
|
+
FilterOr.kind: pre_filter_or,
|
|
255
|
+
FilterNot.kind: pre_filter_not,
|
|
256
|
+
FilterStrStarts.kind: pre_filter_starts_with,
|
|
257
|
+
FilterStrEnds.kind: pre_filter_ends_with,
|
|
258
|
+
FilterStrIn.kind: pre_filter_in,
|
|
259
|
+
FilterStrRe.kind: pre_filter_re,
|
|
260
|
+
FilterEqual.kind: pre_filter_eq,
|
|
261
|
+
FilterNotEqual.kind: pre_filter_ne,
|
|
237
262
|
}
|
|
238
263
|
|
|
239
264
|
self.post_definitions = {
|
|
240
265
|
TypeDef.kind: post_typedef,
|
|
241
266
|
JsonStruct.kind: post_json_struct,
|
|
242
267
|
StartParseMethod.kind: post_start_parse,
|
|
268
|
+
ExprFilter.kind: post_expr_filter,
|
|
269
|
+
FilterAnd.kind: post_filter_and,
|
|
270
|
+
FilterOr.kind: post_filter_or,
|
|
271
|
+
FilterNot.kind: post_filter_not,
|
|
243
272
|
}
|
|
244
273
|
|
|
245
274
|
|
|
@@ -876,3 +905,95 @@ def pre_jsonify(node: ExprJsonify) -> str:
|
|
|
876
905
|
prv, nxt = prev_next_var(node)
|
|
877
906
|
_name, _is_array = node.unpack_args()
|
|
878
907
|
return indent + f"{nxt} = json.loads({prv})"
|
|
908
|
+
|
|
909
|
+
|
|
910
|
+
# FILTERS
|
|
911
|
+
|
|
912
|
+
|
|
913
|
+
def pre_expr_filter(node: ExprFilter) -> str:
|
|
914
|
+
indent = (
|
|
915
|
+
INDENT_DEFAULT_BODY if have_default_expr(node) else INDENT_METHOD_BODY
|
|
916
|
+
)
|
|
917
|
+
prv, nxt = prev_next_var(node)
|
|
918
|
+
return indent + f"{nxt} = [i for i in {prv} if "
|
|
919
|
+
|
|
920
|
+
|
|
921
|
+
def post_expr_filter(_node: ExprFilter) -> str:
|
|
922
|
+
return "]"
|
|
923
|
+
|
|
924
|
+
|
|
925
|
+
def pre_filter_or(_node: FilterOr) -> str:
|
|
926
|
+
return " or ("
|
|
927
|
+
|
|
928
|
+
|
|
929
|
+
def post_filter_or(_node: FilterOr) -> str:
|
|
930
|
+
return ")"
|
|
931
|
+
|
|
932
|
+
|
|
933
|
+
def pre_filter_and(_node: FilterAnd) -> str:
|
|
934
|
+
return " and ("
|
|
935
|
+
|
|
936
|
+
|
|
937
|
+
def post_filter_and(_node: FilterAnd) -> str:
|
|
938
|
+
return ")"
|
|
939
|
+
|
|
940
|
+
|
|
941
|
+
def pre_filter_not(_node: FilterNot) -> str:
|
|
942
|
+
return "not ("
|
|
943
|
+
|
|
944
|
+
|
|
945
|
+
def post_filter_not(_node: FilterNot) -> str:
|
|
946
|
+
return ")"
|
|
947
|
+
|
|
948
|
+
|
|
949
|
+
def pre_filter_in(node: FilterStrIn) -> str:
|
|
950
|
+
substr, *_ = node.unpack_args()
|
|
951
|
+
expr = f"{substr!r} in i"
|
|
952
|
+
if not is_first_node_cond(node) and is_prev_node_atomic_cond(node):
|
|
953
|
+
return f" and {expr}"
|
|
954
|
+
return expr
|
|
955
|
+
|
|
956
|
+
|
|
957
|
+
def pre_filter_starts_with(node: FilterStrStarts) -> str:
|
|
958
|
+
start_, *_ = node.unpack_args()
|
|
959
|
+
expr = f"i.startswith({start_})"
|
|
960
|
+
if not is_first_node_cond(node) and is_prev_node_atomic_cond(node):
|
|
961
|
+
return f" and {expr}"
|
|
962
|
+
return expr
|
|
963
|
+
|
|
964
|
+
|
|
965
|
+
def pre_filter_ends_with(node: FilterStrEnds) -> str:
|
|
966
|
+
suffix_, *_ = node.unpack_args()
|
|
967
|
+
expr = f"i.endswith({suffix_})"
|
|
968
|
+
if not is_first_node_cond(node) and is_prev_node_atomic_cond(node):
|
|
969
|
+
return f" and {expr}"
|
|
970
|
+
return expr
|
|
971
|
+
|
|
972
|
+
|
|
973
|
+
def pre_filter_re(node: FilterStrRe) -> str:
|
|
974
|
+
pattern, ignore_case, *_ = node.unpack_args()
|
|
975
|
+
if ignore_case:
|
|
976
|
+
expr = f"re.search({pattern!r}, i, re.IGNORECASE)"
|
|
977
|
+
else:
|
|
978
|
+
expr = f"re.search({pattern!r}, i)"
|
|
979
|
+
if not is_first_node_cond(node) and is_prev_node_atomic_cond(node):
|
|
980
|
+
return f" and {expr}"
|
|
981
|
+
return expr
|
|
982
|
+
|
|
983
|
+
|
|
984
|
+
def pre_filter_eq(node: FilterEqual) -> str:
|
|
985
|
+
value, *_ = node.unpack_args()
|
|
986
|
+
# currently support only str
|
|
987
|
+
expr = f"i == {value!r}"
|
|
988
|
+
if not is_first_node_cond(node) and is_prev_node_atomic_cond(node):
|
|
989
|
+
return f" and {expr}"
|
|
990
|
+
return expr
|
|
991
|
+
|
|
992
|
+
|
|
993
|
+
def pre_filter_ne(node: FilterNotEqual) -> str:
|
|
994
|
+
value, *_ = node.unpack_args()
|
|
995
|
+
# currently support only str
|
|
996
|
+
expr = f"i != {value!r}"
|
|
997
|
+
if not is_first_node_cond(node) and is_prev_node_atomic_cond(node):
|
|
998
|
+
return f" and {expr}"
|
|
999
|
+
return expr
|
|
@@ -59,6 +59,16 @@ from ssc_codegen.ast_ import (
|
|
|
59
59
|
ExprListStringAnyRegex,
|
|
60
60
|
ExprHasAttr,
|
|
61
61
|
ExprListHasAttr,
|
|
62
|
+
ExprFilter,
|
|
63
|
+
FilterEqual,
|
|
64
|
+
FilterNotEqual,
|
|
65
|
+
FilterStrStarts,
|
|
66
|
+
FilterStrEnds,
|
|
67
|
+
FilterStrIn,
|
|
68
|
+
FilterStrRe,
|
|
69
|
+
FilterAnd,
|
|
70
|
+
FilterOr,
|
|
71
|
+
FilterNot,
|
|
62
72
|
)
|
|
63
73
|
from ssc_codegen.document_utlis import (
|
|
64
74
|
analyze_re_expression,
|
|
@@ -457,6 +467,20 @@ class ArrayDocument(BaseDocument):
|
|
|
457
467
|
self._add(ExprToListLength(accept_type=expected_type))
|
|
458
468
|
return self
|
|
459
469
|
|
|
470
|
+
def filter(self, expr: "DocumentFilter") -> Self:
|
|
471
|
+
"""filter array of resuts by expr
|
|
472
|
+
|
|
473
|
+
- accept LIST_STRING, return LIST_STRING
|
|
474
|
+
"""
|
|
475
|
+
if self.stack_last_ret != VariableType.LIST_STRING:
|
|
476
|
+
LOGGER.warning(
|
|
477
|
+
"to_len(): Expected type(s) %s got %s",
|
|
478
|
+
(VariableType.LIST_STRING.name,),
|
|
479
|
+
self.stack_last_ret.name,
|
|
480
|
+
)
|
|
481
|
+
self._add(ExprFilter(body=expr.stack))
|
|
482
|
+
return self
|
|
483
|
+
|
|
460
484
|
|
|
461
485
|
class StringDocument(BaseDocument):
|
|
462
486
|
def rm_prefix(self, substr: str) -> Self:
|
|
@@ -1236,3 +1260,78 @@ class JsonDocument(BaseDocument):
|
|
|
1236
1260
|
# HACK: store to class instance for generate docstring signature
|
|
1237
1261
|
BaseSchema.__JSON_SCHEMAS__[struct.__name__] = struct # type: ignore
|
|
1238
1262
|
return self
|
|
1263
|
+
|
|
1264
|
+
|
|
1265
|
+
class DocumentFilter(BaseDocument):
|
|
1266
|
+
def eq(self, value: str) -> Self:
|
|
1267
|
+
self._add(FilterEqual(kwargs={"value": value}))
|
|
1268
|
+
return self
|
|
1269
|
+
|
|
1270
|
+
def ne(self, value: str) -> Self:
|
|
1271
|
+
self._add(FilterNotEqual(kwargs={"value": value}))
|
|
1272
|
+
return self
|
|
1273
|
+
|
|
1274
|
+
def starts(self, *values: str) -> Self:
|
|
1275
|
+
self._add(FilterStrStarts(kwargs={"substr": values}))
|
|
1276
|
+
return self
|
|
1277
|
+
|
|
1278
|
+
def ends(self, *values: str) -> Self:
|
|
1279
|
+
self._add(FilterStrEnds(kwargs={"substr": values}))
|
|
1280
|
+
return self
|
|
1281
|
+
|
|
1282
|
+
def contains(self, value: str) -> Self:
|
|
1283
|
+
self._add(FilterStrIn(kwargs={"substr": value}))
|
|
1284
|
+
return self
|
|
1285
|
+
|
|
1286
|
+
def re(
|
|
1287
|
+
self, pattern: str | Pattern[str], ignore_case: bool = False
|
|
1288
|
+
) -> Self:
|
|
1289
|
+
if not isinstance(pattern, str):
|
|
1290
|
+
ignore_case = is_ignore_case_regex(pattern)
|
|
1291
|
+
|
|
1292
|
+
pattern = unverbosify_regex(pattern)
|
|
1293
|
+
result = analyze_re_expression(pattern, allow_empty_groups=True)
|
|
1294
|
+
if not result:
|
|
1295
|
+
LOGGER.warning(result.msg)
|
|
1296
|
+
self._add(
|
|
1297
|
+
FilterStrRe(kwargs={"pattern": pattern, "ignore_case": ignore_case})
|
|
1298
|
+
)
|
|
1299
|
+
return self
|
|
1300
|
+
|
|
1301
|
+
def and_(self, filter_expr: "DocumentFilter") -> Self:
|
|
1302
|
+
if self.stack[0].kind in (FilterAnd.kind, FilterOr.kind):
|
|
1303
|
+
LOGGER.warning(
|
|
1304
|
+
"logic AND: first node is `%s`", self.stack[0].kind.name
|
|
1305
|
+
)
|
|
1306
|
+
tmp_stack = filter_expr.stack.copy()
|
|
1307
|
+
self._add(FilterAnd(body=tmp_stack))
|
|
1308
|
+
return self
|
|
1309
|
+
|
|
1310
|
+
def or_(self, filter_expr: "DocumentFilter") -> Self:
|
|
1311
|
+
if self.stack[0].kind in (FilterAnd.kind, FilterOr.kind):
|
|
1312
|
+
LOGGER.warning(
|
|
1313
|
+
"logic OR: first node is `%s`", self.stack[0].kind.name
|
|
1314
|
+
)
|
|
1315
|
+
tmp_stack = filter_expr.stack.copy()
|
|
1316
|
+
self._add(FilterOr(body=tmp_stack))
|
|
1317
|
+
return self
|
|
1318
|
+
|
|
1319
|
+
def not_(self, filter_expr: "DocumentFilter") -> Self:
|
|
1320
|
+
tmp_stack = filter_expr.stack.copy()
|
|
1321
|
+
self._add(FilterNot(body=tmp_stack))
|
|
1322
|
+
return self
|
|
1323
|
+
|
|
1324
|
+
def __or__(self, other: "DocumentFilter") -> "DocumentFilter":
|
|
1325
|
+
return self.or_(other)
|
|
1326
|
+
|
|
1327
|
+
def __and__(self, other: "DocumentFilter") -> "DocumentFilter":
|
|
1328
|
+
return self.and_(other)
|
|
1329
|
+
|
|
1330
|
+
def __invert__(self) -> "DocumentFilter":
|
|
1331
|
+
return DocumentFilter().not_(self)
|
|
1332
|
+
|
|
1333
|
+
def __eq__(self, other: str) -> Self:
|
|
1334
|
+
return self.eq(other)
|
|
1335
|
+
|
|
1336
|
+
def __ne__(self, other: str) -> Self:
|
|
1337
|
+
return self.ne(other)
|
|
@@ -103,7 +103,7 @@ RE_PY_METHOD_BLOCK = re.compile(
|
|
|
103
103
|
r"""
|
|
104
104
|
def\s_(?:parse|split)\w+\(.*\:\s* # SPLIT_DOC STRUCT_PARSE HEAD
|
|
105
105
|
(?:\n|.)*? # BLOCK OF CODE
|
|
106
|
-
return\s(?P<ret_var>\w+)
|
|
106
|
+
return\s(?P<ret_var>\w+) # RET STMT
|
|
107
107
|
""",
|
|
108
108
|
re.X,
|
|
109
109
|
)
|
|
@@ -148,6 +148,9 @@ def py_optimize_return_naive(py_code: str) -> str:
|
|
|
148
148
|
re_expr = f"{ret_var}\\s*=\\s*(?P<expr>.*)"
|
|
149
149
|
if match := re.search(re_expr, method_block[0]):
|
|
150
150
|
expr = match["expr"] # noqa
|
|
151
|
+
# NAIVE apologize, its .filter(...) expr, skip optimization
|
|
152
|
+
if expr.strip().endswith(" if"):
|
|
153
|
+
continue
|
|
151
154
|
|
|
152
155
|
# optimization function convert double backslash to single (\\ -> \) in regex patterns
|
|
153
156
|
# add `r` prefix for avoid SyntaxWarning then compile generated code to cpython bytecode
|
|
@@ -174,6 +174,21 @@ class TokenType(IntEnum):
|
|
|
174
174
|
# bool
|
|
175
175
|
TO_BOOL = auto()
|
|
176
176
|
|
|
177
|
+
# FILTER
|
|
178
|
+
EXPR_FILTER = auto()
|
|
179
|
+
|
|
180
|
+
FILTER = auto() # TODO: REMOVE?
|
|
181
|
+
FILTER_OR = auto()
|
|
182
|
+
FILTER_AND = auto()
|
|
183
|
+
FILTER_NOT = auto()
|
|
184
|
+
# FILTER EXPR
|
|
185
|
+
FILTER_STR_IN = auto()
|
|
186
|
+
FILTER_STR_STARTS = auto()
|
|
187
|
+
FILTER_STR_ENDS = auto()
|
|
188
|
+
FILTER_STR_RE = auto()
|
|
189
|
+
FILTER_EQ = auto()
|
|
190
|
+
FILTER_NE = auto()
|
|
191
|
+
|
|
177
192
|
@classmethod
|
|
178
193
|
def default_tokens(cls) -> tuple["TokenType", ...]:
|
|
179
194
|
return (
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|