ssc_codegen 0.7.3__tar.gz → 0.7.5__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.3 → ssc_codegen-0.7.5}/PKG-INFO +1 -1
- {ssc_codegen-0.7.3 → ssc_codegen-0.7.5}/pyproject.toml +2 -2
- {ssc_codegen-0.7.3 → ssc_codegen-0.7.5}/ssc_codegen/__init__.py +1 -1
- {ssc_codegen-0.7.3 → ssc_codegen-0.7.5}/ssc_codegen/ast_ssc.py +4 -0
- {ssc_codegen-0.7.3 → ssc_codegen-0.7.5}/ssc_codegen/converters/py_base.py +3 -3
- {ssc_codegen-0.7.3 → ssc_codegen-0.7.5}/ssc_codegen/converters/templates/py.py +27 -3
- {ssc_codegen-0.7.3 → ssc_codegen-0.7.5}/ssc_codegen/document.py +38 -9
- {ssc_codegen-0.7.3 → ssc_codegen-0.7.5}/ssc_codegen/document_utlis.py +41 -1
- {ssc_codegen-0.7.3 → ssc_codegen-0.7.5}/.gitignore +0 -0
- {ssc_codegen-0.7.3 → ssc_codegen-0.7.5}/LICENSE +0 -0
- {ssc_codegen-0.7.3 → ssc_codegen-0.7.5}/README.md +0 -0
- {ssc_codegen-0.7.3 → ssc_codegen-0.7.5}/ssc_codegen/_compat.py +0 -0
- {ssc_codegen-0.7.3 → ssc_codegen-0.7.5}/ssc_codegen/ast_build_utils.py +0 -0
- {ssc_codegen-0.7.3 → ssc_codegen-0.7.5}/ssc_codegen/ast_builder.py +0 -0
- {ssc_codegen-0.7.3 → ssc_codegen-0.7.5}/ssc_codegen/cli/__init__.py +0 -0
- {ssc_codegen-0.7.3 → ssc_codegen-0.7.5}/ssc_codegen/cli/cli_callbacks.py +0 -0
- {ssc_codegen-0.7.3 → ssc_codegen-0.7.5}/ssc_codegen/cli/cli_utils.py +0 -0
- {ssc_codegen-0.7.3 → ssc_codegen-0.7.5}/ssc_codegen/cli/code_callbacks.py +0 -0
- {ssc_codegen-0.7.3 → ssc_codegen-0.7.5}/ssc_codegen/cli/consts.py +0 -0
- {ssc_codegen-0.7.3 → ssc_codegen-0.7.5}/ssc_codegen/cli/main.py +0 -0
- {ssc_codegen-0.7.3 → ssc_codegen-0.7.5}/ssc_codegen/cli/runtime_parse_runners.py +0 -0
- {ssc_codegen-0.7.3 → ssc_codegen-0.7.5}/ssc_codegen/compiler.py +0 -0
- {ssc_codegen-0.7.3 → ssc_codegen-0.7.5}/ssc_codegen/consts.py +0 -0
- {ssc_codegen-0.7.3 → ssc_codegen-0.7.5}/ssc_codegen/converters/__init__.py +0 -0
- {ssc_codegen-0.7.3 → ssc_codegen-0.7.5}/ssc_codegen/converters/ast_utils.py +0 -0
- {ssc_codegen-0.7.3 → ssc_codegen-0.7.5}/ssc_codegen/converters/base.py +0 -0
- {ssc_codegen-0.7.3 → ssc_codegen-0.7.5}/ssc_codegen/converters/dart_universal_html.py +0 -0
- {ssc_codegen-0.7.3 → ssc_codegen-0.7.5}/ssc_codegen/converters/go_goquery.py +0 -0
- {ssc_codegen-0.7.3 → ssc_codegen-0.7.5}/ssc_codegen/converters/js_pure.py +0 -0
- {ssc_codegen-0.7.3 → ssc_codegen-0.7.5}/ssc_codegen/converters/json_to_schema.py +0 -0
- {ssc_codegen-0.7.3 → ssc_codegen-0.7.5}/ssc_codegen/converters/py_bs4.py +0 -0
- {ssc_codegen-0.7.3 → ssc_codegen-0.7.5}/ssc_codegen/converters/py_parsel.py +0 -0
- {ssc_codegen-0.7.3 → ssc_codegen-0.7.5}/ssc_codegen/converters/py_scrapy.py +0 -0
- {ssc_codegen-0.7.3 → ssc_codegen-0.7.5}/ssc_codegen/converters/py_selectolax.py +0 -0
- {ssc_codegen-0.7.3 → ssc_codegen-0.7.5}/ssc_codegen/converters/templates/dart.py +0 -0
- {ssc_codegen-0.7.3 → ssc_codegen-0.7.5}/ssc_codegen/converters/templates/go.py +0 -0
- {ssc_codegen-0.7.3 → ssc_codegen-0.7.5}/ssc_codegen/converters/templates/js.py +0 -0
- {ssc_codegen-0.7.3 → ssc_codegen-0.7.5}/ssc_codegen/converters/templates/template_bindings.py +0 -0
- {ssc_codegen-0.7.3 → ssc_codegen-0.7.5}/ssc_codegen/json_struct.py +0 -0
- {ssc_codegen-0.7.3 → ssc_codegen-0.7.5}/ssc_codegen/schema.py +0 -0
- {ssc_codegen-0.7.3 → ssc_codegen-0.7.5}/ssc_codegen/selector_utils.py +0 -0
- {ssc_codegen-0.7.3 → ssc_codegen-0.7.5}/ssc_codegen/str_utils.py +0 -0
- {ssc_codegen-0.7.3 → ssc_codegen-0.7.5}/ssc_codegen/tokens.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.5
|
|
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.5"
|
|
4
4
|
description = "Python-dsl code converter to html parser for web scraping "
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
requires-python = ">=3.10"
|
|
@@ -13,6 +13,7 @@ dependencies = [
|
|
|
13
13
|
"typer>=0.15.1",
|
|
14
14
|
"typing_extensions;python_version<'3.11'",
|
|
15
15
|
]
|
|
16
|
+
|
|
16
17
|
classifiers = [
|
|
17
18
|
"Development Status :: 3 - Alpha",
|
|
18
19
|
"Environment :: Console",
|
|
@@ -25,7 +26,6 @@ classifiers = [
|
|
|
25
26
|
"Programming Language :: Python :: 3.11",
|
|
26
27
|
"Programming Language :: Python :: 3.12",
|
|
27
28
|
"Programming Language :: Python :: 3.13",
|
|
28
|
-
|
|
29
29
|
]
|
|
30
30
|
|
|
31
31
|
[project.urls]
|
|
@@ -664,6 +664,7 @@ class RegexExpression(BaseExpression):
|
|
|
664
664
|
ret_type: VariableType = VariableType.STRING
|
|
665
665
|
pattern: str
|
|
666
666
|
group: int
|
|
667
|
+
ignore_case: bool = False
|
|
667
668
|
|
|
668
669
|
|
|
669
670
|
@dataclass(kw_only=True)
|
|
@@ -683,6 +684,7 @@ class RegexAllExpression(BaseExpression):
|
|
|
683
684
|
accept_type: VariableType = VariableType.STRING
|
|
684
685
|
ret_type: VariableType = VariableType.LIST_STRING
|
|
685
686
|
pattern: str
|
|
687
|
+
ignore_case: bool = False
|
|
686
688
|
|
|
687
689
|
|
|
688
690
|
@dataclass(kw_only=True)
|
|
@@ -700,6 +702,7 @@ class RegexSubExpression(BaseExpression):
|
|
|
700
702
|
ret_type: VariableType = VariableType.STRING
|
|
701
703
|
pattern: str
|
|
702
704
|
repl: str
|
|
705
|
+
ignore_case: bool = False
|
|
703
706
|
|
|
704
707
|
|
|
705
708
|
@dataclass(kw_only=True)
|
|
@@ -849,6 +852,7 @@ class IsRegexMatchExpression(BaseExpression):
|
|
|
849
852
|
ret_type: VariableType = VariableType.STRING
|
|
850
853
|
pattern: str
|
|
851
854
|
msg: str
|
|
855
|
+
ignore_case: bool = False
|
|
852
856
|
|
|
853
857
|
|
|
854
858
|
@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({})"
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"""high-level AST builder interface"""
|
|
2
2
|
|
|
3
|
-
from typing import Type
|
|
3
|
+
from typing import Type, Pattern
|
|
4
4
|
|
|
5
5
|
from typing_extensions import Self
|
|
6
6
|
|
|
@@ -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,34 +472,48 @@ 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
|
+
|
|
479
|
+
pattern = unverbosify_regex(pattern)
|
|
469
480
|
assert_re_expression(pattern)
|
|
470
481
|
|
|
471
482
|
if self.stack_last_ret != VariableType.STRING:
|
|
472
483
|
self._raise_wrong_type_error(
|
|
473
484
|
self.stack_last_ret, VariableType.STRING
|
|
474
485
|
)
|
|
475
|
-
self._add(
|
|
486
|
+
self._add(
|
|
487
|
+
RegexExpression(
|
|
488
|
+
pattern=pattern, group=group, ignore_case=ignore_case
|
|
489
|
+
)
|
|
490
|
+
)
|
|
476
491
|
return self
|
|
477
492
|
|
|
478
|
-
def re_all(self, pattern: str) -> Self:
|
|
493
|
+
def re_all(self, pattern: str | Pattern, ignore_case: bool = False) -> Self:
|
|
479
494
|
"""extract all regex results.
|
|
480
495
|
|
|
481
496
|
- accept STRING, return LIST_STRING
|
|
482
497
|
"""
|
|
498
|
+
if not isinstance(pattern, str):
|
|
499
|
+
ignore_case = is_ignore_case_regex(pattern)
|
|
500
|
+
|
|
501
|
+
pattern = unverbosify_regex(pattern)
|
|
483
502
|
assert_re_expression(pattern, max_groups=1)
|
|
484
503
|
if self.stack_last_ret != VariableType.STRING:
|
|
485
504
|
self._raise_wrong_type_error(
|
|
486
505
|
self.stack_last_ret, VariableType.STRING
|
|
487
506
|
)
|
|
488
|
-
self._add(RegexAllExpression(pattern=pattern))
|
|
507
|
+
self._add(RegexAllExpression(pattern=pattern, ignore_case=ignore_case))
|
|
489
508
|
return self
|
|
490
509
|
|
|
491
|
-
def re_sub(self, pattern: str, repl: str = "") -> Self:
|
|
510
|
+
def re_sub(self, pattern: str | Pattern, repl: str = "") -> Self:
|
|
492
511
|
"""Replace substring by `pattern` to `repl`.
|
|
493
512
|
|
|
494
513
|
- accept STRING, return STRING
|
|
495
514
|
- accept LIST_STRING, return LIST_STRING
|
|
496
515
|
"""
|
|
516
|
+
pattern = unverbosify_regex(pattern)
|
|
497
517
|
assert_re_expression(pattern, allow_empty_groups=True)
|
|
498
518
|
|
|
499
519
|
match self.stack_last_ret:
|
|
@@ -671,20 +691,29 @@ class AssertDocument(BaseDocument):
|
|
|
671
691
|
self._add(IsContainsExpression(item=item, msg=msg))
|
|
672
692
|
return self
|
|
673
693
|
|
|
674
|
-
def is_regex(
|
|
694
|
+
def is_regex(
|
|
695
|
+
self, pattern: str | Pattern, msg: str = "", ignore_case: bool = False
|
|
696
|
+
) -> Self:
|
|
675
697
|
"""assert value matched by regex. If in generated code check failed - throw exception with passed msg
|
|
676
698
|
|
|
677
699
|
EXPR DO NOT MODIFY variable
|
|
678
700
|
|
|
679
701
|
- accept STRING, return STRING
|
|
680
702
|
"""
|
|
703
|
+
if not isinstance(pattern, str):
|
|
704
|
+
ignore_case = is_ignore_case_regex(pattern)
|
|
705
|
+
pattern = unverbosify_regex(pattern)
|
|
681
706
|
if self.stack_last_ret != VariableType.STRING:
|
|
682
707
|
self._raise_wrong_type_error(
|
|
683
708
|
self.stack_last_ret, VariableType.STRING
|
|
684
709
|
)
|
|
685
710
|
assert_re_expression(pattern, allow_empty_groups=True)
|
|
686
711
|
|
|
687
|
-
self._add(
|
|
712
|
+
self._add(
|
|
713
|
+
IsRegexMatchExpression(
|
|
714
|
+
pattern=pattern, msg=msg, ignore_case=ignore_case
|
|
715
|
+
)
|
|
716
|
+
)
|
|
688
717
|
return self
|
|
689
718
|
|
|
690
719
|
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import re
|
|
2
|
-
from typing import TYPE_CHECKING
|
|
2
|
+
from typing import TYPE_CHECKING, Pattern
|
|
3
3
|
|
|
4
4
|
from ssc_codegen.ast_ssc import (
|
|
5
5
|
BaseExpression,
|
|
@@ -16,6 +16,46 @@ if TYPE_CHECKING:
|
|
|
16
16
|
from .document import BaseDocument
|
|
17
17
|
|
|
18
18
|
|
|
19
|
+
# https://stackoverflow.com/a/14919203
|
|
20
|
+
CM1_RX = r"(?m)(?<!\\)((\\{2})*)#.*$"
|
|
21
|
+
CM2_RX = r"(\\)?((\\{2})*)(#)"
|
|
22
|
+
WS_RX = r"(\\)?((\\{2})*)(\s)\s*"
|
|
23
|
+
|
|
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
|
+
|
|
31
|
+
def unverbosify_regex(pattern: str | Pattern) -> str:
|
|
32
|
+
if isinstance(pattern, str):
|
|
33
|
+
return pattern
|
|
34
|
+
|
|
35
|
+
def strip_escapes(match): # type: ignore
|
|
36
|
+
## if even slashes: delete space and retain slashes
|
|
37
|
+
if match.group(1) is None:
|
|
38
|
+
return match.group(2)
|
|
39
|
+
## if number of slashes is odd: delete slash and keep space (or 'comment')
|
|
40
|
+
elif match.group(1) == "\\":
|
|
41
|
+
return match.group(2) + match.group(4)
|
|
42
|
+
## error
|
|
43
|
+
else:
|
|
44
|
+
raise Exception
|
|
45
|
+
|
|
46
|
+
if pattern.flags & re.X:
|
|
47
|
+
not_verbose_regex = re.sub(
|
|
48
|
+
WS_RX,
|
|
49
|
+
strip_escapes,
|
|
50
|
+
re.sub(
|
|
51
|
+
CM2_RX, strip_escapes, re.sub(CM1_RX, "\\1", pattern.pattern)
|
|
52
|
+
),
|
|
53
|
+
)
|
|
54
|
+
|
|
55
|
+
return not_verbose_regex
|
|
56
|
+
return pattern.pattern
|
|
57
|
+
|
|
58
|
+
|
|
19
59
|
def assert_re_expression(
|
|
20
60
|
pattern: str, allow_empty_groups: bool = False, max_groups: int = -1
|
|
21
61
|
) -> None:
|
|
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.7.3 → ssc_codegen-0.7.5}/ssc_codegen/converters/templates/template_bindings.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|