ssc_codegen 0.9.4__tar.gz → 0.9.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.

Potentially problematic release.


This version of ssc_codegen might be problematic. Click here for more details.

Files changed (57) hide show
  1. {ssc_codegen-0.9.4 → ssc_codegen-0.9.6}/.gitignore +11 -1
  2. {ssc_codegen-0.9.4 → ssc_codegen-0.9.6}/PKG-INFO +3 -1
  3. {ssc_codegen-0.9.4 → ssc_codegen-0.9.6}/pyproject.toml +5 -1
  4. {ssc_codegen-0.9.4 → ssc_codegen-0.9.6}/ssc_codegen/__init__.py +1 -1
  5. ssc_codegen-0.9.6/ssc_codegen/_compat.py +28 -0
  6. {ssc_codegen-0.9.4 → ssc_codegen-0.9.6}/ssc_codegen/ast_/__init__.py +1 -1
  7. {ssc_codegen-0.9.4 → ssc_codegen-0.9.6}/ssc_codegen/ast_/nodes_string.py +15 -1
  8. ssc_codegen-0.9.6/ssc_codegen/ast_grep_rules/js_rules.yml +20 -0
  9. ssc_codegen-0.9.6/ssc_codegen/ast_grep_rules/py_rules.yml +20 -0
  10. ssc_codegen-0.9.6/ssc_codegen/cli/ast_grep.py +7 -0
  11. {ssc_codegen-0.9.4 → ssc_codegen-0.9.6}/ssc_codegen/cli/code_callbacks.py +2 -6
  12. {ssc_codegen-0.9.4 → ssc_codegen-0.9.6}/ssc_codegen/cli/main.py +8 -1
  13. {ssc_codegen-0.9.4 → ssc_codegen-0.9.6}/ssc_codegen/converters/go_goquery.py +1 -0
  14. {ssc_codegen-0.9.4 → ssc_codegen-0.9.6}/ssc_codegen/converters/js_pure.py +51 -0
  15. {ssc_codegen-0.9.4 → ssc_codegen-0.9.6}/ssc_codegen/converters/py_base.py +51 -0
  16. {ssc_codegen-0.9.4 → ssc_codegen-0.9.6}/ssc_codegen/document.py +24 -0
  17. {ssc_codegen-0.9.4 → ssc_codegen-0.9.6}/ssc_codegen/str_utils.py +6 -0
  18. {ssc_codegen-0.9.4 → ssc_codegen-0.9.6}/ssc_codegen/tokens.py +2 -0
  19. ssc_codegen-0.9.4/ssc_codegen/_compat.py +0 -9
  20. {ssc_codegen-0.9.4 → ssc_codegen-0.9.6}/LICENSE +0 -0
  21. {ssc_codegen-0.9.4 → ssc_codegen-0.9.6}/README.md +0 -0
  22. {ssc_codegen-0.9.4 → ssc_codegen-0.9.6}/ssc_codegen/ast_/base.py +0 -0
  23. {ssc_codegen-0.9.4 → ssc_codegen-0.9.6}/ssc_codegen/ast_/nodes_array.py +0 -0
  24. {ssc_codegen-0.9.4 → ssc_codegen-0.9.6}/ssc_codegen/ast_/nodes_cast.py +0 -0
  25. {ssc_codegen-0.9.4 → ssc_codegen-0.9.6}/ssc_codegen/ast_/nodes_core.py +0 -0
  26. {ssc_codegen-0.9.4 → ssc_codegen-0.9.6}/ssc_codegen/ast_/nodes_filter.py +0 -0
  27. {ssc_codegen-0.9.4 → ssc_codegen-0.9.6}/ssc_codegen/ast_/nodes_selectors.py +0 -0
  28. {ssc_codegen-0.9.4 → ssc_codegen-0.9.6}/ssc_codegen/ast_/nodes_validate.py +0 -0
  29. {ssc_codegen-0.9.4 → ssc_codegen-0.9.6}/ssc_codegen/ast_build/__init__.py +0 -0
  30. {ssc_codegen-0.9.4 → ssc_codegen-0.9.6}/ssc_codegen/ast_build/main.py +0 -0
  31. {ssc_codegen-0.9.4 → ssc_codegen-0.9.6}/ssc_codegen/ast_build/utils.py +0 -0
  32. {ssc_codegen-0.9.4 → ssc_codegen-0.9.6}/ssc_codegen/cli/__init__.py +0 -0
  33. {ssc_codegen-0.9.4 → ssc_codegen-0.9.6}/ssc_codegen/cli/cli_callbacks.py +0 -0
  34. {ssc_codegen-0.9.4 → ssc_codegen-0.9.6}/ssc_codegen/cli/cli_utils.py +0 -0
  35. {ssc_codegen-0.9.4 → ssc_codegen-0.9.6}/ssc_codegen/cli/consts.py +0 -0
  36. {ssc_codegen-0.9.4 → ssc_codegen-0.9.6}/ssc_codegen/cli/runtime_parse_runners.py +0 -0
  37. {ssc_codegen-0.9.4 → ssc_codegen-0.9.6}/ssc_codegen/compiler.py +0 -0
  38. {ssc_codegen-0.9.4 → ssc_codegen-0.9.6}/ssc_codegen/consts.py +0 -0
  39. {ssc_codegen-0.9.4 → ssc_codegen-0.9.6}/ssc_codegen/converters/__init__.py +0 -0
  40. {ssc_codegen-0.9.4 → ssc_codegen-0.9.6}/ssc_codegen/converters/base.py +0 -0
  41. {ssc_codegen-0.9.4 → ssc_codegen-0.9.6}/ssc_codegen/converters/helpers.py +0 -0
  42. {ssc_codegen-0.9.4 → ssc_codegen-0.9.6}/ssc_codegen/converters/py_bs4.py +0 -0
  43. {ssc_codegen-0.9.4 → ssc_codegen-0.9.6}/ssc_codegen/converters/py_parsel.py +0 -0
  44. {ssc_codegen-0.9.4 → ssc_codegen-0.9.6}/ssc_codegen/converters/py_selectolax.py +0 -0
  45. {ssc_codegen-0.9.4 → ssc_codegen-0.9.6}/ssc_codegen/converters/templates/__init__.py +0 -0
  46. {ssc_codegen-0.9.4 → ssc_codegen-0.9.6}/ssc_codegen/converters/templates/go_goquery.py +0 -0
  47. {ssc_codegen-0.9.4 → ssc_codegen-0.9.6}/ssc_codegen/converters/templates/js_pure.py +0 -0
  48. {ssc_codegen-0.9.4 → ssc_codegen-0.9.6}/ssc_codegen/document_utlis.py +0 -0
  49. {ssc_codegen-0.9.4 → ssc_codegen-0.9.6}/ssc_codegen/json_struct.py +0 -0
  50. {ssc_codegen-0.9.4 → ssc_codegen-0.9.6}/ssc_codegen/json_to_scc.py +0 -0
  51. {ssc_codegen-0.9.4 → ssc_codegen-0.9.6}/ssc_codegen/logs.py +0 -0
  52. {ssc_codegen-0.9.4 → ssc_codegen-0.9.6}/ssc_codegen/pseudo_selectors.py +0 -0
  53. {ssc_codegen-0.9.4 → ssc_codegen-0.9.6}/ssc_codegen/schema.py +0 -0
  54. {ssc_codegen-0.9.4 → ssc_codegen-0.9.6}/ssc_codegen/selector_utils.py +0 -0
  55. {ssc_codegen-0.9.4 → ssc_codegen-0.9.6}/ssc_codegen/static_checker/__init__.py +0 -0
  56. {ssc_codegen-0.9.4 → ssc_codegen-0.9.6}/ssc_codegen/static_checker/base.py +0 -0
  57. {ssc_codegen-0.9.4 → ssc_codegen-0.9.6}/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.4
