ssc_codegen 0.33.0__tar.gz → 0.34.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.
- {ssc_codegen-0.33.0 → ssc_codegen-0.34.0}/PKG-INFO +1 -1
- {ssc_codegen-0.33.0 → ssc_codegen-0.34.0}/pyproject.toml +1 -1
- {ssc_codegen-0.33.0 → ssc_codegen-0.34.0}/ssc_codegen/ast/__init__.py +4 -0
- ssc_codegen-0.34.0/ssc_codegen/ast/function.py +28 -0
- {ssc_codegen-0.33.0 → ssc_codegen-0.34.0}/ssc_codegen/core/expressions.py +4 -2
- {ssc_codegen-0.33.0 → ssc_codegen-0.34.0}/ssc_codegen/core/linter.py +65 -1
- {ssc_codegen-0.33.0 → ssc_codegen-0.34.0}/ssc_codegen/core/module_handler.py +30 -1
- {ssc_codegen-0.33.0 → ssc_codegen-0.34.0}/ssc_codegen/core/reader.py +6 -0
- {ssc_codegen-0.33.0 → ssc_codegen-0.34.0}/ssc_codegen/core/struct_parser.py +42 -0
- {ssc_codegen-0.33.0 → ssc_codegen-0.34.0}/ssc_codegen/targets/golang/visitor.py +30 -3
- {ssc_codegen-0.33.0 → ssc_codegen-0.34.0}/ssc_codegen/targets/javascript/visitor.py +26 -0
- {ssc_codegen-0.33.0 → ssc_codegen-0.34.0}/ssc_codegen/targets/python/visitor.py +26 -0
- {ssc_codegen-0.33.0 → ssc_codegen-0.34.0}/ssc_codegen/traversal/utils.py +9 -1
- {ssc_codegen-0.33.0 → ssc_codegen-0.34.0}/ssc_codegen/traversal/walker.py +4 -0
- {ssc_codegen-0.33.0 → ssc_codegen-0.34.0}/.gitignore +0 -0
- {ssc_codegen-0.33.0 → ssc_codegen-0.34.0}/LICENSE +0 -0
- {ssc_codegen-0.33.0 → ssc_codegen-0.34.0}/README.md +0 -0
- {ssc_codegen-0.33.0 → ssc_codegen-0.34.0}/ssc_codegen/__init__.py +0 -0
- {ssc_codegen-0.33.0 → ssc_codegen-0.34.0}/ssc_codegen/_logging.py +0 -0
- {ssc_codegen-0.33.0 → ssc_codegen-0.34.0}/ssc_codegen/ast/array.py +0 -0
- {ssc_codegen-0.33.0 → ssc_codegen-0.34.0}/ssc_codegen/ast/base.py +0 -0
- {ssc_codegen-0.33.0 → ssc_codegen-0.34.0}/ssc_codegen/ast/cast.py +0 -0
- {ssc_codegen-0.33.0 → ssc_codegen-0.34.0}/ssc_codegen/ast/control.py +0 -0
- {ssc_codegen-0.33.0 → ssc_codegen-0.34.0}/ssc_codegen/ast/extract.py +0 -0
- {ssc_codegen-0.33.0 → ssc_codegen-0.34.0}/ssc_codegen/ast/helpers.py +0 -0
- {ssc_codegen-0.33.0 → ssc_codegen-0.34.0}/ssc_codegen/ast/jsondef.py +0 -0
- {ssc_codegen-0.33.0 → ssc_codegen-0.34.0}/ssc_codegen/ast/module.py +0 -0
- {ssc_codegen-0.33.0 → ssc_codegen-0.34.0}/ssc_codegen/ast/predicate_containers.py +0 -0
- {ssc_codegen-0.33.0 → ssc_codegen-0.34.0}/ssc_codegen/ast/predicate_ops.py +0 -0
- {ssc_codegen-0.33.0 → ssc_codegen-0.34.0}/ssc_codegen/ast/regex.py +0 -0
- {ssc_codegen-0.33.0 → ssc_codegen-0.34.0}/ssc_codegen/ast/rest.py +0 -0
- {ssc_codegen-0.33.0 → ssc_codegen-0.34.0}/ssc_codegen/ast/selectors.py +0 -0
- {ssc_codegen-0.33.0 → ssc_codegen-0.34.0}/ssc_codegen/ast/string.py +0 -0
- {ssc_codegen-0.33.0 → ssc_codegen-0.34.0}/ssc_codegen/ast/struct.py +0 -0
- {ssc_codegen-0.33.0 → ssc_codegen-0.34.0}/ssc_codegen/ast/typedef.py +0 -0
- {ssc_codegen-0.33.0 → ssc_codegen-0.34.0}/ssc_codegen/ast/types.py +0 -0
- {ssc_codegen-0.33.0 → ssc_codegen-0.34.0}/ssc_codegen/core/__init__.py +0 -0
- {ssc_codegen-0.33.0 → ssc_codegen-0.34.0}/ssc_codegen/core/contexts.py +0 -0
- {ssc_codegen-0.33.0 → ssc_codegen-0.34.0}/ssc_codegen/core/format.py +0 -0
- {ssc_codegen-0.33.0 → ssc_codegen-0.34.0}/ssc_codegen/core/predicates.py +0 -0
- {ssc_codegen-0.33.0 → ssc_codegen-0.34.0}/ssc_codegen/core/rest_artifacts.py +0 -0
- {ssc_codegen-0.33.0 → ssc_codegen-0.34.0}/ssc_codegen/core/type_checking.py +0 -0
- {ssc_codegen-0.33.0 → ssc_codegen-0.34.0}/ssc_codegen/exceptions.py +0 -0
- {ssc_codegen-0.33.0 → ssc_codegen-0.34.0}/ssc_codegen/explore.py +0 -0
- {ssc_codegen-0.33.0 → ssc_codegen-0.34.0}/ssc_codegen/generation/__init__.py +0 -0
- {ssc_codegen-0.33.0 → ssc_codegen-0.34.0}/ssc_codegen/generation/builder.py +0 -0
- {ssc_codegen-0.33.0 → ssc_codegen-0.34.0}/ssc_codegen/generation/runtime.py +0 -0
- {ssc_codegen-0.33.0 → ssc_codegen-0.34.0}/ssc_codegen/health.py +0 -0
- {ssc_codegen-0.33.0 → ssc_codegen-0.34.0}/ssc_codegen/main.py +0 -0
- {ssc_codegen-0.33.0 → ssc_codegen-0.34.0}/ssc_codegen/naming.py +0 -0
- {ssc_codegen-0.33.0 → ssc_codegen-0.34.0}/ssc_codegen/parsers/__init__.py +0 -0
- {ssc_codegen-0.33.0 → ssc_codegen-0.34.0}/ssc_codegen/parsers/curl.py +0 -0
- {ssc_codegen-0.33.0 → ssc_codegen-0.34.0}/ssc_codegen/parsers/http.py +0 -0
- {ssc_codegen-0.33.0 → ssc_codegen-0.34.0}/ssc_codegen/regex_utils.py +0 -0
- {ssc_codegen-0.33.0 → ssc_codegen-0.34.0}/ssc_codegen/request_spec.py +0 -0
- {ssc_codegen-0.33.0 → ssc_codegen-0.34.0}/ssc_codegen/targets/__init__.py +0 -0
- {ssc_codegen-0.33.0 → ssc_codegen-0.34.0}/ssc_codegen/targets/golang/__init__.py +0 -0
- {ssc_codegen-0.33.0 → ssc_codegen-0.34.0}/ssc_codegen/targets/golang/http_libs/__init__.py +0 -0
- {ssc_codegen-0.33.0 → ssc_codegen-0.34.0}/ssc_codegen/targets/golang/http_libs/base.py +0 -0
- {ssc_codegen-0.33.0 → ssc_codegen-0.34.0}/ssc_codegen/targets/golang/http_libs/nethttp.py +0 -0
- {ssc_codegen-0.33.0 → ssc_codegen-0.34.0}/ssc_codegen/targets/golang/literals.py +0 -0
- {ssc_codegen-0.33.0 → ssc_codegen-0.34.0}/ssc_codegen/targets/golang/regex.py +0 -0
- {ssc_codegen-0.33.0 → ssc_codegen-0.34.0}/ssc_codegen/targets/golang/rest.py +0 -0
- {ssc_codegen-0.33.0 → ssc_codegen-0.34.0}/ssc_codegen/targets/golang/runtime.py +0 -0
- {ssc_codegen-0.33.0 → ssc_codegen-0.34.0}/ssc_codegen/targets/javascript/__init__.py +0 -0
- {ssc_codegen-0.33.0 → ssc_codegen-0.34.0}/ssc_codegen/targets/javascript/http_libs/__init__.py +0 -0
- {ssc_codegen-0.33.0 → ssc_codegen-0.34.0}/ssc_codegen/targets/javascript/http_libs/axios.py +0 -0
- {ssc_codegen-0.33.0 → ssc_codegen-0.34.0}/ssc_codegen/targets/javascript/http_libs/base.py +0 -0
- {ssc_codegen-0.33.0 → ssc_codegen-0.34.0}/ssc_codegen/targets/javascript/http_libs/fetch.py +0 -0
- {ssc_codegen-0.33.0 → ssc_codegen-0.34.0}/ssc_codegen/targets/javascript/rest.py +0 -0
- {ssc_codegen-0.33.0 → ssc_codegen-0.34.0}/ssc_codegen/targets/profile.py +0 -0
- {ssc_codegen-0.33.0 → ssc_codegen-0.34.0}/ssc_codegen/targets/python/__init__.py +0 -0
- {ssc_codegen-0.33.0 → ssc_codegen-0.34.0}/ssc_codegen/targets/python/html_libs/__init__.py +0 -0
- {ssc_codegen-0.33.0 → ssc_codegen-0.34.0}/ssc_codegen/targets/python/html_libs/base.py +0 -0
- {ssc_codegen-0.33.0 → ssc_codegen-0.34.0}/ssc_codegen/targets/python/html_libs/bs4.py +0 -0
- {ssc_codegen-0.33.0 → ssc_codegen-0.34.0}/ssc_codegen/targets/python/html_libs/lxml.py +0 -0
- {ssc_codegen-0.33.0 → ssc_codegen-0.34.0}/ssc_codegen/targets/python/html_libs/parsel.py +0 -0
- {ssc_codegen-0.33.0 → ssc_codegen-0.34.0}/ssc_codegen/targets/python/html_libs/slax.py +0 -0
- {ssc_codegen-0.33.0 → ssc_codegen-0.34.0}/ssc_codegen/targets/python/http_libs/__init__.py +0 -0
- {ssc_codegen-0.33.0 → ssc_codegen-0.34.0}/ssc_codegen/targets/python/http_libs/aiohttp.py +0 -0
- {ssc_codegen-0.33.0 → ssc_codegen-0.34.0}/ssc_codegen/targets/python/http_libs/base.py +0 -0
- {ssc_codegen-0.33.0 → ssc_codegen-0.34.0}/ssc_codegen/targets/python/http_libs/httpx.py +0 -0
- {ssc_codegen-0.33.0 → ssc_codegen-0.34.0}/ssc_codegen/targets/python/http_libs/requests.py +0 -0
- {ssc_codegen-0.33.0 → ssc_codegen-0.34.0}/ssc_codegen/targets/python/rest.py +0 -0
- {ssc_codegen-0.33.0 → ssc_codegen-0.34.0}/ssc_codegen/targets/resolver.py +0 -0
- {ssc_codegen-0.33.0 → ssc_codegen-0.34.0}/ssc_codegen/targets/spec.py +0 -0
- {ssc_codegen-0.33.0 → ssc_codegen-0.34.0}/ssc_codegen/traversal/__init__.py +0 -0
- {ssc_codegen-0.33.0 → ssc_codegen-0.34.0}/ssc_codegen/traversal/context.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: ssc_codegen
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.34.0
|
|
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
|
|
@@ -20,6 +20,8 @@ from .module import (
|
|
|
20
20
|
Utilities,
|
|
21
21
|
)
|
|
22
22
|
|
|
23
|
+
from .function import FunctionDef
|
|
24
|
+
|
|
23
25
|
from .typedef import TypeDef, TypeDefField
|
|
24
26
|
|
|
25
27
|
from .jsondef import JsonDef, JsonDefField
|
|
@@ -147,6 +149,8 @@ __all__ = [
|
|
|
147
149
|
"CodeEndHook",
|
|
148
150
|
"Docstring",
|
|
149
151
|
"Utilities",
|
|
152
|
+
# function
|
|
153
|
+
"FunctionDef",
|
|
150
154
|
# typedef
|
|
151
155
|
"TypeDef",
|
|
152
156
|
"TypeDefField",
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from dataclasses import dataclass, field
|
|
4
|
+
|
|
5
|
+
from .base import Node
|
|
6
|
+
from .types import TypeInfo, VariableType
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
@dataclass
|
|
10
|
+
class FunctionDef(Node):
|
|
11
|
+
"""Module-level single-value extraction function.
|
|
12
|
+
|
|
13
|
+
DSL: ``fn <name> { pipeline }`` or ``(raw)fn <name> { pipeline }``.
|
|
14
|
+
|
|
15
|
+
Generates a standalone function instead of a class.
|
|
16
|
+
``is_raw`` selects the document type: HTML (DOCUMENT) or plain text (STRING).
|
|
17
|
+
``ret_type_info`` is resolved after pipeline build (AUTO → concrete type).
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
name: str = ""
|
|
21
|
+
is_raw: bool = False
|
|
22
|
+
doc: str = ""
|
|
23
|
+
accept_type_info: TypeInfo = field(
|
|
24
|
+
default_factory=lambda: TypeInfo(base=VariableType.DOCUMENT)
|
|
25
|
+
)
|
|
26
|
+
ret_type_info: TypeInfo = field(
|
|
27
|
+
default_factory=lambda: TypeInfo(base=VariableType.AUTO)
|
|
28
|
+
)
|
|
@@ -19,6 +19,7 @@ from ssc_codegen.ast import (
|
|
|
19
19
|
Field,
|
|
20
20
|
Filter,
|
|
21
21
|
Fmt,
|
|
22
|
+
FunctionDef,
|
|
22
23
|
Index,
|
|
23
24
|
InitField,
|
|
24
25
|
JsonDef,
|
|
@@ -104,6 +105,7 @@ FieldLikeNode: TypeAlias = (
|
|
|
104
105
|
| Value
|
|
105
106
|
| Field
|
|
106
107
|
| InitField
|
|
108
|
+
| FunctionDef
|
|
107
109
|
)
|
|
108
110
|
|
|
109
111
|
|
|
@@ -480,8 +482,8 @@ def parse_expressions(
|
|
|
480
482
|
)
|
|
481
483
|
parent.ret_type_info = last_ti
|
|
482
484
|
|
|
483
|
-
# Populate type_info on Field/Value/InitField
|
|
484
|
-
if isinstance(parent, (Field, Value, InitField)):
|
|
485
|
+
# Populate type_info on Field/Value/InitField/FunctionDef
|
|
486
|
+
if isinstance(parent, (Field, Value, InitField, FunctionDef)):
|
|
485
487
|
last_ret = last_ti.base
|
|
486
488
|
is_arr = (
|
|
487
489
|
parent.body[-2].is_array if len(parent.body) >= 2 else False
|
|
@@ -168,6 +168,7 @@ def lint_module(
|
|
|
168
168
|
_lint_defines(doc, source_path, diags, children_defines)
|
|
169
169
|
_lint_json_defs(doc, source_path, diags, children_defines)
|
|
170
170
|
_lint_structs(doc, source_path, diags, children_defines)
|
|
171
|
+
_lint_fns(doc, source_path, diags)
|
|
171
172
|
_lint_nested_topdown(doc, source_path, diags)
|
|
172
173
|
_lint_json_refs_topdown(doc, source_path, diags, children_defines)
|
|
173
174
|
return diags
|
|
@@ -178,7 +179,7 @@ def _lint_top_level(
|
|
|
178
179
|
source_path: str,
|
|
179
180
|
diags: list[ReadDiagnostic],
|
|
180
181
|
) -> None:
|
|
181
|
-
for node in doc.select(":root:not(@doc, json, struct, define, import)"):
|
|
182
|
+
for node in doc.select(":root:not(@doc, json, struct, fn, define, import)"):
|
|
182
183
|
diags.append(
|
|
183
184
|
_error(
|
|
184
185
|
node,
|
|
@@ -378,6 +379,69 @@ def _lint_structs(
|
|
|
378
379
|
_lint_single_struct(node, source_path, diags, children_defines)
|
|
379
380
|
|
|
380
381
|
|
|
382
|
+
# Directives allowed inside ``fn`` / ``(raw)fn`` body.
|
|
383
|
+
_FN_ALLOWED_RESERVED: frozenset[str] = frozenset({"@doc"})
|
|
384
|
+
|
|
385
|
+
|
|
386
|
+
def _lint_fns(
|
|
387
|
+
doc: KdlDocument,
|
|
388
|
+
source_path: str,
|
|
389
|
+
diags: list[ReadDiagnostic],
|
|
390
|
+
) -> None:
|
|
391
|
+
"""Structural lint for ``fn`` / ``(raw)fn`` directives."""
|
|
392
|
+
for node in doc.select("fn:root"):
|
|
393
|
+
name = _node_arg(node, 0)
|
|
394
|
+
if not name:
|
|
395
|
+
diags.append(
|
|
396
|
+
_error(
|
|
397
|
+
node,
|
|
398
|
+
"'fn' requires a name",
|
|
399
|
+
source_path,
|
|
400
|
+
code="E001",
|
|
401
|
+
hint='example: fn page_title { css "h1" { text } }',
|
|
402
|
+
)
|
|
403
|
+
)
|
|
404
|
+
continue
|
|
405
|
+
|
|
406
|
+
annotation = node.type_annotation
|
|
407
|
+
if annotation and annotation != "(raw)":
|
|
408
|
+
diags.append(
|
|
409
|
+
_error(
|
|
410
|
+
node,
|
|
411
|
+
f"unsupported fn annotation '{annotation}' — only '(raw)' is recognized",
|
|
412
|
+
source_path,
|
|
413
|
+
code="E400",
|
|
414
|
+
hint="use 'fn { ... }' (HTML) or '(raw)fn { ... }' (plain text)",
|
|
415
|
+
)
|
|
416
|
+
)
|
|
417
|
+
|
|
418
|
+
ops = list(node.children)
|
|
419
|
+
if not ops:
|
|
420
|
+
diags.append(
|
|
421
|
+
_error(
|
|
422
|
+
node,
|
|
423
|
+
f"fn '{name}' must contain at least one pipeline operation",
|
|
424
|
+
source_path,
|
|
425
|
+
code="E001",
|
|
426
|
+
hint='example: fn title { css "h1" { text } }',
|
|
427
|
+
)
|
|
428
|
+
)
|
|
429
|
+
|
|
430
|
+
for child in ops:
|
|
431
|
+
if (
|
|
432
|
+
child.name.startswith("@")
|
|
433
|
+
and child.name not in _FN_ALLOWED_RESERVED
|
|
434
|
+
):
|
|
435
|
+
diags.append(
|
|
436
|
+
_error(
|
|
437
|
+
child,
|
|
438
|
+
f"'{child.name}' is not allowed inside fn — use a struct for init/check/request/etc.",
|
|
439
|
+
source_path,
|
|
440
|
+
code="E203",
|
|
441
|
+
)
|
|
442
|
+
)
|
|
443
|
+
|
|
444
|
+
|
|
381
445
|
def _lint_nested_topdown(
|
|
382
446
|
doc: KdlDocument,
|
|
383
447
|
source_path: str,
|
|
@@ -5,12 +5,15 @@ from __future__ import annotations
|
|
|
5
5
|
from pathlib import Path
|
|
6
6
|
|
|
7
7
|
from ssc_codegen.ast import (
|
|
8
|
+
FunctionDef,
|
|
8
9
|
JsonDef,
|
|
9
10
|
Module,
|
|
10
11
|
StructBase,
|
|
11
12
|
Struct,
|
|
12
13
|
StructRest,
|
|
13
14
|
StructType,
|
|
15
|
+
VariableType,
|
|
16
|
+
TypeInfo,
|
|
14
17
|
)
|
|
15
18
|
from ssc_codegen.exceptions import BuildTimeError
|
|
16
19
|
from kdlquery import KdlNode, parse as kdl_parse
|
|
@@ -23,7 +26,11 @@ from ssc_codegen.core.contexts import (
|
|
|
23
26
|
ParseContext,
|
|
24
27
|
)
|
|
25
28
|
from ssc_codegen.core.expressions import resolve_define_references
|
|
26
|
-
from ssc_codegen.core.struct_parser import
|
|
29
|
+
from ssc_codegen.core.struct_parser import (
|
|
30
|
+
parse_function,
|
|
31
|
+
parse_json_fields,
|
|
32
|
+
parse_struct,
|
|
33
|
+
)
|
|
27
34
|
|
|
28
35
|
_KDL_TEXT_ENCODING = "utf-8-sig"
|
|
29
36
|
|
|
@@ -65,6 +72,28 @@ def handle_struct(
|
|
|
65
72
|
return struct
|
|
66
73
|
|
|
67
74
|
|
|
75
|
+
def handle_function(
|
|
76
|
+
node: KdlNode, module: Module, ctx: ParseContext, lint: LintContext
|
|
77
|
+
) -> FunctionDef:
|
|
78
|
+
raw_annotation = node.type_annotation
|
|
79
|
+
is_raw = raw_annotation == "(raw)"
|
|
80
|
+
if not node.args:
|
|
81
|
+
raise BuildTimeError("'fn' requires a name")
|
|
82
|
+
name = str(node.args[0].value)
|
|
83
|
+
fn = FunctionDef(
|
|
84
|
+
parent=module,
|
|
85
|
+
name=name,
|
|
86
|
+
is_raw=is_raw,
|
|
87
|
+
accept_type_info=(
|
|
88
|
+
TypeInfo(base=VariableType.STRING)
|
|
89
|
+
if is_raw
|
|
90
|
+
else TypeInfo(base=VariableType.DOCUMENT)
|
|
91
|
+
),
|
|
92
|
+
)
|
|
93
|
+
parse_function(node.children, fn, ctx, lint)
|
|
94
|
+
return fn
|
|
95
|
+
|
|
96
|
+
|
|
68
97
|
def handle_json(
|
|
69
98
|
node: KdlNode, module: Module, ctx: ParseContext, lint: LintContext
|
|
70
99
|
) -> JsonDef:
|
|
@@ -16,6 +16,7 @@ from ssc_codegen.core.expressions import typedef_from_struct
|
|
|
16
16
|
from ssc_codegen.core.rest_artifacts import rest_artifacts_from_struct
|
|
17
17
|
from ssc_codegen.core.module_handler import (
|
|
18
18
|
handle_define,
|
|
19
|
+
handle_function,
|
|
19
20
|
handle_json,
|
|
20
21
|
handle_struct,
|
|
21
22
|
resolve_imports,
|
|
@@ -68,6 +69,7 @@ def parse_module(
|
|
|
68
69
|
module.source_file = source_path.name if source_path else ""
|
|
69
70
|
structs: list = []
|
|
70
71
|
typedefs: list = []
|
|
72
|
+
functions: list = []
|
|
71
73
|
|
|
72
74
|
for node in top_nodes:
|
|
73
75
|
lint.push(node.name)
|
|
@@ -79,6 +81,9 @@ def parse_module(
|
|
|
79
81
|
struct = handle_struct(node, module, ctx, lint)
|
|
80
82
|
typedefs.append(typedef_from_struct(struct, module))
|
|
81
83
|
structs.append(struct)
|
|
84
|
+
elif node.name == "fn":
|
|
85
|
+
fn = handle_function(node, module, ctx, lint)
|
|
86
|
+
functions.append(fn)
|
|
82
87
|
elif node.name in ("define", "import"):
|
|
83
88
|
pass # already handled
|
|
84
89
|
else:
|
|
@@ -95,6 +100,7 @@ def parse_module(
|
|
|
95
100
|
if isinstance(struct, StructRest):
|
|
96
101
|
module.body.extend(rest_artifacts_from_struct(struct, module))
|
|
97
102
|
module.body.append(struct)
|
|
103
|
+
module.body.extend(functions)
|
|
98
104
|
|
|
99
105
|
# merge lint diagnostics into walker diagnostics
|
|
100
106
|
diagnostics.extend(lint.diagnostics)
|
|
@@ -13,6 +13,7 @@ from ssc_codegen.ast import (
|
|
|
13
13
|
CssSelectAll,
|
|
14
14
|
ErrorResponse,
|
|
15
15
|
Field,
|
|
16
|
+
FunctionDef,
|
|
16
17
|
InitField,
|
|
17
18
|
InitFieldCall,
|
|
18
19
|
JsonDef,
|
|
@@ -313,6 +314,47 @@ def parse_struct(
|
|
|
313
314
|
lint.walk_context = prev_ctx
|
|
314
315
|
|
|
315
316
|
|
|
317
|
+
def parse_function(
|
|
318
|
+
kdl_nodes: Sequence[KdlNode],
|
|
319
|
+
fn: FunctionDef,
|
|
320
|
+
ctx: ParseContext,
|
|
321
|
+
lint: LintContext,
|
|
322
|
+
) -> None:
|
|
323
|
+
"""Parse the body of a ``fn`` / ``(raw)fn`` directive.
|
|
324
|
+
|
|
325
|
+
The body is a flat pipeline (same ops as a Field), optionally preceded
|
|
326
|
+
by ``@doc``. No struct-level directives (@init, @check, @split-doc, etc.)
|
|
327
|
+
are accepted — those are reported by the structural linter.
|
|
328
|
+
"""
|
|
329
|
+
prev_ctx = lint.walk_context
|
|
330
|
+
lint.walk_context = WalkCtx.PIPELINE
|
|
331
|
+
|
|
332
|
+
pipeline_nodes: list[KdlNode] = []
|
|
333
|
+
for node in kdl_nodes:
|
|
334
|
+
if node.name == "@doc":
|
|
335
|
+
fn.doc = str(node.args[0].value)
|
|
336
|
+
elif node.name.startswith("@"):
|
|
337
|
+
continue # structural linter already reported E203
|
|
338
|
+
else:
|
|
339
|
+
pipeline_nodes.append(node)
|
|
340
|
+
|
|
341
|
+
ops = pipeline_nodes
|
|
342
|
+
parse_expressions(ops, fn, ctx, lint)
|
|
343
|
+
if ops:
|
|
344
|
+
check_pipeline_types(
|
|
345
|
+
ops,
|
|
346
|
+
ctx,
|
|
347
|
+
lint,
|
|
348
|
+
start_type=VariableType.STRING
|
|
349
|
+
if fn.is_raw
|
|
350
|
+
else VariableType.DOCUMENT,
|
|
351
|
+
)
|
|
352
|
+
if fn.is_raw:
|
|
353
|
+
_lint_raw_forbidden_ops(fn, lint)
|
|
354
|
+
|
|
355
|
+
lint.walk_context = prev_ctx
|
|
356
|
+
|
|
357
|
+
|
|
316
358
|
def parse_json_fields(
|
|
317
359
|
nodes: Sequence[KdlNode], parent: JsonDef, ctx: ParseContext
|
|
318
360
|
) -> None:
|
|
@@ -35,6 +35,7 @@ from ssc_codegen.ast import (
|
|
|
35
35
|
Field,
|
|
36
36
|
Filter,
|
|
37
37
|
Fmt,
|
|
38
|
+
FunctionDef,
|
|
38
39
|
Init,
|
|
39
40
|
InitField,
|
|
40
41
|
InitFieldCall,
|
|
@@ -136,8 +137,8 @@ from ssc_codegen.naming import (
|
|
|
136
137
|
)
|
|
137
138
|
from ssc_codegen.traversal.utils import (
|
|
138
139
|
find_predicate_container,
|
|
140
|
+
module_has_html_struct,
|
|
139
141
|
module_has_rest,
|
|
140
|
-
module_is_rest_only,
|
|
141
142
|
module_uses_http,
|
|
142
143
|
)
|
|
143
144
|
from ssc_codegen.generation.builder import ModuleBuilder
|
|
@@ -439,10 +440,9 @@ class GoVisitor(BaseWalker):
|
|
|
439
440
|
lines.append(f"// {doc_line}" if doc_line else "//")
|
|
440
441
|
lines.append("")
|
|
441
442
|
|
|
442
|
-
is_rest_only = module_is_rest_only(node)
|
|
443
443
|
uses_http = module_uses_http(node)
|
|
444
444
|
|
|
445
|
-
if
|
|
445
|
+
if module_has_html_struct(node):
|
|
446
446
|
self._builder.require_import('"github.com/PuerkitoBio/goquery"')
|
|
447
447
|
self._builder.require_import('"strings"')
|
|
448
448
|
if uses_http:
|
|
@@ -842,6 +842,33 @@ class GoVisitor(BaseWalker):
|
|
|
842
842
|
lines.append("")
|
|
843
843
|
return lines
|
|
844
844
|
|
|
845
|
+
def visit_function_def(
|
|
846
|
+
self, node: FunctionDef, ctx: WalkContext
|
|
847
|
+
) -> list[str]:
|
|
848
|
+
name = to_pascal_case(node.name)
|
|
849
|
+
t_ret = self._resolve_type(node.ret_type_info)
|
|
850
|
+
inner = ctx.deeper()
|
|
851
|
+
lines: list[str] = []
|
|
852
|
+
if node.doc:
|
|
853
|
+
for doc_line in node.doc.splitlines():
|
|
854
|
+
lines.append(f"// {doc_line}")
|
|
855
|
+
lines.append(f"func {name}(document string) {t_ret} {{")
|
|
856
|
+
if node.is_raw:
|
|
857
|
+
lines.append(f"{inner.indent}{inner.var_name} := document")
|
|
858
|
+
else:
|
|
859
|
+
self._builder.require_import('"strings"')
|
|
860
|
+
lines.append(
|
|
861
|
+
f"{inner.indent}doc, err := goquery.NewDocumentFromReader(strings.NewReader(document))"
|
|
862
|
+
)
|
|
863
|
+
lines.append(f"{inner.indent}if err != nil {{")
|
|
864
|
+
lines.append(f"{inner.indent}\tpanic(err)")
|
|
865
|
+
lines.append(f"{inner.indent}}}")
|
|
866
|
+
lines.append(f"{inner.indent}{inner.var_name} := doc.Selection")
|
|
867
|
+
lines.extend(self.walk_children(node, ctx))
|
|
868
|
+
lines.append("}")
|
|
869
|
+
lines.append("")
|
|
870
|
+
return lines
|
|
871
|
+
|
|
845
872
|
def visit_split_doc(self, node: SplitDoc, ctx: WalkContext) -> list[str]:
|
|
846
873
|
struct = self._enclosing_struct_name(node)
|
|
847
874
|
rcv = _receiver(struct)
|
|
@@ -31,6 +31,7 @@ from ssc_codegen.ast import (
|
|
|
31
31
|
Field,
|
|
32
32
|
Filter,
|
|
33
33
|
Fmt,
|
|
34
|
+
FunctionDef,
|
|
34
35
|
Init,
|
|
35
36
|
InitField,
|
|
36
37
|
InitFieldCall,
|
|
@@ -528,6 +529,31 @@ class JsVisitor(BaseWalker):
|
|
|
528
529
|
lines.append("}")
|
|
529
530
|
return lines
|
|
530
531
|
|
|
532
|
+
def visit_function_def(
|
|
533
|
+
self, node: FunctionDef, ctx: WalkContext
|
|
534
|
+
) -> list[str]:
|
|
535
|
+
name = to_camel_case(node.name)
|
|
536
|
+
inner = ctx.deeper()
|
|
537
|
+
lines: list[str] = []
|
|
538
|
+
if node.doc:
|
|
539
|
+
lines.extend(_js_docblock(node.doc.splitlines()))
|
|
540
|
+
lines.append(f"function {name}(document) {{")
|
|
541
|
+
if node.is_raw:
|
|
542
|
+
lines.append(f"{inner.indent}let {inner.var_name} = document;")
|
|
543
|
+
else:
|
|
544
|
+
lines.append(f"{inner.indent}let {inner.var_name};")
|
|
545
|
+
lines.append(f"{inner.indent}if (typeof document === 'string') {{")
|
|
546
|
+
lines.append(
|
|
547
|
+
f"{inner.deeper().indent}{inner.var_name} = (new DOMParser()).parseFromString(document, 'text/html');"
|
|
548
|
+
)
|
|
549
|
+
lines.append(f"{inner.indent}}} else {{")
|
|
550
|
+
lines.append(f"{inner.deeper().indent}{inner.var_name} = document;")
|
|
551
|
+
lines.append(f"{inner.indent}}}")
|
|
552
|
+
lines.extend(self.walk_children(node, ctx))
|
|
553
|
+
lines.append("}")
|
|
554
|
+
lines.append("")
|
|
555
|
+
return lines
|
|
556
|
+
|
|
531
557
|
def visit_split_doc(self, node: SplitDoc, ctx: WalkContext) -> list[str]:
|
|
532
558
|
lines = [f"{ctx.indent}_splitDoc(v) {{"]
|
|
533
559
|
lines.extend(self.walk_children(node, ctx))
|
|
@@ -28,6 +28,7 @@ from ssc_codegen.ast import (
|
|
|
28
28
|
InitFieldCall,
|
|
29
29
|
InitField,
|
|
30
30
|
Field,
|
|
31
|
+
FunctionDef,
|
|
31
32
|
PreValidate,
|
|
32
33
|
CheckMethod,
|
|
33
34
|
SplitDoc,
|
|
@@ -661,6 +662,31 @@ class PythonVisitor(BaseWalker):
|
|
|
661
662
|
lines.extend(self.walk_children(node, ctx))
|
|
662
663
|
return lines
|
|
663
664
|
|
|
665
|
+
def visit_function_def(
|
|
666
|
+
self, node: FunctionDef, ctx: WalkContext
|
|
667
|
+
) -> list[str]:
|
|
668
|
+
name = to_snake_case(node.name)
|
|
669
|
+
t_ret = self._resolve_type(node.ret_type_info)
|
|
670
|
+
inner = ctx.deeper()
|
|
671
|
+
lines = [f"{ctx.indent}def {name}(document: str) -> {t_ret}:"]
|
|
672
|
+
if node.doc:
|
|
673
|
+
lines.append(f'{inner.indent}"""')
|
|
674
|
+
for doc_line in node.doc.splitlines():
|
|
675
|
+
lines.append(inner.indent + doc_line)
|
|
676
|
+
lines.append(f'{inner.indent}"""')
|
|
677
|
+
if node.is_raw:
|
|
678
|
+
lines.append(f"{inner.indent}{inner.var_name} = document")
|
|
679
|
+
else:
|
|
680
|
+
lines.append(f"{inner.indent}if isinstance(document, str):")
|
|
681
|
+
lines.append(
|
|
682
|
+
f"{inner.deeper().indent}{inner.var_name} = {self._dom.init_from_str_expr}"
|
|
683
|
+
)
|
|
684
|
+
lines.append(f"{inner.indent}else:")
|
|
685
|
+
lines.append(f"{inner.deeper().indent}{inner.var_name} = document")
|
|
686
|
+
lines.extend(self.walk_children(node, ctx))
|
|
687
|
+
lines.append("")
|
|
688
|
+
return lines
|
|
689
|
+
|
|
664
690
|
def visit_split_doc(self, node: SplitDoc, ctx: WalkContext) -> list[str]:
|
|
665
691
|
t_arg = self._resolve_type(node.accept_type_info)
|
|
666
692
|
t_ret = self._resolve_type(node.ret_type_info)
|
|
@@ -6,6 +6,7 @@ from ssc_codegen.ast import (
|
|
|
6
6
|
Assert,
|
|
7
7
|
ErrorResponse,
|
|
8
8
|
Filter,
|
|
9
|
+
FunctionDef,
|
|
9
10
|
Match,
|
|
10
11
|
MethodBase,
|
|
11
12
|
Module,
|
|
@@ -49,13 +50,20 @@ def module_is_rest_only(module: Module) -> bool:
|
|
|
49
50
|
|
|
50
51
|
|
|
51
52
|
def module_has_html_struct(module: Module) -> bool:
|
|
52
|
-
"""True if the module has at least one HTML-parsing struct
|
|
53
|
+
"""True if the module has at least one HTML-parsing struct or function.
|
|
54
|
+
|
|
55
|
+
RAW structs, (raw)fn, and REST structs are excluded — they don't need
|
|
56
|
+
an HTML parser backend (bs4/lxml/goquery/DOMParser).
|
|
57
|
+
"""
|
|
53
58
|
for n in module.body:
|
|
54
59
|
if isinstance(n, StructRest):
|
|
55
60
|
continue
|
|
56
61
|
if isinstance(n, Struct):
|
|
57
62
|
if n.type != StructType.RAW:
|
|
58
63
|
return True
|
|
64
|
+
if isinstance(n, FunctionDef):
|
|
65
|
+
if not n.is_raw:
|
|
66
|
+
return True
|
|
59
67
|
return False
|
|
60
68
|
|
|
61
69
|
|
|
@@ -16,6 +16,7 @@ from ssc_codegen.ast import (
|
|
|
16
16
|
InitFieldCall,
|
|
17
17
|
InitField,
|
|
18
18
|
Field,
|
|
19
|
+
FunctionDef,
|
|
19
20
|
PreValidate,
|
|
20
21
|
CheckMethod,
|
|
21
22
|
SplitDoc,
|
|
@@ -115,6 +116,7 @@ _CONTAINER_NODES = (JsonDef, TypeDef, StructBase, Init)
|
|
|
115
116
|
# Pipeline: index advances after each node (delegated to walk_pipeline).
|
|
116
117
|
_PIPELINE_NODES = (
|
|
117
118
|
Field,
|
|
119
|
+
FunctionDef,
|
|
118
120
|
InitField,
|
|
119
121
|
PreValidate,
|
|
120
122
|
CheckMethod,
|
|
@@ -179,6 +181,8 @@ class BaseWalker:
|
|
|
179
181
|
MethodFetch: "visit_method_fetch",
|
|
180
182
|
MethodRest: "visit_method_rest",
|
|
181
183
|
ErrorResponse: "visit_error_response",
|
|
184
|
+
# function
|
|
185
|
+
FunctionDef: "visit_function_def",
|
|
182
186
|
# REST result artifacts
|
|
183
187
|
ResultVariantDef: "visit_result_variant_def",
|
|
184
188
|
ResultAliasDef: "visit_result_alias_def",
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{ssc_codegen-0.33.0 → ssc_codegen-0.34.0}/ssc_codegen/targets/javascript/http_libs/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|