ssc_codegen 0.13.0__tar.gz → 0.13.2__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.
- {ssc_codegen-0.13.0 → ssc_codegen-0.13.2}/PKG-INFO +1 -1
- {ssc_codegen-0.13.0 → ssc_codegen-0.13.2}/pyproject.toml +1 -1
- {ssc_codegen-0.13.0 → ssc_codegen-0.13.2}/ssc_codegen/__init__.py +1 -1
- {ssc_codegen-0.13.0 → ssc_codegen-0.13.2}/ssc_codegen/ast_/__init__.py +0 -1
- {ssc_codegen-0.13.0 → ssc_codegen-0.13.2}/ssc_codegen/ast_build/main.py +4 -1
- ssc_codegen-0.13.2/ssc_codegen/ast_grep_rules/py_drop_prefix_suffix_backport.yml +47 -0
- {ssc_codegen-0.13.0 → ssc_codegen-0.13.2}/ssc_codegen/cli/ast_grep.py +1 -0
- {ssc_codegen-0.13.0 → ssc_codegen-0.13.2}/ssc_codegen/cli/main.py +8 -1
- {ssc_codegen-0.13.0 → ssc_codegen-0.13.2}/ssc_codegen/converters/base.py +183 -24
- {ssc_codegen-0.13.0 → ssc_codegen-0.13.2}/ssc_codegen/converters/js_pure.py +4 -5
- {ssc_codegen-0.13.0 → ssc_codegen-0.13.2}/ssc_codegen/converters/py_base.py +1 -1
- {ssc_codegen-0.13.0 → ssc_codegen-0.13.2}/ssc_codegen/converters/py_lxml.py +52 -45
- {ssc_codegen-0.13.0 → ssc_codegen-0.13.2}/ssc_codegen/converters/py_selectolax.py +1 -1
- {ssc_codegen-0.13.0 → ssc_codegen-0.13.2}/ssc_codegen/converters/templates/lua_re_compat.py +1 -1
- {ssc_codegen-0.13.0 → ssc_codegen-0.13.2}/ssc_codegen/logs.py +2 -2
- {ssc_codegen-0.13.0 → ssc_codegen-0.13.2}/ssc_codegen/static_checker/base.py +0 -1
- {ssc_codegen-0.13.0 → ssc_codegen-0.13.2}/.gitignore +0 -0
- {ssc_codegen-0.13.0 → ssc_codegen-0.13.2}/LICENSE +0 -0
- {ssc_codegen-0.13.0 → ssc_codegen-0.13.2}/README.md +0 -0
- {ssc_codegen-0.13.0 → ssc_codegen-0.13.2}/ssc_codegen/_compat.py +0 -0
- {ssc_codegen-0.13.0 → ssc_codegen-0.13.2}/ssc_codegen/ast_/base.py +0 -0
- {ssc_codegen-0.13.0 → ssc_codegen-0.13.2}/ssc_codegen/ast_/nodes_array.py +0 -0
- {ssc_codegen-0.13.0 → ssc_codegen-0.13.2}/ssc_codegen/ast_/nodes_cast.py +0 -0
- {ssc_codegen-0.13.0 → ssc_codegen-0.13.2}/ssc_codegen/ast_/nodes_core.py +0 -0
- {ssc_codegen-0.13.0 → ssc_codegen-0.13.2}/ssc_codegen/ast_/nodes_filter.py +0 -0
- {ssc_codegen-0.13.0 → ssc_codegen-0.13.2}/ssc_codegen/ast_/nodes_selectors.py +0 -0
- {ssc_codegen-0.13.0 → ssc_codegen-0.13.2}/ssc_codegen/ast_/nodes_string.py +0 -0
- {ssc_codegen-0.13.0 → ssc_codegen-0.13.2}/ssc_codegen/ast_/nodes_validate.py +0 -0
- {ssc_codegen-0.13.0 → ssc_codegen-0.13.2}/ssc_codegen/ast_build/__init__.py +0 -0
- {ssc_codegen-0.13.0 → ssc_codegen-0.13.2}/ssc_codegen/ast_build/builder.py +0 -0
- {ssc_codegen-0.13.0 → ssc_codegen-0.13.2}/ssc_codegen/ast_build/utils.py +0 -0
- {ssc_codegen-0.13.0 → ssc_codegen-0.13.2}/ssc_codegen/ast_grep_rules/js_rules.yml +0 -0
- {ssc_codegen-0.13.0 → ssc_codegen-0.13.2}/ssc_codegen/ast_grep_rules/py_rules.yml +0 -0
- {ssc_codegen-0.13.0 → ssc_codegen-0.13.2}/ssc_codegen/cli/__init__.py +0 -0
- {ssc_codegen-0.13.0 → ssc_codegen-0.13.2}/ssc_codegen/cli/cli_callbacks.py +0 -0
- {ssc_codegen-0.13.0 → ssc_codegen-0.13.2}/ssc_codegen/cli/cli_utils.py +0 -0
- {ssc_codegen-0.13.0 → ssc_codegen-0.13.2}/ssc_codegen/cli/code_callbacks.py +0 -0
- {ssc_codegen-0.13.0 → ssc_codegen-0.13.2}/ssc_codegen/cli/consts.py +0 -0
- {ssc_codegen-0.13.0 → ssc_codegen-0.13.2}/ssc_codegen/cli/runtime_parse_runners.py +0 -0
- {ssc_codegen-0.13.0 → ssc_codegen-0.13.2}/ssc_codegen/compiler.py +0 -0
- {ssc_codegen-0.13.0 → ssc_codegen-0.13.2}/ssc_codegen/converters/__init__.py +0 -0
- {ssc_codegen-0.13.0 → ssc_codegen-0.13.2}/ssc_codegen/converters/go_goquery.py +0 -0
- {ssc_codegen-0.13.0 → ssc_codegen-0.13.2}/ssc_codegen/converters/helpers.py +0 -0
- {ssc_codegen-0.13.0 → ssc_codegen-0.13.2}/ssc_codegen/converters/lua_htmlparser.py +0 -0
- {ssc_codegen-0.13.0 → ssc_codegen-0.13.2}/ssc_codegen/converters/py_bs4.py +0 -0
- {ssc_codegen-0.13.0 → ssc_codegen-0.13.2}/ssc_codegen/converters/py_parsel.py +0 -0
- {ssc_codegen-0.13.0 → ssc_codegen-0.13.2}/ssc_codegen/converters/templates/__init__.py +0 -0
- {ssc_codegen-0.13.0 → ssc_codegen-0.13.2}/ssc_codegen/converters/templates/go_goquery.py +0 -0
- {ssc_codegen-0.13.0 → ssc_codegen-0.13.2}/ssc_codegen/converters/templates/js_pure.py +0 -0
- {ssc_codegen-0.13.0 → ssc_codegen-0.13.2}/ssc_codegen/converters/templates/lua_base.py +0 -0
- {ssc_codegen-0.13.0 → ssc_codegen-0.13.2}/ssc_codegen/converters/templates/lua_css_compat.py +0 -0
- {ssc_codegen-0.13.0 → ssc_codegen-0.13.2}/ssc_codegen/converters/templates/py_base.py +0 -0
- {ssc_codegen-0.13.0 → ssc_codegen-0.13.2}/ssc_codegen/document.py +0 -0
- {ssc_codegen-0.13.0 → ssc_codegen-0.13.2}/ssc_codegen/document_utlis.py +0 -0
- {ssc_codegen-0.13.0 → ssc_codegen-0.13.2}/ssc_codegen/json_struct.py +0 -0
- {ssc_codegen-0.13.0 → ssc_codegen-0.13.2}/ssc_codegen/json_to_scc.py +0 -0
- {ssc_codegen-0.13.0 → ssc_codegen-0.13.2}/ssc_codegen/pseudo_selectors.py +0 -0
- {ssc_codegen-0.13.0 → ssc_codegen-0.13.2}/ssc_codegen/schema.py +0 -0
- {ssc_codegen-0.13.0 → ssc_codegen-0.13.2}/ssc_codegen/selector_utils.py +0 -0
- {ssc_codegen-0.13.0 → ssc_codegen-0.13.2}/ssc_codegen/static_checker/__init__.py +0 -0
- {ssc_codegen-0.13.0 → ssc_codegen-0.13.2}/ssc_codegen/static_checker/callbacks.py +0 -0
- {ssc_codegen-0.13.0 → ssc_codegen-0.13.2}/ssc_codegen/str_utils.py +0 -0
- {ssc_codegen-0.13.0 → ssc_codegen-0.13.2}/ssc_codegen/tokens.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: ssc_codegen
|
|
3
|
-
Version: 0.13.
|
|
3
|
+
Version: 0.13.2
|
|
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
|
|
@@ -13,7 +13,7 @@ from ssc_codegen.schema import BaseSchema
|
|
|
13
13
|
from ssc_codegen.static_checker import run_analyze_schema
|
|
14
14
|
import logging
|
|
15
15
|
|
|
16
|
-
LOGGER = logging.getLogger("
|
|
16
|
+
LOGGER = logging.getLogger("ssc_gen")
|
|
17
17
|
|
|
18
18
|
|
|
19
19
|
def build_ast_module_parser(
|
|
@@ -37,6 +37,9 @@ def build_ast_module_parser(
|
|
|
37
37
|
py_module = exec_module_code(path)
|
|
38
38
|
# check code configs before build AST
|
|
39
39
|
schemas = extract_schemas_from_module(py_module)
|
|
40
|
+
if len(schemas) == 0:
|
|
41
|
+
LOGGER.warning(f"{path} does not contains defined schemas")
|
|
42
|
+
|
|
40
43
|
count_errors = 0
|
|
41
44
|
for schema in schemas:
|
|
42
45
|
errors = run_analyze_schema(schema)
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
id: py_drop_prefix_backport
|
|
2
|
+
language: python
|
|
3
|
+
rule:
|
|
4
|
+
pattern:
|
|
5
|
+
context: |-
|
|
6
|
+
def ssc_rm_prefix($$$) -> str:$$$
|
|
7
|
+
fix: ''
|
|
8
|
+
---
|
|
9
|
+
id: py_fix_new_prefix_syntax
|
|
10
|
+
language: python
|
|
11
|
+
rule:
|
|
12
|
+
pattern:
|
|
13
|
+
context: ssc_rm_prefix($V, $P)
|
|
14
|
+
fix: '$V.removeprefix($P)'
|
|
15
|
+
|
|
16
|
+
---
|
|
17
|
+
id: py_drop_suffix_backport
|
|
18
|
+
language: python
|
|
19
|
+
rule:
|
|
20
|
+
pattern:
|
|
21
|
+
context: |-
|
|
22
|
+
def ssc_rm_suffix($$$) -> str:$$$
|
|
23
|
+
fix: ''
|
|
24
|
+
---
|
|
25
|
+
id: py_fix_new_suffix_syntax
|
|
26
|
+
language: python
|
|
27
|
+
rule:
|
|
28
|
+
pattern:
|
|
29
|
+
context: ssc_rm_suffix($V, $P)
|
|
30
|
+
fix: '$V.removesuffix($P)'
|
|
31
|
+
---
|
|
32
|
+
|
|
33
|
+
id: py_drop_prefix_suffix_backport
|
|
34
|
+
language: python
|
|
35
|
+
rule:
|
|
36
|
+
pattern:
|
|
37
|
+
context: |-
|
|
38
|
+
def ssc_rm_prefix_and_suffix($$$) -> str:$$$
|
|
39
|
+
fix: ''
|
|
40
|
+
---
|
|
41
|
+
id: py_fix_new_prefix_suffix_syntax
|
|
42
|
+
language: python
|
|
43
|
+
rule:
|
|
44
|
+
pattern:
|
|
45
|
+
context: ssc_rm_prefix_and_suffix($V, $P1, $P2)
|
|
46
|
+
fix:
|
|
47
|
+
'$V.removeprefix($P1).removesuffix($P2)'
|
|
@@ -4,4 +4,5 @@ from importlib import resources
|
|
|
4
4
|
RULES_PATH = resources.files("ssc_codegen.ast_grep_rules")
|
|
5
5
|
|
|
6
6
|
PY_RULES = str(RULES_PATH.joinpath("py_rules.yml"))
|
|
7
|
+
PY_NEW_RM_PREFIX_SUFFIX_SYNTAX = str(RULES_PATH.joinpath("py_drop_prefix_suffix_backport.yml"))
|
|
7
8
|
JS_RULES = str(RULES_PATH.joinpath("js_rules.yml"))
|
|
@@ -11,7 +11,7 @@ from ichrome.exceptions import ChromeRuntimeError
|
|
|
11
11
|
from typer import Argument, BadParameter, Option, Typer
|
|
12
12
|
|
|
13
13
|
from ssc_codegen.ast_build import build_ast_module_parser
|
|
14
|
-
from ssc_codegen.cli.ast_grep import JS_RULES, PY_RULES
|
|
14
|
+
from ssc_codegen.cli.ast_grep import JS_RULES, PY_RULES, PY_NEW_RM_PREFIX_SUFFIX_SYNTAX
|
|
15
15
|
from ssc_codegen.cli.cli_callbacks import cb_check_ssc_files, cb_folder_out
|
|
16
16
|
from ssc_codegen.cli.cli_utils import (
|
|
17
17
|
ConverterLike,
|
|
@@ -168,6 +168,9 @@ def gen_py(
|
|
|
168
168
|
docstring: Annotated[
|
|
169
169
|
bool, Option(help=HELP_DOCSTRING, is_flag=True)
|
|
170
170
|
] = True,
|
|
171
|
+
fmt_prefix_suffix_backport: Annotated[
|
|
172
|
+
bool, Option("-frps", '--fmt-remove-prefix-suffix',
|
|
173
|
+
help="[PY39+]: replace backport funcs to str.removeprefix(...) and str.removesuffix(...)")] = False
|
|
171
174
|
) -> None:
|
|
172
175
|
converter = import_converter(f"py_{lib.value}")
|
|
173
176
|
if fmt:
|
|
@@ -176,10 +179,14 @@ def gen_py(
|
|
|
176
179
|
"ruff check {} --fix",
|
|
177
180
|
f"ast-grep scan --rule {PY_RULES} -U " + "{}",
|
|
178
181
|
]
|
|
182
|
+
if fmt_prefix_suffix_backport:
|
|
183
|
+
commands.append(f"ast-grep scan --rule {PY_NEW_RM_PREFIX_SUFFIX_SYNTAX} -U " + "{}")
|
|
179
184
|
fmt_cmd = create_fmt_cmd(ssc_files, prefix, suffix, out, commands)
|
|
180
185
|
else:
|
|
181
186
|
fmt_cmd = []
|
|
182
187
|
|
|
188
|
+
|
|
189
|
+
|
|
183
190
|
generate_code(
|
|
184
191
|
converter=converter,
|
|
185
192
|
out=out,
|
|
@@ -28,6 +28,18 @@ CB_AST_DECORATOR = Callable[[CB_AST_BIND], CB_AST_BIND]
|
|
|
28
28
|
|
|
29
29
|
|
|
30
30
|
def debug_comment_cb(node: BaseAstNode, comment_prefix: str) -> str:
|
|
31
|
+
"""Generate a debug comment for an AST node.
|
|
32
|
+
|
|
33
|
+
Creates a formatted string containing information about the node's type,
|
|
34
|
+
return type (if applicable), and other relevant details for debugging purposes.
|
|
35
|
+
|
|
36
|
+
Args:
|
|
37
|
+
node: The AST node to generate a comment for.
|
|
38
|
+
comment_prefix: The prefix to add to each line of the comment.
|
|
39
|
+
|
|
40
|
+
Returns:
|
|
41
|
+
A formatted string containing debug information about the node.
|
|
42
|
+
"""
|
|
31
43
|
match node.kind:
|
|
32
44
|
case TokenType.EXPR_RETURN:
|
|
33
45
|
token = f"{comment_prefix}Token: {node.kind.name} ret_type: {node.ret_type.name}"
|
|
@@ -48,16 +60,60 @@ def debug_comment_cb(node: BaseAstNode, comment_prefix: str) -> str:
|
|
|
48
60
|
|
|
49
61
|
|
|
50
62
|
class BaseCodeConverter:
|
|
51
|
-
"""
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
-
|
|
58
|
-
- @pre -
|
|
59
|
-
- @post -
|
|
60
|
-
|
|
63
|
+
"""Base AST visitor class and code converter.
|
|
64
|
+
|
|
65
|
+
This class serves as the foundation for converting AST nodes into code.
|
|
66
|
+
Visitors are set using decorators that point to specific TokenTypes.
|
|
67
|
+
|
|
68
|
+
Decorator hooks can be overridden:
|
|
69
|
+
- As defaults, __call__ methods set @pre decorator
|
|
70
|
+
- @pre - First trigger visitor entrypoint
|
|
71
|
+
- @post - Second trigger visitor entrypoint (for closing brackets or other logic).
|
|
72
|
+
It will be called when the broadcast of all nodes and the container body is transpiled.
|
|
73
|
+
|
|
74
|
+
Example:
|
|
75
|
+
```
|
|
76
|
+
CONVERTER = BaseCodeConverter()
|
|
77
|
+
|
|
78
|
+
# recommended use <node>.kind call as key in decorators instead TokenType.<TOKEN>
|
|
79
|
+
@CONVERTER(ModuleImports.kind)
|
|
80
|
+
def pre_imports(_node: ModuleImports) -> str:
|
|
81
|
+
# implement imports stmt
|
|
82
|
+
return '''from bs4 import BeautifulSoap'''
|
|
83
|
+
|
|
84
|
+
# TypeDef and StartParseMethod node allow add extra second shortcut as StructType for
|
|
85
|
+
# increase code readability and simplify codegen
|
|
86
|
+
|
|
87
|
+
# TYPEDEF Impl
|
|
88
|
+
# post_callback - shortcut for minify typical end expr (eg: close parens)
|
|
89
|
+
|
|
90
|
+
# generate type for StructType.ITEM parser
|
|
91
|
+
@CONVERTER(TypeDef.kind, StructType.ITEM, post_callback=lambda _: "})")
|
|
92
|
+
def pre_typedef_item(node: TypeDef) -> str:
|
|
93
|
+
name, _ = node.unpack_args()
|
|
94
|
+
return f'T_{name} = TypedDict("T_{name}", ' + "{"
|
|
95
|
+
|
|
96
|
+
# generate type for StructType.DICT parser
|
|
97
|
+
@CONVERTER(TypeDef.kind, StructType.DICT)
|
|
98
|
+
def pre_typedef_dict(node: TypeDef) -> str:
|
|
99
|
+
name, _ = node.unpack_args()
|
|
100
|
+
type_ = get_typedef_field_by_name(node, "__VALUE__")
|
|
101
|
+
return f"T_{name} = Dict[str, {type_}]"
|
|
102
|
+
|
|
103
|
+
# StartParse impl
|
|
104
|
+
@CONVERTER(StartParseMethod.kind, StructType.ITEM)
|
|
105
|
+
def pre_start_parse_item(node: StartParseMethod) -> str:
|
|
106
|
+
# some impl for ItEM struct
|
|
107
|
+
return ""
|
|
108
|
+
|
|
109
|
+
@CONVERTER(StartParseMethod.kind, StructType.LIST)
|
|
110
|
+
def pre_start_parse_list(node: StartParseMethod) -> str:
|
|
111
|
+
# some impl for LIST struct
|
|
112
|
+
return ""
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
Attributes:
|
|
116
|
+
TEST_EXCLUDE_NODES: List of token types to exclude from tests.
|
|
61
117
|
"""
|
|
62
118
|
|
|
63
119
|
TEST_EXCLUDE_NODES: ClassVar[list[TokenType]] = [
|
|
@@ -70,7 +126,7 @@ class BaseCodeConverter:
|
|
|
70
126
|
TokenType.STRUCT_CALL_FUNCTION,
|
|
71
127
|
TokenType.STRUCT_CALL_CLASSVAR,
|
|
72
128
|
]
|
|
73
|
-
"""
|
|
129
|
+
"""Developer class variable that marks nodes to exclude in tests."""
|
|
74
130
|
|
|
75
131
|
def __init__(
|
|
76
132
|
self,
|
|
@@ -80,10 +136,10 @@ class BaseCodeConverter:
|
|
|
80
136
|
debug_cb: CB_FMT_DEBUG_COMMENT = debug_comment_cb,
|
|
81
137
|
) -> None:
|
|
82
138
|
"""
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
139
|
+
Arguments:
|
|
140
|
+
debug_instructions: enable debug instructions (add comment for every generated instruction. used in debug codegen output)
|
|
141
|
+
debug_comment_prefix: comment line prefix (used in debug codegen output)
|
|
142
|
+
debug_cb: callback formatting comment (used in debug codegen output)
|
|
87
143
|
"""
|
|
88
144
|
self._debug_instructions = debug_instructions
|
|
89
145
|
self._debug_comment_prefix = debug_comment_prefix
|
|
@@ -131,14 +187,14 @@ class BaseCodeConverter:
|
|
|
131
187
|
|
|
132
188
|
optional allow set post callback for simple string casts like close brackets etc
|
|
133
189
|
|
|
134
|
-
StartParseMethod has shortcut (StartParseMethod.kind, StructType.<TYPE>) for simplify callback generators
|
|
190
|
+
StartParseMethod and TypeDef nodes has shortcut (StartParseMethod.kind, StructType.<TYPE>) for simplify callback generators
|
|
135
191
|
"""
|
|
136
192
|
if for_struct_definition and for_definition not in (
|
|
137
193
|
StartParseMethod.kind,
|
|
138
194
|
TypeDef.kind,
|
|
139
195
|
):
|
|
140
196
|
raise TypeError(
|
|
141
|
-
"Add struct definition allowed only for `StartParseMethod` nodes"
|
|
197
|
+
"Add struct definition allowed only for `StartParseMethod` and `TypeDef` nodes"
|
|
142
198
|
)
|
|
143
199
|
|
|
144
200
|
def decorator(func: CB_AST_BIND) -> CB_AST_BIND:
|
|
@@ -164,7 +220,23 @@ class BaseCodeConverter:
|
|
|
164
220
|
for_definition: TokenType,
|
|
165
221
|
for_struct_definition: StructType | None = None,
|
|
166
222
|
) -> CB_AST_DECORATOR:
|
|
167
|
-
"""Define a post-conversion decorator for the given TokenType.
|
|
223
|
+
"""Define a post-conversion decorator for the given TokenType.
|
|
224
|
+
|
|
225
|
+
This method creates a decorator that registers a function to be called
|
|
226
|
+
after the main conversion of a node of the specified type. It's typically
|
|
227
|
+
used for closing brackets or other logic that should happen after the
|
|
228
|
+
main node processing.
|
|
229
|
+
|
|
230
|
+
Args:
|
|
231
|
+
for_definition: The TokenType to associate with this post-conversion function.
|
|
232
|
+
for_struct_definition: Optional StructType for specific struct definitions (for StartParseMethod and TypeDef).
|
|
233
|
+
|
|
234
|
+
Returns:
|
|
235
|
+
A decorator function that registers the post-conversion handler.
|
|
236
|
+
|
|
237
|
+
Raises:
|
|
238
|
+
TypeError: If struct definition is provided for unsupported node types.
|
|
239
|
+
"""
|
|
168
240
|
if for_struct_definition and for_definition not in (
|
|
169
241
|
StartParseMethod.kind,
|
|
170
242
|
TypeDef.kind,
|
|
@@ -191,7 +263,19 @@ class BaseCodeConverter:
|
|
|
191
263
|
*,
|
|
192
264
|
post_callback: Callable[[BaseAstNode], str] | None = None,
|
|
193
265
|
) -> CB_AST_DECORATOR:
|
|
194
|
-
"""Alias for pre decorator.
|
|
266
|
+
"""Alias for pre decorator.
|
|
267
|
+
|
|
268
|
+
Provides a convenient way to use the pre decorator by calling the class instance
|
|
269
|
+
directly. This is equivalent to calling the pre() method.
|
|
270
|
+
|
|
271
|
+
Args:
|
|
272
|
+
for_definition: The TokenType to associate with this conversion function.
|
|
273
|
+
for_struct_definition: Optional StructType for specific struct definitions (only for StartParseMethod and TypeDef nodes).
|
|
274
|
+
post_callback: Optional callback function for post-processing.
|
|
275
|
+
|
|
276
|
+
Returns:
|
|
277
|
+
A decorator function that registers the conversion handler.
|
|
278
|
+
"""
|
|
195
279
|
return self.pre(
|
|
196
280
|
for_definition, for_struct_definition, post_callback=post_callback
|
|
197
281
|
)
|
|
@@ -199,7 +283,20 @@ class BaseCodeConverter:
|
|
|
199
283
|
def _pre_convert_node(
|
|
200
284
|
self, node: BaseAstNode, st_type: StructType | None = None
|
|
201
285
|
) -> str:
|
|
202
|
-
"""Convert the AST node using the pre-definition function.
|
|
286
|
+
"""Convert the AST node using the pre-definition function.
|
|
287
|
+
|
|
288
|
+
This method applies the registered pre-conversion function to an AST node,
|
|
289
|
+
optionally using a specific struct type for more granular control. It handles
|
|
290
|
+
both simple node conversions and struct-specific conversions.
|
|
291
|
+
|
|
292
|
+
Args:
|
|
293
|
+
node: The AST node to convert.
|
|
294
|
+
st_type: Optional StructType for struct-specific conversions.
|
|
295
|
+
|
|
296
|
+
Returns:
|
|
297
|
+
The result of applying the pre-conversion function to the node,
|
|
298
|
+
or an empty string if no matching function is found.
|
|
299
|
+
"""
|
|
203
300
|
# syntax sugar: split generate start_parse methods by part callbacks
|
|
204
301
|
if (
|
|
205
302
|
node.kind in (StartParseMethod.kind, TypeDef.kind)
|
|
@@ -220,7 +317,21 @@ class BaseCodeConverter:
|
|
|
220
317
|
def _post_convert_node(
|
|
221
318
|
self, node: BaseAstNode, st_type: StructType | None = None
|
|
222
319
|
) -> str:
|
|
223
|
-
"""Convert the AST node using the post-definition function.
|
|
320
|
+
"""Convert the AST node using the post-definition function.
|
|
321
|
+
|
|
322
|
+
This method applies the registered post-conversion function to an AST node,
|
|
323
|
+
optionally using a specific struct type for more granular control. It's typically
|
|
324
|
+
used for closing brackets or other logic that should happen after the main
|
|
325
|
+
node processing.
|
|
326
|
+
|
|
327
|
+
Args:
|
|
328
|
+
node: The AST node to convert.
|
|
329
|
+
st_type: Optional StructType for struct-specific conversions.
|
|
330
|
+
|
|
331
|
+
Returns:
|
|
332
|
+
The result of applying the post-conversion function to the node,
|
|
333
|
+
or an empty string if no matching function is found.
|
|
334
|
+
"""
|
|
224
335
|
# syntax sugar: split generate start_parse methods by part callbacks
|
|
225
336
|
if (
|
|
226
337
|
node.kind in (StartParseMethod.kind, TypeDef.kind)
|
|
@@ -239,7 +350,19 @@ class BaseCodeConverter:
|
|
|
239
350
|
def convert_program(
|
|
240
351
|
self, ast_program: ModuleProgram, comment: str = ""
|
|
241
352
|
) -> list[str]:
|
|
242
|
-
"""Convert the module AST to code parts.
|
|
353
|
+
"""Convert the module AST to code parts.
|
|
354
|
+
|
|
355
|
+
This method converts an entire module AST into a list of code parts,
|
|
356
|
+
starting with an optional comment and processing all nodes in the AST.
|
|
357
|
+
|
|
358
|
+
Args:
|
|
359
|
+
ast_program: The ModuleProgram AST node to convert.
|
|
360
|
+
comment: An optional comment to include at the beginning of the output.
|
|
361
|
+
|
|
362
|
+
Returns:
|
|
363
|
+
A list of strings representing the converted code parts,
|
|
364
|
+
with empty strings filtered out.
|
|
365
|
+
"""
|
|
243
366
|
acc = [comment]
|
|
244
367
|
result = self.convert(ast_program, acc)
|
|
245
368
|
return [i for i in result if i]
|
|
@@ -247,7 +370,19 @@ class BaseCodeConverter:
|
|
|
247
370
|
def convert(
|
|
248
371
|
self, ast_entry: BaseAstNode, acc: list[str] | None = None
|
|
249
372
|
) -> list[str]:
|
|
250
|
-
"""Convert an AST node into code parts.
|
|
373
|
+
"""Convert an AST node into code parts.
|
|
374
|
+
|
|
375
|
+
This method dispatches the conversion of an AST node to the appropriate
|
|
376
|
+
conversion method based on the node's type. It builds up a list of code
|
|
377
|
+
parts by processing the node and its children.
|
|
378
|
+
|
|
379
|
+
Args:
|
|
380
|
+
ast_entry: The AST node to convert.
|
|
381
|
+
acc: An optional accumulator list to append code parts to.
|
|
382
|
+
|
|
383
|
+
Returns:
|
|
384
|
+
A list of strings representing the converted code parts.
|
|
385
|
+
"""
|
|
251
386
|
acc = acc or []
|
|
252
387
|
match ast_entry.kind:
|
|
253
388
|
case TokenType.MODULE:
|
|
@@ -307,6 +442,22 @@ class BaseCodeConverter:
|
|
|
307
442
|
def _convert_logic_filter(
|
|
308
443
|
self, ast_entry: FilterOr | FilterAnd | FilterNot, acc: list[str]
|
|
309
444
|
) -> None:
|
|
445
|
+
"""Convert a logical filter node (OR, AND, NOT) and its children.
|
|
446
|
+
|
|
447
|
+
This method handles the conversion of logical filter operations, appending
|
|
448
|
+
the pre-conversion result, processing all child nodes, and then appending
|
|
449
|
+
the post-conversion result.
|
|
450
|
+
|
|
451
|
+
For example, an AND filter with two children would be converted as:
|
|
452
|
+
AND(body=[F1, F2]) ->
|
|
453
|
+
AND ( (OPEN)
|
|
454
|
+
F1 and F2
|
|
455
|
+
) (CLOSE)
|
|
456
|
+
|
|
457
|
+
Args:
|
|
458
|
+
ast_entry: The logical filter node (FilterOr, FilterAnd, or FilterNot) to convert.
|
|
459
|
+
acc: The accumulator list to append code parts to.
|
|
460
|
+
"""
|
|
310
461
|
# AND(body=[F1, F2]) ->
|
|
311
462
|
# AND ( (OPEN)
|
|
312
463
|
# F1 and F2
|
|
@@ -317,7 +468,15 @@ class BaseCodeConverter:
|
|
|
317
468
|
acc.append(self._post_convert_node(ast_entry))
|
|
318
469
|
|
|
319
470
|
def _convert_module(self, ast_entry: ModuleProgram, acc: list[str]) -> None:
|
|
320
|
-
"""Handle module conversion.
|
|
471
|
+
"""Handle module conversion.
|
|
472
|
+
|
|
473
|
+
This method processes all nodes in a module's body by recursively converting
|
|
474
|
+
each one and appending the results to the accumulator.
|
|
475
|
+
|
|
476
|
+
Args:
|
|
477
|
+
ast_entry: The ModuleProgram node to convert.
|
|
478
|
+
acc: The accumulator list to append code parts to.
|
|
479
|
+
"""
|
|
321
480
|
for node in ast_entry.body:
|
|
322
481
|
self.convert(node, acc)
|
|
323
482
|
|
|
@@ -51,7 +51,6 @@ from ssc_codegen.ast_ import (
|
|
|
51
51
|
ExprIndex,
|
|
52
52
|
ExprListStringJoin,
|
|
53
53
|
ExprIsEqual,
|
|
54
|
-
ExprIsNotEqual,
|
|
55
54
|
ExprIsContains,
|
|
56
55
|
ExprStringIsRegex,
|
|
57
56
|
ExprToInt,
|
|
@@ -848,7 +847,7 @@ def pre_is_css(node: ExprIsCss) -> str:
|
|
|
848
847
|
expr = f"({prv}.querySelector({query}) === null)"
|
|
849
848
|
if invert:
|
|
850
849
|
expr = f"!{expr}"
|
|
851
|
-
expr = f
|
|
850
|
+
expr = f"if {expr}"
|
|
852
851
|
expr = f"{expr} throw new Error({msg});"
|
|
853
852
|
if is_last_var_no_ret(node):
|
|
854
853
|
return expr
|
|
@@ -865,7 +864,7 @@ def pre_is_xpath(node: ExprIsXpath) -> str:
|
|
|
865
864
|
expr = f"(document.evaluate({query}, {prv}, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue === null)"
|
|
866
865
|
if invert:
|
|
867
866
|
expr = f"!{expr}"
|
|
868
|
-
expr = f
|
|
867
|
+
expr = f"if {expr}"
|
|
869
868
|
code = [
|
|
870
869
|
f"{expr}",
|
|
871
870
|
"{",
|
|
@@ -1076,8 +1075,8 @@ def pre_has_attr(node: ExprHasAttr) -> str:
|
|
|
1076
1075
|
invert = node.kwargs["invert"]
|
|
1077
1076
|
expr = f"!{prv}?.hasAttribute({key})"
|
|
1078
1077
|
if invert:
|
|
1079
|
-
expr = f
|
|
1080
|
-
expr = f"if ({expr})"
|
|
1078
|
+
expr = f"!({expr})"
|
|
1079
|
+
expr = f"if ({expr}) throw new Error({msg}); "
|
|
1081
1080
|
if is_last_var_no_ret(node):
|
|
1082
1081
|
return expr
|
|
1083
1082
|
return expr + f"let {nxt} = {prv};"
|
|
@@ -67,17 +67,16 @@ CONVERTER = BasePyCodeConverter()
|
|
|
67
67
|
def pre_init(_node: StructInitMethod) -> str:
|
|
68
68
|
return (
|
|
69
69
|
INDENT_METHOD
|
|
70
|
-
+ "def __init__(self, document: Union[str,
|
|
70
|
+
+ "def __init__(self, document: Union[str, html.HtmlElement]) -> None:\n"
|
|
71
71
|
+ INDENT_METHOD_BODY
|
|
72
|
-
+ "self._document =
|
|
72
|
+
+ "self._document = html.fromstring(document) if isinstance(document, str) else document"
|
|
73
73
|
)
|
|
74
74
|
|
|
75
75
|
|
|
76
76
|
@CONVERTER(StructPreValidateMethod.kind)
|
|
77
77
|
def pre_struct_pre_validate(_node: StructPreValidateMethod) -> str:
|
|
78
78
|
return (
|
|
79
|
-
INDENT_METHOD
|
|
80
|
-
+ "def _pre_validate(self, v: Union[etree._Element, List[etree._Element]]) -> None:"
|
|
79
|
+
INDENT_METHOD + "def _pre_validate(self, v: html.HtmlElement) -> None:"
|
|
81
80
|
)
|
|
82
81
|
|
|
83
82
|
|
|
@@ -85,7 +84,7 @@ def pre_struct_pre_validate(_node: StructPreValidateMethod) -> str:
|
|
|
85
84
|
def pre_struct_part_doc_method(_node: StructPartDocMethod) -> str:
|
|
86
85
|
return (
|
|
87
86
|
INDENT_METHOD
|
|
88
|
-
+ "def _split_doc(self, v:
|
|
87
|
+
+ "def _split_doc(self, v: html.HtmlElement) -> List[html.HtmlElement]:"
|
|
89
88
|
)
|
|
90
89
|
|
|
91
90
|
|
|
@@ -96,13 +95,13 @@ def pre_struct_field_method(node: StructFieldMethod) -> str:
|
|
|
96
95
|
name = MAGIC_METHODS.get(name, name)
|
|
97
96
|
return (
|
|
98
97
|
INDENT_METHOD
|
|
99
|
-
+ f"def _parse_{name}(self, v:
|
|
98
|
+
+ f"def _parse_{name}(self, v: html.HtmlElement) -> {type_}:"
|
|
100
99
|
)
|
|
101
100
|
|
|
102
101
|
|
|
103
102
|
@CONVERTER(ModuleImports.kind)
|
|
104
103
|
def pre_imports(_: ModuleImports) -> str:
|
|
105
|
-
return IMPORTS_MIN + """from lxml import
|
|
104
|
+
return IMPORTS_MIN + """from lxml import html, etree"""
|
|
106
105
|
|
|
107
106
|
|
|
108
107
|
@CONVERTER(ExprCss.kind)
|
|
@@ -112,7 +111,7 @@ def pre_css(node: ExprCss) -> str:
|
|
|
112
111
|
)
|
|
113
112
|
prv, nxt = prev_next_var(node)
|
|
114
113
|
query = py_get_classvar_hook_or_value(node, "query")
|
|
115
|
-
return indent + f"{nxt} =
|
|
114
|
+
return indent + f"{nxt} = {prv}.cssselect({query})[0]"
|
|
116
115
|
|
|
117
116
|
|
|
118
117
|
@CONVERTER(ExprCssAll.kind)
|
|
@@ -122,7 +121,7 @@ def pre_css_all(node: ExprCssAll) -> str:
|
|
|
122
121
|
)
|
|
123
122
|
prv, nxt = prev_next_var(node)
|
|
124
123
|
query = py_get_classvar_hook_or_value(node, "query")
|
|
125
|
-
return indent + f"{nxt} =
|
|
124
|
+
return indent + f"{nxt} = {prv}.cssselect({query})"
|
|
126
125
|
|
|
127
126
|
|
|
128
127
|
@CONVERTER(ExprXpath.kind)
|
|
@@ -154,8 +153,9 @@ def pre_html_attr(node: ExprGetHtmlAttr) -> str:
|
|
|
154
153
|
keys = node.kwargs["key"]
|
|
155
154
|
if len(keys) == 1:
|
|
156
155
|
key = keys[0]
|
|
157
|
-
return indent + f"{nxt} = {prv}.
|
|
158
|
-
|
|
156
|
+
return indent + f"{nxt} = {prv}.get({key!r})"
|
|
157
|
+
key_accesses = [f"{prv}.get({k!r})" for k in keys]
|
|
158
|
+
return indent + f"{nxt} = [{', '.join(key_accesses)}]"
|
|
159
159
|
|
|
160
160
|
|
|
161
161
|
@CONVERTER(ExprGetHtmlAttrAll.kind)
|
|
@@ -167,8 +167,8 @@ def pre_html_attr_all(node: ExprGetHtmlAttrAll) -> str:
|
|
|
167
167
|
keys = node.kwargs["key"]
|
|
168
168
|
if len(keys) == 1:
|
|
169
169
|
key = keys[0]
|
|
170
|
-
return indent + f"{nxt} = [e.
|
|
171
|
-
return indent + f"{nxt} = [e.
|
|
170
|
+
return indent + f"{nxt} = [e.get({key!r}) for e in {prv}]"
|
|
171
|
+
return indent + f"{nxt} = [e.get(k) for e in {prv} for k in {keys}]"
|
|
172
172
|
|
|
173
173
|
|
|
174
174
|
@CONVERTER(ExprGetHtmlText.kind)
|
|
@@ -177,7 +177,7 @@ def pre_html_text(node: ExprGetHtmlText) -> str:
|
|
|
177
177
|
INDENT_DEFAULT_BODY if have_default_expr(node) else INDENT_METHOD_BODY
|
|
178
178
|
)
|
|
179
179
|
prv, nxt = prev_next_var(node)
|
|
180
|
-
return indent + f"{nxt} =
|
|
180
|
+
return indent + f"{nxt} = {prv}.text_content()"
|
|
181
181
|
|
|
182
182
|
|
|
183
183
|
@CONVERTER(ExprGetHtmlTextAll.kind)
|
|
@@ -186,7 +186,7 @@ def pre_html_text_all(node: ExprGetHtmlTextAll) -> str:
|
|
|
186
186
|
INDENT_DEFAULT_BODY if have_default_expr(node) else INDENT_METHOD_BODY
|
|
187
187
|
)
|
|
188
188
|
prv, nxt = prev_next_var(node)
|
|
189
|
-
return indent + f"{nxt} = [
|
|
189
|
+
return indent + f"{nxt} = [e.text_content() for e in {prv}]"
|
|
190
190
|
|
|
191
191
|
|
|
192
192
|
@CONVERTER(ExprGetHtmlRaw.kind)
|
|
@@ -195,7 +195,7 @@ def pre_html_raw(node: ExprGetHtmlRaw) -> str:
|
|
|
195
195
|
INDENT_DEFAULT_BODY if have_default_expr(node) else INDENT_METHOD_BODY
|
|
196
196
|
)
|
|
197
197
|
prv, nxt = prev_next_var(node)
|
|
198
|
-
return indent + f"{nxt} =
|
|
198
|
+
return indent + f"{nxt} = html.tostring({prv}, encoding='unicode')"
|
|
199
199
|
|
|
200
200
|
|
|
201
201
|
@CONVERTER(ExprGetHtmlRawAll.kind)
|
|
@@ -206,7 +206,7 @@ def pre_html_raw_all(node: ExprGetHtmlRawAll) -> str:
|
|
|
206
206
|
prv, nxt = prev_next_var(node)
|
|
207
207
|
return (
|
|
208
208
|
indent
|
|
209
|
-
+ f"{nxt} = [
|
|
209
|
+
+ f"{nxt} = [html.tostring(e, encoding='unicode') for e in {prv}]"
|
|
210
210
|
)
|
|
211
211
|
|
|
212
212
|
|
|
@@ -220,9 +220,9 @@ def pre_is_css(node: ExprIsCss) -> str:
|
|
|
220
220
|
msg = py_get_classvar_hook_or_value(node, "msg")
|
|
221
221
|
invert = node.kwargs["invert"]
|
|
222
222
|
|
|
223
|
-
expr = f"
|
|
223
|
+
expr = f"{prv}.cssselect({query})"
|
|
224
224
|
if invert:
|
|
225
|
-
expr = f"not
|
|
225
|
+
expr = f"not {expr}"
|
|
226
226
|
|
|
227
227
|
expr = indent + f"assert {expr}, {msg}"
|
|
228
228
|
if is_last_var_no_ret(node):
|
|
@@ -255,19 +255,19 @@ def pre_has_attr(node: ExprHasAttr) -> str:
|
|
|
255
255
|
INDENT_DEFAULT_BODY if have_default_expr(node) else INDENT_METHOD_BODY
|
|
256
256
|
)
|
|
257
257
|
prv, nxt = prev_next_var(node)
|
|
258
|
-
key, msg = node.unpack_args()
|
|
259
258
|
key = py_get_classvar_hook_or_value(node, "key")
|
|
260
259
|
msg = py_get_classvar_hook_or_value(node, "msg")
|
|
261
260
|
invert = node.kwargs["invert"]
|
|
262
261
|
|
|
263
|
-
expr = f"{prv}.
|
|
262
|
+
expr = f"{prv}.get({key}) is not None"
|
|
264
263
|
if invert:
|
|
265
264
|
expr = f"not ({expr})"
|
|
266
265
|
|
|
267
266
|
expr = indent + f"assert {expr}, {msg}"
|
|
268
267
|
if is_last_var_no_ret(node):
|
|
269
268
|
return expr
|
|
270
|
-
|
|
269
|
+
# Исправлено: добавлена запятая в join
|
|
270
|
+
return "\n".join([expr, indent + f"{nxt} = {prv}"])
|
|
271
271
|
|
|
272
272
|
|
|
273
273
|
@CONVERTER(ExprListHasAttr.kind)
|
|
@@ -280,7 +280,7 @@ def pre_list_has_attr(node: ExprListHasAttr) -> str:
|
|
|
280
280
|
msg = py_get_classvar_hook_or_value(node, "msg")
|
|
281
281
|
invert = node.kwargs["invert"]
|
|
282
282
|
|
|
283
|
-
expr = f"all(i.
|
|
283
|
+
expr = f"all(i.get({key}) is not None for i in {prv})"
|
|
284
284
|
if invert:
|
|
285
285
|
expr = f"not ({expr})"
|
|
286
286
|
|
|
@@ -318,7 +318,7 @@ def pre_css_remove(node: ExprCssElementRemove) -> str:
|
|
|
318
318
|
|
|
319
319
|
return (
|
|
320
320
|
indent
|
|
321
|
-
+ f"[e.getparent().remove(e) for e in
|
|
321
|
+
+ f"[e.getparent().remove(e) for e in {prv}.cssselect({query}) if e.getparent() is not None]\n"
|
|
322
322
|
+ indent
|
|
323
323
|
+ f"{nxt} = {prv}"
|
|
324
324
|
)
|
|
@@ -365,9 +365,9 @@ def pre_doc_filter_css(node: FilterDocCss) -> str:
|
|
|
365
365
|
def pre_doc_filter_has_attr(node: FilterDocHasAttr) -> str:
|
|
366
366
|
keys = node.kwargs["keys"]
|
|
367
367
|
if len(keys) == 1:
|
|
368
|
-
expr = f"{keys[0]!r}
|
|
368
|
+
expr = f"e.get({keys[0]!r}) is not None"
|
|
369
369
|
else:
|
|
370
|
-
expr = f"any(i
|
|
370
|
+
expr = f"any(e.get(i) is not None for i in {keys})"
|
|
371
371
|
if not is_first_node_cond(node) and is_prev_node_atomic_cond(node):
|
|
372
372
|
return "and " + expr
|
|
373
373
|
return expr
|
|
@@ -377,9 +377,9 @@ def pre_doc_filter_has_attr(node: FilterDocHasAttr) -> str:
|
|
|
377
377
|
def pre_doc_filter_attr_eq(node: FilterDocAttrEqual) -> str:
|
|
378
378
|
key, values = node.unpack_args()
|
|
379
379
|
if len(values) == 1:
|
|
380
|
-
expr = f"e.
|
|
380
|
+
expr = f"e.get({key!r}) == {values[0]!r}"
|
|
381
381
|
else:
|
|
382
|
-
expr = f"e.
|
|
382
|
+
expr = f"e.get({key!r}) in {values}"
|
|
383
383
|
if not is_first_node_cond(node) and is_prev_node_atomic_cond(node):
|
|
384
384
|
return "and " + expr
|
|
385
385
|
return expr
|
|
@@ -389,9 +389,9 @@ def pre_doc_filter_attr_eq(node: FilterDocAttrEqual) -> str:
|
|
|
389
389
|
def pre_doc_filter_attr_contains(node: FilterDocAttrContains) -> str:
|
|
390
390
|
key, values = node.unpack_args()
|
|
391
391
|
if len(values) == 1:
|
|
392
|
-
expr = f"{values[0]!r} in e.
|
|
392
|
+
expr = f"{values[0]!r} in (e.get({key!r}) or '')"
|
|
393
393
|
else:
|
|
394
|
-
expr = f"any(i in e.
|
|
394
|
+
expr = f"any(i in (e.get({key!r}) or '') for i in {values})"
|
|
395
395
|
if not is_first_node_cond(node) and is_prev_node_atomic_cond(node):
|
|
396
396
|
return "and " + expr
|
|
397
397
|
return expr
|
|
@@ -401,10 +401,10 @@ def pre_doc_filter_attr_contains(node: FilterDocAttrContains) -> str:
|
|
|
401
401
|
def pre_doc_filter_attr_starts(node: FilterDocAttrContains) -> str:
|
|
402
402
|
key, values = node.unpack_args()
|
|
403
403
|
if len(values) == 1:
|
|
404
|
-
expr = f"e.
|
|
404
|
+
expr = f"(e.get({key!r}) or '').startswith({values[0]!r})"
|
|
405
405
|
else:
|
|
406
406
|
# str.startswith() arg allow tuple[str, ...]
|
|
407
|
-
expr = f"e.
|
|
407
|
+
expr = f"(e.get({key!r}) or '').startswith({values})"
|
|
408
408
|
if not is_first_node_cond(node) and is_prev_node_atomic_cond(node):
|
|
409
409
|
return "and " + expr
|
|
410
410
|
return expr
|
|
@@ -414,10 +414,10 @@ def pre_doc_filter_attr_starts(node: FilterDocAttrContains) -> str:
|
|
|
414
414
|
def pre_doc_filter_attr_ends(node: FilterDocAttrContains) -> str:
|
|
415
415
|
key, values = node.unpack_args()
|
|
416
416
|
if len(values) == 1:
|
|
417
|
-
expr = f"e.
|
|
417
|
+
expr = f"(e.get({key!r}) or '').endswith({values[0]!r})"
|
|
418
418
|
else:
|
|
419
419
|
# str.endswith() arg allow tuple[str, ...]
|
|
420
|
-
expr = f"e.
|
|
420
|
+
expr = f"(e.get({key!r}) or '').endswith({values})"
|
|
421
421
|
if not is_first_node_cond(node) and is_prev_node_atomic_cond(node):
|
|
422
422
|
return "and " + expr
|
|
423
423
|
return expr
|
|
@@ -427,11 +427,12 @@ def pre_doc_filter_attr_ends(node: FilterDocAttrContains) -> str:
|
|
|
427
427
|
def pre_doc_filter_attr_re(node: FilterDocAttrRegex) -> str:
|
|
428
428
|
key, pattern, ignore_case = node.unpack_args()
|
|
429
429
|
flags = py_regex_flags(ignore_case)
|
|
430
|
+
|
|
431
|
+
attr_value = f"e.get({key!r}) or ''"
|
|
430
432
|
if flags:
|
|
431
|
-
expr = f"re.search({pattern!r},
|
|
433
|
+
expr = f"re.search({pattern!r}, {attr_value}, {flags})"
|
|
432
434
|
else:
|
|
433
|
-
expr = f"re.search({pattern!r},
|
|
434
|
-
expr = f"bool({expr})"
|
|
435
|
+
expr = f"re.search({pattern!r}, {attr_value})"
|
|
435
436
|
if not is_first_node_cond(node) and is_prev_node_atomic_cond(node):
|
|
436
437
|
return "and " + expr
|
|
437
438
|
return expr
|
|
@@ -442,10 +443,12 @@ def pre_doc_filter_is_regex_text(node: FilterDocIsRegexText) -> str:
|
|
|
442
443
|
pattern, ignore_case = node.unpack_args()
|
|
443
444
|
|
|
444
445
|
flags = py_regex_flags(ignore_case)
|
|
446
|
+
|
|
447
|
+
text_content_call = "e.text_content()"
|
|
445
448
|
if flags:
|
|
446
|
-
expr = f"re.search({pattern!r},
|
|
449
|
+
expr = f"re.search({pattern!r}, {text_content_call}, {flags})"
|
|
447
450
|
else:
|
|
448
|
-
expr = f"re.search({pattern!r},
|
|
451
|
+
expr = f"re.search({pattern!r}, {text_content_call})"
|
|
449
452
|
expr = f"bool({expr})"
|
|
450
453
|
if not is_first_node_cond(node) and is_prev_node_atomic_cond(node):
|
|
451
454
|
return "and " + expr
|
|
@@ -457,10 +460,11 @@ def pre_doc_filter_is_regex_raw(node: FilterDocIsRegexText) -> str:
|
|
|
457
460
|
pattern, ignore_case = node.unpack_args()
|
|
458
461
|
|
|
459
462
|
flags = py_regex_flags(ignore_case)
|
|
463
|
+
raw_content_call = "etree.tostring(e, encoding='unicode')"
|
|
460
464
|
if flags:
|
|
461
|
-
expr = f"re.search({pattern!r},
|
|
465
|
+
expr = f"re.search({pattern!r}, {raw_content_call}, {flags})"
|
|
462
466
|
else:
|
|
463
|
-
expr = f"re.search({pattern!r},
|
|
467
|
+
expr = f"re.search({pattern!r}, {raw_content_call})"
|
|
464
468
|
expr = f"bool({expr})"
|
|
465
469
|
if not is_first_node_cond(node) and is_prev_node_atomic_cond(node):
|
|
466
470
|
return "and " + expr
|
|
@@ -470,10 +474,12 @@ def pre_doc_filter_is_regex_raw(node: FilterDocIsRegexText) -> str:
|
|
|
470
474
|
@CONVERTER(FilterDocHasText.kind)
|
|
471
475
|
def pre_doc_filter_has_text(node: FilterDocHasText) -> str:
|
|
472
476
|
values = node.kwargs["values"]
|
|
477
|
+
|
|
478
|
+
text_content_call = "e.text_content()"
|
|
473
479
|
if len(values) == 1:
|
|
474
|
-
expr = f"{values[0]!r} in
|
|
480
|
+
expr = f"{values[0]!r} in {text_content_call}"
|
|
475
481
|
else:
|
|
476
|
-
expr = f"any(i in
|
|
482
|
+
expr = f"any(i in {text_content_call} for i in {values})"
|
|
477
483
|
if not is_first_node_cond(node) and is_prev_node_atomic_cond(node):
|
|
478
484
|
return "and " + expr
|
|
479
485
|
return expr
|
|
@@ -482,11 +488,12 @@ def pre_doc_filter_has_text(node: FilterDocHasText) -> str:
|
|
|
482
488
|
@CONVERTER(FilterDocHasRaw.kind)
|
|
483
489
|
def pre_doc_filter_has_raw(node: FilterDocHasText) -> str:
|
|
484
490
|
values = node.kwargs["values"]
|
|
491
|
+
|
|
485
492
|
if len(values) == 1:
|
|
486
|
-
expr = f"{values[0]!r} in
|
|
493
|
+
expr = f"{values[0]!r} in html.tostring(e, encoding='unicode')"
|
|
487
494
|
else:
|
|
488
495
|
expr = (
|
|
489
|
-
f"any(i in
|
|
496
|
+
f"any(i in html.tostring(e, encoding='unicode') for i in {values})"
|
|
490
497
|
)
|
|
491
498
|
if not is_first_node_cond(node) and is_prev_node_atomic_cond(node):
|
|
492
499
|
return "and " + expr
|
|
@@ -26,8 +26,8 @@ def setup_logger(
|
|
|
26
26
|
name: str = "ssc_gen",
|
|
27
27
|
level: int = logging.INFO,
|
|
28
28
|
colored: bool = True,
|
|
29
|
-
fmt: str = "[%(levelname)-8s] %(name)s: %(
|
|
30
|
-
datefmt: str = "%Y-%m-%d %H:%M:%S",
|
|
29
|
+
fmt: str = "[%(levelname)-8s] %(name)s: - %(message)s", # %(asctime)s
|
|
30
|
+
datefmt: str | None = None, # "%Y-%m-%d %H:%M:%S",
|
|
31
31
|
) -> logging.Logger:
|
|
32
32
|
logger = logging.getLogger(name)
|
|
33
33
|
logger.setLevel(level)
|
|
@@ -58,7 +58,6 @@ FMT_MAPPING_METHODS = {
|
|
|
58
58
|
TokenType.EXPR_LIST_JOIN: "join",
|
|
59
59
|
TokenType.EXPR_LIST_LEN: "to_len",
|
|
60
60
|
TokenType.IS_EQUAL: "is_equal",
|
|
61
|
-
TokenType.IS_NOT_EQUAL: "is_not_equal",
|
|
62
61
|
TokenType.IS_CONTAINS: "is_contains",
|
|
63
62
|
TokenType.IS_CSS: "is_css",
|
|
64
63
|
TokenType.IS_XPATH: "is_xpath",
|
|
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
|
{ssc_codegen-0.13.0 → ssc_codegen-0.13.2}/ssc_codegen/converters/templates/lua_css_compat.py
RENAMED
|
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
|