3
+ Version: 0.9.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
@@ -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.4"
3
+ version = "0.9.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"
@@ -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 = [
@@ -7,7 +7,7 @@ from ssc_codegen.schema import ItemSchema, DictSchema, ListSchema, FlatListSchem
7
7
 
8
8
  setup_logger()
9
9
 
10
- VERSION = "0.9.4"
10
+ VERSION = "0.9.6"
11
11
 
12
12
 
13
13
  class __MISSING(object):
@@ -0,0 +1,28 @@
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
+ def deprecated(reason=""):
16
+ def decorator(func):
17
+ @functools.wraps(func)
18
+ def wrapper(*args, **kwargs):
19
+ warnings.warn(
20
+ f"Function {func.__name__} is deprecated. {reason}",
21
+ category=DeprecationWarning,
22
+ stacklevel=2,
23
+ )
24
+ return func(*args, **kwargs)
25
+
26
+ return wrapper
27
+
28
+ 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: ''
@@ -0,0 +1,7 @@
1
+ from importlib import resources
2
+
3
+
4
+ RULES_PATH = resources.files("ssc_codegen.ast_grep_rules")
5
+
6
+ PY_RULES = str(RULES_PATH.joinpath("py_rules.yml"))
7
+ JS_RULES = str(RULES_PATH.joinpath("js_rules.yml"))
@@ -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(js_pure_optimize_return)
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 = ["ruff format {}", "ruff check {} --fix"]
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)
@@ -1266,3 +1266,4 @@ def pre_list_str_rm_prefix_and_suffix(
1266
1266
  # TODO: impl ExprListUnique
1267
1267
  # TODO: impl ExprStringMapReplace, ExprListStringMapReplace
1268
1268
  # TODO: impl ExprAttr, ExprListAttr for multiple extract keys
1269
+ # TODO impl unescape
@@ -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(/&amp;/g, '&')"
983
+ + ".replace(/&lt;/g, '<')"
984
+ + ".replace(/&gt;/g, '>')"
985
+ + ".replace(/&quot;/g, '\"')"
986
+ + '.replace(/&#039;/g, "\'")'
987
+ + ".replace(/&#x2F;/g, '/')"
988
+ + ".replace(/&nbsp;/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(/&amp;/g, '&')"
1003
+ + ".replace(/&lt;/g, '<')"
1004
+ + ".replace(/&gt;/g, '>')"
1005
+ + ".replace(/&quot;/g, '\"')"
1006
+ + '.replace(/&#039;/g, "\'")'
1007
+ + ".replace(/&#x2F;/g, '/')"
1008
+ + ".replace(/&nbsp;/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
 
@@ -109,6 +110,7 @@ return\s(?P<ret_var>\w+) # RET STMT
109
110
  )
