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.
Files changed (43) hide show
  1. {ssc_codegen-0.7.3 → ssc_codegen-0.7.5}/PKG-INFO +1 -1
  2. {ssc_codegen-0.7.3 → ssc_codegen-0.7.5}/pyproject.toml +2 -2
  3. {ssc_codegen-0.7.3 → ssc_codegen-0.7.5}/ssc_codegen/__init__.py +1 -1
  4. {ssc_codegen-0.7.3 → ssc_codegen-0.7.5}/ssc_codegen/ast_ssc.py +4 -0
  5. {ssc_codegen-0.7.3 → ssc_codegen-0.7.5}/ssc_codegen/converters/py_base.py +3 -3
  6. {ssc_codegen-0.7.3 → ssc_codegen-0.7.5}/ssc_codegen/converters/templates/py.py +27 -3
  7. {ssc_codegen-0.7.3 → ssc_codegen-0.7.5}/ssc_codegen/document.py +38 -9
  8. {ssc_codegen-0.7.3 → ssc_codegen-0.7.5}/ssc_codegen/document_utlis.py +41 -1
  9. {ssc_codegen-0.7.3 → ssc_codegen-0.7.5}/.gitignore +0 -0
  10. {ssc_codegen-0.7.3 → ssc_codegen-0.7.5}/LICENSE +0 -0
  11. {ssc_codegen-0.7.3 → ssc_codegen-0.7.5}/README.md +0 -0
  12. {ssc_codegen-0.7.3 → ssc_codegen-0.7.5}/ssc_codegen/_compat.py +0 -0
  13. {ssc_codegen-0.7.3 → ssc_codegen-0.7.5}/ssc_codegen/ast_build_utils.py +0 -0
  14. {ssc_codegen-0.7.3 → ssc_codegen-0.7.5}/ssc_codegen/ast_builder.py +0 -0
  15. {ssc_codegen-0.7.3 → ssc_codegen-0.7.5}/ssc_codegen/cli/__init__.py +0 -0
  16. {ssc_codegen-0.7.3 → ssc_codegen-0.7.5}/ssc_codegen/cli/cli_callbacks.py +0 -0
  17. {ssc_codegen-0.7.3 → ssc_codegen-0.7.5}/ssc_codegen/cli/cli_utils.py +0 -0
  18. {ssc_codegen-0.7.3 → ssc_codegen-0.7.5}/ssc_codegen/cli/code_callbacks.py +0 -0
  19. {ssc_codegen-0.7.3 → ssc_codegen-0.7.5}/ssc_codegen/cli/consts.py +0 -0
  20. {ssc_codegen-0.7.3 → ssc_codegen-0.7.5}/ssc_codegen/cli/main.py +0 -0
  21. {ssc_codegen-0.7.3 → ssc_codegen-0.7.5}/ssc_codegen/cli/runtime_parse_runners.py +0 -0
  22. {ssc_codegen-0.7.3 → ssc_codegen-0.7.5}/ssc_codegen/compiler.py +0 -0
  23. {ssc_codegen-0.7.3 → ssc_codegen-0.7.5}/ssc_codegen/consts.py +0 -0
  24. {ssc_codegen-0.7.3 → ssc_codegen-0.7.5}/ssc_codegen/converters/__init__.py +0 -0
  25. {ssc_codegen-0.7.3 → ssc_codegen-0.7.5}/ssc_codegen/converters/ast_utils.py +0 -0
  26. {ssc_codegen-0.7.3 → ssc_codegen-0.7.5}/ssc_codegen/converters/base.py +0 -0
  27. {ssc_codegen-0.7.3 → ssc_codegen-0.7.5}/ssc_codegen/converters/dart_universal_html.py +0 -0
  28. {ssc_codegen-0.7.3 → ssc_codegen-0.7.5}/ssc_codegen/converters/go_goquery.py +0 -0
  29. {ssc_codegen-0.7.3 → ssc_codegen-0.7.5}/ssc_codegen/converters/js_pure.py +0 -0
  30. {ssc_codegen-0.7.3 → ssc_codegen-0.7.5}/ssc_codegen/converters/json_to_schema.py +0 -0
  31. {ssc_codegen-0.7.3 → ssc_codegen-0.7.5}/ssc_codegen/converters/py_bs4.py +0 -0
  32. {ssc_codegen-0.7.3 → ssc_codegen-0.7.5}/ssc_codegen/converters/py_parsel.py +0 -0
  33. {ssc_codegen-0.7.3 → ssc_codegen-0.7.5}/ssc_codegen/converters/py_scrapy.py +0 -0
  34. {ssc_codegen-0.7.3 → ssc_codegen-0.7.5}/ssc_codegen/converters/py_selectolax.py +0 -0
  35. {ssc_codegen-0.7.3 → ssc_codegen-0.7.5}/ssc_codegen/converters/templates/dart.py +0 -0
  36. {ssc_codegen-0.7.3 → ssc_codegen-0.7.5}/ssc_codegen/converters/templates/go.py +0 -0
  37. {ssc_codegen-0.7.3 → ssc_codegen-0.7.5}/ssc_codegen/converters/templates/js.py +0 -0
  38. {ssc_codegen-0.7.3 → ssc_codegen-0.7.5}/ssc_codegen/converters/templates/template_bindings.py +0 -0
  39. {ssc_codegen-0.7.3 → ssc_codegen-0.7.5}/ssc_codegen/json_struct.py +0 -0
  40. {ssc_codegen-0.7.3 → ssc_codegen-0.7.5}/ssc_codegen/schema.py +0 -0
  41. {ssc_codegen-0.7.3 → ssc_codegen-0.7.5}/ssc_codegen/selector_utils.py +0 -0
  42. {ssc_codegen-0.7.3 → ssc_codegen-0.7.5}/ssc_codegen/str_utils.py +0 -0
  43. {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
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"
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]
@@ -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.3"
6
+ VERSION = "0.7.5"
7
7
 
8
8
  class __MISSING(object):
9
9
  pass
@@ -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
- 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({})"
@@ -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 assert_re_expression
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, 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,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(RegexExpression(pattern=pattern, group=group))
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(self, pattern: str, msg: str = "") -> Self:
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(IsRegexMatchExpression(pattern=pattern, msg=msg))
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