ssc_codegen 0.9.4__tar.gz → 0.9.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.9.4 → ssc_codegen-0.9.5}/.gitignore +11 -1
- {ssc_codegen-0.9.4 → ssc_codegen-0.9.5}/PKG-INFO +3 -1
- {ssc_codegen-0.9.4 → ssc_codegen-0.9.5}/pyproject.toml +5 -1
- {ssc_codegen-0.9.4 → ssc_codegen-0.9.5}/ssc_codegen/__init__.py +1 -1
- ssc_codegen-0.9.5/ssc_codegen/_compat.py +29 -0
- {ssc_codegen-0.9.4 → ssc_codegen-0.9.5}/ssc_codegen/ast_/__init__.py +1 -1
- {ssc_codegen-0.9.4 → ssc_codegen-0.9.5}/ssc_codegen/ast_/nodes_string.py +15 -1
- ssc_codegen-0.9.5/ssc_codegen/ast_grep_rules/js_rules.yml +20 -0
- ssc_codegen-0.9.5/ssc_codegen/ast_grep_rules/py_rules.yml +20 -0
- ssc_codegen-0.9.5/ssc_codegen/cli/ast_grep.py +7 -0
- {ssc_codegen-0.9.4 → ssc_codegen-0.9.5}/ssc_codegen/cli/code_callbacks.py +2 -6
- {ssc_codegen-0.9.4 → ssc_codegen-0.9.5}/ssc_codegen/cli/main.py +8 -1
- {ssc_codegen-0.9.4 → ssc_codegen-0.9.5}/ssc_codegen/converters/go_goquery.py +1 -0
- {ssc_codegen-0.9.4 → ssc_codegen-0.9.5}/ssc_codegen/converters/js_pure.py +51 -0
- {ssc_codegen-0.9.4 → ssc_codegen-0.9.5}/ssc_codegen/converters/py_base.py +51 -0
- {ssc_codegen-0.9.4 → ssc_codegen-0.9.5}/ssc_codegen/document.py +24 -0
- {ssc_codegen-0.9.4 → ssc_codegen-0.9.5}/ssc_codegen/str_utils.py +6 -1
- {ssc_codegen-0.9.4 → ssc_codegen-0.9.5}/ssc_codegen/tokens.py +2 -0
- ssc_codegen-0.9.4/ssc_codegen/_compat.py +0 -9
- {ssc_codegen-0.9.4 → ssc_codegen-0.9.5}/LICENSE +0 -0
- {ssc_codegen-0.9.4 → ssc_codegen-0.9.5}/README.md +0 -0
- {ssc_codegen-0.9.4 → ssc_codegen-0.9.5}/ssc_codegen/ast_/base.py +0 -0
- {ssc_codegen-0.9.4 → ssc_codegen-0.9.5}/ssc_codegen/ast_/nodes_array.py +0 -0
- {ssc_codegen-0.9.4 → ssc_codegen-0.9.5}/ssc_codegen/ast_/nodes_cast.py +0 -0
- {ssc_codegen-0.9.4 → ssc_codegen-0.9.5}/ssc_codegen/ast_/nodes_core.py +0 -0
- {ssc_codegen-0.9.4 → ssc_codegen-0.9.5}/ssc_codegen/ast_/nodes_filter.py +0 -0
- {ssc_codegen-0.9.4 → ssc_codegen-0.9.5}/ssc_codegen/ast_/nodes_selectors.py +0 -0
- {ssc_codegen-0.9.4 → ssc_codegen-0.9.5}/ssc_codegen/ast_/nodes_validate.py +0 -0
- {ssc_codegen-0.9.4 → ssc_codegen-0.9.5}/ssc_codegen/ast_build/__init__.py +0 -0
- {ssc_codegen-0.9.4 → ssc_codegen-0.9.5}/ssc_codegen/ast_build/main.py +0 -0
- {ssc_codegen-0.9.4 → ssc_codegen-0.9.5}/ssc_codegen/ast_build/utils.py +0 -0
- {ssc_codegen-0.9.4 → ssc_codegen-0.9.5}/ssc_codegen/cli/__init__.py +0 -0
- {ssc_codegen-0.9.4 → ssc_codegen-0.9.5}/ssc_codegen/cli/cli_callbacks.py +0 -0
- {ssc_codegen-0.9.4 → ssc_codegen-0.9.5}/ssc_codegen/cli/cli_utils.py +0 -0
- {ssc_codegen-0.9.4 → ssc_codegen-0.9.5}/ssc_codegen/cli/consts.py +0 -0
- {ssc_codegen-0.9.4 → ssc_codegen-0.9.5}/ssc_codegen/cli/runtime_parse_runners.py +0 -0
- {ssc_codegen-0.9.4 → ssc_codegen-0.9.5}/ssc_codegen/compiler.py +0 -0
- {ssc_codegen-0.9.4 → ssc_codegen-0.9.5}/ssc_codegen/consts.py +0 -0
- {ssc_codegen-0.9.4 → ssc_codegen-0.9.5}/ssc_codegen/converters/__init__.py +0 -0
- {ssc_codegen-0.9.4 → ssc_codegen-0.9.5}/ssc_codegen/converters/base.py +0 -0
- {ssc_codegen-0.9.4 → ssc_codegen-0.9.5}/ssc_codegen/converters/helpers.py +0 -0
- {ssc_codegen-0.9.4 → ssc_codegen-0.9.5}/ssc_codegen/converters/py_bs4.py +0 -0
- {ssc_codegen-0.9.4 → ssc_codegen-0.9.5}/ssc_codegen/converters/py_parsel.py +0 -0
- {ssc_codegen-0.9.4 → ssc_codegen-0.9.5}/ssc_codegen/converters/py_selectolax.py +0 -0
- {ssc_codegen-0.9.4 → ssc_codegen-0.9.5}/ssc_codegen/converters/templates/__init__.py +0 -0
- {ssc_codegen-0.9.4 → ssc_codegen-0.9.5}/ssc_codegen/converters/templates/go_goquery.py +0 -0
- {ssc_codegen-0.9.4 → ssc_codegen-0.9.5}/ssc_codegen/converters/templates/js_pure.py +0 -0
- {ssc_codegen-0.9.4 → ssc_codegen-0.9.5}/ssc_codegen/document_utlis.py +0 -0
- {ssc_codegen-0.9.4 → ssc_codegen-0.9.5}/ssc_codegen/json_struct.py +0 -0
- {ssc_codegen-0.9.4 → ssc_codegen-0.9.5}/ssc_codegen/json_to_scc.py +0 -0
- {ssc_codegen-0.9.4 → ssc_codegen-0.9.5}/ssc_codegen/logs.py +0 -0
- {ssc_codegen-0.9.4 → ssc_codegen-0.9.5}/ssc_codegen/pseudo_selectors.py +0 -0
- {ssc_codegen-0.9.4 → ssc_codegen-0.9.5}/ssc_codegen/schema.py +0 -0
- {ssc_codegen-0.9.4 → ssc_codegen-0.9.5}/ssc_codegen/selector_utils.py +0 -0
- {ssc_codegen-0.9.4 → ssc_codegen-0.9.5}/ssc_codegen/static_checker/__init__.py +0 -0
- {ssc_codegen-0.9.4 → ssc_codegen-0.9.5}/ssc_codegen/static_checker/base.py +0 -0
- {ssc_codegen-0.9.4 → ssc_codegen-0.9.5}/ssc_codegen/static_checker/callbacks.py +0 -0
|
@@ -130,4 +130,14 @@ dmypy.json
|
|
|
130
130
|
.pyre/
|
|
131
131
|
|
|
132
132
|
# project
|
|
133
|
-
test_schemas/
|
|
133
|
+
test_schemas/
|
|
134
|
+
.idea/.gitignore
|
|
135
|
+
.idea/.name
|
|
136
|
+
.idea/misc.xml
|
|
137
|
+
.idea/modules.xml
|
|
138
|
+
.idea/selector_schema_codegen.iml
|
|
139
|
+
.idea/vcs.xml
|
|
140
|
+
.idea/yh_parser.iml
|
|
141
|
+
.idea/inspectionProfiles/profiles_settings.xml
|
|
142
|
+
.idea/inspectionProfiles/Project_Default.xml
|
|
143
|
+
.idea/libraries/Dart_SDK.xml
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: ssc_codegen
|
|
3
|
-
Version: 0.9.
|
|
3
|
+
Version: 0.9.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
|
|
@@ -19,7 +19,9 @@ Classifier: Topic :: Software Development :: Code Generators
|
|
|
19
19
|
Classifier: Topic :: Text Processing :: Markup :: HTML
|
|
20
20
|
Classifier: Topic :: Utilities
|
|
21
21
|
Requires-Python: >=3.10
|
|
22
|
+
Requires-Dist: ast-grep-cli>=0.38.1
|
|
22
23
|
Requires-Dist: bs4>=0.0.2
|
|
24
|
+
Requires-Dist: click<8.2.0
|
|
23
25
|
Requires-Dist: colorama>=0.4.6; sys_platform == 'win32'
|
|
24
26
|
Requires-Dist: cssselect>=1.2.0
|
|
25
27
|
Requires-Dist: httpx>=0.28.1
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "ssc_codegen"
|
|
3
|
-
version = "0.9.
|
|
3
|
+
version = "0.9.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"
|
|
@@ -16,6 +16,10 @@ dependencies = [
|
|
|
16
16
|
"soupsieve>=2.6",
|
|
17
17
|
"typer>=0.15.1",
|
|
18
18
|
"typing_extensions;python_version<'3.11'",
|
|
19
|
+
# https://github.com/fastapi/typer/discussions/1215
|
|
20
|
+
# https://github.com/fastapi/typer/pull/1145
|
|
21
|
+
"click<8.2.0",
|
|
22
|
+
"ast-grep-cli>=0.38.1",
|
|
19
23
|
]
|
|
20
24
|
|
|
21
25
|
classifiers = [
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import sys
|
|
2
|
+
import warnings
|
|
3
|
+
import functools
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
if sys.version_info >= (3, 11):
|
|
7
|
+
from enum import StrEnum
|
|
8
|
+
else:
|
|
9
|
+
from enum import Enum
|
|
10
|
+
|
|
11
|
+
class StrEnum(str, Enum): # noqa
|
|
12
|
+
pass
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def deprecated(reason=""):
|
|
17
|
+
def decorator(func):
|
|
18
|
+
@functools.wraps(func)
|
|
19
|
+
def wrapper(*args, **kwargs):
|
|
20
|
+
warnings.warn(
|
|
21
|
+
f"Function {func.__name__} is deprecated. {reason}",
|
|
22
|
+
category=DeprecationWarning,
|
|
23
|
+
stacklevel=2,
|
|
24
|
+
)
|
|
25
|
+
return func(*args, **kwargs)
|
|
26
|
+
|
|
27
|
+
return wrapper
|
|
28
|
+
|
|
29
|
+
return decorator
|
|
@@ -31,6 +31,6 @@ from .nodes_string import ExprStringTrim, ExprStringRegex, ExprStringSplit, Expr
|
|
|
31
31
|
ExprListStringFormat, ExprListStringReplace, ExprListStringRightTrim, ExprListStringLeftTrim, \
|
|
32
32
|
ExprListStringRegexSub, \
|
|
33
33
|
ExprStringRmPrefix, ExprStringRmSuffix, ExprStringRmPrefixAndSuffix, ExprListStringRmPrefixAndSuffix, \
|
|
34
|
-
ExprListStringRmPrefix, ExprListStringRmSuffix, ExprStringMapReplace, ExprListStringMapReplace
|
|
34
|
+
ExprListStringRmPrefix, ExprListStringRmSuffix, ExprStringMapReplace, ExprListStringMapReplace, ExprStringUnescape, ExprListStringUnescape
|
|
35
35
|
from .nodes_validate import ExprIsCss, ExprIsEqual, ExprStringIsRegex, ExprIsXpath, ExprIsNotEqual, ExprIsContains, \
|
|
36
36
|
ExprListStringAnyRegex, ExprListStringAllRegex, ExprHasAttr, ExprListHasAttr
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
from dataclasses import dataclass
|
|
2
2
|
from typing import TypedDict, ClassVar
|
|
3
3
|
|
|
4
|
-
from ssc_codegen.ast_.base import BaseAstNode
|
|
4
|
+
from ssc_codegen.ast_.base import BaseAstNode, T_EMPTY_KWARGS
|
|
5
5
|
from ssc_codegen.tokens import TokenType, VariableType
|
|
6
6
|
|
|
7
7
|
KW_STR_TRIM = TypedDict("KW_STR_TRIM", {"substr": str})
|
|
@@ -212,3 +212,17 @@ class ExprListStringRmPrefixAndSuffix(
|
|
|
212
212
|
kind: ClassVar[TokenType] = TokenType.EXPR_LIST_STRING_RM_PREFIX_AND_SUFFIX
|
|
213
213
|
accept_type: VariableType = VariableType.LIST_STRING
|
|
214
214
|
ret_type: VariableType = VariableType.LIST_STRING
|
|
215
|
+
|
|
216
|
+
|
|
217
|
+
@dataclass(kw_only=True)
|
|
218
|
+
class ExprStringUnescape(BaseAstNode[T_EMPTY_KWARGS, tuple]):
|
|
219
|
+
kind: ClassVar[TokenType] = TokenType.EXPR_STRING_UNESCAPE
|
|
220
|
+
accept_type: VariableType = VariableType.STRING
|
|
221
|
+
ret_type: VariableType = VariableType.STRING
|
|
222
|
+
|
|
223
|
+
|
|
224
|
+
@dataclass(kw_only=True)
|
|
225
|
+
class ExprListStringUnescape(BaseAstNode[T_EMPTY_KWARGS, tuple]):
|
|
226
|
+
kind: ClassVar[TokenType] = TokenType.EXPR_LIST_STRING_UNESCAPE
|
|
227
|
+
accept_type: VariableType = VariableType.LIST_STRING
|
|
228
|
+
ret_type: VariableType = VariableType.LIST_STRING
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
id: replace-last-decl
|
|
2
|
+
language: js
|
|
3
|
+
rule:
|
|
4
|
+
follows:
|
|
5
|
+
pattern:
|
|
6
|
+
context: let $VAR = $$$EXPR
|
|
7
|
+
selector: lexical_declaration
|
|
8
|
+
pattern: return $VAR
|
|
9
|
+
fix: |-
|
|
10
|
+
return $$$EXPR:
|
|
11
|
+
---
|
|
12
|
+
id: remove-last-decl
|
|
13
|
+
language: js
|
|
14
|
+
rule:
|
|
15
|
+
pattern:
|
|
16
|
+
context: let $VAR = $$$EXPR
|
|
17
|
+
selector: lexical_declaration
|
|
18
|
+
precedes:
|
|
19
|
+
pattern: "return $VAR"
|
|
20
|
+
fix: ''
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
id: replace-last-decl
|
|
2
|
+
language: python
|
|
3
|
+
rule:
|
|
4
|
+
follows:
|
|
5
|
+
pattern:
|
|
6
|
+
context: $VAR = $$$EXPR
|
|
7
|
+
selector: expression_statement
|
|
8
|
+
pattern: return $VAR
|
|
9
|
+
fix: |-
|
|
10
|
+
return $$$EXPR
|
|
11
|
+
---
|
|
12
|
+
id: remove-last-decl
|
|
13
|
+
language: python
|
|
14
|
+
rule:
|
|
15
|
+
pattern:
|
|
16
|
+
context: $VAR = $$$EXPR
|
|
17
|
+
selector: expression_statement
|
|
18
|
+
precedes:
|
|
19
|
+
pattern: "return $VAR"
|
|
20
|
+
fix: ''
|
|
@@ -3,8 +3,6 @@
|
|
|
3
3
|
from typing import Callable
|
|
4
4
|
from ssc_codegen.str_utils import (
|
|
5
5
|
go_unimport_naive,
|
|
6
|
-
py_optimize_return_naive,
|
|
7
|
-
js_pure_optimize_return,
|
|
8
6
|
py_remove_unused_none_type_import,
|
|
9
7
|
)
|
|
10
8
|
|
|
@@ -30,9 +28,7 @@ class BaseCodeCallback:
|
|
|
30
28
|
return code
|
|
31
29
|
|
|
32
30
|
|
|
33
|
-
CB_PY_CODE = BaseCodeCallback(
|
|
34
|
-
py_optimize_return_naive, py_remove_unused_none_type_import
|
|
35
|
-
)
|
|
31
|
+
CB_PY_CODE = BaseCodeCallback(py_remove_unused_none_type_import)
|
|
36
32
|
CB_GO_CODE = BaseCodeCallback(go_unimport_naive, remove_empty_lines=True)
|
|
37
33
|
CB_DART_CODE = BaseCodeCallback(remove_empty_lines=True)
|
|
38
|
-
CB_JS_CODE = BaseCodeCallback(
|
|
34
|
+
CB_JS_CODE = BaseCodeCallback(remove_empty_lines=True)
|
|
@@ -11,6 +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
15
|
from ssc_codegen.cli.cli_callbacks import cb_check_ssc_files, cb_folder_out
|
|
15
16
|
from ssc_codegen.cli.cli_utils import (
|
|
16
17
|
ConverterLike,
|
|
@@ -164,10 +165,15 @@ def gen_py(
|
|
|
164
165
|
) -> None:
|
|
165
166
|
converter = import_converter(f"py_{lib.value}")
|
|
166
167
|
if fmt:
|
|
167
|
-
commands = [
|
|
168
|
+
commands = [
|
|
169
|
+
"ruff format {}",
|
|
170
|
+
"ruff check {} --fix",
|
|
171
|
+
f"ast-grep scan --rule {PY_RULES} -U " + "{}",
|
|
172
|
+
]
|
|
168
173
|
fmt_cmd = create_fmt_cmd(ssc_files, prefix, suffix, out, commands)
|
|
169
174
|
else:
|
|
170
175
|
fmt_cmd = []
|
|
176
|
+
|
|
171
177
|
generate_code(
|
|
172
178
|
converter=converter,
|
|
173
179
|
out=out,
|
|
@@ -216,6 +222,7 @@ def gen_js(
|
|
|
216
222
|
converter = import_converter(f"js_{lib.value}")
|
|
217
223
|
if fmt:
|
|
218
224
|
commands: list[str] = [
|
|
225
|
+
f"ast-grep scan --rule {JS_RULES} -U " + "{}",
|
|
219
226
|
"prettier --write --tab-width 4 --single-quote --trailing-comma all --semi {}",
|
|
220
227
|
]
|
|
221
228
|
fmt_cmd = create_fmt_cmd(ssc_files, prefix, suffix, out, commands)
|
|
@@ -105,6 +105,11 @@ from ssc_codegen.ast_ import (
|
|
|
105
105
|
ExprStringMapReplace,
|
|
106
106
|
ExprListStringMapReplace,
|
|
107
107
|
)
|
|
108
|
+
from ssc_codegen.ast_.nodes_core import ModuleImports
|
|
109
|
+
from ssc_codegen.ast_.nodes_string import (
|
|
110
|
+
ExprListStringUnescape,
|
|
111
|
+
ExprStringUnescape,
|
|
112
|
+
)
|
|
108
113
|
from ssc_codegen.converters.base import BaseCodeConverter
|
|
109
114
|
from ssc_codegen.converters.helpers import (
|
|
110
115
|
prev_next_var,
|
|
@@ -181,6 +186,12 @@ def py_sequence_to_js_array(values: tuple[str, ...] | list[str]) -> str:
|
|
|
181
186
|
return "[" + val_arr[1:-1] + "]"
|
|
182
187
|
|
|
183
188
|
|
|
189
|
+
@CONVERTER(ModuleImports.kind)
|
|
190
|
+
def pre_imports(_node: ModuleImports) -> str:
|
|
191
|
+
# HACK: used imports node for provide helpers scripts
|
|
192
|
+
pass
|
|
193
|
+
|
|
194
|
+
|
|
184
195
|
@CONVERTER(Docstring.kind)
|
|
185
196
|
def pre_docstring(node: Docstring) -> str:
|
|
186
197
|
value = node.kwargs["value"]
|
|
@@ -961,3 +972,43 @@ def pre_list_str_map_repl(node: ExprListStringMapReplace) -> str:
|
|
|
961
972
|
new_arr = list(new_arr) # type: ignore
|
|
962
973
|
prv, nxt = prev_next_var(node)
|
|
963
974
|
return f"let {nxt} = {prv}.map(s => {old_arr}.reduce((s, v, i) => s.replaceAll(v, {new_arr}[i] ?? ''), s));"
|
|
975
|
+
|
|
976
|
+
|
|
977
|
+
@CONVERTER(ExprStringUnescape.kind)
|
|
978
|
+
def pre_str_unescape(node: ExprStringUnescape) -> str:
|
|
979
|
+
prv, nxt = prev_next_var(node)
|
|
980
|
+
return (
|
|
981
|
+
f"let {nxt} = {prv}"
|
|
982
|
+
+ ".replace(/&/g, '&')"
|
|
983
|
+
+ ".replace(/</g, '<')"
|
|
984
|
+
+ ".replace(/>/g, '>')"
|
|
985
|
+
+ ".replace(/"/g, '\"')"
|
|
986
|
+
+ '.replace(/'/g, "\'")'
|
|
987
|
+
+ ".replace(///g, '/')"
|
|
988
|
+
+ ".replace(/ /g, ' ')"
|
|
989
|
+
+ ".replace(/&#x([0-9a-fA-F]+);/g, (_, hex) => String.fromCharCode(parseInt(hex, 16)))"
|
|
990
|
+
+ r".replace(/\\u([0-9a-fA-F]{4})/g, (_, hex) => String.fromCharCode(parseInt(hex, 16)))"
|
|
991
|
+
+ r".replace(/\\x([0-9a-fA-F]{2})/g, (_, hex) => String.fromCharCode(parseInt(hex, 16)))"
|
|
992
|
+
+ r".replace(/\\([bfnrt])/g, (_, ch) => ({b:'\\b',f:'\\f',n:'\\n',r:'\\r',t:'\\t'})[ch])"
|
|
993
|
+
+ ";"
|
|
994
|
+
)
|
|
995
|
+
|
|
996
|
+
|
|
997
|
+
@CONVERTER(ExprListStringUnescape.kind)
|
|
998
|
+
def pre_list_str_unescape(node: ExprListStringUnescape) -> str:
|
|
999
|
+
prv, nxt = prev_next_var(node)
|
|
1000
|
+
return (
|
|
1001
|
+
f"let {nxt} = {prv}.map(s => s"
|
|
1002
|
+
+ ".replace(/&/g, '&')"
|
|
1003
|
+
+ ".replace(/</g, '<')"
|
|
1004
|
+
+ ".replace(/>/g, '>')"
|
|
1005
|
+
+ ".replace(/"/g, '\"')"
|
|
1006
|
+
+ '.replace(/'/g, "\'")'
|
|
1007
|
+
+ ".replace(///g, '/')"
|
|
1008
|
+
+ ".replace(/ /g, ' ')"
|
|
1009
|
+
+ ".replace(/&#x([0-9a-fA-F]+);/g, (_, hex) => String.fromCharCode(parseInt(hex, 16)))"
|
|
1010
|
+
+ r".replace(/\\u([0-9a-fA-F]{4})/g, (_, hex) => String.fromCharCode(parseInt(hex, 16)))"
|
|
1011
|
+
+ r".replace(/\\x([0-9a-fA-F]{2})/g, (_, hex) => String.fromCharCode(parseInt(hex, 16)))"
|
|
1012
|
+
+ r".replace(/\\([bfnrt])/g, (_, ch) => ({b:'\b',f:'\f',n:'\n',r:'\r',t:'\\t'})[ch])"
|
|
1013
|
+
+ ");"
|
|
1014
|
+
)
|
|
@@ -107,6 +107,10 @@ from ssc_codegen.ast_ import (
|
|
|
107
107
|
ExprStringMapReplace,
|
|
108
108
|
ExprListStringMapReplace,
|
|
109
109
|
)
|
|
110
|
+
from ssc_codegen.ast_.nodes_string import (
|
|
111
|
+
ExprListStringUnescape,
|
|
112
|
+
ExprStringUnescape,
|
|
113
|
+
)
|
|
110
114
|
from ssc_codegen.converters.base import (
|
|
111
115
|
BaseCodeConverter,
|
|
112
116
|
CB_FMT_DEBUG_COMMENT,
|
|
@@ -176,6 +180,7 @@ IMPORTS_MIN = """
|
|
|
176
180
|
import re
|
|
177
181
|
import sys
|
|
178
182
|
import json
|
|
183
|
+
from html import unescape as _html_unescape
|
|
179
184
|
from typing import List, Dict, TypedDict, Union, Optional
|
|
180
185
|
from contextlib import suppress
|
|
181
186
|
from functools import reduce
|
|
@@ -278,6 +283,8 @@ class BasePyCodeConverter(BaseCodeConverter):
|
|
|
278
283
|
ExprListUnique.kind: pre_list_unique,
|
|
279
284
|
ExprStringMapReplace.kind: pre_str_map_replace,
|
|
280
285
|
ExprListStringMapReplace.kind: pre_list_str_map_replace,
|
|
286
|
+
ExprStringUnescape.kind: pre_str_unescape,
|
|
287
|
+
ExprListStringUnescape.kind: pre_list_str_unescape,
|
|
281
288
|
}
|
|
282
289
|
|
|
283
290
|
self.post_definitions = {
|
|
@@ -1125,3 +1132,47 @@ def pre_list_str_map_replace(node: ExprListStringMapReplace) -> str:
|
|
|
1125
1132
|
prv, nxt = prev_next_var(node)
|
|
1126
1133
|
expr = f"[reduce(lambda acc, kv: acc.replace(kv[0], kv[1]), {replacements}.items(), i) for i in {prv}]"
|
|
1127
1134
|
return f"{indent}{nxt} = {expr}"
|
|
1135
|
+
|
|
1136
|
+
|
|
1137
|
+
def pre_str_unescape(node: ExprStringUnescape) -> str:
|
|
1138
|
+
indent = (
|
|
1139
|
+
INDENT_DEFAULT_BODY if have_default_expr(node) else INDENT_METHOD_BODY
|
|
1140
|
+
)
|
|
1141
|
+
prv, nxt = prev_next_var(node)
|
|
1142
|
+
return (
|
|
1143
|
+
f"{indent}{nxt} = _html_unescape({prv})\n"
|
|
1144
|
+
# hex unescape
|
|
1145
|
+
+ f"{indent}{nxt} = re.sub(r'&#x([0-9a-fA-F]+);', lambda m: chr(int(m.group(1), 16)), {nxt})\n"
|
|
1146
|
+
+ f"{indent}{nxt} = re.sub(r'\\\\u([0-9a-fA-F]{{4}})', lambda m: chr(int(m.group(1), 16)), {nxt})\n"
|
|
1147
|
+
+ f"{indent}{nxt} = re.sub(r'\\\\x([0-9a-fA-F]{{2}})', lambda m: chr(int(m.group(1), 16)), {nxt})\n"
|
|
1148
|
+
# chars
|
|
1149
|
+
+ f"{indent}{nxt} = {nxt}"
|
|
1150
|
+
+ r".replace('\\b', '\b')"
|
|
1151
|
+
+ r".replace('\\f', '\f')"
|
|
1152
|
+
+ r".replace('\\n', '\n')"
|
|
1153
|
+
+ r".replace('\\r', '\r')"
|
|
1154
|
+
+ r".replace('\\t', '\t')"
|
|
1155
|
+
)
|
|
1156
|
+
|
|
1157
|
+
|
|
1158
|
+
def pre_list_str_unescape(node: ExprListStringUnescape) -> str:
|
|
1159
|
+
indent = (
|
|
1160
|
+
INDENT_DEFAULT_BODY if have_default_expr(node) else INDENT_METHOD_BODY
|
|
1161
|
+
)
|
|
1162
|
+
prv, nxt = prev_next_var(node)
|
|
1163
|
+
return (
|
|
1164
|
+
f"{indent}{nxt} = [_html_unescape(i) for i in {prv}]\n"
|
|
1165
|
+
# hex unescape
|
|
1166
|
+
+ f"{indent}{nxt} = [re.sub(r'&#x([0-9a-fA-F]+);', lambda m: chr(int(m.group(1), 16)), i) for i in {nxt}]\n"
|
|
1167
|
+
+ f"{indent}{nxt} = [re.sub(r'\\\\u([0-9a-fA-F]{{4}})', lambda m: chr(int(m.group(1), 16)), i) for i in {nxt}]\n"
|
|
1168
|
+
+ f"{indent}{nxt} = [re.sub(r'\\\\x([0-9a-fA-F]{{2}})', lambda m: chr(int(m.group(1), 16)), i) for i in {nxt}]\n"
|
|
1169
|
+
+ f"{indent}{nxt} = "
|
|
1170
|
+
# chars
|
|
1171
|
+
+ "[i"
|
|
1172
|
+
+ r".replace('\\b', '\b')"
|
|
1173
|
+
+ r".replace('\\f', '\f')"
|
|
1174
|
+
+ r".replace('\\n', '\n')"
|
|
1175
|
+
+ r".replace('\\r', '\r')"
|
|
1176
|
+
+ r".replace('\\t', '\t')"
|
|
1177
|
+
+ f" for i in {nxt}]"
|
|
1178
|
+
)
|
|
@@ -79,6 +79,10 @@ from ssc_codegen.ast_ import (
|
|
|
79
79
|
ExprListStringMapReplace,
|
|
80
80
|
ExprStringMapReplace,
|
|
81
81
|
)
|
|
82
|
+
from ssc_codegen.ast_.nodes_string import (
|
|
83
|
+
ExprListStringUnescape,
|
|
84
|
+
ExprStringUnescape,
|
|
85
|
+
)
|
|
82
86
|
from ssc_codegen.document_utlis import (
|
|
83
87
|
analyze_re_expression,
|
|
84
88
|
unverbosify_regex,
|
|
@@ -852,6 +856,26 @@ class StringDocument(BaseDocument):
|
|
|
852
856
|
self.re_sub(f"{pattern}$")
|
|
853
857
|
return self
|
|
854
858
|
|
|
859
|
+
def unescape(self) -> Self:
|
|
860
|
+
"""unescape string output
|
|
861
|
+
|
|
862
|
+
- accept STRING, return STRING
|
|
863
|
+
- accept LIST_STRING, return LIST_STRING
|
|
864
|
+
"""
|
|
865
|
+
match self.stack_last_ret:
|
|
866
|
+
case VariableType.STRING:
|
|
867
|
+
self._add(ExprStringUnescape())
|
|
868
|
+
case VariableType.LIST_STRING:
|
|
869
|
+
self._add(ExprListStringUnescape())
|
|
870
|
+
case _:
|
|
871
|
+
LOGGER.warning(
|
|
872
|
+
"unescape(): Expected type(s) %s got %s",
|
|
873
|
+
(VariableType.STRING.name, VariableType.LIST_STRING.name),
|
|
874
|
+
self.stack_last_ret.name,
|
|
875
|
+
)
|
|
876
|
+
self._add(ExprStringUnescape())
|
|
877
|
+
return self
|
|
878
|
+
|
|
855
879
|
|
|
856
880
|
class AssertDocument(BaseDocument):
|
|
857
881
|
def is_css(self, query: str, msg: str = "") -> Self:
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
"""post-processing functions for fix code output and utils operations with strings"""
|
|
2
2
|
|
|
3
3
|
import re
|
|
4
|
+
from ssc_codegen._compat import deprecated
|
|
4
5
|
|
|
5
6
|
RE_GO_IMPORTS_BLOCK = re.compile(r"import\s*\(\s*([\s\S]*?)\s*\)")
|
|
6
7
|
|
|
@@ -108,7 +109,7 @@ return\s(?P<ret_var>\w+) # RET STMT
|
|
|
108
109
|
re.X,
|
|
109
110
|
)
|
|
110
111
|
|
|
111
|
-
|
|
112
|
+
@deprecated("use ast-grep rule")
|
|
112
113
|
def py_optimize_return_naive(py_code: str) -> str:
|
|
113
114
|
"""optimize _parse_[a-zA-Z_0-9] and _split_doc return statements
|
|
114
115
|
|
|
@@ -151,6 +152,9 @@ def py_optimize_return_naive(py_code: str) -> str:
|
|
|
151
152
|
# NAIVE apologize, its .filter(...) expr, skip optimization
|
|
152
153
|
if expr.strip().endswith(" if"):
|
|
153
154
|
continue
|
|
155
|
+
# unescape expr, skip
|
|
156
|
+
if "_html_unescape" in expr.strip():
|
|
157
|
+
continue
|
|
154
158
|
|
|
155
159
|
# optimization function convert double backslash to single (\\ -> \) in regex patterns
|
|
156
160
|
# add `r` prefix for avoid SyntaxWarning then compile generated code to cpython bytecode
|
|
@@ -205,6 +209,7 @@ def py_remove_unused_none_type_import(py_code: str) -> str:
|
|
|
205
209
|
return py_code
|
|
206
210
|
|
|
207
211
|
|
|
212
|
+
@deprecated("use ast-grep rule")
|
|
208
213
|
def js_pure_optimize_return(js_code: str) -> str:
|
|
209
214
|
tmp_code = js_code
|
|
210
215
|
for method_code in RE_JS_METHOD_BLOCK.finditer(tmp_code):
|
|
@@ -133,6 +133,7 @@ class TokenType(IntEnum):
|
|
|
133
133
|
EXPR_STRING_RM_SUFFIX = auto()
|
|
134
134
|
EXPR_STRING_RM_PREFIX_AND_SUFFIX = auto()
|
|
135
135
|
EXPR_STRING_MAP_REPLACE = auto()
|
|
136
|
+
EXPR_STRING_UNESCAPE = auto()
|
|
136
137
|
|
|
137
138
|
# LIST_STRING
|
|
138
139
|
EXPR_LIST_REGEX_SUB = auto()
|
|
@@ -145,6 +146,7 @@ class TokenType(IntEnum):
|
|
|
145
146
|
EXPR_LIST_STRING_RM_SUFFIX = auto()
|
|
146
147
|
EXPR_LIST_STRING_RM_PREFIX_AND_SUFFIX = auto()
|
|
147
148
|
EXPR_LIST_STRING_MAP_REPLACE = auto()
|
|
149
|
+
EXPR_LIST_STRING_UNESCAPE = auto()
|
|
148
150
|
|
|
149
151
|
# ARRAY
|
|
150
152
|
EXPR_LIST_ANY_INDEX = auto()
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|