ssc_codegen 0.10.2__tar.gz → 0.11.0__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.10.2 → ssc_codegen-0.11.0}/PKG-INFO +5 -3
- {ssc_codegen-0.10.2 → ssc_codegen-0.11.0}/README.md +4 -2
- {ssc_codegen-0.10.2 → ssc_codegen-0.11.0}/pyproject.toml +1 -1
- {ssc_codegen-0.10.2 → ssc_codegen-0.11.0}/ssc_codegen/__init__.py +2 -3
- {ssc_codegen-0.10.2 → ssc_codegen-0.11.0}/ssc_codegen/ast_/base.py +7 -1
- ssc_codegen-0.11.0/ssc_codegen/ast_build/builder.py +422 -0
- ssc_codegen-0.11.0/ssc_codegen/ast_build/main.py +91 -0
- {ssc_codegen-0.10.2 → ssc_codegen-0.11.0}/ssc_codegen/compiler.py +5 -23
- {ssc_codegen-0.10.2 → ssc_codegen-0.11.0}/ssc_codegen/converters/base.py +117 -22
- {ssc_codegen-0.10.2 → ssc_codegen-0.11.0}/ssc_codegen/converters/helpers.py +17 -9
- {ssc_codegen-0.10.2 → ssc_codegen-0.11.0}/ssc_codegen/converters/js_pure.py +241 -61
- {ssc_codegen-0.10.2 → ssc_codegen-0.11.0}/ssc_codegen/converters/lua_htmlparser.py +109 -64
- {ssc_codegen-0.10.2 → ssc_codegen-0.11.0}/ssc_codegen/converters/py_base.py +149 -102
- {ssc_codegen-0.10.2 → ssc_codegen-0.11.0}/ssc_codegen/converters/templates/js_pure.py +0 -31
- {ssc_codegen-0.10.2 → ssc_codegen-0.11.0}/ssc_codegen/document.py +24 -11
- {ssc_codegen-0.10.2 → ssc_codegen-0.11.0}/ssc_codegen/schema.py +1 -1
- {ssc_codegen-0.10.2 → ssc_codegen-0.11.0}/ssc_codegen/static_checker/__init__.py +2 -0
- {ssc_codegen-0.10.2 → ssc_codegen-0.11.0}/ssc_codegen/static_checker/base.py +4 -2
- {ssc_codegen-0.10.2 → ssc_codegen-0.11.0}/ssc_codegen/static_checker/callbacks.py +54 -4
- ssc_codegen-0.10.2/ssc_codegen/ast_build/main.py +0 -516
- {ssc_codegen-0.10.2 → ssc_codegen-0.11.0}/.gitignore +0 -0
- {ssc_codegen-0.10.2 → ssc_codegen-0.11.0}/LICENSE +0 -0
- {ssc_codegen-0.10.2 → ssc_codegen-0.11.0}/ssc_codegen/_compat.py +0 -0
- {ssc_codegen-0.10.2 → ssc_codegen-0.11.0}/ssc_codegen/ast_/__init__.py +0 -0
- {ssc_codegen-0.10.2 → ssc_codegen-0.11.0}/ssc_codegen/ast_/nodes_array.py +0 -0
- {ssc_codegen-0.10.2 → ssc_codegen-0.11.0}/ssc_codegen/ast_/nodes_cast.py +0 -0
- {ssc_codegen-0.10.2 → ssc_codegen-0.11.0}/ssc_codegen/ast_/nodes_core.py +0 -0
- {ssc_codegen-0.10.2 → ssc_codegen-0.11.0}/ssc_codegen/ast_/nodes_filter.py +0 -0
- {ssc_codegen-0.10.2 → ssc_codegen-0.11.0}/ssc_codegen/ast_/nodes_selectors.py +0 -0
- {ssc_codegen-0.10.2 → ssc_codegen-0.11.0}/ssc_codegen/ast_/nodes_string.py +0 -0
- {ssc_codegen-0.10.2 → ssc_codegen-0.11.0}/ssc_codegen/ast_/nodes_validate.py +0 -0
- {ssc_codegen-0.10.2 → ssc_codegen-0.11.0}/ssc_codegen/ast_build/__init__.py +0 -0
- {ssc_codegen-0.10.2 → ssc_codegen-0.11.0}/ssc_codegen/ast_build/utils.py +0 -0
- {ssc_codegen-0.10.2 → ssc_codegen-0.11.0}/ssc_codegen/ast_grep_rules/js_rules.yml +0 -0
- {ssc_codegen-0.10.2 → ssc_codegen-0.11.0}/ssc_codegen/ast_grep_rules/py_rules.yml +0 -0
- {ssc_codegen-0.10.2 → ssc_codegen-0.11.0}/ssc_codegen/cli/__init__.py +0 -0
- {ssc_codegen-0.10.2 → ssc_codegen-0.11.0}/ssc_codegen/cli/ast_grep.py +0 -0
- {ssc_codegen-0.10.2 → ssc_codegen-0.11.0}/ssc_codegen/cli/cli_callbacks.py +0 -0
- {ssc_codegen-0.10.2 → ssc_codegen-0.11.0}/ssc_codegen/cli/cli_utils.py +0 -0
- {ssc_codegen-0.10.2 → ssc_codegen-0.11.0}/ssc_codegen/cli/code_callbacks.py +0 -0
- {ssc_codegen-0.10.2 → ssc_codegen-0.11.0}/ssc_codegen/cli/consts.py +0 -0
- {ssc_codegen-0.10.2 → ssc_codegen-0.11.0}/ssc_codegen/cli/main.py +0 -0
- {ssc_codegen-0.10.2 → ssc_codegen-0.11.0}/ssc_codegen/cli/runtime_parse_runners.py +0 -0
- {ssc_codegen-0.10.2 → ssc_codegen-0.11.0}/ssc_codegen/converters/__init__.py +0 -0
- {ssc_codegen-0.10.2 → ssc_codegen-0.11.0}/ssc_codegen/converters/go_goquery.py +0 -0
- {ssc_codegen-0.10.2 → ssc_codegen-0.11.0}/ssc_codegen/converters/py_bs4.py +0 -0
- {ssc_codegen-0.10.2 → ssc_codegen-0.11.0}/ssc_codegen/converters/py_lxml.py +0 -0
- {ssc_codegen-0.10.2 → ssc_codegen-0.11.0}/ssc_codegen/converters/py_parsel.py +0 -0
- {ssc_codegen-0.10.2 → ssc_codegen-0.11.0}/ssc_codegen/converters/py_selectolax.py +0 -0
- {ssc_codegen-0.10.2 → ssc_codegen-0.11.0}/ssc_codegen/converters/templates/__init__.py +0 -0
- {ssc_codegen-0.10.2 → ssc_codegen-0.11.0}/ssc_codegen/converters/templates/go_goquery.py +0 -0
- {ssc_codegen-0.10.2 → ssc_codegen-0.11.0}/ssc_codegen/converters/templates/lua_base.py +0 -0
- {ssc_codegen-0.10.2 → ssc_codegen-0.11.0}/ssc_codegen/converters/templates/lua_css_compat.py +0 -0
- {ssc_codegen-0.10.2 → ssc_codegen-0.11.0}/ssc_codegen/converters/templates/lua_re_compat.py +0 -0
- {ssc_codegen-0.10.2 → ssc_codegen-0.11.0}/ssc_codegen/converters/templates/py_base.py +0 -0
- {ssc_codegen-0.10.2 → ssc_codegen-0.11.0}/ssc_codegen/document_utlis.py +0 -0
- {ssc_codegen-0.10.2 → ssc_codegen-0.11.0}/ssc_codegen/json_struct.py +0 -0
- {ssc_codegen-0.10.2 → ssc_codegen-0.11.0}/ssc_codegen/json_to_scc.py +0 -0
- {ssc_codegen-0.10.2 → ssc_codegen-0.11.0}/ssc_codegen/logs.py +0 -0
- {ssc_codegen-0.10.2 → ssc_codegen-0.11.0}/ssc_codegen/pseudo_selectors.py +0 -0
- {ssc_codegen-0.10.2 → ssc_codegen-0.11.0}/ssc_codegen/selector_utils.py +0 -0
- {ssc_codegen-0.10.2 → ssc_codegen-0.11.0}/ssc_codegen/str_utils.py +0 -0
- {ssc_codegen-0.10.2 → ssc_codegen-0.11.0}/ssc_codegen/tokens.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: ssc_codegen
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.11.0
|
|
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
|
|
@@ -248,8 +248,10 @@ You can use any html source:
|
|
|
248
248
|
## See also
|
|
249
249
|
|
|
250
250
|
|
|
251
|
-
- [Brief](docs/briefing.md) about css selectors and regular expressions
|
|
252
|
-
- [
|
|
251
|
+
- [Brief](docs/briefing.md) about css selectors and regular expressions
|
|
252
|
+
- [Explain](docs/explain.md) short document on how to understand DSL syntax
|
|
253
|
+
- [LLM](llm.md) experimental prompt for generate code
|
|
254
|
+
- [Explain](docs/explain.md) short note how to explain and read sscgen schema configs
|
|
253
255
|
- [Quickstart](docs/quickstart.md) about css selectors and regular expressions.
|
|
254
256
|
- [Tutorial](docs/tutorial.md) basic usage ssc-gen
|
|
255
257
|
- [AST reference](docs/ast_reference.md) about generation code from AST
|
|
@@ -211,8 +211,10 @@ You can use any html source:
|
|
|
211
211
|
## See also
|
|
212
212
|
|
|
213
213
|
|
|
214
|
-
- [Brief](docs/briefing.md) about css selectors and regular expressions
|
|
215
|
-
- [
|
|
214
|
+
- [Brief](docs/briefing.md) about css selectors and regular expressions
|
|
215
|
+
- [Explain](docs/explain.md) short document on how to understand DSL syntax
|
|
216
|
+
- [LLM](llm.md) experimental prompt for generate code
|
|
217
|
+
- [Explain](docs/explain.md) short note how to explain and read sscgen schema configs
|
|
216
218
|
- [Quickstart](docs/quickstart.md) about css selectors and regular expressions.
|
|
217
219
|
- [Tutorial](docs/tutorial.md) basic usage ssc-gen
|
|
218
220
|
- [AST reference](docs/ast_reference.md) about generation code from AST
|
|
@@ -7,7 +7,7 @@ from ssc_codegen.schema import ItemSchema, DictSchema, ListSchema, FlatListSchem
|
|
|
7
7
|
|
|
8
8
|
setup_logger()
|
|
9
9
|
|
|
10
|
-
VERSION = "0.
|
|
10
|
+
VERSION = "0.11.0"
|
|
11
11
|
|
|
12
12
|
|
|
13
13
|
class __MISSING(object):
|
|
@@ -68,7 +68,7 @@ def F() -> DocumentFilter:
|
|
|
68
68
|
return DocumentFilter()
|
|
69
69
|
|
|
70
70
|
|
|
71
|
-
def CV(value, self_cls_path: str | None = None, *, returns: bool = False) -> ClassVarDocument:
|
|
71
|
+
def CV(value: int | str | float | bool | None | list, self_cls_path: str | None = None, *, returns: bool = False) -> ClassVarDocument:
|
|
72
72
|
"""Shortcut as ClassVarDocument(value, self_cls, parse_returns) object
|
|
73
73
|
|
|
74
74
|
pass `self_cls_path` argument if need use this var inner schema context:
|
|
@@ -90,5 +90,4 @@ class A(ItemShema):
|
|
|
90
90
|
```
|
|
91
91
|
|
|
92
92
|
"""
|
|
93
|
-
|
|
94
93
|
return ClassVarDocument(value, self_cls_path, parse_returns=returns)
|
|
@@ -45,10 +45,16 @@ class BaseAstNode(Generic[T_MAPPING_FIELD, T_ARGUMENTS]):
|
|
|
45
45
|
"""extract all values from kwargs field wout keys"""
|
|
46
46
|
return tuple(self.kwargs.values()) # type: ignore
|
|
47
47
|
|
|
48
|
-
# todo: cached property?
|
|
49
48
|
@property
|
|
50
49
|
def index(self) -> int:
|
|
51
50
|
if self.parent:
|
|
51
|
+
# self.parent.body.index(self) add side effect if expr has same operations:
|
|
52
|
+
# 0 1 1 1
|
|
53
|
+
# D().raw().trim().trim().trim()
|
|
54
|
+
# use compare by id
|
|
55
|
+
for i, child in enumerate(self.parent.body):
|
|
56
|
+
if id(child) == id(self):
|
|
57
|
+
return i
|
|
52
58
|
return self.parent.body.index(self)
|
|
53
59
|
return -1
|
|
54
60
|
|
|
@@ -0,0 +1,422 @@
|
|
|
1
|
+
from types import ModuleType
|
|
2
|
+
from typing import MutableSequence, Type, cast
|
|
3
|
+
import warnings
|
|
4
|
+
from typing_extensions import Self
|
|
5
|
+
|
|
6
|
+
from ssc_codegen.ast_.base import BaseAstNode
|
|
7
|
+
from ssc_codegen.ast_.nodes_cast import ExprJsonify, ExprNested
|
|
8
|
+
from ssc_codegen.ast_.nodes_core import (
|
|
9
|
+
CodeEnd,
|
|
10
|
+
ExprCallStructClassVar,
|
|
11
|
+
ExprCallStructMethod,
|
|
12
|
+
ExprClassVar,
|
|
13
|
+
ExprDefaultValueEnd,
|
|
14
|
+
ExprDefaultValueStart,
|
|
15
|
+
ExprDefaultValueWrapper,
|
|
16
|
+
ExprNoReturn,
|
|
17
|
+
ExprReturn,
|
|
18
|
+
JsonStruct,
|
|
19
|
+
JsonStructField,
|
|
20
|
+
ModuleProgram,
|
|
21
|
+
Docstring,
|
|
22
|
+
ModuleImports,
|
|
23
|
+
CodeStart,
|
|
24
|
+
StartParseMethod,
|
|
25
|
+
StructFieldMethod,
|
|
26
|
+
StructInitMethod,
|
|
27
|
+
StructParser,
|
|
28
|
+
StructPartDocMethod,
|
|
29
|
+
StructPreValidateMethod,
|
|
30
|
+
TypeDef,
|
|
31
|
+
TypeDefField,
|
|
32
|
+
)
|
|
33
|
+
from ssc_codegen.ast_build.utils import (
|
|
34
|
+
generate_docstring_signature,
|
|
35
|
+
extract_schemas_from_module,
|
|
36
|
+
extract_json_structs_from_module,
|
|
37
|
+
is_literals_only_schema,
|
|
38
|
+
)
|
|
39
|
+
from ssc_codegen.document import BaseDocument, ClassVarDocument
|
|
40
|
+
from ssc_codegen.document_utlis import (
|
|
41
|
+
convert_css_to_xpath,
|
|
42
|
+
convert_xpath_to_css,
|
|
43
|
+
)
|
|
44
|
+
from ssc_codegen.json_struct import Json
|
|
45
|
+
from ssc_codegen.schema import BaseSchema
|
|
46
|
+
from ssc_codegen.tokens import StructType, TokenType, VariableType
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
class AstBuilder:
|
|
50
|
+
"""USAGE:
|
|
51
|
+
|
|
52
|
+
builder = AstBuilder()
|
|
53
|
+
builder.add_header("test").add_json_types(*[]).add_struct_parsers(*[]).module
|
|
54
|
+
"""
|
|
55
|
+
|
|
56
|
+
def __init__(
|
|
57
|
+
self,
|
|
58
|
+
gen_docstr: bool = True,
|
|
59
|
+
css_to_xpath: bool = False,
|
|
60
|
+
xpath_to_css: bool = False,
|
|
61
|
+
):
|
|
62
|
+
self.module = ModuleProgram()
|
|
63
|
+
self.gen_docstr = gen_docstr
|
|
64
|
+
if css_to_xpath and xpath_to_css:
|
|
65
|
+
raise TypeError("allowed css_to_xpath=True or xpath_to_css=True")
|
|
66
|
+
self.css_to_xpath = css_to_xpath
|
|
67
|
+
self.xpath_to_css = xpath_to_css
|
|
68
|
+
|
|
69
|
+
@classmethod
|
|
70
|
+
def build_from_moduletype(
|
|
71
|
+
cls,
|
|
72
|
+
module: ModuleType,
|
|
73
|
+
gen_docstr: bool = True,
|
|
74
|
+
css_to_xpath: bool = False,
|
|
75
|
+
xpath_to_css: bool = False,
|
|
76
|
+
) -> ModuleProgram:
|
|
77
|
+
builder = cls(
|
|
78
|
+
gen_docstr=gen_docstr,
|
|
79
|
+
css_to_xpath=css_to_xpath,
|
|
80
|
+
xpath_to_css=xpath_to_css,
|
|
81
|
+
)
|
|
82
|
+
structs = extract_schemas_from_module(module)
|
|
83
|
+
jsons = extract_json_structs_from_module(module)
|
|
84
|
+
return (
|
|
85
|
+
builder.add_header(module.__dict__.get("__doc__", None))
|
|
86
|
+
.add_json_types(*jsons)
|
|
87
|
+
.add_struct_parsers(*structs)
|
|
88
|
+
.module
|
|
89
|
+
)
|
|
90
|
+
|
|
91
|
+
@classmethod
|
|
92
|
+
def build_from_ssc_schemas(
|
|
93
|
+
cls,
|
|
94
|
+
*schemas: Type[BaseSchema], # TODO: it not support json serialized
|
|
95
|
+
gen_docstr: bool = False,
|
|
96
|
+
css_to_xpath: bool = False,
|
|
97
|
+
xpath_to_css: bool = False,
|
|
98
|
+
) -> ModuleProgram:
|
|
99
|
+
builder = cls(
|
|
100
|
+
gen_docstr=gen_docstr,
|
|
101
|
+
css_to_xpath=css_to_xpath,
|
|
102
|
+
xpath_to_css=xpath_to_css,
|
|
103
|
+
)
|
|
104
|
+
return (
|
|
105
|
+
builder.add_header(module_doc=None)
|
|
106
|
+
.add_struct_parsers(*schemas)
|
|
107
|
+
.module
|
|
108
|
+
)
|
|
109
|
+
|
|
110
|
+
def add_header(self, module_doc: str | None = None) -> Self:
|
|
111
|
+
if self.gen_docstr and module_doc:
|
|
112
|
+
self.module.body.append(
|
|
113
|
+
Docstring(kwargs={"value": module_doc}, parent=self.module)
|
|
114
|
+
)
|
|
115
|
+
# import node
|
|
116
|
+
self.module.body.append(ModuleImports(parent=self.module))
|
|
117
|
+
# CodeStart hook node
|
|
118
|
+
self.module.body.append(CodeStart(parent=self.module))
|
|
119
|
+
return self
|
|
120
|
+
|
|
121
|
+
def _json_field_nodes(
|
|
122
|
+
self, jsn_obj: Type[Json], ast_jsn: JsonStruct
|
|
123
|
+
) -> None:
|
|
124
|
+
for name, field_type in jsn_obj.get_fields().items():
|
|
125
|
+
ast_jsn.body.append(
|
|
126
|
+
JsonStructField(
|
|
127
|
+
kwargs={"name": name, "type": field_type}, parent=ast_jsn
|
|
128
|
+
)
|
|
129
|
+
)
|
|
130
|
+
|
|
131
|
+
def add_json_types(self, *json_structs: Type[Json]) -> Self:
|
|
132
|
+
ast_jsons = []
|
|
133
|
+
for jsn_st in json_structs:
|
|
134
|
+
ast_jsn = JsonStruct(
|
|
135
|
+
kwargs={
|
|
136
|
+
"name": jsn_st.__name__,
|
|
137
|
+
"is_array": jsn_st.__IS_ARRAY__,
|
|
138
|
+
},
|
|
139
|
+
parent=self.module,
|
|
140
|
+
)
|
|
141
|
+
self._json_field_nodes(jsn_st, ast_jsn)
|
|
142
|
+
ast_jsons.append(ast_jsn)
|
|
143
|
+
self.module.body.extend(ast_jsons)
|
|
144
|
+
return self
|
|
145
|
+
|
|
146
|
+
def _ref_body_parent(self, node: BaseAstNode) -> None:
|
|
147
|
+
parent = node
|
|
148
|
+
for child in node.body:
|
|
149
|
+
child.parent = parent
|
|
150
|
+
|
|
151
|
+
def _literal_struct_node(self, schema: Type[BaseSchema]) -> StructParser:
|
|
152
|
+
# dont required TypeDef struct generate
|
|
153
|
+
literals = schema.__get_mro_literals__().copy()
|
|
154
|
+
st = StructParser(
|
|
155
|
+
kwargs={
|
|
156
|
+
"name": schema.__name__,
|
|
157
|
+
"struct_type": StructType.CONFIG_CLASSVARS,
|
|
158
|
+
"docstring": schema.__doc__ or "" if self.gen_docstr else "",
|
|
159
|
+
},
|
|
160
|
+
body=[f.expr() for f in literals.values()],
|
|
161
|
+
parent=self.module,
|
|
162
|
+
)
|
|
163
|
+
self._ref_body_parent(st)
|
|
164
|
+
return st
|
|
165
|
+
|
|
166
|
+
def _ref_filter_expr(
|
|
167
|
+
self, parent: BaseAstNode, parent_body: MutableSequence[BaseAstNode]
|
|
168
|
+
) -> None:
|
|
169
|
+
for i in parent_body:
|
|
170
|
+
i.parent = parent
|
|
171
|
+
if i.body:
|
|
172
|
+
self._ref_filter_expr(i, i.body)
|
|
173
|
+
|
|
174
|
+
def _ref_method_exprs(self, node_method: BaseAstNode) -> None:
|
|
175
|
+
parent = node_method
|
|
176
|
+
for expr in node_method.body:
|
|
177
|
+
expr.parent = parent
|
|
178
|
+
if expr.kind == TokenType.EXPR_FILTER:
|
|
179
|
+
self._ref_filter_expr(expr, expr.body)
|
|
180
|
+
|
|
181
|
+
def _unwrap_default_node(
|
|
182
|
+
self,
|
|
183
|
+
stack: list[BaseAstNode], # noqa (use sideeffect for path stack exprs)
|
|
184
|
+
ret_type: VariableType,
|
|
185
|
+
) -> None:
|
|
186
|
+
if stack[0].kind == ExprDefaultValueWrapper.kind:
|
|
187
|
+
default_expr = stack.pop(0)
|
|
188
|
+
value = default_expr.kwargs["value"]
|
|
189
|
+
classvar_hooks = default_expr.classvar_hooks
|
|
190
|
+
default_type = ret_type
|
|
191
|
+
if value is None:
|
|
192
|
+
match ret_type:
|
|
193
|
+
case VariableType.STRING:
|
|
194
|
+
default_type = VariableType.OPTIONAL_STRING
|
|
195
|
+
case VariableType.INT:
|
|
196
|
+
default_type = VariableType.OPTIONAL_INT
|
|
197
|
+
case VariableType.FLOAT:
|
|
198
|
+
default_type = VariableType.OPTIONAL_FLOAT
|
|
199
|
+
case VariableType.LIST_STRING:
|
|
200
|
+
default_type = VariableType.OPTIONAL_LIST_STRING
|
|
201
|
+
case VariableType.LIST_INT:
|
|
202
|
+
default_type = VariableType.OPTIONAL_LIST_INT
|
|
203
|
+
case VariableType.LIST_FLOAT:
|
|
204
|
+
default_type = VariableType.OPTIONAL_LIST_FLOAT
|
|
205
|
+
# TODO: warning for BOOL type
|
|
206
|
+
case _:
|
|
207
|
+
warnings.warn(
|
|
208
|
+
f"'None' default value not allowed return type '{ret_type.name}'. ",
|
|
209
|
+
category=SyntaxWarning,
|
|
210
|
+
)
|
|
211
|
+
default_type = VariableType.ANY
|
|
212
|
+
elif isinstance(value, list):
|
|
213
|
+
# todo: check if empty list passed
|
|
214
|
+
match ret_type:
|
|
215
|
+
case VariableType.LIST_STRING:
|
|
216
|
+
default_type = VariableType.LIST_STRING
|
|
217
|
+
case VariableType.LIST_INT:
|
|
218
|
+
default_type = VariableType.LIST_INT
|
|
219
|
+
case VariableType.LIST_FLOAT:
|
|
220
|
+
default_type = VariableType.LIST_FLOAT
|
|
221
|
+
case _:
|
|
222
|
+
warnings.warn(
|
|
223
|
+
f"`empty list` default value not allowed return type `{ret_type.name}`. "
|
|
224
|
+
f"Expected types `{(VariableType.LIST_STRING.name, VariableType.LIST_INT, VariableType.LIST_FLOAT)}`",
|
|
225
|
+
category=SyntaxWarning,
|
|
226
|
+
)
|
|
227
|
+
default_type = VariableType.ANY
|
|
228
|
+
expr_default_start = ExprDefaultValueStart(
|
|
229
|
+
kwargs={"value": value}, classvar_hooks=classvar_hooks
|
|
230
|
+
)
|
|
231
|
+
expr_default_end = ExprDefaultValueEnd(
|
|
232
|
+
kwargs={"value": value},
|
|
233
|
+
ret_type=default_type,
|
|
234
|
+
classvar_hooks=classvar_hooks,
|
|
235
|
+
)
|
|
236
|
+
stack.insert(0, expr_default_start)
|
|
237
|
+
stack.append(expr_default_end)
|
|
238
|
+
|
|
239
|
+
def _struct_pop_pre_validate(
|
|
240
|
+
self,
|
|
241
|
+
node_start_parse: StartParseMethod,
|
|
242
|
+
fields: dict[str, BaseDocument],
|
|
243
|
+
) -> StructPreValidateMethod:
|
|
244
|
+
# fmt: off
|
|
245
|
+
assert "__PRE_VALIDATE__" in fields, "check `__PRE_VALIDATE__` key before extract"
|
|
246
|
+
|
|
247
|
+
validate_field = fields.pop("__PRE_VALIDATE__")
|
|
248
|
+
method = StructPreValidateMethod()
|
|
249
|
+
exprs = validate_field.stack.copy()
|
|
250
|
+
# not allowed use default exprs, skip check
|
|
251
|
+
exprs.append(ExprNoReturn())
|
|
252
|
+
method.body.extend(exprs)
|
|
253
|
+
self._ref_method_exprs(method)
|
|
254
|
+
node_start_parse.body.append(ExprCallStructMethod(kwargs={"type": VariableType.NULL, "name": "__PRE_VALIDATE__",}))
|
|
255
|
+
return method
|
|
256
|
+
# fmt: on
|
|
257
|
+
|
|
258
|
+
def _struct_pop_split_doc(
|
|
259
|
+
self,
|
|
260
|
+
node_start_parse: StartParseMethod,
|
|
261
|
+
fields: dict[str, BaseDocument],
|
|
262
|
+
) -> StructPartDocMethod:
|
|
263
|
+
# fmt: off
|
|
264
|
+
assert "__SPLIT_DOC__" in fields, "check `__SPLIT_DOC__` key before extract"
|
|
265
|
+
|
|
266
|
+
split_field = fields.pop("__SPLIT_DOC__")
|
|
267
|
+
method = StructPartDocMethod()
|
|
268
|
+
exprs = split_field.stack.copy()
|
|
269
|
+
# not allowed use default exprs, skip check
|
|
270
|
+
exprs.append(ExprReturn(ret_type=VariableType.LIST_DOCUMENT))
|
|
271
|
+
method.body.extend(exprs)
|
|
272
|
+
self._ref_method_exprs(method)
|
|
273
|
+
node_start_parse.body.append(ExprCallStructMethod(kwargs={"type": VariableType.LIST_DOCUMENT, "name": "__SPLIT_DOC__",}))
|
|
274
|
+
return method
|
|
275
|
+
# fmt: on
|
|
276
|
+
|
|
277
|
+
def _struct_call_literals(
|
|
278
|
+
self,
|
|
279
|
+
node_start_parse: StartParseMethod,
|
|
280
|
+
literals: dict[str, ClassVarDocument],
|
|
281
|
+
) -> None:
|
|
282
|
+
# fmt: off
|
|
283
|
+
for literal in literals.values():
|
|
284
|
+
expr = literal.expr()
|
|
285
|
+
if expr.kwargs["parse_returns"]:
|
|
286
|
+
_, struct_name, field_name, _, _ = expr.unpack_args()
|
|
287
|
+
node_start_parse.body.append(
|
|
288
|
+
ExprCallStructClassVar(
|
|
289
|
+
kwargs={"struct_name": struct_name, "field_name": field_name, "type": expr.ret_type,}))
|
|
290
|
+
# fmt: on
|
|
291
|
+
|
|
292
|
+
def _struct_fields_parse(
|
|
293
|
+
self,
|
|
294
|
+
node_start_parse: StartParseMethod,
|
|
295
|
+
fields: dict[str, BaseDocument],
|
|
296
|
+
) -> list[StructFieldMethod]:
|
|
297
|
+
st_fields = []
|
|
298
|
+
# fmt: off
|
|
299
|
+
for field_name, document in fields.items():
|
|
300
|
+
if self.css_to_xpath:
|
|
301
|
+
document = convert_css_to_xpath(document)
|
|
302
|
+
elif self.xpath_to_css:
|
|
303
|
+
document = convert_xpath_to_css(document)
|
|
304
|
+
|
|
305
|
+
ret_type = document.stack_last_ret
|
|
306
|
+
method = StructFieldMethod(kwargs={"name": field_name}, ret_type=ret_type,)
|
|
307
|
+
exprs = document.stack.copy()
|
|
308
|
+
# in inheritance schemas, child classes use same fields are used as in the parent class
|
|
309
|
+
# avoid duplicate ExprReturn or ExprDefaultValueWrapper node
|
|
310
|
+
if (exprs[-1].kind != ExprReturn.kind and exprs[-1].kind != ExprDefaultValueEnd.kind):
|
|
311
|
+
exprs.append(ExprReturn(accept_type=ret_type, ret_type=ret_type))
|
|
312
|
+
self._unwrap_default_node(exprs, exprs[-1].ret_type)
|
|
313
|
+
method.body.extend(exprs)
|
|
314
|
+
self._ref_method_exprs(method)
|
|
315
|
+
st_fields.append(method)
|
|
316
|
+
node_start_parse.body.append(
|
|
317
|
+
ExprCallStructMethod(kwargs={"type": exprs[-1].ret_type, "name": field_name,}))
|
|
318
|
+
return st_fields
|
|
319
|
+
# fmt: on
|
|
320
|
+
|
|
321
|
+
def _node_typedef(self, struct: StructParser) -> TypeDef | None:
|
|
322
|
+
# fmt: off
|
|
323
|
+
if struct.struct_type == StructType.CONFIG_CLASSVARS:
|
|
324
|
+
return None
|
|
325
|
+
typedef = TypeDef(
|
|
326
|
+
parent=self.module,
|
|
327
|
+
kwargs={"name": struct.kwargs["name"], "struct_type": struct.struct_type},
|
|
328
|
+
)
|
|
329
|
+
for sc_field in struct.body:
|
|
330
|
+
if sc_field.kind not in (TokenType.STRUCT_FIELD, TokenType.CLASSVAR,):
|
|
331
|
+
continue
|
|
332
|
+
elif sc_field.kind == ExprClassVar.kind and sc_field.kwargs["parse_returns"]:
|
|
333
|
+
_value, _struct_name, field_name, _parse_returns, _is_regex = sc_field.unpack_args()
|
|
334
|
+
tdef_field = TypeDefField(
|
|
335
|
+
parent=typedef,
|
|
336
|
+
# cls_nested: always self-used field, set None
|
|
337
|
+
kwargs={"name": field_name, "type": sc_field.ret_type, "cls_nested": None, "cls_nested_type": None,},
|
|
338
|
+
)
|
|
339
|
+
typedef.body.append(tdef_field)
|
|
340
|
+
elif sc_field.kind == StructFieldMethod.kind:
|
|
341
|
+
if sc_field.body[-1].ret_type == VariableType.NESTED:
|
|
342
|
+
node = [i for i in sc_field.body if i.kind == TokenType.EXPR_NESTED][0]
|
|
343
|
+
node = cast(ExprNested, node)
|
|
344
|
+
cls_name = node.kwargs["schema_name"]
|
|
345
|
+
cls_nested_type = node.kwargs["schema_type"]
|
|
346
|
+
elif sc_field.body[-1].ret_type == VariableType.JSON:
|
|
347
|
+
node = [i for i in sc_field.body if i.kind == TokenType.TO_JSON][0]
|
|
348
|
+
node = cast(ExprJsonify, node)
|
|
349
|
+
cls_name = node.kwargs["json_struct_name"]
|
|
350
|
+
# hack: for provide more consistent Typedef field gen api reuse StructType enum
|
|
351
|
+
# or perceive it as bad AST design
|
|
352
|
+
cls_nested_type = StructType.LIST if node.kwargs["is_array"] else StructType.ITEM
|
|
353
|
+
else:
|
|
354
|
+
cls_name, cls_nested_type = None, None
|
|
355
|
+
tdef_field = TypeDefField(
|
|
356
|
+
parent=typedef,
|
|
357
|
+
kwargs={"name": sc_field.kwargs["name"], "type": sc_field.body[-1].ret_type,
|
|
358
|
+
"cls_nested": cls_name, "cls_nested_type": cls_nested_type,}
|
|
359
|
+
)
|
|
360
|
+
typedef.body.append(tdef_field)
|
|
361
|
+
return typedef
|
|
362
|
+
# fmt: on
|
|
363
|
+
|
|
364
|
+
def _parser_struct_node(self, schema: Type[BaseSchema]) -> StructParser:
|
|
365
|
+
# fmt: off
|
|
366
|
+
docstring = ((schema.__doc__ or "") + "\n\n" + generate_docstring_signature(schema))
|
|
367
|
+
st = StructParser(
|
|
368
|
+
kwargs={"name": schema.__name__, "struct_type": schema.__SCHEMA_TYPE__, "docstring": docstring if self.gen_docstr else "",},
|
|
369
|
+
parent=self.module,
|
|
370
|
+
)
|
|
371
|
+
literals = schema.__get_mro_literals__().copy()
|
|
372
|
+
|
|
373
|
+
for literal in literals.values():
|
|
374
|
+
st.body.append(literal.expr())
|
|
375
|
+
st.body.append(StructInitMethod())
|
|
376
|
+
|
|
377
|
+
fields = schema.__get_mro_fields__().copy()
|
|
378
|
+
node_start_parse = StartParseMethod(parent=st) # insert to END
|
|
379
|
+
if "__PRE_VALIDATE__" in fields:
|
|
380
|
+
pre_validate = self._struct_pop_pre_validate(node_start_parse, fields)
|
|
381
|
+
st.body.append(pre_validate)
|
|
382
|
+
|
|
383
|
+
if fields.get("__SPLIT_DOC__"):
|
|
384
|
+
split_doc = self._struct_pop_split_doc(node_start_parse, fields)
|
|
385
|
+
st.body.append(split_doc)
|
|
386
|
+
# literals call (if required return)
|
|
387
|
+
self._struct_call_literals(node_start_parse, literals)
|
|
388
|
+
|
|
389
|
+
fields_st = self._struct_fields_parse(node_start_parse, fields)
|
|
390
|
+
# assembly struct node
|
|
391
|
+
st.body.extend(fields_st)
|
|
392
|
+
st.body.append(node_start_parse)
|
|
393
|
+
self._ref_body_parent(st)
|
|
394
|
+
# generate typedef as annotation node
|
|
395
|
+
return st
|
|
396
|
+
|
|
397
|
+
def add_struct_parsers(self, *struct_parsers: Type[BaseSchema]) -> Self:
|
|
398
|
+
ssc_structs = []
|
|
399
|
+
ssc_typedefs = []
|
|
400
|
+
for schema in struct_parsers:
|
|
401
|
+
if is_literals_only_schema(schema):
|
|
402
|
+
if schema.__SCHEMA_TYPE__ != StructType.ITEM:
|
|
403
|
+
raise TypeError(
|
|
404
|
+
"Literals-only init allowed only ItemSchema"
|
|
405
|
+
)
|
|
406
|
+
struct = self._literal_struct_node(schema)
|
|
407
|
+
ssc_structs.append(struct)
|
|
408
|
+
continue
|
|
409
|
+
else:
|
|
410
|
+
struct = self._parser_struct_node(schema)
|
|
411
|
+
ssc_structs.append(struct)
|
|
412
|
+
for struct in ssc_structs:
|
|
413
|
+
typedef = self._node_typedef(struct)
|
|
414
|
+
# literals-only schema skip
|
|
415
|
+
if not typedef:
|
|
416
|
+
continue
|
|
417
|
+
ssc_typedefs.append(typedef)
|
|
418
|
+
# assembly
|
|
419
|
+
self.module.body.extend(ssc_typedefs)
|
|
420
|
+
self.module.body.extend(ssc_structs)
|
|
421
|
+
self.module.body.append(CodeEnd(parent=self.module))
|
|
422
|
+
return self
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
from pathlib import Path
|
|
2
|
+
from typing import Type
|
|
3
|
+
|
|
4
|
+
from ssc_codegen.ast_ import ModuleProgram
|
|
5
|
+
|
|
6
|
+
from ssc_codegen.ast_build.builder import AstBuilder
|
|
7
|
+
from ssc_codegen.ast_build.utils import (
|
|
8
|
+
exec_module_code,
|
|
9
|
+
extract_schemas_from_module,
|
|
10
|
+
)
|
|
11
|
+
|
|
12
|
+
from ssc_codegen.schema import BaseSchema
|
|
13
|
+
from ssc_codegen.static_checker import run_analyze_schema
|
|
14
|
+
import logging
|
|
15
|
+
|
|
16
|
+
LOGGER = logging.getLogger("ssc-gen")
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def build_ast_module_parser(
|
|
20
|
+
path: str | Path,
|
|
21
|
+
*,
|
|
22
|
+
css_to_xpath: bool = False,
|
|
23
|
+
xpath_to_css: bool = False,
|
|
24
|
+
gen_docstring: bool = True,
|
|
25
|
+
) -> ModuleProgram:
|
|
26
|
+
"""build ast from python sscgen config file
|
|
27
|
+
|
|
28
|
+
WARNING!!!
|
|
29
|
+
DO NOT PASS MODULES FROM UNKNOWN SOURCE/INPUT FOR SECURITY REASONS.
|
|
30
|
+
|
|
31
|
+
THIS FUNCTION COMPILE AND EXEC PYTHON CODE in runtime WOUT CHECKS
|
|
32
|
+
"""
|
|
33
|
+
if css_to_xpath and xpath_to_css:
|
|
34
|
+
raise AttributeError(
|
|
35
|
+
"Should be chosen one variant (css_to_xpath OR xpath_to_css)"
|
|
36
|
+
)
|
|
37
|
+
py_module = exec_module_code(path)
|
|
38
|
+
# check code configs before build AST
|
|
39
|
+
schemas = extract_schemas_from_module(py_module)
|
|
40
|
+
count_errors = 0
|
|
41
|
+
for schema in schemas:
|
|
42
|
+
errors = run_analyze_schema(schema)
|
|
43
|
+
if errors > 0:
|
|
44
|
+
msg = f"{schema.__name__} founded errors: {errors}"
|
|
45
|
+
LOGGER.error(msg)
|
|
46
|
+
count_errors += errors
|
|
47
|
+
|
|
48
|
+
if count_errors > 0:
|
|
49
|
+
msg = f"{path} errors count: {count_errors}"
|
|
50
|
+
LOGGER.error(msg)
|
|
51
|
+
raise SyntaxError("")
|
|
52
|
+
|
|
53
|
+
ast_module = AstBuilder.build_from_moduletype(
|
|
54
|
+
py_module,
|
|
55
|
+
css_to_xpath=css_to_xpath,
|
|
56
|
+
xpath_to_css=xpath_to_css,
|
|
57
|
+
gen_docstr=gen_docstring,
|
|
58
|
+
)
|
|
59
|
+
return ast_module
|
|
60
|
+
# static check
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def build_ast_schemas(
|
|
64
|
+
*schemas: Type[BaseSchema],
|
|
65
|
+
css_to_xpath: bool = False,
|
|
66
|
+
xpath_to_css: bool = False,
|
|
67
|
+
gen_docstring: bool = False,
|
|
68
|
+
) -> ModuleProgram:
|
|
69
|
+
if css_to_xpath and xpath_to_css:
|
|
70
|
+
raise AttributeError(
|
|
71
|
+
"Should be chosen one variant (css_to_xpath OR xpath_to_css)"
|
|
72
|
+
)
|
|
73
|
+
count_errors = 0
|
|
74
|
+
for schema in schemas:
|
|
75
|
+
errors = run_analyze_schema(schema)
|
|
76
|
+
if errors > 0:
|
|
77
|
+
msg = f"{schema.__name__} founded errors: {errors}"
|
|
78
|
+
LOGGER.error(msg)
|
|
79
|
+
count_errors += errors
|
|
80
|
+
|
|
81
|
+
if count_errors > 0:
|
|
82
|
+
msg = f"scehmas errors count: {count_errors}"
|
|
83
|
+
LOGGER.error(msg)
|
|
84
|
+
raise SyntaxError
|
|
85
|
+
ast_module = AstBuilder.build_from_ssc_schemas(
|
|
86
|
+
*schemas,
|
|
87
|
+
css_to_xpath=css_to_xpath,
|
|
88
|
+
xpath_to_css=xpath_to_css,
|
|
89
|
+
gen_docstr=gen_docstring,
|
|
90
|
+
)
|
|
91
|
+
return ast_module
|
|
@@ -5,12 +5,8 @@ import types
|
|
|
5
5
|
from os import PathLike
|
|
6
6
|
from typing import Any, Type
|
|
7
7
|
|
|
8
|
-
from ssc_codegen.
|
|
9
|
-
from ssc_codegen.ast_build import build_ast_module_parser
|
|
10
|
-
from ssc_codegen.ast_build.main import (
|
|
11
|
-
build_ast_struct_parser,
|
|
12
|
-
build_ast_typedef,
|
|
13
|
-
)
|
|
8
|
+
from ssc_codegen.ast_build.builder import AstBuilder
|
|
9
|
+
from ssc_codegen.ast_build.main import build_ast_module_parser
|
|
14
10
|
from ssc_codegen.converters.py_base import BasePyCodeConverter
|
|
15
11
|
from ssc_codegen.schema import BaseSchema
|
|
16
12
|
|
|
@@ -46,22 +42,8 @@ def compile(
|
|
|
46
42
|
print(module.Demo(document.text).parse())
|
|
47
43
|
```
|
|
48
44
|
"""
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
module.body.append(ModuleImports())
|
|
52
|
-
|
|
53
|
-
structs = []
|
|
54
|
-
|
|
55
|
-
# TODO: add json structs support
|
|
56
|
-
for s in schemas:
|
|
57
|
-
struct = build_ast_struct_parser(s, module, gen_docstring=False)
|
|
58
|
-
structs.append(struct)
|
|
59
|
-
|
|
60
|
-
t_defs = build_ast_typedef(module, *structs)
|
|
61
|
-
module.body.extend(t_defs)
|
|
62
|
-
module.body.extend(structs)
|
|
63
|
-
|
|
64
|
-
code_parts = converter.convert_program(module)
|
|
45
|
+
ast_module = AstBuilder.build_from_ssc_schemas(*schemas)
|
|
46
|
+
code_parts = converter.convert_program(ast_module)
|
|
65
47
|
module = types.ModuleType("_ssc_single_eval")
|
|
66
48
|
code = "\n".join(code_parts)
|
|
67
49
|
exec(code, module.__dict__)
|
|
@@ -99,7 +81,7 @@ class Compiler:
|
|
|
99
81
|
return instance
|
|
100
82
|
|
|
101
83
|
@property
|
|
102
|
-
def cache(self):
|
|
84
|
+
def cache(self) -> dict[str, "Compiler"]:
|
|
103
85
|
return self._cache
|
|
104
86
|
|
|
105
87
|
def get_class(self, class_name: str) -> Any:
|