ssc_codegen 0.7.4__tar.gz → 0.7.6__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.7.4 → ssc_codegen-0.7.6}/PKG-INFO +1 -1
- {ssc_codegen-0.7.4 → ssc_codegen-0.7.6}/pyproject.toml +1 -2
- {ssc_codegen-0.7.4 → ssc_codegen-0.7.6}/ssc_codegen/__init__.py +1 -1
- {ssc_codegen-0.7.4 → ssc_codegen-0.7.6}/ssc_codegen/ast_build_utils.py +18 -0
- {ssc_codegen-0.7.4 → ssc_codegen-0.7.6}/ssc_codegen/ast_ssc.py +5 -6
- {ssc_codegen-0.7.4 → ssc_codegen-0.7.6}/ssc_codegen/converters/py_base.py +3 -3
- {ssc_codegen-0.7.4 → ssc_codegen-0.7.6}/ssc_codegen/converters/templates/py.py +27 -3
- {ssc_codegen-0.7.4 → ssc_codegen-0.7.6}/ssc_codegen/document.py +33 -7
- {ssc_codegen-0.7.4 → ssc_codegen-0.7.6}/ssc_codegen/document_utlis.py +6 -0
- {ssc_codegen-0.7.4 → ssc_codegen-0.7.6}/ssc_codegen/tokens.py +4 -0
- {ssc_codegen-0.7.4 → ssc_codegen-0.7.6}/.gitignore +0 -0
- {ssc_codegen-0.7.4 → ssc_codegen-0.7.6}/LICENSE +0 -0
- {ssc_codegen-0.7.4 → ssc_codegen-0.7.6}/README.md +0 -0
- {ssc_codegen-0.7.4 → ssc_codegen-0.7.6}/ssc_codegen/_compat.py +0 -0
- {ssc_codegen-0.7.4 → ssc_codegen-0.7.6}/ssc_codegen/ast_builder.py +0 -0
- {ssc_codegen-0.7.4 → ssc_codegen-0.7.6}/ssc_codegen/cli/__init__.py +0 -0
- {ssc_codegen-0.7.4 → ssc_codegen-0.7.6}/ssc_codegen/cli/cli_callbacks.py +0 -0
- {ssc_codegen-0.7.4 → ssc_codegen-0.7.6}/ssc_codegen/cli/cli_utils.py +0 -0
- {ssc_codegen-0.7.4 → ssc_codegen-0.7.6}/ssc_codegen/cli/code_callbacks.py +0 -0
- {ssc_codegen-0.7.4 → ssc_codegen-0.7.6}/ssc_codegen/cli/consts.py +0 -0
- {ssc_codegen-0.7.4 → ssc_codegen-0.7.6}/ssc_codegen/cli/main.py +0 -0
- {ssc_codegen-0.7.4 → ssc_codegen-0.7.6}/ssc_codegen/cli/runtime_parse_runners.py +0 -0
- {ssc_codegen-0.7.4 → ssc_codegen-0.7.6}/ssc_codegen/compiler.py +0 -0
- {ssc_codegen-0.7.4 → ssc_codegen-0.7.6}/ssc_codegen/consts.py +0 -0
- {ssc_codegen-0.7.4 → ssc_codegen-0.7.6}/ssc_codegen/converters/__init__.py +0 -0
- {ssc_codegen-0.7.4 → ssc_codegen-0.7.6}/ssc_codegen/converters/ast_utils.py +0 -0
- {ssc_codegen-0.7.4 → ssc_codegen-0.7.6}/ssc_codegen/converters/base.py +0 -0
- {ssc_codegen-0.7.4 → ssc_codegen-0.7.6}/ssc_codegen/converters/dart_universal_html.py +0 -0
- {ssc_codegen-0.7.4 → ssc_codegen-0.7.6}/ssc_codegen/converters/go_goquery.py +0 -0
- {ssc_codegen-0.7.4 → ssc_codegen-0.7.6}/ssc_codegen/converters/js_pure.py +0 -0
- {ssc_codegen-0.7.4 → ssc_codegen-0.7.6}/ssc_codegen/converters/json_to_schema.py +0 -0
- {ssc_codegen-0.7.4 → ssc_codegen-0.7.6}/ssc_codegen/converters/py_bs4.py +0 -0
- {ssc_codegen-0.7.4 → ssc_codegen-0.7.6}/ssc_codegen/converters/py_parsel.py +0 -0
- {ssc_codegen-0.7.4 → ssc_codegen-0.7.6}/ssc_codegen/converters/py_scrapy.py +0 -0
- {ssc_codegen-0.7.4 → ssc_codegen-0.7.6}/ssc_codegen/converters/py_selectolax.py +0 -0
- {ssc_codegen-0.7.4 → ssc_codegen-0.7.6}/ssc_codegen/converters/templates/dart.py +0 -0
- {ssc_codegen-0.7.4 → ssc_codegen-0.7.6}/ssc_codegen/converters/templates/go.py +0 -0
- {ssc_codegen-0.7.4 → ssc_codegen-0.7.6}/ssc_codegen/converters/templates/js.py +0 -0
- {ssc_codegen-0.7.4 → ssc_codegen-0.7.6}/ssc_codegen/converters/templates/template_bindings.py +0 -0
- {ssc_codegen-0.7.4 → ssc_codegen-0.7.6}/ssc_codegen/json_struct.py +0 -0
- {ssc_codegen-0.7.4 → ssc_codegen-0.7.6}/ssc_codegen/schema.py +0 -0
- {ssc_codegen-0.7.4 → ssc_codegen-0.7.6}/ssc_codegen/selector_utils.py +0 -0
- {ssc_codegen-0.7.4 → ssc_codegen-0.7.6}/ssc_codegen/str_utils.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: ssc_codegen
|
|
3
|
-
Version: 0.7.
|
|
3
|
+
Version: 0.7.6
|
|
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,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "ssc_codegen"
|
|
3
|
-
version = "0.7.
|
|
3
|
+
version = "0.7.6"
|
|
4
4
|
description = "Python-dsl code converter to html parser for web scraping "
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
requires-python = ">=3.10"
|
|
@@ -26,7 +26,6 @@ classifiers = [
|
|
|
26
26
|
"Programming Language :: Python :: 3.11",
|
|
27
27
|
"Programming Language :: Python :: 3.12",
|
|
28
28
|
"Programming Language :: Python :: 3.13",
|
|
29
|
-
|
|
30
29
|
]
|
|
31
30
|
|
|
32
31
|
[project.urls]
|
|
@@ -126,6 +126,10 @@ def extract_json_structs_from_module(module: ModuleType) -> list[Type[Json]]:
|
|
|
126
126
|
def assert_ret_type_not_document(
|
|
127
127
|
field: "BaseDocument", name: str, schema: Type[BaseSchema]
|
|
128
128
|
) -> None:
|
|
129
|
+
# always ret nil, skip
|
|
130
|
+
if name == "__PRE_VALIDATE__":
|
|
131
|
+
return
|
|
132
|
+
|
|
129
133
|
if field.stack_last_ret == VariableType.DOCUMENT:
|
|
130
134
|
msg = f"{schema.__name__}.{name} cannot return type {VariableType.DOCUMENT.name}"
|
|
131
135
|
raise TypeError(msg)
|
|
@@ -222,6 +226,19 @@ def assert_field_document_variable_types(field: BaseDocument) -> None:
|
|
|
222
226
|
if var_cursor == expr.accept_type:
|
|
223
227
|
var_cursor = expr.ret_type
|
|
224
228
|
continue
|
|
229
|
+
# currently used in to_len() operation
|
|
230
|
+
elif expr.accept_type == VariableType.LIST_ANY:
|
|
231
|
+
if var_cursor in (
|
|
232
|
+
VariableType.LIST_STRING,
|
|
233
|
+
VariableType.LIST_DOCUMENT,
|
|
234
|
+
VariableType.LIST_FLOAT,
|
|
235
|
+
VariableType.LIST_INT,
|
|
236
|
+
):
|
|
237
|
+
var_cursor = expr.ret_type
|
|
238
|
+
continue
|
|
239
|
+
msg = f".to_len() expected LIST_ANY type, got '{var_cursor.name}'"
|
|
240
|
+
raise TypeError(msg)
|
|
241
|
+
|
|
225
242
|
# this type always first (naive, used in DEFAULT and RETURN expr)
|
|
226
243
|
elif expr.accept_type == VariableType.ANY:
|
|
227
244
|
var_cursor = VariableType.DOCUMENT
|
|
@@ -232,5 +249,6 @@ def assert_field_document_variable_types(field: BaseDocument) -> None:
|
|
|
232
249
|
elif var_cursor == VariableType.NESTED:
|
|
233
250
|
raise TypeError("sub_parser not allowed next instructions")
|
|
234
251
|
|
|
252
|
+
# not covered cases
|
|
235
253
|
msg = f"'{expr.kind.name}' expected type '{expr.accept_type.name}', got '{var_cursor.name}'"
|
|
236
254
|
raise TypeError(msg)
|
|
@@ -427,12 +427,7 @@ class JoinExpression(BaseExpression):
|
|
|
427
427
|
@dataclass(kw_only=True)
|
|
428
428
|
class ArrayLengthExpression(BaseExpression):
|
|
429
429
|
kind: ClassVar[TokenType] = TokenType.EXPR_LIST_LEN
|
|
430
|
-
accept_type: VariableType =
|
|
431
|
-
VariableType.LIST_STRING
|
|
432
|
-
| VariableType.LIST_DOCUMENT
|
|
433
|
-
| VariableType.LIST_INT
|
|
434
|
-
| VariableType.LIST_FLOAT
|
|
435
|
-
)
|
|
430
|
+
accept_type: VariableType = VariableType.LIST_ANY
|
|
436
431
|
ret_type: VariableType = VariableType.INT
|
|
437
432
|
|
|
438
433
|
|
|
@@ -664,6 +659,7 @@ class RegexExpression(BaseExpression):
|
|
|
664
659
|
ret_type: VariableType = VariableType.STRING
|
|
665
660
|
pattern: str
|
|
666
661
|
group: int
|
|
662
|
+
ignore_case: bool = False
|
|
667
663
|
|
|
668
664
|
|
|
669
665
|
@dataclass(kw_only=True)
|
|
@@ -683,6 +679,7 @@ class RegexAllExpression(BaseExpression):
|
|
|
683
679
|
accept_type: VariableType = VariableType.STRING
|
|
684
680
|
ret_type: VariableType = VariableType.LIST_STRING
|
|
685
681
|
pattern: str
|
|
682
|
+
ignore_case: bool = False
|
|
686
683
|
|
|
687
684
|
|
|
688
685
|
@dataclass(kw_only=True)
|
|
@@ -700,6 +697,7 @@ class RegexSubExpression(BaseExpression):
|
|
|
700
697
|
ret_type: VariableType = VariableType.STRING
|
|
701
698
|
pattern: str
|
|
702
699
|
repl: str
|
|
700
|
+
ignore_case: bool = False
|
|
703
701
|
|
|
704
702
|
|
|
705
703
|
@dataclass(kw_only=True)
|
|
@@ -849,6 +847,7 @@ class IsRegexMatchExpression(BaseExpression):
|
|
|
849
847
|
ret_type: VariableType = VariableType.STRING
|
|
850
848
|
pattern: str
|
|
851
849
|
msg: str
|
|
850
|
+
ignore_case: bool = False
|
|
852
851
|
|
|
853
852
|
|
|
854
853
|
@dataclass(kw_only=True)
|
|
@@ -401,7 +401,7 @@ def tt_regex(node: RegexExpression) -> str:
|
|
|
401
401
|
prv, nxt = lr_var_names(variable=node.variable)
|
|
402
402
|
pattern = repr(node.pattern)
|
|
403
403
|
group = node.group
|
|
404
|
-
code = py.BINDINGS[node.kind, nxt, pattern, prv, group]
|
|
404
|
+
code = py.BINDINGS[node.kind, nxt, pattern, prv, group, node.ignore_case]
|
|
405
405
|
return indent + code
|
|
406
406
|
|
|
407
407
|
|
|
@@ -410,7 +410,7 @@ def tt_regex_all(node: RegexAllExpression) -> str:
|
|
|
410
410
|
|
|
411
411
|
prv, nxt = lr_var_names(variable=node.variable)
|
|
412
412
|
pattern = repr(node.pattern)
|
|
413
|
-
code = py.BINDINGS[node.kind, nxt, pattern, prv]
|
|
413
|
+
code = py.BINDINGS[node.kind, nxt, pattern, prv, node.ignore_case]
|
|
414
414
|
return indent + code
|
|
415
415
|
|
|
416
416
|
|
|
@@ -497,7 +497,7 @@ def tt_is_regex(node: IsRegexMatchExpression) -> str:
|
|
|
497
497
|
pattern = repr(node.pattern)
|
|
498
498
|
msg = repr(node.msg)
|
|
499
499
|
|
|
500
|
-
code = py.BINDINGS[node.kind, pattern, prv, msg]
|
|
500
|
+
code = py.BINDINGS[node.kind, pattern, prv, msg, node.ignore_case]
|
|
501
501
|
if node.next.kind == TokenType.EXPR_NO_RETURN:
|
|
502
502
|
return indent + code
|
|
503
503
|
return indent + code + "\n" + indent + f"{nxt} = {prv}"
|
|
@@ -103,9 +103,25 @@ BINDINGS[TokenType.EXPR_LIST_STRING_REPLACE] = (
|
|
|
103
103
|
"{} = [e.replace({}, {}) for e in {}]"
|
|
104
104
|
)
|
|
105
105
|
BINDINGS[TokenType.EXPR_STRING_SPLIT] = "{} = {}.split({})"
|
|
106
|
+
|
|
107
|
+
|
|
106
108
|
# regex
|
|
107
|
-
|
|
108
|
-
|
|
109
|
+
def expr_re(
|
|
110
|
+
nxt: str, pattern: str, prv: str, group: int, ignore_case: bool
|
|
111
|
+
) -> str:
|
|
112
|
+
if ignore_case:
|
|
113
|
+
return f"{nxt} = re.search({pattern}, '{prv}', re.IGNORECASE)[{group}]"
|
|
114
|
+
return f"{nxt} = re.search({pattern}, '{prv}')[{group}]"
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
def expr_re_all(nxt: str, pattern: str, prv: str, ignore_case: bool) -> str:
|
|
118
|
+
if ignore_case:
|
|
119
|
+
return f"{nxt} = re.findall({pattern}, '{prv}', re.IGNORECASE)"
|
|
120
|
+
return f"{nxt} = re.findall({pattern}, '{prv}')"
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
BINDINGS[TokenType.EXPR_REGEX] = expr_re
|
|
124
|
+
BINDINGS[TokenType.EXPR_REGEX_ALL] = expr_re_all
|
|
109
125
|
BINDINGS[TokenType.EXPR_REGEX_SUB] = "{} = re.sub({}, {}, {})"
|
|
110
126
|
BINDINGS[TokenType.EXPR_LIST_REGEX_SUB] = "{} = [re.sub({}, {}, e) for e in {}]"
|
|
111
127
|
|
|
@@ -118,7 +134,15 @@ BINDINGS[TokenType.EXPR_LIST_JOIN] = "{} = {}.join({})"
|
|
|
118
134
|
BINDINGS[TokenType.IS_EQUAL] = "assert {} == {}, {}"
|
|
119
135
|
BINDINGS[TokenType.IS_NOT_EQUAL] = "assert {} != {}, {}"
|
|
120
136
|
BINDINGS[TokenType.IS_CONTAINS] = "assert {} in {}, {}"
|
|
121
|
-
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
def is_regex(pattern: str, prv: str, msg: str, ignore_case: bool) -> str:
|
|
140
|
+
if ignore_case:
|
|
141
|
+
return f"assert re.search({pattern}, {prv}, re.IGNORECASE), {msg}"
|
|
142
|
+
return f"assert re.search({pattern}, {prv}), {msg}"
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
BINDINGS[TokenType.IS_REGEX_MATCH] = is_regex
|
|
122
146
|
|
|
123
147
|
# int, float converters
|
|
124
148
|
BINDINGS[TokenType.TO_INT] = "{} = int({})"
|
|
@@ -51,7 +51,11 @@ from .ast_ssc import (
|
|
|
51
51
|
ToBool,
|
|
52
52
|
ArrayLengthExpression,
|
|
53
53
|
)
|
|
54
|
-
from .document_utlis import
|
|
54
|
+
from .document_utlis import (
|
|
55
|
+
assert_re_expression,
|
|
56
|
+
unverbosify_regex,
|
|
57
|
+
is_ignore_case_regex,
|
|
58
|
+
)
|
|
55
59
|
from .schema import BaseSchema
|
|
56
60
|
from .selector_utils import validate_css_query, validate_xpath_query
|
|
57
61
|
from .tokens import VariableType
|
|
@@ -458,7 +462,9 @@ class StringDocument(BaseDocument):
|
|
|
458
462
|
)
|
|
459
463
|
return self
|
|
460
464
|
|
|
461
|
-
def re(
|
|
465
|
+
def re(
|
|
466
|
+
self, pattern: str | Pattern, group: int = 1, ignore_case: bool = False
|
|
467
|
+
) -> Self:
|
|
462
468
|
"""extract first regex result.
|
|
463
469
|
|
|
464
470
|
NOTE:
|
|
@@ -466,6 +472,10 @@ class StringDocument(BaseDocument):
|
|
|
466
472
|
|
|
467
473
|
- accept STRING, return STRING
|
|
468
474
|
"""
|
|
475
|
+
# TODO: implement, js, go, dart ignorecase flag
|
|
476
|
+
if not isinstance(pattern, str):
|
|
477
|
+
ignore_case = is_ignore_case_regex(pattern)
|
|
478
|
+
|
|
469
479
|
pattern = unverbosify_regex(pattern)
|
|
470
480
|
assert_re_expression(pattern)
|
|
471
481
|
|
|
@@ -473,21 +483,28 @@ class StringDocument(BaseDocument):
|
|
|
473
483
|
self._raise_wrong_type_error(
|
|
474
484
|
self.stack_last_ret, VariableType.STRING
|
|
475
485
|
)
|
|
476
|
-
self._add(
|
|
486
|
+
self._add(
|
|
487
|
+
RegexExpression(
|
|
488
|
+
pattern=pattern, group=group, ignore_case=ignore_case
|
|
489
|
+
)
|
|
490
|
+
)
|
|
477
491
|
return self
|
|
478
492
|
|
|
479
|
-
def re_all(self, pattern: str | Pattern) -> Self:
|
|
493
|
+
def re_all(self, pattern: str | Pattern, ignore_case: bool = False) -> Self:
|
|
480
494
|
"""extract all regex results.
|
|
481
495
|
|
|
482
496
|
- accept STRING, return LIST_STRING
|
|
483
497
|
"""
|
|
498
|
+
if not isinstance(pattern, str):
|
|
499
|
+
ignore_case = is_ignore_case_regex(pattern)
|
|
500
|
+
|
|
484
501
|
pattern = unverbosify_regex(pattern)
|
|
485
502
|
assert_re_expression(pattern, max_groups=1)
|
|
486
503
|
if self.stack_last_ret != VariableType.STRING:
|
|
487
504
|
self._raise_wrong_type_error(
|
|
488
505
|
self.stack_last_ret, VariableType.STRING
|
|
489
506
|
)
|
|
490
|
-
self._add(RegexAllExpression(pattern=pattern))
|
|
507
|
+
self._add(RegexAllExpression(pattern=pattern, ignore_case=ignore_case))
|
|
491
508
|
return self
|
|
492
509
|
|
|
493
510
|
def re_sub(self, pattern: str | Pattern, repl: str = "") -> Self:
|
|
@@ -674,20 +691,29 @@ class AssertDocument(BaseDocument):
|
|
|
674
691
|
self._add(IsContainsExpression(item=item, msg=msg))
|
|
675
692
|
return self
|
|
676
693
|
|
|
677
|
-
def is_regex(
|
|
694
|
+
def is_regex(
|
|
695
|
+
self, pattern: str | Pattern, msg: str = "", ignore_case: bool = False
|
|
696
|
+
) -> Self:
|
|
678
697
|
"""assert value matched by regex. If in generated code check failed - throw exception with passed msg
|
|
679
698
|
|
|
680
699
|
EXPR DO NOT MODIFY variable
|
|
681
700
|
|
|
682
701
|
- accept STRING, return STRING
|
|
683
702
|
"""
|
|
703
|
+
if not isinstance(pattern, str):
|
|
704
|
+
ignore_case = is_ignore_case_regex(pattern)
|
|
705
|
+
pattern = unverbosify_regex(pattern)
|
|
684
706
|
if self.stack_last_ret != VariableType.STRING:
|
|
685
707
|
self._raise_wrong_type_error(
|
|
686
708
|
self.stack_last_ret, VariableType.STRING
|
|
687
709
|
)
|
|
688
710
|
assert_re_expression(pattern, allow_empty_groups=True)
|
|
689
711
|
|
|
690
|
-
self._add(
|
|
712
|
+
self._add(
|
|
713
|
+
IsRegexMatchExpression(
|
|
714
|
+
pattern=pattern, msg=msg, ignore_case=ignore_case
|
|
715
|
+
)
|
|
716
|
+
)
|
|
691
717
|
return self
|
|
692
718
|
|
|
693
719
|
|
|
@@ -22,6 +22,12 @@ CM2_RX = r"(\\)?((\\{2})*)(#)"
|
|
|
22
22
|
WS_RX = r"(\\)?((\\{2})*)(\s)\s*"
|
|
23
23
|
|
|
24
24
|
|
|
25
|
+
def is_ignore_case_regex(pattern: str | Pattern) -> bool:
|
|
26
|
+
if isinstance(pattern, str):
|
|
27
|
+
return False
|
|
28
|
+
return bool(pattern.flags & re.IGNORECASE)
|
|
29
|
+
|
|
30
|
+
|
|
25
31
|
def unverbosify_regex(pattern: str | Pattern) -> str:
|
|
26
32
|
if isinstance(pattern, str):
|
|
27
33
|
return pattern
|
|
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.7.4 → ssc_codegen-0.7.6}/ssc_codegen/converters/templates/template_bindings.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|