ssc_codegen 0.9.8__tar.gz → 0.10.0__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 (69) hide show
  1. ssc_codegen-0.10.0/PKG-INFO +253 -0
  2. ssc_codegen-0.10.0/README.md +216 -0
  3. {ssc_codegen-0.9.8 → ssc_codegen-0.10.0}/pyproject.toml +2 -1
  4. {ssc_codegen-0.9.8 → ssc_codegen-0.10.0}/ssc_codegen/__init__.py +29 -3
  5. {ssc_codegen-0.9.8 → ssc_codegen-0.10.0}/ssc_codegen/ast_/__init__.py +6 -2
  6. {ssc_codegen-0.9.8 → ssc_codegen-0.10.0}/ssc_codegen/ast_/base.py +42 -1
  7. {ssc_codegen-0.9.8 → ssc_codegen-0.10.0}/ssc_codegen/ast_/nodes_array.py +5 -1
  8. {ssc_codegen-0.9.8 → ssc_codegen-0.10.0}/ssc_codegen/ast_/nodes_cast.py +2 -2
  9. {ssc_codegen-0.9.8 → ssc_codegen-0.10.0}/ssc_codegen/ast_/nodes_core.py +71 -1
  10. {ssc_codegen-0.9.8 → ssc_codegen-0.10.0}/ssc_codegen/ast_/nodes_selectors.py +22 -0
  11. {ssc_codegen-0.9.8 → ssc_codegen-0.10.0}/ssc_codegen/ast_/nodes_string.py +15 -7
  12. {ssc_codegen-0.9.8 → ssc_codegen-0.10.0}/ssc_codegen/ast_/nodes_validate.py +2 -2
  13. {ssc_codegen-0.9.8 → ssc_codegen-0.10.0}/ssc_codegen/ast_build/main.py +128 -38
  14. {ssc_codegen-0.9.8 → ssc_codegen-0.10.0}/ssc_codegen/ast_build/utils.py +54 -8
  15. {ssc_codegen-0.9.8 → ssc_codegen-0.10.0}/ssc_codegen/cli/consts.py +10 -1
  16. {ssc_codegen-0.9.8 → ssc_codegen-0.10.0}/ssc_codegen/cli/main.py +70 -5
  17. {ssc_codegen-0.9.8 → ssc_codegen-0.10.0}/ssc_codegen/compiler.py +65 -1
  18. {ssc_codegen-0.9.8 → ssc_codegen-0.10.0}/ssc_codegen/converters/base.py +40 -8
  19. ssc_codegen-0.10.0/ssc_codegen/converters/go_goquery.py +1415 -0
  20. ssc_codegen-0.10.0/ssc_codegen/converters/helpers.py +251 -0
  21. {ssc_codegen-0.9.8 → ssc_codegen-0.10.0}/ssc_codegen/converters/js_pure.py +258 -214
  22. ssc_codegen-0.10.0/ssc_codegen/converters/lua_htmlparser.py +1382 -0
  23. {ssc_codegen-0.9.8 → ssc_codegen-0.10.0}/ssc_codegen/converters/py_base.py +256 -163
  24. {ssc_codegen-0.9.8 → ssc_codegen-0.10.0}/ssc_codegen/converters/py_bs4.py +53 -14
  25. ssc_codegen-0.10.0/ssc_codegen/converters/py_lxml.py +303 -0
  26. {ssc_codegen-0.9.8 → ssc_codegen-0.10.0}/ssc_codegen/converters/py_parsel.py +65 -18
  27. {ssc_codegen-0.9.8 → ssc_codegen-0.10.0}/ssc_codegen/converters/py_selectolax.py +50 -13
  28. ssc_codegen-0.10.0/ssc_codegen/converters/templates/go_goquery.py +737 -0
  29. {ssc_codegen-0.9.8 → ssc_codegen-0.10.0}/ssc_codegen/converters/templates/js_pure.py +54 -7
  30. ssc_codegen-0.10.0/ssc_codegen/converters/templates/lua_base.py +710 -0
  31. ssc_codegen-0.10.0/ssc_codegen/converters/templates/lua_css_compat.py +157 -0
  32. ssc_codegen-0.10.0/ssc_codegen/converters/templates/lua_re_compat.py +517 -0
  33. ssc_codegen-0.10.0/ssc_codegen/converters/templates/py_base.py +53 -0
  34. {ssc_codegen-0.9.8 → ssc_codegen-0.10.0}/ssc_codegen/document.py +683 -137
  35. {ssc_codegen-0.9.8 → ssc_codegen-0.10.0}/ssc_codegen/document_utlis.py +31 -0
  36. ssc_codegen-0.10.0/ssc_codegen/json_struct.py +272 -0
  37. ssc_codegen-0.10.0/ssc_codegen/json_to_scc.py +179 -0
  38. {ssc_codegen-0.9.8 → ssc_codegen-0.10.0}/ssc_codegen/schema.py +89 -9
  39. {ssc_codegen-0.9.8 → ssc_codegen-0.10.0}/ssc_codegen/static_checker/callbacks.py +11 -3
  40. {ssc_codegen-0.9.8 → ssc_codegen-0.10.0}/ssc_codegen/tokens.py +65 -36
  41. ssc_codegen-0.9.8/PKG-INFO +0 -251
  42. ssc_codegen-0.9.8/README.md +0 -215
  43. ssc_codegen-0.9.8/ssc_codegen/consts.py +0 -55
  44. ssc_codegen-0.9.8/ssc_codegen/converters/go_goquery.py +0 -1270
  45. ssc_codegen-0.9.8/ssc_codegen/converters/helpers.py +0 -58
  46. ssc_codegen-0.9.8/ssc_codegen/converters/templates/go_goquery.py +0 -450
  47. ssc_codegen-0.9.8/ssc_codegen/json_struct.py +0 -162
  48. ssc_codegen-0.9.8/ssc_codegen/json_to_scc.py +0 -80
  49. {ssc_codegen-0.9.8 → ssc_codegen-0.10.0}/.gitignore +0 -0
  50. {ssc_codegen-0.9.8 → ssc_codegen-0.10.0}/LICENSE +0 -0
  51. {ssc_codegen-0.9.8 → ssc_codegen-0.10.0}/ssc_codegen/_compat.py +0 -0
  52. {ssc_codegen-0.9.8 → ssc_codegen-0.10.0}/ssc_codegen/ast_/nodes_filter.py +0 -0
  53. {ssc_codegen-0.9.8 → ssc_codegen-0.10.0}/ssc_codegen/ast_build/__init__.py +0 -0
  54. {ssc_codegen-0.9.8 → ssc_codegen-0.10.0}/ssc_codegen/ast_grep_rules/js_rules.yml +0 -0
  55. {ssc_codegen-0.9.8 → ssc_codegen-0.10.0}/ssc_codegen/ast_grep_rules/py_rules.yml +0 -0
  56. {ssc_codegen-0.9.8 → ssc_codegen-0.10.0}/ssc_codegen/cli/__init__.py +0 -0
  57. {ssc_codegen-0.9.8 → ssc_codegen-0.10.0}/ssc_codegen/cli/ast_grep.py +0 -0
  58. {ssc_codegen-0.9.8 → ssc_codegen-0.10.0}/ssc_codegen/cli/cli_callbacks.py +0 -0
  59. {ssc_codegen-0.9.8 → ssc_codegen-0.10.0}/ssc_codegen/cli/cli_utils.py +0 -0
  60. {ssc_codegen-0.9.8 → ssc_codegen-0.10.0}/ssc_codegen/cli/code_callbacks.py +0 -0
  61. {ssc_codegen-0.9.8 → ssc_codegen-0.10.0}/ssc_codegen/cli/runtime_parse_runners.py +0 -0
  62. {ssc_codegen-0.9.8 → ssc_codegen-0.10.0}/ssc_codegen/converters/__init__.py +0 -0
  63. {ssc_codegen-0.9.8 → ssc_codegen-0.10.0}/ssc_codegen/converters/templates/__init__.py +0 -0
  64. {ssc_codegen-0.9.8 → ssc_codegen-0.10.0}/ssc_codegen/logs.py +0 -0
  65. {ssc_codegen-0.9.8 → ssc_codegen-0.10.0}/ssc_codegen/pseudo_selectors.py +0 -0
  66. {ssc_codegen-0.9.8 → ssc_codegen-0.10.0}/ssc_codegen/selector_utils.py +0 -0
  67. {ssc_codegen-0.9.8 → ssc_codegen-0.10.0}/ssc_codegen/static_checker/__init__.py +0 -0
  68. {ssc_codegen-0.9.8 → ssc_codegen-0.10.0}/ssc_codegen/static_checker/base.py +0 -0
  69. {ssc_codegen-0.9.8 → ssc_codegen-0.10.0}/ssc_codegen/str_utils.py +0 -0
