ssc_codegen 0.8.20__tar.gz → 0.8.22__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.
Files changed (53) hide show
  1. {ssc_codegen-0.8.20 → ssc_codegen-0.8.22}/PKG-INFO +1 -1
  2. {ssc_codegen-0.8.20 → ssc_codegen-0.8.22}/pyproject.toml +1 -1
  3. {ssc_codegen-0.8.20 → ssc_codegen-0.8.22}/ssc_codegen/__init__.py +1 -1
  4. {ssc_codegen-0.8.20 → ssc_codegen-0.8.22}/ssc_codegen/ast_/__init__.py +2 -2
  5. {ssc_codegen-0.8.20 → ssc_codegen-0.8.22}/ssc_codegen/ast_/nodes_array.py +8 -0
  6. {ssc_codegen-0.8.20 → ssc_codegen-0.8.22}/ssc_codegen/ast_/nodes_string.py +20 -0
  7. {ssc_codegen-0.8.20 → ssc_codegen-0.8.22}/ssc_codegen/converters/go_goquery.py +2 -0
  8. {ssc_codegen-0.8.20 → ssc_codegen-0.8.22}/ssc_codegen/converters/js_pure.py +28 -2
  9. {ssc_codegen-0.8.20 → ssc_codegen-0.8.22}/ssc_codegen/converters/py_base.py +40 -0
  10. {ssc_codegen-0.8.20 → ssc_codegen-0.8.22}/ssc_codegen/document.py +42 -0
  11. {ssc_codegen-0.8.20 → ssc_codegen-0.8.22}/ssc_codegen/tokens.py +4 -0
  12. {ssc_codegen-0.8.20 → ssc_codegen-0.8.22}/.gitignore +0 -0
  13. {ssc_codegen-0.8.20 → ssc_codegen-0.8.22}/LICENSE +0 -0
  14. {ssc_codegen-0.8.20 → ssc_codegen-0.8.22}/README.md +0 -0
  15. {ssc_codegen-0.8.20 → ssc_codegen-0.8.22}/ssc_codegen/_compat.py +0 -0
  16. {ssc_codegen-0.8.20 → ssc_codegen-0.8.22}/ssc_codegen/ast_/base.py +0 -0
  17. {ssc_codegen-0.8.20 → ssc_codegen-0.8.22}/ssc_codegen/ast_/nodes_cast.py +0 -0
  18. {ssc_codegen-0.8.20 → ssc_codegen-0.8.22}/ssc_codegen/ast_/nodes_core.py +0 -0
  19. {ssc_codegen-0.8.20 → ssc_codegen-0.8.22}/ssc_codegen/ast_/nodes_filter.py +0 -0
  20. {ssc_codegen-0.8.20 → ssc_codegen-0.8.22}/ssc_codegen/ast_/nodes_selectors.py +0 -0
  21. {ssc_codegen-0.8.20 → ssc_codegen-0.8.22}/ssc_codegen/ast_/nodes_validate.py +0 -0
  22. {ssc_codegen-0.8.20 → ssc_codegen-0.8.22}/ssc_codegen/ast_build/__init__.py +0 -0
  23. {ssc_codegen-0.8.20 → ssc_codegen-0.8.22}/ssc_codegen/ast_build/main.py +0 -0
  24. {ssc_codegen-0.8.20 → ssc_codegen-0.8.22}/ssc_codegen/ast_build/utils.py +0 -0
  25. {ssc_codegen-0.8.20 → ssc_codegen-0.8.22}/ssc_codegen/cli/__init__.py +0 -0
  26. {ssc_codegen-0.8.20 → ssc_codegen-0.8.22}/ssc_codegen/cli/cli_callbacks.py +0 -0
  27. {ssc_codegen-0.8.20 → ssc_codegen-0.8.22}/ssc_codegen/cli/cli_utils.py +0 -0
  28. {ssc_codegen-0.8.20 → ssc_codegen-0.8.22}/ssc_codegen/cli/code_callbacks.py +0 -0
  29. {ssc_codegen-0.8.20 → ssc_codegen-0.8.22}/ssc_codegen/cli/consts.py +0 -0
  30. {ssc_codegen-0.8.20 → ssc_codegen-0.8.22}/ssc_codegen/cli/main.py +0 -0
  31. {ssc_codegen-0.8.20 → ssc_codegen-0.8.22}/ssc_codegen/cli/runtime_parse_runners.py +0 -0
  32. {ssc_codegen-0.8.20 → ssc_codegen-0.8.22}/ssc_codegen/compiler.py +0 -0
  33. {ssc_codegen-0.8.20 → ssc_codegen-0.8.22}/ssc_codegen/consts.py +0 -0
  34. {ssc_codegen-0.8.20 → ssc_codegen-0.8.22}/ssc_codegen/converters/__init__.py +0 -0
  35. {ssc_codegen-0.8.20 → ssc_codegen-0.8.22}/ssc_codegen/converters/base.py +0 -0
  36. {ssc_codegen-0.8.20 → ssc_codegen-0.8.22}/ssc_codegen/converters/helpers.py +0 -0
  37. {ssc_codegen-0.8.20 → ssc_codegen-0.8.22}/ssc_codegen/converters/py_bs4.py +0 -0
  38. {ssc_codegen-0.8.20 → ssc_codegen-0.8.22}/ssc_codegen/converters/py_parsel.py +0 -0
  39. {ssc_codegen-0.8.20 → ssc_codegen-0.8.22}/ssc_codegen/converters/py_selectolax.py +0 -0
  40. {ssc_codegen-0.8.20 → ssc_codegen-0.8.22}/ssc_codegen/converters/templates/__init__.py +0 -0
  41. {ssc_codegen-0.8.20 → ssc_codegen-0.8.22}/ssc_codegen/converters/templates/go_goquery.py +0 -0
  42. {ssc_codegen-0.8.20 → ssc_codegen-0.8.22}/ssc_codegen/converters/templates/js_pure.py +0 -0
  43. {ssc_codegen-0.8.20 → ssc_codegen-0.8.22}/ssc_codegen/document_utlis.py +0 -0
  44. {ssc_codegen-0.8.20 → ssc_codegen-0.8.22}/ssc_codegen/json_struct.py +0 -0
  45. {ssc_codegen-0.8.20 → ssc_codegen-0.8.22}/ssc_codegen/json_to_scc.py +0 -0
  46. {ssc_codegen-0.8.20 → ssc_codegen-0.8.22}/ssc_codegen/logs.py +0 -0
  47. {ssc_codegen-0.8.20 → ssc_codegen-0.8.22}/ssc_codegen/pseudo_selectors.py +0 -0
  48. {ssc_codegen-0.8.20 → ssc_codegen-0.8.22}/ssc_codegen/schema.py +0 -0
  49. {ssc_codegen-0.8.20 → ssc_codegen-0.8.22}/ssc_codegen/selector_utils.py +0 -0
  50. {ssc_codegen-0.8.20 → ssc_codegen-0.8.22}/ssc_codegen/static_checker/__init__.py +0 -0
  51. {ssc_codegen-0.8.20 → ssc_codegen-0.8.22}/ssc_codegen/static_checker/base.py +0 -0
  52. {ssc_codegen-0.8.20 → ssc_codegen-0.8.22}/ssc_codegen/static_checker/callbacks.py +0 -0
  53. {ssc_codegen-0.8.20 → ssc_codegen-0.8.22}/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.8.20
