ssc_codegen 0.7.2__tar.gz → 0.7.4__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 (43) hide show
  1. {ssc_codegen-0.7.2 → ssc_codegen-0.7.4}/PKG-INFO +5 -2
  2. {ssc_codegen-0.7.2 → ssc_codegen-0.7.4}/README.md +4 -1
  3. {ssc_codegen-0.7.2 → ssc_codegen-0.7.4}/pyproject.toml +2 -1
  4. {ssc_codegen-0.7.2 → ssc_codegen-0.7.4}/ssc_codegen/__init__.py +12 -3
  5. {ssc_codegen-0.7.2 → ssc_codegen-0.7.4}/ssc_codegen/ast_builder.py +1 -0
  6. {ssc_codegen-0.7.2 → ssc_codegen-0.7.4}/ssc_codegen/ast_ssc.py +96 -53
  7. {ssc_codegen-0.7.2 → ssc_codegen-0.7.4}/ssc_codegen/converters/dart_universal_html.py +37 -2
  8. {ssc_codegen-0.7.2 → ssc_codegen-0.7.4}/ssc_codegen/converters/go_goquery.py +48 -0
  9. {ssc_codegen-0.7.2 → ssc_codegen-0.7.4}/ssc_codegen/converters/js_pure.py +14 -0
  10. {ssc_codegen-0.7.2 → ssc_codegen-0.7.4}/ssc_codegen/converters/py_base.py +20 -1
  11. {ssc_codegen-0.7.2 → ssc_codegen-0.7.4}/ssc_codegen/converters/templates/dart.py +1 -0
  12. {ssc_codegen-0.7.2 → ssc_codegen-0.7.4}/ssc_codegen/converters/templates/go.py +1 -0
  13. {ssc_codegen-0.7.2 → ssc_codegen-0.7.4}/ssc_codegen/converters/templates/py.py +1 -0
  14. {ssc_codegen-0.7.2 → ssc_codegen-0.7.4}/ssc_codegen/document.py +61 -7
  15. {ssc_codegen-0.7.2 → ssc_codegen-0.7.4}/ssc_codegen/document_utlis.py +35 -1
  16. {ssc_codegen-0.7.2 → ssc_codegen-0.7.4}/ssc_codegen/tokens.py +6 -0
  17. {ssc_codegen-0.7.2 → ssc_codegen-0.7.4}/.gitignore +0 -0
  18. {ssc_codegen-0.7.2 → ssc_codegen-0.7.4}/LICENSE +0 -0
  19. {ssc_codegen-0.7.2 → ssc_codegen-0.7.4}/ssc_codegen/_compat.py +0 -0
  20. {ssc_codegen-0.7.2 → ssc_codegen-0.7.4}/ssc_codegen/ast_build_utils.py +0 -0
  21. {ssc_codegen-0.7.2 → ssc_codegen-0.7.4}/ssc_codegen/cli/__init__.py +0 -0
  22. {ssc_codegen-0.7.2 → ssc_codegen-0.7.4}/ssc_codegen/cli/cli_callbacks.py +0 -0
  23. {ssc_codegen-0.7.2 → ssc_codegen-0.7.4}/ssc_codegen/cli/cli_utils.py +0 -0
  24. {ssc_codegen-0.7.2 → ssc_codegen-0.7.4}/ssc_codegen/cli/code_callbacks.py +0 -0
  25. {ssc_codegen-0.7.2 → ssc_codegen-0.7.4}/ssc_codegen/cli/consts.py +0 -0
  26. {ssc_codegen-0.7.2 → ssc_codegen-0.7.4}/ssc_codegen/cli/main.py +0 -0
  27. {ssc_codegen-0.7.2 → ssc_codegen-0.7.4}/ssc_codegen/cli/runtime_parse_runners.py +0 -0
  28. {ssc_codegen-0.7.2 → ssc_codegen-0.7.4}/ssc_codegen/compiler.py +0 -0
  29. {ssc_codegen-0.7.2 → ssc_codegen-0.7.4}/ssc_codegen/consts.py +0 -0
  30. {ssc_codegen-0.7.2 → ssc_codegen-0.7.4}/ssc_codegen/converters/__init__.py +0 -0
  31. {ssc_codegen-0.7.2 → ssc_codegen-0.7.4}/ssc_codegen/converters/ast_utils.py +0 -0
  32. {ssc_codegen-0.7.2 → ssc_codegen-0.7.4}/ssc_codegen/converters/base.py +0 -0
  33. {ssc_codegen-0.7.2 → ssc_codegen-0.7.4}/ssc_codegen/converters/json_to_schema.py +0 -0
  34. {ssc_codegen-0.7.2 → ssc_codegen-0.7.4}/ssc_codegen/converters/py_bs4.py +0 -0
  35. {ssc_codegen-0.7.2 → ssc_codegen-0.7.4}/ssc_codegen/converters/py_parsel.py +0 -0
  36. {ssc_codegen-0.7.2 → ssc_codegen-0.7.4}/ssc_codegen/converters/py_scrapy.py +0 -0
  37. {ssc_codegen-0.7.2 → ssc_codegen-0.7.4}/ssc_codegen/converters/py_selectolax.py +0 -0
  38. {ssc_codegen-0.7.2 → ssc_codegen-0.7.4}/ssc_codegen/converters/templates/js.py +0 -0
  39. {ssc_codegen-0.7.2 → ssc_codegen-0.7.4}/ssc_codegen/converters/templates/template_bindings.py +0 -0
  40. {ssc_codegen-0.7.2 → ssc_codegen-0.7.4}/ssc_codegen/json_struct.py +0 -0
  41. {ssc_codegen-0.7.2 → ssc_codegen-0.7.4}/ssc_codegen/schema.py +0 -0
  42. {ssc_codegen-0.7.2 → ssc_codegen-0.7.4}/ssc_codegen/selector_utils.py +0 -0
  43. {ssc_codegen-0.7.2 → ssc_codegen-0.7.4}/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.2
