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.
Files changed (43) hide show
  1. {ssc_codegen-0.7.4 → ssc_codegen-0.7.6}/PKG-INFO +1 -1
  2. {ssc_codegen-0.7.4 → ssc_codegen-0.7.6}/pyproject.toml +1 -2
  3. {ssc_codegen-0.7.4 → ssc_codegen-0.7.6}/ssc_codegen/__init__.py +1 -1
  4. {ssc_codegen-0.7.4 → ssc_codegen-0.7.6}/ssc_codegen/ast_build_utils.py +18 -0
  5. {ssc_codegen-0.7.4 → ssc_codegen-0.7.6}/ssc_codegen/ast_ssc.py +5 -6
  6. {ssc_codegen-0.7.4 → ssc_codegen-0.7.6}/ssc_codegen/converters/py_base.py +3 -3
  7. {ssc_codegen-0.7.4 → ssc_codegen-0.7.6}/ssc_codegen/converters/templates/py.py +27 -3
  8. {ssc_codegen-0.7.4 → ssc_codegen-0.7.6}/ssc_codegen/document.py +33 -7
  9. {ssc_codegen-0.7.4 → ssc_codegen-0.7.6}/ssc_codegen/document_utlis.py +6 -0
  10. {ssc_codegen-0.7.4 → ssc_codegen-0.7.6}/ssc_codegen/tokens.py +4 -0
  11. {ssc_codegen-0.7.4 → ssc_codegen-0.7.6}/.gitignore +0 -0
  12. {ssc_codegen-0.7.4 → ssc_codegen-0.7.6}/LICENSE +0 -0
  13. {ssc_codegen-0.7.4 → ssc_codegen-0.7.6}/README.md +0 -0
  14. {ssc_codegen-0.7.4 → ssc_codegen-0.7.6}/ssc_codegen/_compat.py +0 -0
  15. {ssc_codegen-0.7.4 → ssc_codegen-0.7.6}/ssc_codegen/ast_builder.py +0 -0
  16. {ssc_codegen-0.7.4 → ssc_codegen-0.7.6}/ssc_codegen/cli/__init__.py +0 -0
  17. {ssc_codegen-0.7.4 → ssc_codegen-0.7.6}/ssc_codegen/cli/cli_callbacks.py +0 -0
  18. {ssc_codegen-0.7.4 → ssc_codegen-0.7.6}/ssc_codegen/cli/cli_utils.py +0 -0
  19. {ssc_codegen-0.7.4 → ssc_codegen-0.7.6}/ssc_codegen/cli/code_callbacks.py +0 -0
  20. {ssc_codegen-0.7.4 → ssc_codegen-0.7.6}/ssc_codegen/cli/consts.py +0 -0
  21. {ssc_codegen-0.7.4 → ssc_codegen-0.7.6}/ssc_codegen/cli/main.py +0 -0
  22. {ssc_codegen-0.7.4 → ssc_codegen-0.7.6}/ssc_codegen/cli/runtime_parse_runners.py +0 -0
  23. {ssc_codegen-0.7.4 → ssc_codegen-0.7.6}/ssc_codegen/compiler.py +0 -0
  24. {ssc_codegen-0.7.4 → ssc_codegen-0.7.6}/ssc_codegen/consts.py +0 -0
  25. {ssc_codegen-0.7.4 → ssc_codegen-0.7.6}/ssc_codegen/converters/__init__.py +0 -0
  26. {ssc_codegen-0.7.4 → ssc_codegen-0.7.6}/ssc_codegen/converters/ast_utils.py +0 -0
  27. {ssc_codegen-0.7.4 → ssc_codegen-0.7.6}/ssc_codegen/converters/base.py +0 -0
  28. {ssc_codegen-0.7.4 → ssc_codegen-0.7.6}/ssc_codegen/converters/dart_universal_html.py +0 -0
  29. {ssc_codegen-0.7.4 → ssc_codegen-0.7.6}/ssc_codegen/converters/go_goquery.py +0 -0
  30. {ssc_codegen-0.7.4 → ssc_codegen-0.7.6}/ssc_codegen/converters/js_pure.py +0 -0
  31. {ssc_codegen-0.7.4 → ssc_codegen-0.7.6}/ssc_codegen/converters/json_to_schema.py +0 -0
  32. {ssc_codegen-0.7.4 → ssc_codegen-0.7.6}/ssc_codegen/converters/py_bs4.py +0 -0
  33. {ssc_codegen-0.7.4 → ssc_codegen-0.7.6}/ssc_codegen/converters/py_parsel.py +0 -0
  34. {ssc_codegen-0.7.4 → ssc_codegen-0.7.6}/ssc_codegen/converters/py_scrapy.py +0 -0
  35. {ssc_codegen-0.7.4 → ssc_codegen-0.7.6}/ssc_codegen/converters/py_selectolax.py +0 -0
  36. {ssc_codegen-0.7.4 → ssc_codegen-0.7.6}/ssc_codegen/converters/templates/dart.py +0 -0
  37. {ssc_codegen-0.7.4 → ssc_codegen-0.7.6}/ssc_codegen/converters/templates/go.py +0 -0
  38. {ssc_codegen-0.7.4 → ssc_codegen-0.7.6}/ssc_codegen/converters/templates/js.py +0 -0
  39. {ssc_codegen-0.7.4 → ssc_codegen-0.7.6}/ssc_codegen/converters/templates/template_bindings.py +0 -0
  40. {ssc_codegen-0.7.4 → ssc_codegen-0.7.6}/ssc_codegen/json_struct.py +0 -0
  41. {ssc_codegen-0.7.4 → ssc_codegen-0.7.6}/ssc_codegen/schema.py +0 -0
  42. {ssc_codegen-0.7.4 → ssc_codegen-0.7.6}/ssc_codegen/selector_utils.py +0 -0
  43. {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.4
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.4"
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]
@@ -3,7 +3,7 @@ from .document import HTMLDocument, StringDocument, ArrayDocument, AssertDocumen
3
3
  from .json_struct import Json
4
4
  from .schema import ItemSchema, DictSchema, ListSchema, FlatListSchema
5
5
 
6
- VERSION = "0.7.4"
6
+ VERSION = "0.7.6"
7
7
 
8
8
  class __MISSING(object):
9
9
  pass
@@ -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
- BINDINGS[TokenType.EXPR_REGEX] = "{} = re.search({}, {})[{}]"
108
- BINDINGS[TokenType.EXPR_REGEX_ALL] = "{} = re.findall({}, {})"
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
- BINDINGS[TokenType.IS_REGEX_MATCH] = "assert re.search({}, {}), {}"
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 assert_re_expression, unverbosify_regex
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(self, pattern: str | Pattern, group: int = 1) -> Self:
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(RegexExpression(pattern=pattern, group=group))
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(self, pattern: str, msg: str = "") -> Self:
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(IsRegexMatchExpression(pattern=pattern, msg=msg))
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
@@ -14,6 +14,10 @@ class VariableType(IntEnum):
14
14
  # mark accept any variable type
15
15
  # used in return and default expr
16
16
  ANY = auto()
17
+
18
+ # currently used for to_len() operation
19
+ LIST_ANY = auto()
20
+
17
21
  # nested functions
18
22
  NESTED = auto()
19
23
  INT = auto()
File without changes
File without changes
File without changes