3
+ Version: 0.8.22
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.8.20"
3
+ version = "0.8.22"
4
4
  description = "Python-dsl code converter to html parser for web scraping "
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.10"
@@ -7,7 +7,7 @@ from ssc_codegen.schema import ItemSchema, DictSchema, ListSchema, FlatListSchem
7
7
 
8
8
  setup_logger()
9
9
 
10
- VERSION = "0.8.20"
10
+ VERSION = "0.8.22"
11
11
 
12
12
 
13
13
  class __MISSING(object):
@@ -1,5 +1,5 @@
1
1
  from .base import BaseAstNode
2
- from .nodes_array import ExprIndex, ExprToListLength, ExprListStringJoin
2
+ from .nodes_array import ExprIndex, ExprToListLength, ExprListStringJoin, ExprListUnique
3
3
  from .nodes_cast import ExprNested, ExprJsonify, ExprToInt, ExprToFloat, ExprToListInt, ExprToListFloat, ExprToBool
4
4
  from .nodes_core import (ExprReturn,
5
5
  ExprNoReturn,
@@ -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
34
+ ExprListStringRmPrefix, ExprListStringRmSuffix, ExprStringMapReplace, ExprListStringMapReplace
35
35
  from .nodes_validate import ExprIsCss, ExprIsEqual, ExprStringIsRegex, ExprIsXpath, ExprIsNotEqual, ExprIsContains, \
36
36
  ExprListStringAnyRegex, ExprListStringAllRegex, ExprHasAttr, ExprListHasAttr
@@ -31,3 +31,11 @@ class ExprToListLength(BaseAstNode[T_EMPTY_KWARGS, tuple]):
31
31
  kind: ClassVar[TokenType] = TokenType.EXPR_LIST_LEN
32
32
  accept_type: VariableType = VariableType.LIST_ANY
33
33
  ret_type: VariableType = VariableType.INT
34
+
35
+
36
+ @dataclass(kw_only=True)
37
+ class ExprListUnique(BaseAstNode[T_EMPTY_KWARGS, tuple]):
38
+ # TODO: support LIST_INT, LIST_FLOAT
39
+ kind: ClassVar[TokenType] = TokenType.EXPR_LIST_UNIQUE
40
+ accept_type: VariableType = VariableType.LIST_STRING
41
+ ret_type: VariableType = VariableType.LIST_STRING
@@ -93,6 +93,26 @@ class ExprListStringReplace(BaseAstNode[KW_STR_REPL, tuple[str, str]]):
93
93
  ret_type: VariableType = VariableType.LIST_STRING
94
94
 
95
95
 
96
+ KW_STR_MAP_REPL = TypedDict(
97
+ "KW_STR_MAP_REPL", {"old": tuple[str, ...], "new": tuple[str, ...]}
98
+ )
99
+ ARGS_STR_MAP_REPL = tuple[tuple[str, ...], tuple[str, ...]]
100
+
101
+
102
+ @dataclass(kw_only=True)
103
+ class ExprStringMapReplace(BaseAstNode[KW_STR_MAP_REPL, ARGS_STR_MAP_REPL]):
104
+ kind: ClassVar[TokenType] = TokenType.EXPR_STRING_MAP_REPLACE
105
+ accept_type: VariableType = VariableType.STRING
106
+ ret_type: VariableType = VariableType.STRING
107
+
108
+
109
+ @dataclass(kw_only=True)
110
+ class ExprListStringMapReplace(BaseAstNode[KW_STR_MAP_REPL, ARGS_STR_MAP_REPL]):
111
+ kind: ClassVar[TokenType] = TokenType.EXPR_LIST_STRING_MAP_REPLACE
112
+ accept_type: VariableType = VariableType.LIST_STRING
113
+ ret_type: VariableType = VariableType.LIST_STRING
114
+
115
+
96
116
  KW_STR_RE = TypedDict(
97
117
  "KW_STR_RE", {"pattern": str, "group": int, "ignore_case": bool}
98
118
  )
@@ -1263,3 +1263,5 @@ def pre_list_str_rm_prefix_and_suffix(
1263
1263
 
1264
1264
  # TODO impl ExprHasAttr, ExprListHasAttr
1265
1265
  # TODO: impl Filter Expr
1266
+ # TODO: impl ExprListUnique
1267
+ # TODO: inpt ExprStringMapReplace, ExprListStringMapReplace
@@ -101,6 +101,9 @@ from ssc_codegen.ast_ import (
101
101
  FilterStrLenLe,
102
102
  FilterStrLenGt,
103
103
  FilterStrLenGe,
104
+ ExprListUnique,
105
+ ExprStringMapReplace,
106
+ ExprListStringMapReplace,
104
107
  )
105
108
  from ssc_codegen.converters.base import BaseCodeConverter
106
109
  from ssc_codegen.converters.helpers import (
@@ -376,14 +379,14 @@ def pre_str_split(node: ExprStringSplit) -> str:
376
379
  def pre_str_replace(node: ExprStringReplace) -> str:
377
380
  prv, nxt = prev_next_var(node)
378
381
  old, new = node.unpack_args()
379
- return f"let {nxt} = {prv}.replace({old!r}, {new!r});"
382
+ return f"let {nxt} = {prv}.replaceAll({old!r}, {new!r});"
380
383
 
381
384
 
382
385
  @CONVERTER(ExprListStringReplace.kind)
383
386
  def pre_list_str_replace(node: ExprListStringReplace) -> str:
384
387
  prv, nxt = prev_next_var(node)
385
388
  old, new = node.unpack_args()
386
- return f"let {nxt} = {prv}.map(e => e.replace({old!r}, {new!r}));"
389
+ return f"let {nxt} = {prv}.map(e => e.replaceAll({old!r}, {new!r}));"
387
390
 
388
391
 
389
392
  @CONVERTER(ExprStringRegex.kind)
@@ -914,3 +917,26 @@ def pre_filter_str_len_ge(node: FilterStrLenGe) -> str:
914
917
  if not is_first_node_cond(node) and is_prev_node_atomic_cond(node):
915
918
  return f" && {expr}"
916
919
  return expr
920
+
921
+
922
+ @CONVERTER(ExprListUnique.kind)
923
+ def pre_list_unique(node: ExprListUnique) -> str:
924
+ prv, nxt = prev_next_var(node)
925
+ # save order guaranteed
926
+ # https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set#description
927
+ return f"let {nxt} = [...new Set({prv})]; "
928
+
929
+
930
+ @CONVERTER(ExprStringMapReplace.kind)
931
+ def pre_str_map_repl(node: ExprStringMapReplace) -> str:
932
+ old_arr, new_arr = node.unpack_args()
933
+ prv, nxt = prev_next_var(node)
934
+ # py list<str> literal syntax equal js Array<string> literak
935
+ return f"let {nxt} = {old_arr}.reduce((s, v, i) => s.replaceAll(v, {new_arr}[i] ?? ''), {prv});"
936
+
937
+
938
+ @CONVERTER(ExprListStringMapReplace.kind)
939
+ def pre_list_str_map_repl(node: ExprListStringMapReplace) -> str:
940
+ old_arr, new_arr = node.unpack_args()
941
+ prv, nxt = prev_next_var(node)
942
+ return f"let {nxt} = {prv}.map(s => {old_arr}.reduce((s, v, i) => s.replaceAll(v, {new_arr}[i] ?? ''), s));"
@@ -103,6 +103,9 @@ from ssc_codegen.ast_ import (
103
103
  FilterStrLenLe,
104
104
  FilterStrLenGt,
105
105
  FilterStrLenGe,
106
+ ExprListUnique,
107
+ ExprStringMapReplace,
108
+ ExprListStringMapReplace,
106
109
  )
107
110
  from ssc_codegen.converters.base import (
108
111
  BaseCodeConverter,
@@ -175,6 +178,7 @@ import sys
175
178
  import json
176
179
  from typing import List, Dict, TypedDict, Union, Optional
177
180
  from contextlib import suppress
181
+ from functools import reduce
178
182
 
179
183
  if sys.version_info >= (3, 10):
180
184
  from types import NoneType
@@ -271,6 +275,9 @@ class BasePyCodeConverter(BaseCodeConverter):
271
275
  FilterStrLenLe.kind: pre_filter_str_len_le,
272
276
  FilterStrLenGe.kind: pre_filter_str_len_ge,
273
277
  FilterStrLenGt.kind: pre_filter_str_len_gt,
278
+ ExprListUnique.kind: pre_list_unique,
279
+ ExprStringMapReplace.kind: pre_str_map_replace,
280
+ ExprListStringMapReplace.kind: pre_list_str_map_replace,
274
281
  }
275
282
 
276
283
  self.post_definitions = {
@@ -1069,3 +1076,36 @@ def pre_filter_str_len_ge(node: FilterStrLenGe) -> str:
1069
1076
  if not is_first_node_cond(node) and is_prev_node_atomic_cond(node):
1070
1077
  return f" and {expr}"
1071
1078
  return expr
1079
+
1080
+
1081
+ def pre_list_unique(node: ExprListUnique) -> str:
1082
+ indent = (
1083
+ INDENT_DEFAULT_BODY if have_default_expr(node) else INDENT_METHOD_BODY
1084
+ )
1085
+ prv, nxt = prev_next_var(node)
1086
+ # use `dict.fromkeys()` instead `set()` for guarantees insertion order (py 3.7+) reason
1087
+ return f"{indent}{nxt} = list(dict.fromkeys({prv}))"
1088
+
1089
+
1090
+ def pre_str_map_replace(node: ExprStringMapReplace) -> str:
1091
+ indent = (
1092
+ INDENT_DEFAULT_BODY if have_default_expr(node) else INDENT_METHOD_BODY
1093
+ )
1094
+ old_arr, new_arr = node.unpack_args()
1095
+ replacements = dict(zip(old_arr, new_arr))
1096
+
1097
+ prv, nxt = prev_next_var(node)
1098
+ expr = f"reduce(lambda acc, kv: acc.replace(kv[0], kv[1]), {replacements}.items(), {prv})"
1099
+ return f"{indent}{nxt} = {expr}"
1100
+
1101
+
1102
+ def pre_list_str_map_replace(node: ExprListStringMapReplace) -> str:
1103
+ indent = (
1104
+ INDENT_DEFAULT_BODY if have_default_expr(node) else INDENT_METHOD_BODY
1105
+ )
1106
+ old_arr, new_arr = node.unpack_args()
1107
+ replacements = dict(zip(old_arr, new_arr))
1108
+
1109
+ prv, nxt = prev_next_var(node)
1110
+ expr = f"[reduce(lambda acc, kv: acc.replace(kv[0], kv[1]), {replacements}.items(), i) for i in {prv}]"
1111
+ return f"{indent}{nxt} = {expr}"
@@ -75,6 +75,9 @@ from ssc_codegen.ast_ import (
75
75
  FilterStrLenLe,
76
76
  FilterStrLenGt,
77
77
  FilterStrLenGe,
78
+ ExprListUnique,
79
+ ExprListStringMapReplace,
80
+ ExprStringMapReplace,
78
81
  )
79
82
  from ssc_codegen.document_utlis import (
80
83
  analyze_re_expression,
@@ -487,8 +490,47 @@ class ArrayDocument(BaseDocument):
487
490
  self._add(ExprFilter(body=expr.stack))
488
491
  return self
489
492
 
493
+ def unique(self) -> Self:
494
+ """Remove duplicates from array-like object
495
+
496
+ - accept LIST_STRING, return LIST_STRING
497
+ """
498
+ self._add(ExprListUnique())
499
+ return self
500
+
490
501
 
491
502
  class StringDocument(BaseDocument):
503
+ def repl_map(self, replace_table: dict[str, str]) -> Self:
504
+ old_args = tuple(replace_table.keys())
505
+ new_args = tuple(replace_table.values())
506
+
507
+ match self.stack_last_ret:
508
+ case VariableType.LIST_STRING:
509
+ self._add(
510
+ ExprListStringMapReplace(
511
+ kwargs={"old": old_args, "new": new_args}
512
+ )
513
+ )
514
+ case VariableType.STRING:
515
+ self._add(
516
+ ExprStringMapReplace(
517
+ kwargs={"old": old_args, "new": new_args}
518
+ )
519
+ )
520
+ case _:
521
+ LOGGER.warning(
522
+ "repl_map(%s): Expected type(s) %s got %s",
523
+ replace_table,
524
+ (VariableType.LIST_STRING.name, VariableType.STRING.name),
525
+ self.stack_last_ret.name,
526
+ )
527
+ self._add(
528
+ ExprStringMapReplace(
529
+ kwargs={"old": old_args, "new": new_args}
530
+ )
531
+ )
532
+ return self
533
+
492
534
  def rm_prefix(self, substr: str) -> Self:
493
535
  """remove prefix from string by substr
494
536
 
@@ -131,6 +131,7 @@ class TokenType(IntEnum):
131
131
  EXPR_STRING_RM_PREFIX = auto()
132
132
  EXPR_STRING_RM_SUFFIX = auto()
133
133
  EXPR_STRING_RM_PREFIX_AND_SUFFIX = auto()
134
+ EXPR_STRING_MAP_REPLACE = auto()
134
135
 
135
136
  # LIST_STRING
136
137
  EXPR_LIST_REGEX_SUB = auto()
@@ -142,10 +143,13 @@ class TokenType(IntEnum):
142
143
  EXPR_LIST_STRING_RM_PREFIX = auto()
143
144
  EXPR_LIST_STRING_RM_SUFFIX = auto()
144
145
  EXPR_LIST_STRING_RM_PREFIX_AND_SUFFIX = auto()
146
+ EXPR_LIST_STRING_MAP_REPLACE = auto()
147
+
145
148
  # ARRAY
146
149
  EXPR_LIST_ANY_INDEX = auto()
147
150
  EXPR_LIST_JOIN = auto()
148
151
  EXPR_LIST_LEN = auto()
152
+ EXPR_LIST_UNIQUE = auto()
149
153
 
150
154
  # ASSERT
151
155
  IS_EQUAL = auto()
File without changes
File without changes
File without changes