3
+ Version: 0.7.4
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
@@ -230,9 +230,12 @@ You can use any html source:
230
230
  - get from browser (playwright, selenium, chrome-cdp)
231
231
  - paste code to developer console (js)
232
232
  - or call curl in shell and parse stdin
233
+ - use in third-party tools in stdin pipeline:
234
+ (from [ProjectDiscovery](https://github.com/projectdiscovery), for example)
233
235
 
234
236
 
235
237
  ## See also
236
238
  - [Brief](docs/brief.md) about css selectors and regular expressions.
237
239
  - [Tutorial](docs/tutorial.md) how to use ssc-gen
238
- - [Reference](docs/reference.md) about high-level API
240
+ - [Reference](docs/reference.md) about high-level API
241
+ - [AST reference](docs/ast_reference.md) about generation code from AST
@@ -200,9 +200,12 @@ You can use any html source:
200
200
  - get from browser (playwright, selenium, chrome-cdp)
201
201
  - paste code to developer console (js)
202
202
  - or call curl in shell and parse stdin
203
+ - use in third-party tools in stdin pipeline:
204
+ (from [ProjectDiscovery](https://github.com/projectdiscovery), for example)
203
205
 
204
206
 
205
207
  ## See also
206
208
  - [Brief](docs/brief.md) about css selectors and regular expressions.
207
209
  - [Tutorial](docs/tutorial.md) how to use ssc-gen
208
- - [Reference](docs/reference.md) about high-level API
210
+ - [Reference](docs/reference.md) about high-level API
211
+ - [AST reference](docs/ast_reference.md) about generation code from AST
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "ssc_codegen"
3
- version = "0.7.2"
3
+ version = "0.7.4"
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",
@@ -1,16 +1,25 @@
1
1
  from .document import HTMLDocument, StringDocument, ArrayDocument, AssertDocument, NestedDocument, DefaultDocument, \
2
- NumericDocument, JsonDocument
2
+ NumericDocument, JsonDocument, BooleanDocument
3
3
  from .json_struct import Json
4
4
  from .schema import ItemSchema, DictSchema, ListSchema, FlatListSchema
5
5
 
6
- VERSION = "0.7.2"
6
+ VERSION = "0.7.4"
7
7
 
8
8
  class __MISSING(object):
9
9
  pass
10
10
 
11
11
  _NO_DEFAULT = __MISSING()
12
12
 
13
- class Document(HTMLDocument, StringDocument, ArrayDocument, AssertDocument, DefaultDocument, NumericDocument, JsonDocument):
13
+ class Document(
14
+ HTMLDocument,
15
+ StringDocument,
16
+ ArrayDocument,
17
+ AssertDocument,
18
+ DefaultDocument,
19
+ NumericDocument,
20
+ JsonDocument,
21
+ BooleanDocument
22
+ ):
14
23
  pass
15
24
 
16
25
 
@@ -207,6 +207,7 @@ def build_ast_struct(
207
207
  assert_schema_dict_key_is_string(field, name, schema)
208
208
  assert_split_doc_is_list_document(field, name, schema)
209
209
  assert_ret_type_not_document(field, name, schema)
210
+ # TODO: compare default placeholder value type and last expr ret_type
210
211
 
211
212
  ast_struct_parser = StructParser(
212
213
  type=schema.__SCHEMA_TYPE__,
@@ -69,6 +69,11 @@ class Variable(BaseAstNode):
69
69
  """
70
70
  return self.num + 1
71
71
 
72
+ def __repr__(self) -> str:
73
+ return (
74
+ f"{self.kind.name!r}({self.num!r} of {self.count!r}) {self.type!r}"
75
+ )
76
+
72
77
 
73
78
  @dataclass(kw_only=True)
74
79
  class Docstring(BaseAstNode):
@@ -80,8 +85,8 @@ class Docstring(BaseAstNode):
80
85
 
81
86
  kind: ClassVar[TokenType] = TokenType.DOCSTRING
82
87
  value: str
83
- parent: Optional[Union["StructParser", "ModuleProgram"]] = (
84
- None # LATE INIT in builder
88
+ parent: Optional[Union["StructParser", "ModuleProgram"]] = field(
89
+ default=None, repr=False
85
90
  )
86
91
 
87
92
 
@@ -104,7 +109,7 @@ class TypeDefField(BaseAstNode):
104
109
  kind: ClassVar[TokenType] = TokenType.TYPEDEF_FIELD
105
110
  name: str
106
111
  ret_type: VariableType
107
- parent: Optional["TypeDef"] = None
112
+ parent: Optional["TypeDef"] = field(default=None, repr=False)
108
113
 
109
114
  @property
110
115
  def nested_class(self) -> str | None:
@@ -146,8 +151,8 @@ class TypeDef(BaseAstNode):
146
151
  kind: ClassVar[TokenType] = TokenType.TYPEDEF
147
152
  name: str
148
153
  body: list[TypeDefField]
149
- struct_ref: Optional["StructParser"] = None
150
- parent: Optional[ModuleProgram] = None
154
+ struct_ref: Optional["StructParser"] = field(default=None, repr=False)
155
+ parent: Optional[ModuleProgram] = field(default=None, repr=False)
151
156
 
152
157
  def __post_init__(self) -> None:
153
158
  for f in self.body:
@@ -174,9 +179,13 @@ class BaseExpression(BaseAstNode):
174
179
  "PreValidateFunction",
175
180
  "PartDocFunction",
176
181
  ]
177
- ] = None # LATE INIT
178
- prev: Optional["BaseExpression"] = None # LATE INIT
179
- next: Optional["BaseExpression"] = None # LATE INIT
182
+ ] = field(init=False, repr=False) # LATE INIT
183
+ prev: Optional["BaseExpression"] = field(
184
+ default=None, repr=False
185
+ ) # LATE INIT
186
+ next: Optional["BaseExpression"] = field(
187
+ default=None, repr=False
188
+ ) # LATE INIT
180
189
 
181
190
  accept_type: VariableType
182
191
  ret_type: VariableType
@@ -218,7 +227,7 @@ class DefaultValueWrapper(BaseExpression):
218
227
 
219
228
  accept_type: VariableType = VariableType.DOCUMENT
220
229
  ret_type: VariableType = VariableType.DOCUMENT
221
- value: str | int | float | None
230
+ value: str | int | float | bool | None
222
231
 
223
232
 
224
233
  @dataclass(kw_only=True)
@@ -233,7 +242,7 @@ class DefaultStart(BaseExpression):
233
242
  kind: ClassVar[TokenType] = TokenType.EXPR_DEFAULT_START
234
243
  accept_type: VariableType = VariableType.ANY
235
244
  ret_type: VariableType = VariableType.ANY
236
- value: str | int | float | None
245
+ value: str | int | float | bool | None
237
246
 
238
247
 
239
248
  @dataclass(kw_only=True)
@@ -249,7 +258,7 @@ class DefaultEnd(BaseExpression):
249
258
  kind: ClassVar[TokenType] = TokenType.EXPR_DEFAULT_END
250
259
  accept_type: VariableType = VariableType.ANY
251
260
  ret_type: VariableType = VariableType.ANY
252
- value: str | int | float | None
261
+ value: str | int | float | bool | None
253
262
 
254
263
 
255
264
  # DOCUMENT
@@ -415,6 +424,18 @@ class JoinExpression(BaseExpression):
415
424
  ret_type: VariableType = VariableType.STRING
416
425
 
417
426
 
427
+ @dataclass(kw_only=True)
428
+ class ArrayLengthExpression(BaseExpression):
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
+ )
436
+ ret_type: VariableType = VariableType.INT
437
+
438
+
418
439
  # STRING
419
440
  @dataclass(kw_only=True)
420
441
  class __TrimNode(BaseExpression):
@@ -482,9 +503,9 @@ class RTrimExpression(__TrimNode):
482
503
 
483
504
  @dataclass(kw_only=True)
484
505
  class __MapTrimNode(BaseExpression):
485
- value: str
486
506
  accept_type: VariableType = VariableType.LIST_STRING
487
507
  ret_type: VariableType = VariableType.LIST_STRING
508
+ value: str
488
509
 
489
510
 
490
511
  @dataclass(kw_only=True)
@@ -552,9 +573,9 @@ class SplitExpression(BaseExpression):
552
573
  """
553
574
 
554
575
  kind: ClassVar[TokenType] = TokenType.EXPR_STRING_SPLIT
555
- sep: str
556
576
  accept_type: VariableType = VariableType.STRING
557
577
  ret_type: VariableType = VariableType.LIST_STRING
578
+ sep: str
558
579
 
559
580
 
560
581
  @dataclass(kw_only=True)
@@ -570,9 +591,9 @@ class FormatExpression(BaseExpression):
570
591
  """
571
592
 
572
593
  kind: ClassVar[TokenType] = TokenType.EXPR_STRING_FORMAT
573
- fmt: str
574
594
  accept_type: VariableType = VariableType.STRING
575
595
  ret_type: VariableType = VariableType.STRING
596
+ fmt: str
576
597
 
577
598
 
578
599
  @dataclass(kw_only=True)
@@ -588,9 +609,9 @@ class MapFormatExpression(BaseExpression):
588
609
  """
589
610
 
590
611
  kind: ClassVar[TokenType] = TokenType.EXPR_LIST_STRING_FORMAT
591
- fmt: str
592
612
  accept_type: VariableType = VariableType.LIST_STRING
593
613
  ret_type: VariableType = VariableType.LIST_STRING
614
+ fmt: str
594
615
 
595
616
 
596
617
  @dataclass(kw_only=True)
@@ -603,10 +624,10 @@ class ReplaceExpression(BaseExpression):
603
624
  """
604
625
 
605
626
  kind: ClassVar[TokenType] = TokenType.EXPR_STRING_REPLACE
606
- old: str
607
- new: str
608
627
  accept_type: VariableType = VariableType.STRING
609
628
  ret_type: VariableType = VariableType.STRING
629
+ old: str
630
+ new: str
610
631
 
611
632
 
612
633
  @dataclass(kw_only=True)
@@ -619,10 +640,10 @@ class MapReplaceExpression(BaseExpression):
619
640
  """
620
641
 
621
642
  kind: ClassVar[TokenType] = TokenType.EXPR_LIST_STRING_REPLACE
622
- old: str
623
- new: str
624
643
  accept_type: VariableType = VariableType.LIST_STRING
625
644
  ret_type: VariableType = VariableType.LIST_STRING
645
+ old: str
646
+ new: str
626
647
 
627
648
 
628
649
  # REGEX
@@ -639,11 +660,10 @@ class RegexExpression(BaseExpression):
639
660
  """
640
661
 
641
662
  kind: ClassVar[TokenType] = TokenType.EXPR_REGEX
642
-
643
- pattern: str
644
- group: int
645
663
  accept_type: VariableType = VariableType.STRING
646
664
  ret_type: VariableType = VariableType.STRING
665
+ pattern: str
666
+ group: int
647
667
 
648
668
 
649
669
  @dataclass(kw_only=True)
@@ -660,10 +680,9 @@ class RegexAllExpression(BaseExpression):
660
680
  """
661
681
 
662
682
  kind: ClassVar[TokenType] = TokenType.EXPR_REGEX_ALL
663
-
664
- pattern: str
665
683
  accept_type: VariableType = VariableType.STRING
666
684
  ret_type: VariableType = VariableType.LIST_STRING
685
+ pattern: str
667
686
 
668
687
 
669
688
  @dataclass(kw_only=True)
@@ -677,11 +696,10 @@ class RegexSubExpression(BaseExpression):
677
696
  """
678
697
 
679
698
  kind: ClassVar[TokenType] = TokenType.EXPR_REGEX_SUB
680
-
681
- pattern: str
682
- repl: str
683
699
  accept_type: VariableType = VariableType.STRING
684
700
  ret_type: VariableType = VariableType.STRING
701
+ pattern: str
702
+ repl: str
685
703
 
686
704
 
687
705
  @dataclass(kw_only=True)
@@ -695,11 +713,10 @@ class MapRegexSubExpression(BaseExpression):
695
713
  """
696
714
 
697
715
  kind: ClassVar[TokenType] = TokenType.EXPR_LIST_REGEX_SUB
698
-
699
- pattern: str
700
- repl: str
701
716
  accept_type: VariableType = VariableType.LIST_STRING
702
717
  ret_type: VariableType = VariableType.LIST_STRING
718
+ pattern: str
719
+ repl: str
703
720
 
704
721
 
705
722
  # asserts - validators - not modify variables
@@ -717,11 +734,10 @@ class IsEqualExpression(BaseExpression):
717
734
  """
718
735
 
719
736
  kind: ClassVar[TokenType] = TokenType.IS_EQUAL
720
-
721
- value: str | int | float
722
- msg: str
723
737
  accept_type: VariableType = VariableType.STRING
724
738
  ret_type: VariableType = VariableType.STRING
739
+ value: str | int | float
740
+ msg: str
725
741
 
726
742
 
727
743
  @dataclass(kw_only=True)
@@ -738,11 +754,10 @@ class IsNotEqualExpression(BaseExpression):
738
754
  """
739
755
 
740
756
  kind: ClassVar[TokenType] = TokenType.IS_NOT_EQUAL
741
-
742
- value: str | int | float
743
- msg: str
744
757
  accept_type: VariableType = VariableType.STRING
745
758
  ret_type: VariableType = VariableType.STRING
759
+ value: str | int | float
760
+ msg: str
746
761
 
747
762
 
748
763
  @dataclass(kw_only=True)
@@ -762,10 +777,10 @@ class IsCssExpression(BaseExpression):
762
777
  """
763
778
 
764
779
  kind: ClassVar[TokenType] = TokenType.IS_CSS
765
- query: str
766
- msg: str
767
780
  accept_type: VariableType = VariableType.DOCUMENT
768
781
  ret_type: VariableType = VariableType.DOCUMENT
782
+ query: str
783
+ msg: str
769
784
 
770
785
 
771
786
  @dataclass(kw_only=True)
@@ -785,10 +800,10 @@ class IsXPathExpression(BaseExpression):
785
800
  """
786
801
 
787
802
  kind: ClassVar[TokenType] = TokenType.IS_XPATH
788
- query: str
789
- msg: str
790
803
  accept_type: VariableType = VariableType.DOCUMENT
791
804
  ret_type: VariableType = VariableType.DOCUMENT
805
+ query: str
806
+ msg: str
792
807
 
793
808
 
794
809
  @dataclass(kw_only=True)
@@ -808,10 +823,10 @@ class IsContainsExpression(BaseExpression):
808
823
  """
809
824
 
810
825
  kind: ClassVar[TokenType] = TokenType.IS_CONTAINS
811
- item: str | int | float
812
- msg: str
813
826
  accept_type: VariableType = VariableType.LIST_STRING
814
827
  ret_type: VariableType = VariableType.LIST_STRING
828
+ item: str | int | float
829
+ msg: str
815
830
 
816
831
 
817
832
  @dataclass(kw_only=True)
@@ -830,10 +845,10 @@ class IsRegexMatchExpression(BaseExpression):
830
845
  """
831
846
 
832
847
  kind: ClassVar[TokenType] = TokenType.IS_REGEX_MATCH
833
- pattern: str
834
- msg: str
835
848
  accept_type: VariableType = VariableType.STRING
836
849
  ret_type: VariableType = VariableType.STRING
850
+ pattern: str
851
+ msg: str
837
852
 
838
853
 
839
854
  @dataclass(kw_only=True)
@@ -917,7 +932,7 @@ class CallStructFunctionExpression(BaseExpression):
917
932
  accept_type: VariableType = VariableType.DOCUMENT
918
933
  fn_ref: Union[
919
934
  "StructFieldFunction", "PreValidateFunction", "PartDocFunction", None
920
- ]
935
+ ] = field(default=None, repr=False)
921
936
  nested_cls_name_ref: str | None = None
922
937
  name: str
923
938
  ret_type: VariableType
@@ -1029,7 +1044,9 @@ class StructFieldFunction(__StructNode):
1029
1044
 
1030
1045
  name: M_ITEM | M_KEY | M_VALUE | str
1031
1046
  kind: ClassVar[TokenType] = TokenType.STRUCT_FIELD
1032
- parent: Optional["StructParser"] = None # LATE INIT
1047
+ parent: Optional["StructParser"] = field(
1048
+ default=None, repr=False
1049
+ ) # LATE INIT
1033
1050
 
1034
1051
  def find_associated_typedef(self) -> TypeDef | None:
1035
1052
  """try get associated typedef Node
@@ -1093,7 +1110,9 @@ class PreValidateFunction(__StructNode):
1093
1110
 
1094
1111
  kind: ClassVar[TokenType] = TokenType.STRUCT_PRE_VALIDATE
1095
1112
  name: M_PRE_VALIDATE = "__PRE_VALIDATE__"
1096
- parent: Optional["StructParser"] = None # LATE INIT
1113
+ parent: Optional["StructParser"] = field(
1114
+ default=None, init=False, repr=False
1115
+ ) # LATE INIT
1097
1116
 
1098
1117
 
1099
1118
  @dataclass(kw_only=True)
@@ -1126,7 +1145,9 @@ class StartParseFunction(__StructNode):
1126
1145
  kind: ClassVar[TokenType] = TokenType.STRUCT_PARSE_START
1127
1146
  body: list[CallStructFunctionExpression]
1128
1147
  type: StructType
1129
- parent: Optional["StructParser"] = None # LATE INIT
1148
+ parent: Optional["StructParser"] = field(
1149
+ default=None, repr=False
1150
+ ) # LATE INIT
1130
1151
 
1131
1152
  def have_assert_expr(self) -> bool:
1132
1153
  for expr in self.body:
@@ -1155,7 +1176,9 @@ class StructInit(BaseAstNode):
1155
1176
  """
1156
1177
 
1157
1178
  kind: ClassVar[TokenType] = TokenType.STRUCT_INIT
1158
- parent: Optional["StructParser"] = None # LATE INIT
1179
+ parent: Optional["StructParser"] = field(
1180
+ default=None, repr=False
1181
+ ) # LATE INIT
1159
1182
  name: str
1160
1183
 
1161
1184
 
@@ -1185,8 +1208,10 @@ class StructParser(BaseAstNode):
1185
1208
  | PreValidateFunction
1186
1209
  | PartDocFunction
1187
1210
  ]
1188
- typedef: Optional["TypeDef"] = field(init=False)
1189
- parent: Optional[ModuleProgram] = None # LATE INIT
1211
+ typedef: Optional["TypeDef"] = field(default=None, repr=False)
1212
+ parent: Optional[ModuleProgram] = field(
1213
+ default=None, repr=False
1214
+ ) # LATE INIT
1190
1215
 
1191
1216
  def _build_typedef(self) -> None:
1192
1217
  ast_typedef = TypeDef(
@@ -1241,7 +1266,7 @@ class JsonStruct(BaseAstNode):
1241
1266
  name: str
1242
1267
  body: list["JsonStructField"]
1243
1268
 
1244
- parent: Optional[ModuleProgram] = None
1269
+ parent: Optional[ModuleProgram] = field(default=None, repr=False)
1245
1270
 
1246
1271
  def __post_init__(self) -> None:
1247
1272
  for f in self.body:
@@ -1254,7 +1279,7 @@ class JsonStructField(BaseAstNode):
1254
1279
  name: str
1255
1280
  value: BaseJsonType
1256
1281
 
1257
- parent: Optional[JsonStruct] = None # LATE INIT
1282
+ parent: Optional[JsonStruct] = field(default=None, repr=False) # LATE INIT
1258
1283
 
1259
1284
  @property
1260
1285
  def ret_type(self) -> BaseJsonType | dict[str, BaseJsonType]:
@@ -1270,6 +1295,8 @@ class JsonStructField(BaseAstNode):
1270
1295
 
1271
1296
  @dataclass(kw_only=True)
1272
1297
  class ToJson(BaseExpression):
1298
+ """jsonify string to structure"""
1299
+
1273
1300
  kind: ClassVar[TokenType] = TokenType.TO_JSON
1274
1301
  accept_type: VariableType = VariableType.STRING
1275
1302
  ret_type: VariableType = VariableType.JSON
@@ -1282,3 +1309,19 @@ class ToJson(BaseExpression):
1282
1309
  @property
1283
1310
  def is_array_entrypoint(self) -> bool:
1284
1311
  return self.value.__IS_ARRAY__
1312
+
1313
+
1314
+ # BOOLEAN
1315
+ @dataclass(kw_only=True)
1316
+ class ToBool(BaseExpression):
1317
+ """convert variable to boolean
1318
+
1319
+ boolean convert rules
1320
+
1321
+ None, nil, empty array, empty string - False
1322
+ other - True
1323
+ """
1324
+
1325
+ kind: ClassVar[TokenType] = TokenType.TO_BOOL
1326
+ accept_type: VariableType = VariableType.ANY
1327
+ ret_type: VariableType = VariableType.BOOL
@@ -1,6 +1,7 @@
1
1
  # TODO: required enchant, not tested
2
2
  import warnings
3
3
  from functools import partial
4
+ from typing_extensions import assert_never
4
5
 
5
6
  from ssc_codegen.ast_ssc import (
6
7
  DefaultValueWrapper,
@@ -55,8 +56,10 @@ from ssc_codegen.ast_ssc import (
55
56
  TypeDef,
56
57
  ToJson,
57
58
  JsonStruct,
59
+ ToBool,
60
+ ArrayLengthExpression,
58
61
  )
59
- from ssc_codegen.tokens import StructType, TokenType
62
+ from ssc_codegen.tokens import StructType, TokenType, VariableType
60
63
  from .base import BaseCodeConverter, left_right_var_names
61
64
  from .templates import dart
62
65
  from ssc_codegen.str_utils import (
@@ -520,9 +523,41 @@ def tt_to_json(node: ToJson) -> str:
520
523
 
521
524
 
522
525
  @converter.pre(TokenType.JSON_STRUCT)
523
- def tt_json_struct(node: JsonStruct) -> str:
526
+ def tt_json_struct(_: JsonStruct) -> str:
524
527
  warnings.warn(
525
528
  "Serialization current not available, generate dynamic struct",
526
529
  category=FutureWarning,
527
530
  )
528
531
  return ""
532
+
533
+
534
+ @converter.pre(TokenType.TO_BOOL)
535
+ def to_bool(node: ToBool) -> str:
536
+ prv, nxt = lr_var_names(variable=node.variable)
537
+ match node.ret_type:
538
+ case VariableType.LIST_DOCUMENT:
539
+ code = f"var {nxt} = ({prv} != null) && !{prv}.isEmpty; "
540
+ case VariableType.LIST_INT:
541
+ code = f"var {nxt} = ({prv} != null) && !{prv}.isEmpty; "
542
+ case VariableType.LIST_FLOAT:
543
+ code = f"var {nxt} = ({prv} != null) && !{prv}.isEmpty; "
544
+ case VariableType.LIST_STRING:
545
+ code = f"var {nxt} = ({prv} != null) && !{prv}.isEmpty; "
546
+ case VariableType.STRING:
547
+ code = f"var {nxt} = ({prv} != null) && !{prv}.isEmpty; "
548
+
549
+ case VariableType.INT:
550
+ code = f"var {nxt} = {prv} != null; "
551
+ case VariableType.FLOAT:
552
+ code = f"var {nxt} = {prv} != null; "
553
+ case VariableType.DOCUMENT:
554
+ code = f"var {nxt} = {prv} != null; "
555
+ case _:
556
+ assert_never(node.ret_type)
557
+ return code
558
+
559
+
560
+ @converter.pre(TokenType.EXPR_LIST_LEN)
561
+ def to_len(node: ArrayLengthExpression) -> str:
562
+ prv, nxt = lr_var_names(variable=node.variable)
563
+ return f"var {nxt} = {prv}.length; "
@@ -51,6 +51,8 @@ from ssc_codegen.ast_ssc import (
51
51
  ToJson,
52
52
  JsonStruct,
53
53
  TypeDefField,
54
+ ArrayLengthExpression,
55
+ ToBool,
54
56
  )
55
57
  from ssc_codegen.tokens import (
56
58
  StructType,
@@ -304,6 +306,8 @@ def tt_default(node: DefaultStart) -> str:
304
306
  # defer func() wrapper, set default value if code throw error
305
307
  if isinstance(node.value, str):
306
308
  value = wrap_double_quotes(node.value)
309
+ elif isinstance(node.value, bool):
310
+ value = "true" if node.value else "false"
307
311
  elif isinstance(node.value, (int, float)):
308
312
  value = node.value
309
313
  else:
@@ -753,3 +757,47 @@ def tt_json_struct(node: JsonStruct) -> str:
753
757
  case _:
754
758
  assert_never(field.value.kind)
755
759
  return code + go.BRACKET_END
760
+
761
+
762
+ @converter.pre(TokenType.EXPR_LIST_LEN)
763
+ def tt_to_len(node: ArrayLengthExpression) -> str:
764
+ prv, nxt = left_right_var_names("value", node.variable)
765
+ match node.ret_type:
766
+ # https://pkg.go.dev/gopkg.in/goquery.v1#Selection.Length
767
+ case VariableType.LIST_DOCUMENT:
768
+ code = f"{nxt} := {prv}.Length();"
769
+ # build-in array used
770
+ case _:
771
+ code = f"{nxt} := len({prv});"
772
+ return code
773
+
774
+
775
+ @converter.pre(TokenType.TO_BOOL)
776
+ def tt_to_bool(node: ToBool) -> str:
777
+ prv, nxt = left_right_var_names("value", node.variable)
778
+ match node.ret_type:
779
+ # https://pkg.go.dev/gopkg.in/goquery.v1#Selection.Length
780
+ case VariableType.DOCUMENT:
781
+ code = f"{nxt} := {prv} != nil && {prv}.Length() > 0; "
782
+ case VariableType.LIST_DOCUMENT:
783
+ code = f"{nxt} := {prv} != nil && {prv}.Length() > 0; "
784
+
785
+ case VariableType.STRING:
786
+ code = f'{nxt} := {prv} != nil && {prv} != ""; '
787
+
788
+ # `0` is true
789
+ case VariableType.INT:
790
+ code = f"{nxt} := {prv} != nil; "
791
+ case VariableType.FLOAT:
792
+ code = f"{nxt} := {prv} != nil; "
793
+
794
+ # build-in array
795
+ case VariableType.LIST_STRING:
796
+ code = f"{nxt} := {prv} != nil && len({prv}) > 0; "
797
+ case VariableType.LIST_INT:
798
+ code = f"{nxt} := {prv} != nil && len({prv}) > 0; "
799
+ case VariableType.LIST_FLOAT:
800
+ code = f"{nxt} := {prv} != nil && len({prv}) > 0; "
801
+ case _:
802
+ assert_never(node.prev.ret_type)
803
+ return code
@@ -50,6 +50,8 @@ from ssc_codegen.ast_ssc import (
50
50
  ToFloat,
51
51
  ToListFloat,
52
52
  ToJson,
53
+ ToBool,
54
+ ArrayLengthExpression,
53
55
  )
54
56
  from ssc_codegen.tokens import StructType, TokenType
55
57
  from .base import BaseCodeConverter, left_right_var_names
@@ -473,3 +475,15 @@ def tt_to_list_float(node: ToListFloat) -> str:
473
475
  def tt_to_json(node: ToJson) -> str:
474
476
  prv, nxt = left_right_var_names("value", node.variable)
475
477
  return f"let {nxt} = JSON.parse({prv}); "
478
+
479
+
480
+ @converter.pre(TokenType.TO_BOOL)
481
+ def tt_to_bool(node: ToBool) -> str:
482
+ prv, nxt = left_right_var_names("value", node.variable)
483
+ return f"let {nxt} = {prv} || {prv} === 0 ? true : false;"
484
+
485
+
486
+ @converter.pre(TokenType.EXPR_LIST_LEN)
487
+ def tt_to_len(node: ArrayLengthExpression) -> str:
488
+ prv, nxt = left_right_var_names("value", node.variable)
489
+ return f"let {nxt} = {prv}.length; "
@@ -47,6 +47,8 @@ from ssc_codegen.ast_ssc import (
47
47
  ToJson,
48
48
  JsonStruct,
49
49
  JsonStructField,
50
+ ArrayLengthExpression,
51
+ ToBool,
50
52
  )
51
53
  from ssc_codegen.tokens import (
52
54
  StructType,
@@ -133,6 +135,9 @@ class BasePyCodeConverter(BaseCodeConverter):
133
135
  self.post_definitions[TokenType.JSON_STRUCT] = tt_json_struct_post
134
136
  self.pre_definitions[TokenType.JSON_FIELD] = tt_json_field
135
137
 
138
+ self.pre_definitions[TokenType.EXPR_LIST_LEN] = tt_to_len
139
+ self.pre_definitions[TokenType.TO_BOOL] = tt_to_bool
140
+
136
141
 
137
142
  def tt_imports(node: ModuleImports) -> str:
138
143
  return py.BINDINGS[node.kind]
@@ -287,7 +292,7 @@ def tt_default_start(node: DefaultStart) -> str:
287
292
 
288
293
 
289
294
  def tt_default_end(node: DefaultEnd) -> str:
290
- # prv, nxt = lr_var_names(variable=node.variable)
295
+ # bool, int, float, None represent as same
291
296
  val = repr(node.value) if isinstance(node.value, str) else node.value
292
297
  return py.INDENT_METHOD_BODY + py.BINDINGS[node.kind, val]
293
298
 
@@ -562,3 +567,17 @@ def tt_to_json(node: ToJson) -> str:
562
567
  prv, nxt = lr_var_names(variable=node.variable)
563
568
  code = f"{nxt} = json.loads({prv})"
564
569
  return indent + code
570
+
571
+
572
+ def tt_to_len(node: ArrayLengthExpression) -> str:
573
+ indent = py.suggest_indent(node)
574
+ prv, nxt = lr_var_names(variable=node.variable)
575
+ code = f"{nxt} = len({prv})"
576
+ return indent + code
577
+
578
+
579
+ def tt_to_bool(node: ToBool) -> str:
580
+ indent = py.suggest_indent(node)
581
+ prv, nxt = lr_var_names(variable=node.variable)
582
+ code = f"{nxt} = bool({prv} or {prv} == 0)"
583
+ return indent + code
@@ -20,6 +20,7 @@ TYPES = {
20
20
  VariableType.OPTIONAL_FLOAT: "double?",
21
21
  VariableType.LIST_FLOAT: "List<double>",
22
22
  VariableType.OPTIONAL_LIST_FLOAT: "List<double>?",
23
+ VariableType.BOOL: "bool",
23
24
  }
24
25
 
25
26
 
@@ -39,6 +39,7 @@ TYPES = {
39
39
  VariableType.OPTIONAL_FLOAT: "*float64",
40
40
  VariableType.LIST_FLOAT: "[]float64",
41
41
  VariableType.OPTIONAL_LIST_FLOAT: "*[]float64",
42
+ VariableType.BOOL: "bool",
42
43
  }
43
44
 
44
45
  JSON_TYPES = {
@@ -31,6 +31,7 @@ TYPES = {
31
31
  VariableType.FLOAT: "float",
32
32
  VariableType.LIST_INT: "List[int]",
33
33
  VariableType.LIST_FLOAT: "List[float]",
34
+ VariableType.BOOL: "bool",
34
35
  }
35
36
 
36
37
  JSON_TYPES = {
@@ -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
 
@@ -48,8 +48,10 @@ from .ast_ssc import (
48
48
  ToListInteger,
49
49
  TrimExpression,
50
50
  ToJson,
51
+ ToBool,
52
+ ArrayLengthExpression,
51
53
  )
52
- from .document_utlis import assert_re_expression
54
+ from .document_utlis import assert_re_expression, unverbosify_regex
53
55
  from .schema import BaseSchema
54
56
  from .selector_utils import validate_css_query, validate_xpath_query
55
57
  from .tokens import VariableType
@@ -269,9 +271,15 @@ class ArrayDocument(BaseDocument):
269
271
 
270
272
  match self.stack_last_ret:
271
273
  case VariableType.LIST_DOCUMENT:
272
- self._add(IndexDocumentExpression(value=i, ret_type=VariableType.DOCUMENT))
274
+ self._add(
275
+ IndexDocumentExpression(
276
+ value=i, ret_type=VariableType.DOCUMENT
277
+ )
278
+ )
273
279
  case VariableType.LIST_STRING:
274
- self._add(IndexStringExpression(value=i, ret_type=VariableType.STRING))
280
+ self._add(
281
+ IndexStringExpression(value=i, ret_type=VariableType.STRING)
282
+ )
275
283
  case VariableType.LIST_INT:
276
284
  self._add(
277
285
  IndexStringExpression(value=i, ret_type=VariableType.INT)
@@ -315,6 +323,32 @@ class ArrayDocument(BaseDocument):
315
323
  )
316
324
  return self
317
325
 
326
+ def to_len(self) -> Self:
327
+ """Get length of items in array object
328
+
329
+ - accept LIST_STRING | LIST_DOCUMENT | LIST_INT | LIST_FLOAT, return INT
330
+
331
+ """
332
+ match self.stack_last_ret:
333
+ case VariableType.LIST_STRING:
334
+ pass
335
+ case VariableType.LIST_DOCUMENT:
336
+ pass
337
+ case VariableType.LIST_INT:
338
+ pass
339
+ case VariableType.LIST_FLOAT:
340
+ pass
341
+ case _:
342
+ self._raise_wrong_type_error(
343
+ self.stack_last_ret,
344
+ VariableType.LIST_STRING,
345
+ VariableType.LIST_DOCUMENT,
346
+ VariableType.LIST_INT,
347
+ VariableType.LIST_FLOAT,
348
+ )
349
+ self._add(ArrayLengthExpression())
350
+ return self
351
+
318
352
 
319
353
  class StringDocument(BaseDocument):
320
354
  def trim(self, substr: str = " ") -> Self:
@@ -424,7 +458,7 @@ class StringDocument(BaseDocument):
424
458
  )
425
459
  return self
426
460
 
427
- def re(self, pattern: str, group: int = 1) -> Self:
461
+ def re(self, pattern: str | Pattern, group: int = 1) -> Self:
428
462
  """extract first regex result.
429
463
 
430
464
  NOTE:
@@ -432,6 +466,7 @@ class StringDocument(BaseDocument):
432
466
 
433
467
  - accept STRING, return STRING
434
468
  """
469
+ pattern = unverbosify_regex(pattern)
435
470
  assert_re_expression(pattern)
436
471
 
437
472
  if self.stack_last_ret != VariableType.STRING:
@@ -441,11 +476,12 @@ class StringDocument(BaseDocument):
441
476
  self._add(RegexExpression(pattern=pattern, group=group))
442
477
  return self
443
478
 
444
- def re_all(self, pattern: str) -> Self:
479
+ def re_all(self, pattern: str | Pattern) -> Self:
445
480
  """extract all regex results.
446
481
 
447
482
  - accept STRING, return LIST_STRING
448
483
  """
484
+ pattern = unverbosify_regex(pattern)
449
485
  assert_re_expression(pattern, max_groups=1)
450
486
  if self.stack_last_ret != VariableType.STRING:
451
487
  self._raise_wrong_type_error(
@@ -454,12 +490,13 @@ class StringDocument(BaseDocument):
454
490
  self._add(RegexAllExpression(pattern=pattern))
455
491
  return self
456
492
 
457
- def re_sub(self, pattern: str, repl: str = "") -> Self:
493
+ def re_sub(self, pattern: str | Pattern, repl: str = "") -> Self:
458
494
  """Replace substring by `pattern` to `repl`.
459
495
 
460
496
  - accept STRING, return STRING
461
497
  - accept LIST_STRING, return LIST_STRING
462
498
  """
499
+ pattern = unverbosify_regex(pattern)
463
500
  assert_re_expression(pattern, allow_empty_groups=True)
464
501
 
465
502
  match self.stack_last_ret:
@@ -710,6 +747,23 @@ class NumericDocument(BaseDocument):
710
747
  return self
711
748
 
712
749
 
750
+ class BooleanDocument(BaseDocument):
751
+ def to_bool(self) -> Self:
752
+ """convert current value to bool. Accept any type
753
+
754
+ value returns false if:
755
+
756
+ - None
757
+ - empty sequence
758
+ - empty string
759
+
760
+ other - true
761
+
762
+ """
763
+ self._add(ToBool())
764
+ return self
765
+
766
+
713
767
  class JsonDocument(BaseDocument):
714
768
  def jsonify(self, struct: Type[Json]) -> Self:
715
769
  """marshal json string to object
@@ -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,40 @@ 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 unverbosify_regex(pattern: str | Pattern) -> str:
26
+ if isinstance(pattern, str):
27
+ return pattern
28
+
29
+ def strip_escapes(match): # type: ignore
30
+ ## if even slashes: delete space and retain slashes
31
+ if match.group(1) is None:
32
+ return match.group(2)
33
+ ## if number of slashes is odd: delete slash and keep space (or 'comment')
34
+ elif match.group(1) == "\\":
35
+ return match.group(2) + match.group(4)
36
+ ## error
37
+ else:
38
+ raise Exception
39
+
40
+ if pattern.flags & re.X:
41
+ not_verbose_regex = re.sub(
42
+ WS_RX,
43
+ strip_escapes,
44
+ re.sub(
45
+ CM2_RX, strip_escapes, re.sub(CM1_RX, "\\1", pattern.pattern)
46
+ ),
47
+ )
48
+
49
+ return not_verbose_regex
50
+ return pattern.pattern
51
+
52
+
19
53
  def assert_re_expression(
20
54
  pattern: str, allow_empty_groups: bool = False, max_groups: int = -1
21
55
  ) -> None:
@@ -28,6 +28,8 @@ class VariableType(IntEnum):
28
28
  JSON = auto()
29
29
  OPTIONAL_JSON = auto()
30
30
 
31
+ BOOL = auto()
32
+
31
33
 
32
34
  class JsonVariableType(IntEnum):
33
35
  # https://json-schema.org/understanding-json-schema/reference/type
@@ -135,6 +137,7 @@ class TokenType(IntEnum):
135
137
  EXPR_LIST_STRING_INDEX = auto()
136
138
  EXPR_LIST_DOCUMENT_INDEX = auto()
137
139
  EXPR_LIST_JOIN = auto()
140
+ EXPR_LIST_LEN = auto()
138
141
 
139
142
  # ASSERT
140
143
  IS_EQUAL = auto()
@@ -155,3 +158,6 @@ class TokenType(IntEnum):
155
158
  # STRUCTS
156
159
  JSON_STRUCT = auto()
157
160
  JSON_FIELD = auto()
161
+
162
+ # bool
163
+ TO_BOOL = auto()
File without changes
File without changes