110
111
 
111
112
 
113
+ @deprecated("use ast-grep rule")
112
114
  def py_optimize_return_naive(py_code: str) -> str:
113
115
  """optimize _parse_[a-zA-Z_0-9] and _split_doc return statements
114
116
 
@@ -151,6 +153,9 @@ def py_optimize_return_naive(py_code: str) -> str:
151
153
  # NAIVE apologize, its .filter(...) expr, skip optimization
152
154
  if expr.strip().endswith(" if"):
153
155
  continue
156
+ # unescape expr, skip
157
+ if "_html_unescape" in expr.strip():
158
+ continue
154
159
 
155
160
  # optimization function convert double backslash to single (\\ -> \) in regex patterns
156
161
  # add `r` prefix for avoid SyntaxWarning then compile generated code to cpython bytecode
@@ -205,6 +210,7 @@ def py_remove_unused_none_type_import(py_code: str) -> str:
205
210
  return py_code
206
211
 
207
212
 
213
+ @deprecated("use ast-grep rule")
208
214
  def js_pure_optimize_return(js_code: str) -> str:
209
215
  tmp_code = js_code
210
216
  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()
@@ -1,9 +0,0 @@
1
- import sys
2
-
3
- if sys.version_info >= (3, 11):
4
- from enum import StrEnum
5
- else:
6
- from enum import Enum
7
-
8
- class StrEnum(str, Enum): # noqa
9
- pass
File without changes
File without changes