@@ -0,0 +1,253 @@
1
+ Metadata-Version: 2.4
2
+ Name: ssc_codegen
3
+ Version: 0.10.0
4
+ Summary: Python-dsl code converter to html parser for web scraping
5
+ Project-URL: Documentation, https://github.com/vypivshiy/selector_schema_codegen#readme
6
+ Project-URL: Issues, https://github.com/vypivshiy/selector_schema_codegen/issues
7
+ Project-URL: Source, https://github.com/vypivshiy/selector_schema_codegen
8
+ Project-URL: Examples, https://github.com/vypivshiy/selector_schema_codegen/examples
9
+ License-File: LICENSE
10
+ Classifier: Development Status :: 3 - Alpha
11
+ Classifier: Environment :: Console
12
+ Classifier: Intended Audience :: Developers
13
+ Classifier: License :: OSI Approved :: MIT License
14
+ Classifier: Programming Language :: Python :: 3.10
15
+ Classifier: Programming Language :: Python :: 3.11
16
+ Classifier: Programming Language :: Python :: 3.12
17
+ Classifier: Programming Language :: Python :: 3.13
18
+ Classifier: Topic :: Software Development :: Code Generators
19
+ Classifier: Topic :: Text Processing :: Markup :: HTML
20
+ Classifier: Topic :: Utilities
21
+ Requires-Python: >=3.10
22
+ Requires-Dist: ast-grep-cli>=0.38.1
23
+ Requires-Dist: bs4>=0.0.2
24
+ Requires-Dist: click<8.2.0
25
+ Requires-Dist: colorama>=0.4.6; sys_platform == 'win32'
26
+ Requires-Dist: cssselect>=1.2.0
27
+ Requires-Dist: httpx>=0.28.1
28
+ Requires-Dist: ichrome>=4.0.4
29
+ Requires-Dist: jinja2>=3.1.6
30
+ Requires-Dist: lxml>=5.3.0
31
+ Requires-Dist: parsel>=1.10.0
32
+ Requires-Dist: soupsieve>=2.6
33
+ Requires-Dist: tinycss2>=1.4.0
34
+ Requires-Dist: typer>=0.15.1
35
+ Requires-Dist: typing-extensions; python_version < '3.11'
36
+ Description-Content-Type: text/markdown
37
+
38
+ # Selector Schema codegen
39
+
40
+ ## Introduction
41
+
42
+ ssc-gen - a python-based DSL to describe parsers for html documents, which is translated into a standalone parsing module
43
+
44
+ ### For a better experience using this library, you should know:
45
+
46
+ - HTML CSS selectors (CSS3 standard min), Xpath
47
+ - regular expressions (PCRE)
48
+
49
+ ### Project solving next problems:
50
+
51
+ - designed for SSR (server-side-render) html pages parsers, **NOT FOR REST-API, GRAPHQL ENDPOINTS**
52
+ - decrease boilerplate code
53
+ - generates independent modules from the project that can be reused.
54
+ - generates docstring documentation and the signature of the parser output.
55
+ - for a better IDE experience, generates a typedefs, type annotations (if the target programming language supports it).
56
+ - support annotation and parsing of JSON-like strings from a document
57
+ - AST API codegen for developing a converter for parsing
58
+
59
+ ## Support converters
60
+
61
+ Current support converters
62
+
63
+ | Language | HTML parser lib + dependencies | XPath | CSS3 | CSS4 | Generated annotations, types, structs | formatter dependency |
64
+ | ---------------------------------------- | -------------------------------------- | ----- | ---- | ---- | ------------------------------------- | -------------------- |
65
+ | Python (3.8+) | bs4, lxml | N | Y | Y | TypedDict`1`, list, dict | ruff |
66
+ | ... | parsel | Y | Y | N | ... | ... |
67
+ | ... | selectolax (lexbor) | N | Y | N | ... | ... |
68
+ | ... | lxml | Y | Y | N | ... | ... |
69
+ | js (ES6)`2` | pure (firefox/chrome extension/nodejs) | Y | Y | Y | Array, Map`3` | prettier |
70
+ | go (1.10+) **(UNSTABLE)** | goquery, gjson (`4`) | N | Y | N | struct(+json anchors), array, map | gofmt |
71
+ | lua (5.2+), luajit(2+) **(UNSTABLE)**`5` | lua-htmlparser, lrexlib(opt), dkjson | N | Y | N | EmmyLua | LuaFormatter |
72
+
73
+ - **CSS3** means support next selectors:
74
+ - basic: (`tag`, `.class`, `#id`, `tag1,tag2`)
75
+ - combined: (`div p`, `ul > li`, `h2 +p`, `title ~head`)
76
+ - attribute: (`a[href]`, `input[type='text']`, `a[href*='...']`, ...)
77
+ - CSS3 pseudo classes: (`:nth-child(n)`, `:first-child`, `:last-child`)
78
+ - **CSS4** means support next selectors:
79
+ - `:nth-of-type()`, `:where()`, `:is()`, `:not()` etc
80
+ - `1`this annotation type was deliberately chosen as a compromise reasons:
81
+ Python has many ways of serialization: `namedtuple, dataclass, attrs, pydantic, msgspec, etc`
82
+ - TypedDict is like a build-in dict, but with IDE and linter hint support, and you can easily implement an adapter for the required structure.
83
+ - `2`ES8 standart required if needed use PCRE `re.S | re.DOTALL` flag
84
+ - `3`js exclude build-in serialization methods, used standard Array and Map types. Focus on the singanutur documentation!
85
+ - `4`golang has not been tested much, there may be issues
86
+ - **formatter dependency** - optional dependency for prettify and fix codestyle
87
+
88
+ - `5`lua
89
+ - Experimental Research PoC, performance and stability are not guaranteed
90
+ - Priority on generation to pure lua without C-libs dependencies. using [mva/htmlparser](https://luarocks.org/modules/mva/htmlparser) and [dhkolf/dkjson](https://luarocks.org/modules/dhkolf/dkjson)
91
+ - Translates unsupported CSS3 selectors into the equivalent in the form of function calls:
92
+ - for example, `div +p` is equivalent to `CssExt.combine_plus(root:select("div"), "p")`
93
+ - Translates PCRE regex to [string pattern matching](https://www.lua.org/manual/5.4/manual.html#6.4.1) (with restrictions) for more information in [lua_re_compat.py ](ssc_codegen/converters/templates/lua_re_compat.py)
94
+
95
+ ### Limitations
96
+
97
+ For maximum portability of the configuration to the target language:
98
+
99
+ - If possible, use CSS selectors: they are guaranteed to be converted to XPATH
100
+ - Unlike javascript, most html parse libs implement [CSS3 selectors standard](https://www.w3.org/TR/selectors-3/). They may not fully implement the functionality!
101
+ Check the html parser lib documentation aboud CSS selectors before implement code. Examples:
102
+ 1. Several libs not support `+` operations (eg: [selectolax(modest)](https://github.com/rushter/selectolax), [dart.universal_html](https://pub.dev/packages/universal_html))
103
+ 2. For research purpose, lua_htmlparser include converter for unsupported CSS3 query syntax
104
+ 2. HTML parser libs maybe not supports attribute selectors: `*=`, `~=`, `|=`, `^=`, `$=`
105
+ 3. Several libs not support pseudo classes (eg: standard [dart.html](https://dart.dev/libraries/dart-html) lib miss this feature).
106
+
107
+ ## Getting started
108
+
109
+ ssc_gen required python 3.10 version or higher
110
+
111
+ ### Install
112
+
113
+ pip:
114
+
115
+ ```shell
116
+ pip install ssc_codegen
117
+ ```
118
+
119
+ uv:
120
+
121
+ ```shell
122
+ uv pip install ssc_codegen
123
+ ```
124
+
125
+ ## Example
126
+
127
+ ### Create a file `schema.py` with:
128
+
129
+ ```python
130
+ from ssc_codegen import ItemSchema, D
131
+
132
+ class HelloWorld(ItemSchema):
133
+ title = D().css('title').text()
134
+ a_hrefs = D().css_all('a').attr('href')
135
+ ```
136
+
137
+ ### try it in cli
138
+
139
+ > [!note]
140
+ > this tools developed for testing purposes, not for web-scraping tasks
141
+
142
+ ### eval from file
143
+
144
+ Download any html file and pass as argument:
145
+
146
+ ```shell
147
+ ssc-gen parse-from-file index.html -t schema.py:HelloWorld
148
+ ```
149
+
150
+ Short options descriptions:
151
+
152
+ - `-t --target` - config schema file and class from where to start the parser
153
+
154
+ ![out1](docs/assets/parse_from_file.gif)
155
+
156
+ ### send GET request to url and parse response
157
+
158
+ ```shell
159
+ ssc-gen parse-from-url https://example.com -t schema.py:HelloWorld
160
+ ```
161
+
162
+ ![out1](docs/assets/parse_from_url.gif)
163
+
164
+ ### send request via Chromium browser (CDP protocol)
165
+
166
+ ```shell
167
+ ssc-gen parse-from-chrome https://example.com -t schema.py:HelloWorld
168
+ ```
169
+
170
+ > [!note]
171
+ > if script cannot found chrome executable - provide it manually:
172
+
173
+ ```shell
174
+ ssc-gen parse-from-chrome https://example.com -t schema.py:HelloWorld -sc /usr/bin/chromium
175
+ ```
176
+
177
+ ### Convert to code
178
+
179
+ Convert to code for use in projects:
180
+
181
+ > [!note]
182
+ > for example, used js: it can be fast test in developer console
183
+
184
+ ```shell
185
+ ssc-gen js schema.py -o .
186
+ ```
187
+
188
+ Code output looks like this:
189
+
190
+ ```javascript
191
+ // autogenerated by ssc-gen DO NOT_EDIT
192
+ /***
193
+ *
194
+ * {
195
+ * "title": "String",
196
+ * "a_hrefs": "Array<String>"
197
+ * }*/
198
+ class HelloWorld {
199
+ constructor(doc) {
200
+ if (typeof doc === "string") {
201
+ this._doc = new DOMParser().parseFromString(doc, "text/html");
202
+ } else if (doc instanceof Document || doc instanceof Element) {
203
+ this._doc = doc;
204
+ } else {
205
+ throw new Error("Invalid input: Expected a Document, Element, or string");
206
+ }
207
+ }
208
+
209
+ _parseTitle(v) {
210
+ let v0 = v.querySelector("title");
211
+ return typeof v0.textContent === "undefined"
212
+ ? v0.documentElement.textContent
213
+ : v0.textContent;
214
+ }
215
+
216
+ _parseAHrefs(v) {
217
+ let v0 = Array.from(v.querySelectorAll("a"));
218
+ return v0.map((e) => e.getAttribute("href"));
219
+ }
220
+
221
+ parse() {
222
+ return {
223
+ title: this._parseTitle(this._doc),
224
+ a_hrefs: this._parseAHrefs(this._doc),
225
+ };
226
+ }
227
+ }
228
+ ```
229
+
230
+ ### Copy code output and past to developer console:
231
+
232
+ Print output:
233
+
234
+ ```javascript
235
+ alert(JSON.stringify(new HelloWorld(document).parse()));
236
+ ```
237
+
238
+ ![example](docs/assets/example.png)
239
+
240
+ You can use any html source:
241
+
242
+ - parse from html files
243
+ - parse from http responses
244
+ - parse from browsers: playwright, selenium, chrome-cdp, etc.
245
+ - call curl in shell and parse STDIN
246
+ - use in STDIN pipelines with third-party tools like [projectdiscovery/httpx](https://github.com/projectdiscovery/httpx)
247
+
248
+ ## See also
249
+
250
+ - [Brief](docs/brief.md) about css selectors and regular expressions.
251
+ - [Tutorial](docs/tutorial.md) basic usage ssc-gen
252
+ - [Reference](docs/reference.md) about lib API
253
+ - [AST reference](docs/ast_reference.md) about generation code from AST
@@ -0,0 +1,216 @@
1
+ # Selector Schema codegen
2
+
3
+ ## Introduction
4
+
5
+ ssc-gen - a python-based DSL to describe parsers for html documents, which is translated into a standalone parsing module
6
+
7
+ ### For a better experience using this library, you should know:
8
+
9
+ - HTML CSS selectors (CSS3 standard min), Xpath
10
+ - regular expressions (PCRE)
11
+
12
+ ### Project solving next problems:
13
+
14
+ - designed for SSR (server-side-render) html pages parsers, **NOT FOR REST-API, GRAPHQL ENDPOINTS**
15
+ - decrease boilerplate code
16
+ - generates independent modules from the project that can be reused.
17
+ - generates docstring documentation and the signature of the parser output.
18
+ - for a better IDE experience, generates a typedefs, type annotations (if the target programming language supports it).
19
+ - support annotation and parsing of JSON-like strings from a document
20
+ - AST API codegen for developing a converter for parsing
21
+
22
+ ## Support converters
23
+
24
+ Current support converters
25
+
26
+ | Language | HTML parser lib + dependencies | XPath | CSS3 | CSS4 | Generated annotations, types, structs | formatter dependency |
27
+ | ---------------------------------------- | -------------------------------------- | ----- | ---- | ---- | ------------------------------------- | -------------------- |
28
+ | Python (3.8+) | bs4, lxml | N | Y | Y | TypedDict`1`, list, dict | ruff |
29
+ | ... | parsel | Y | Y | N | ... | ... |
30
+ | ... | selectolax (lexbor) | N | Y | N | ... | ... |
31
+ | ... | lxml | Y | Y | N | ... | ... |
32
+ | js (ES6)`2` | pure (firefox/chrome extension/nodejs) | Y | Y | Y | Array, Map`3` | prettier |
33
+ | go (1.10+) **(UNSTABLE)** | goquery, gjson (`4`) | N | Y | N | struct(+json anchors), array, map | gofmt |
34
+ | lua (5.2+), luajit(2+) **(UNSTABLE)**`5` | lua-htmlparser, lrexlib(opt), dkjson | N | Y | N | EmmyLua | LuaFormatter |
35
+
36
+ - **CSS3** means support next selectors:
37
+ - basic: (`tag`, `.class`, `#id`, `tag1,tag2`)
38
+ - combined: (`div p`, `ul > li`, `h2 +p`, `title ~head`)
39
+ - attribute: (`a[href]`, `input[type='text']`, `a[href*='...']`, ...)
40
+ - CSS3 pseudo classes: (`:nth-child(n)`, `:first-child`, `:last-child`)
41
+ - **CSS4** means support next selectors:
42
+ - `:nth-of-type()`, `:where()`, `:is()`, `:not()` etc
43
+ - `1`this annotation type was deliberately chosen as a compromise reasons:
44
+ Python has many ways of serialization: `namedtuple, dataclass, attrs, pydantic, msgspec, etc`
45
+ - TypedDict is like a build-in dict, but with IDE and linter hint support, and you can easily implement an adapter for the required structure.
46
+ - `2`ES8 standart required if needed use PCRE `re.S | re.DOTALL` flag
47
+ - `3`js exclude build-in serialization methods, used standard Array and Map types. Focus on the singanutur documentation!
48
+ - `4`golang has not been tested much, there may be issues
49
+ - **formatter dependency** - optional dependency for prettify and fix codestyle
50
+
51
+ - `5`lua
52
+ - Experimental Research PoC, performance and stability are not guaranteed
53
+ - Priority on generation to pure lua without C-libs dependencies. using [mva/htmlparser](https://luarocks.org/modules/mva/htmlparser) and [dhkolf/dkjson](https://luarocks.org/modules/dhkolf/dkjson)
54
+ - Translates unsupported CSS3 selectors into the equivalent in the form of function calls:
55
+ - for example, `div +p` is equivalent to `CssExt.combine_plus(root:select("div"), "p")`
56
+ - Translates PCRE regex to [string pattern matching](https://www.lua.org/manual/5.4/manual.html#6.4.1) (with restrictions) for more information in [lua_re_compat.py ](ssc_codegen/converters/templates/lua_re_compat.py)
57
+
58
+ ### Limitations
59
+
60
+ For maximum portability of the configuration to the target language:
61
+
62
+ - If possible, use CSS selectors: they are guaranteed to be converted to XPATH
63
+ - Unlike javascript, most html parse libs implement [CSS3 selectors standard](https://www.w3.org/TR/selectors-3/). They may not fully implement the functionality!
64
+ Check the html parser lib documentation aboud CSS selectors before implement code. Examples:
65
+ 1. Several libs not support `+` operations (eg: [selectolax(modest)](https://github.com/rushter/selectolax), [dart.universal_html](https://pub.dev/packages/universal_html))
66
+ 2. For research purpose, lua_htmlparser include converter for unsupported CSS3 query syntax
67
+ 2. HTML parser libs maybe not supports attribute selectors: `*=`, `~=`, `|=`, `^=`, `$=`
68
+ 3. Several libs not support pseudo classes (eg: standard [dart.html](https://dart.dev/libraries/dart-html) lib miss this feature).
69
+
70
+ ## Getting started
71
+
72
+ ssc_gen required python 3.10 version or higher
73
+
74
+ ### Install
75
+
76
+ pip:
77
+
78
+ ```shell
79
+ pip install ssc_codegen
80
+ ```
81
+
82
+ uv:
83
+
84
+ ```shell
85
+ uv pip install ssc_codegen
86
+ ```
87
+
88
+ ## Example
89
+
90
+ ### Create a file `schema.py` with:
91
+
92
+ ```python
93
+ from ssc_codegen import ItemSchema, D
94
+
95
+ class HelloWorld(ItemSchema):
96
+ title = D().css('title').text()
97
+ a_hrefs = D().css_all('a').attr('href')
98
+ ```
99
+
100
+ ### try it in cli
101
+
102
+ > [!note]
103
+ > this tools developed for testing purposes, not for web-scraping tasks
104
+
105
+ ### eval from file
106
+
107
+ Download any html file and pass as argument:
108
+
109
+ ```shell
110
+ ssc-gen parse-from-file index.html -t schema.py:HelloWorld
111
+ ```
112
+
113
+ Short options descriptions:
114
+
115
+ - `-t --target` - config schema file and class from where to start the parser
116
+
117
+ ![out1](docs/assets/parse_from_file.gif)
118
+
119
+ ### send GET request to url and parse response
120
+
121
+ ```shell
122
+ ssc-gen parse-from-url https://example.com -t schema.py:HelloWorld
123
+ ```
124
+
125
+ ![out1](docs/assets/parse_from_url.gif)
126
+
127
+ ### send request via Chromium browser (CDP protocol)
128
+
129
+ ```shell
130
+ ssc-gen parse-from-chrome https://example.com -t schema.py:HelloWorld
131
+ ```
132
+
133
+ > [!note]
134
+ > if script cannot found chrome executable - provide it manually:
135
+
136
+ ```shell
137
+ ssc-gen parse-from-chrome https://example.com -t schema.py:HelloWorld -sc /usr/bin/chromium
138
+ ```
139
+
140
+ ### Convert to code
141
+
142
+ Convert to code for use in projects:
143
+
144
+ > [!note]
145
+ > for example, used js: it can be fast test in developer console
146
+
147
+ ```shell
148
+ ssc-gen js schema.py -o .
149
+ ```
150
+
151
+ Code output looks like this:
152
+
153
+ ```javascript
154
+ // autogenerated by ssc-gen DO NOT_EDIT
155
+ /***
156
+ *
157
+ * {
158
+ * "title": "String",
159
+ * "a_hrefs": "Array<String>"
160
+ * }*/
161
+ class HelloWorld {
162
+ constructor(doc) {
163
+ if (typeof doc === "string") {
164
+ this._doc = new DOMParser().parseFromString(doc, "text/html");
165
+ } else if (doc instanceof Document || doc instanceof Element) {
166
+ this._doc = doc;
167
+ } else {
168
+ throw new Error("Invalid input: Expected a Document, Element, or string");
169
+ }
170
+ }
171
+
172
+ _parseTitle(v) {
173
+ let v0 = v.querySelector("title");
174
+ return typeof v0.textContent === "undefined"
175
+ ? v0.documentElement.textContent
176
+ : v0.textContent;
177
+ }
178
+
179
+ _parseAHrefs(v) {
180
+ let v0 = Array.from(v.querySelectorAll("a"));
181
+ return v0.map((e) => e.getAttribute("href"));
182
+ }
183
+
184
+ parse() {
185
+ return {
186
+ title: this._parseTitle(this._doc),
187
+ a_hrefs: this._parseAHrefs(this._doc),
188
+ };
189
+ }
190
+ }
191
+ ```
192
+
193
+ ### Copy code output and past to developer console:
194
+
195
+ Print output:
196
+
197
+ ```javascript
198
+ alert(JSON.stringify(new HelloWorld(document).parse()));
199
+ ```
200
+
201
+ ![example](docs/assets/example.png)
202
+
203
+ You can use any html source:
204
+
205
+ - parse from html files
206
+ - parse from http responses
207
+ - parse from browsers: playwright, selenium, chrome-cdp, etc.
208
+ - call curl in shell and parse STDIN
209
+ - use in STDIN pipelines with third-party tools like [projectdiscovery/httpx](https://github.com/projectdiscovery/httpx)
210
+
211
+ ## See also
212
+
213
+ - [Brief](docs/brief.md) about css selectors and regular expressions.
214
+ - [Tutorial](docs/tutorial.md) basic usage ssc-gen
215
+ - [Reference](docs/reference.md) about lib API
216
+ - [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.9.8"
3
+ version = "0.10.0"
4
4
  description = "Python-dsl code converter to html parser for web scraping "
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.10"
@@ -20,6 +20,7 @@ dependencies = [
20
20
  # https://github.com/fastapi/typer/pull/1145
21
21
  "click<8.2.0",
22
22
  "ast-grep-cli>=0.38.1",
23
+ "tinycss2>=1.4.0",
23
24
  ]
24
25
 
25
26
  classifiers = [
@@ -1,4 +1,4 @@
1
- from ssc_codegen.document import HTMLDocument, StringDocument, ArrayDocument, AssertDocument, NestedDocument, \
1
+ from ssc_codegen.document import HTMLDocument, ClassVarDocument, StringDocument, ArrayDocument, AssertDocument, NestedDocument, \
2
2
  DefaultDocument, \
3
3
  NumericDocument, JsonDocument, BooleanDocument, DocumentFilter
4
4
  from ssc_codegen.json_struct import Json
@@ -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.8"
10
+ VERSION = "0.10.0"
11
11
 
12
12
 
13
13
  class __MISSING(object):
@@ -63,4 +63,30 @@ def R(default: None | str | int | float | list | __MISSING = _NO_DEFAULT) -> Doc
63
63
 
64
64
  def F() -> DocumentFilter:
65
65
  """Shortcut as a DocumentFilter() object"""
66
- return DocumentFilter()
66
+ return DocumentFilter()
67
+
68
+
69
+ def CV(value, self_cls_path: str | None = None, *, returns: bool = False) -> ClassVarDocument:
70
+ """Shortcut as ClassVarDocument(value, self_cls, parse_returns) object
71
+
72
+ pass `self_cls_path` argument if need use this var inner schema context:
73
+
74
+ self_cls_path use case example:
75
+
76
+ ```
77
+ class B(ItemSchema):
78
+ # dont need pass class and attr names
79
+ C = CV("test {{}}")
80
+
81
+ class A(ItemShema):
82
+ # current impl cannot be extract class and field names
83
+ # need pass it manually
84
+ C1 = CV("title", "A.C1")
85
+
86
+ title = D().css(C1).text().fmt(C)
87
+
88
+ ```
89
+
90
+ """
91
+
92
+ return ClassVarDocument(value, self_cls_path, parse_returns=returns)
@@ -19,13 +19,17 @@ from .nodes_core import (ExprReturn,
19
19
  JsonStructField,
20
20
  TypeDef,
21
21
  TypeDefField,
22
- ExprCallStructMethod)
22
+ ExprCallStructMethod,
23
+ ExprClassVar,
24
+ ExprCallStructClassVar,
25
+ CodeStart,
26
+ CodeEnd)
23
27
  from .nodes_filter import FilterOr, FilterAnd, FilterNot, FilterNotEqual, FilterEqual, FilterStrIn, \
24
28
  FilterStrStarts, FilterStrEnds, FilterStrRe, ExprFilter, FilterStrLenEq, FilterStrLenNe, FilterStrLenLt, \
25
29
  FilterStrLenLe, FilterStrLenGt, FilterStrLenGe
26
30
 
27
31
  from .nodes_selectors import ExprCss, ExprCssAll, ExprXpathAll, ExprXpath, ExprGetHtmlText, ExprGetHtmlRaw, \
28
- ExprGetHtmlAttr, ExprGetHtmlAttrAll, ExprGetHtmlRawAll, ExprGetHtmlTextAll, ExprMapAttrs, ExprMapAttrsAll
32
+ ExprGetHtmlAttr, ExprGetHtmlAttrAll, ExprGetHtmlRawAll, ExprGetHtmlTextAll, ExprMapAttrs, ExprMapAttrsAll, ExprCssElementRemove, ExprXpathElementRemove
29
33
  from .nodes_string import ExprStringTrim, ExprStringRegex, ExprStringSplit, ExprStringReplace, ExprStringLeftTrim, \
30
34
  ExprStringFormat, ExprListStringTrim, ExprStringRegexAll, ExprStringRegexSub, ExprStringRightTrim, \
31
35
  ExprListStringFormat, ExprListStringReplace, ExprListStringRightTrim, ExprListStringLeftTrim, \
@@ -1,5 +1,6 @@
1
1
  from dataclasses import dataclass, field
2
2
  from typing import (
3
+ Callable,
3
4
  ClassVar,
4
5
  Mapping,
5
6
  TypeVar,
@@ -9,10 +10,14 @@ from typing import (
9
10
  Optional,
10
11
  MutableSequence,
11
12
  Sequence,
13
+ TYPE_CHECKING,
12
14
  )
13
15
 
14
16
  from ssc_codegen.tokens import TokenType, VariableType
15
17
 
18
+ if TYPE_CHECKING:
19
+ from ssc_codegen.ast_.nodes_core import ExprClassVar
20
+
16
21
  T_ARGUMENTS = TypeVar("T_ARGUMENTS")
17
22
  T_MAPPING_FIELD = TypeVar("T_MAPPING_FIELD", bound=Mapping[str, Any])
18
23
  T_EMPTY_KWARGS = TypedDict("T_EMPTY_KWARGS", {})
@@ -31,7 +36,10 @@ class BaseAstNode(Generic[T_MAPPING_FIELD, T_ARGUMENTS]):
31
36
  # used for type check expressions
32
37
  accept_type: VariableType = VariableType.ANY
33
38
  ret_type: VariableType = VariableType.ANY
34
- exclude_types: Sequence[VariableType] = field(default_factory=tuple)
39
+ exclude_types: Sequence[VariableType] = field(
40
+ default_factory=tuple, repr=False
41
+ )
42
+ classvar_hooks: dict[str, "ExprClassVar"] = field(default_factory=dict)
35
43
 
36
44
  def unpack_args(self) -> T_ARGUMENTS:
37
45
  """extract all values from kwargs field wout keys"""
@@ -67,6 +75,39 @@ class BaseAstNode(Generic[T_MAPPING_FIELD, T_ARGUMENTS]):
67
75
  return self.kwargs[name]
68
76
  raise e
69
77
 
78
+ def find_node(
79
+ self, func: Callable[["BaseAstNode"], bool]
80
+ ) -> Optional["BaseAstNode"]:
81
+ """find nodes in body by func filter
82
+
83
+ return None if node not founded or node.body is empty
84
+ """
85
+ if len(self.body) == 0:
86
+ return None
87
+
88
+ for n in self.body:
89
+ if func(n):
90
+ return n
91
+ return None
92
+
93
+ def find_node_all(
94
+ self, func: Callable[["BaseAstNode"], bool]
95
+ ) -> "BaseAstNode":
96
+ """find nodes in body by func filter"""
97
+ if len(self.body) == 0:
98
+ return []
99
+ return [i for i in self.body if i.kind == func(i)]
100
+
101
+ def find_node_by_token(
102
+ self, token_type: TokenType
103
+ ) -> Optional["BaseAstNode"]:
104
+ """find node by TokenType"""
105
+ return self.find_node(lambda n: n.kind == token_type)
106
+
107
+ def find_nodes_by_token(self, token_type: TokenType) -> list["BaseAstNode"]:
108
+ """find nodes by TokenType"""
109
+ return self.find_nodes_by_token(lambda n: n.kind == token_type)
110
+
70
111
 
71
112
  @dataclass(kw_only=True)
72
113
  class _DisableReprBody(BaseAstNode):
@@ -33,8 +33,12 @@ class ExprToListLength(BaseAstNode[T_EMPTY_KWARGS, tuple]):
33
33
  ret_type: VariableType = VariableType.INT
34
34
 
35
35
 
36
+ KW_UNIQUE = TypedDict("KW_UNIQUE", {"keep_order": bool})
37
+ ARGS_UNIQUE = tuple[bool]
38
+
39
+
36
40
  @dataclass(kw_only=True)
37
- class ExprListUnique(BaseAstNode[T_EMPTY_KWARGS, tuple]):
41
+ class ExprListUnique(BaseAstNode[KW_UNIQUE, ARGS_UNIQUE]):
38
42
  # TODO: support LIST_INT, LIST_FLOAT
39
43
  kind: ClassVar[TokenType] = TokenType.EXPR_LIST_UNIQUE
40
44
  accept_type: VariableType = VariableType.LIST_STRING
@@ -58,9 +58,9 @@ class ExprNested(BaseAstNode[KW_EXPR_NESTED, EXPR_NESTED_ARGS]):
58
58
 
59
59
 
60
60
  KW_EXPR_JSONIFY = TypedDict(
61
- "KW_EXPR_JSONIFY", {"json_struct_name": str, "is_array": bool}
61
+ "KW_EXPR_JSONIFY", {"json_struct_name": str, "is_array": bool, "query": str}
62
62
  )
63
- ARGS_EXPR_JSONIFY = tuple[str, bool]
63
+ ARGS_EXPR_JSONIFY = tuple[str, bool, str]
64
64
 
65
65
 
66
66
  @dataclass(kw_only=True)