pulse-framework 0.1.62__py3-none-any.whl

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 (126) hide show
  1. pulse/__init__.py +1493 -0
  2. pulse/_examples.py +29 -0
  3. pulse/app.py +1086 -0
  4. pulse/channel.py +607 -0
  5. pulse/cli/__init__.py +0 -0
  6. pulse/cli/cmd.py +575 -0
  7. pulse/cli/dependencies.py +181 -0
  8. pulse/cli/folder_lock.py +134 -0
  9. pulse/cli/helpers.py +271 -0
  10. pulse/cli/logging.py +102 -0
  11. pulse/cli/models.py +35 -0
  12. pulse/cli/packages.py +262 -0
  13. pulse/cli/processes.py +292 -0
  14. pulse/cli/secrets.py +39 -0
  15. pulse/cli/uvicorn_log_config.py +87 -0
  16. pulse/code_analysis.py +38 -0
  17. pulse/codegen/__init__.py +0 -0
  18. pulse/codegen/codegen.py +359 -0
  19. pulse/codegen/templates/__init__.py +0 -0
  20. pulse/codegen/templates/layout.py +106 -0
  21. pulse/codegen/templates/route.py +345 -0
  22. pulse/codegen/templates/routes_ts.py +42 -0
  23. pulse/codegen/utils.py +20 -0
  24. pulse/component.py +237 -0
  25. pulse/components/__init__.py +0 -0
  26. pulse/components/for_.py +83 -0
  27. pulse/components/if_.py +86 -0
  28. pulse/components/react_router.py +94 -0
  29. pulse/context.py +108 -0
  30. pulse/cookies.py +322 -0
  31. pulse/decorators.py +344 -0
  32. pulse/dom/__init__.py +0 -0
  33. pulse/dom/elements.py +1024 -0
  34. pulse/dom/events.py +445 -0
  35. pulse/dom/props.py +1250 -0
  36. pulse/dom/svg.py +0 -0
  37. pulse/dom/tags.py +328 -0
  38. pulse/dom/tags.pyi +480 -0
  39. pulse/env.py +178 -0
  40. pulse/form.py +538 -0
  41. pulse/helpers.py +541 -0
  42. pulse/hooks/__init__.py +0 -0
  43. pulse/hooks/core.py +452 -0
  44. pulse/hooks/effects.py +88 -0
  45. pulse/hooks/init.py +668 -0
  46. pulse/hooks/runtime.py +464 -0
  47. pulse/hooks/setup.py +254 -0
  48. pulse/hooks/stable.py +138 -0
  49. pulse/hooks/state.py +192 -0
  50. pulse/js/__init__.py +125 -0
  51. pulse/js/__init__.pyi +115 -0
  52. pulse/js/_types.py +299 -0
  53. pulse/js/array.py +339 -0
  54. pulse/js/console.py +50 -0
  55. pulse/js/date.py +119 -0
  56. pulse/js/document.py +145 -0
  57. pulse/js/error.py +140 -0
  58. pulse/js/json.py +66 -0
  59. pulse/js/map.py +97 -0
  60. pulse/js/math.py +69 -0
  61. pulse/js/navigator.py +79 -0
  62. pulse/js/number.py +57 -0
  63. pulse/js/obj.py +81 -0
  64. pulse/js/object.py +172 -0
  65. pulse/js/promise.py +172 -0
  66. pulse/js/pulse.py +115 -0
  67. pulse/js/react.py +495 -0
  68. pulse/js/regexp.py +57 -0
  69. pulse/js/set.py +124 -0
  70. pulse/js/string.py +38 -0
  71. pulse/js/weakmap.py +53 -0
  72. pulse/js/weakset.py +48 -0
  73. pulse/js/window.py +205 -0
  74. pulse/messages.py +202 -0
  75. pulse/middleware.py +471 -0
  76. pulse/plugin.py +96 -0
  77. pulse/proxy.py +242 -0
  78. pulse/py.typed +0 -0
  79. pulse/queries/__init__.py +0 -0
  80. pulse/queries/client.py +609 -0
  81. pulse/queries/common.py +101 -0
  82. pulse/queries/effect.py +55 -0
  83. pulse/queries/infinite_query.py +1418 -0
  84. pulse/queries/mutation.py +295 -0
  85. pulse/queries/protocol.py +136 -0
  86. pulse/queries/query.py +1314 -0
  87. pulse/queries/store.py +120 -0
  88. pulse/react_component.py +88 -0
  89. pulse/reactive.py +1208 -0
  90. pulse/reactive_extensions.py +1172 -0
  91. pulse/render_session.py +768 -0
  92. pulse/renderer.py +584 -0
  93. pulse/request.py +205 -0
  94. pulse/routing.py +598 -0
  95. pulse/serializer.py +279 -0
  96. pulse/state.py +556 -0
  97. pulse/test_helpers.py +15 -0
  98. pulse/transpiler/__init__.py +111 -0
  99. pulse/transpiler/assets.py +81 -0
  100. pulse/transpiler/builtins.py +1029 -0
  101. pulse/transpiler/dynamic_import.py +130 -0
  102. pulse/transpiler/emit_context.py +49 -0
  103. pulse/transpiler/errors.py +96 -0
  104. pulse/transpiler/function.py +611 -0
  105. pulse/transpiler/id.py +18 -0
  106. pulse/transpiler/imports.py +341 -0
  107. pulse/transpiler/js_module.py +336 -0
  108. pulse/transpiler/modules/__init__.py +33 -0
  109. pulse/transpiler/modules/asyncio.py +57 -0
  110. pulse/transpiler/modules/json.py +24 -0
  111. pulse/transpiler/modules/math.py +265 -0
  112. pulse/transpiler/modules/pulse/__init__.py +5 -0
  113. pulse/transpiler/modules/pulse/tags.py +250 -0
  114. pulse/transpiler/modules/typing.py +63 -0
  115. pulse/transpiler/nodes.py +1987 -0
  116. pulse/transpiler/py_module.py +135 -0
  117. pulse/transpiler/transpiler.py +1100 -0
  118. pulse/transpiler/vdom.py +256 -0
  119. pulse/types/__init__.py +0 -0
  120. pulse/types/event_handler.py +50 -0
  121. pulse/user_session.py +386 -0
  122. pulse/version.py +69 -0
  123. pulse_framework-0.1.62.dist-info/METADATA +198 -0
  124. pulse_framework-0.1.62.dist-info/RECORD +126 -0
  125. pulse_framework-0.1.62.dist-info/WHEEL +4 -0
  126. pulse_framework-0.1.62.dist-info/entry_points.txt +3 -0
@@ -0,0 +1,57 @@
1
+ """Python asyncio module transpilation to JavaScript Promise operations."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import Any, cast, final
6
+
7
+ from pulse.transpiler.nodes import (
8
+ Array,
9
+ Binary,
10
+ Call,
11
+ Expr,
12
+ Identifier,
13
+ Literal,
14
+ Member,
15
+ Ternary,
16
+ )
17
+ from pulse.transpiler.py_module import PyModule
18
+ from pulse.transpiler.transpiler import Transpiler
19
+
20
+ _Promise = Identifier("Promise")
21
+
22
+
23
+ @final
24
+ class PyAsyncio(PyModule):
25
+ """Provides transpilation for Python asyncio functions to JavaScript Promise methods."""
26
+
27
+ @staticmethod
28
+ def gather(*coros: Any, return_exceptions: Any = False, ctx: Transpiler) -> Expr:
29
+ """Transpile asyncio.gather to Promise.all or Promise.allSettled.
30
+
31
+ Args:
32
+ *coros: Variable number of coroutine/promise expressions
33
+ return_exceptions: If True, use Promise.allSettled
34
+ ctx: Transpiler context
35
+ """
36
+ promises = Array([ctx.emit_expr(c) for c in coros])
37
+ all_call = Call(Member(_Promise, "all"), [promises])
38
+ all_settled_call = Call(Member(_Promise, "allSettled"), [promises])
39
+
40
+ # Optimized: literal True -> allSettled
41
+ if return_exceptions is True or (
42
+ isinstance(return_exceptions, Literal) and return_exceptions.value is True
43
+ ):
44
+ return all_settled_call
45
+
46
+ # Optimized: literal False or default -> all
47
+ if return_exceptions is False or (
48
+ isinstance(return_exceptions, Literal) and return_exceptions.value is False
49
+ ):
50
+ return all_call
51
+
52
+ # General case: emit ternary on the expression
53
+ return Ternary(
54
+ Binary(ctx.emit_expr(cast(Any, return_exceptions)), "===", Literal(True)),
55
+ all_settled_call,
56
+ all_call,
57
+ )
@@ -0,0 +1,24 @@
1
+ """Python json module transpilation to JavaScript JSON."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import Any, final
6
+
7
+ from pulse.transpiler.nodes import Call, Expr, Identifier, Member
8
+ from pulse.transpiler.py_module import PyModule
9
+ from pulse.transpiler.transpiler import Transpiler
10
+
11
+ _JSON = Identifier("JSON")
12
+
13
+
14
+ @final
15
+ class PyJson(PyModule):
16
+ """Provides transpilation for Python json functions to JavaScript."""
17
+
18
+ @staticmethod
19
+ def dumps(obj: Any, *, ctx: Transpiler) -> Expr:
20
+ return Call(Member(_JSON, "stringify"), [ctx.emit_expr(obj)])
21
+
22
+ @staticmethod
23
+ def loads(s: Any, *, ctx: Transpiler) -> Expr:
24
+ return Call(Member(_JSON, "parse"), [ctx.emit_expr(s)])
@@ -0,0 +1,265 @@
1
+ """Python math module transpilation to JavaScript Math.
2
+
3
+ Provides transpilation from Python's `math` module to JavaScript's `Math` object.
4
+ """
5
+
6
+ from __future__ import annotations
7
+
8
+ from typing import Any, final
9
+
10
+ from pulse.transpiler.nodes import (
11
+ Binary,
12
+ Call,
13
+ Expr,
14
+ Identifier,
15
+ Literal,
16
+ Member,
17
+ Unary,
18
+ )
19
+ from pulse.transpiler.py_module import PyModule
20
+ from pulse.transpiler.transpiler import Transpiler
21
+
22
+ # Helpers for building Math.* calls
23
+ _Math = Identifier("Math")
24
+ _Number = Identifier("Number")
25
+
26
+
27
+ def _math_prop(name: str) -> Expr:
28
+ return Member(_Math, name)
29
+
30
+
31
+ def _math_call(name: str, args: list[Expr]) -> Expr:
32
+ return Call(Member(_Math, name), args)
33
+
34
+
35
+ def _number_call(name: str, args: list[Expr]) -> Expr:
36
+ return Call(Member(_Number, name), args)
37
+
38
+
39
+ @final
40
+ class PyMath(PyModule):
41
+ """Provides transpilation for Python math functions to JavaScript."""
42
+
43
+ # Constants
44
+ pi = _math_prop("PI")
45
+ e = _math_prop("E")
46
+ tau = Binary(Literal(2), "*", _math_prop("PI"))
47
+ inf = Identifier("Infinity")
48
+ nan = Identifier("NaN")
49
+
50
+ # Basic functions
51
+ @staticmethod
52
+ def acos(x: Any, *, ctx: Transpiler) -> Expr:
53
+ return _math_call("acos", [ctx.emit_expr(x)])
54
+
55
+ @staticmethod
56
+ def acosh(x: Any, *, ctx: Transpiler) -> Expr:
57
+ return _math_call("acosh", [ctx.emit_expr(x)])
58
+
59
+ @staticmethod
60
+ def asin(x: Any, *, ctx: Transpiler) -> Expr:
61
+ return _math_call("asin", [ctx.emit_expr(x)])
62
+
63
+ @staticmethod
64
+ def asinh(x: Any, *, ctx: Transpiler) -> Expr:
65
+ return _math_call("asinh", [ctx.emit_expr(x)])
66
+
67
+ @staticmethod
68
+ def atan(x: Any, *, ctx: Transpiler) -> Expr:
69
+ return _math_call("atan", [ctx.emit_expr(x)])
70
+
71
+ @staticmethod
72
+ def atan2(y: Any, x: Any, *, ctx: Transpiler) -> Expr:
73
+ return _math_call("atan2", [ctx.emit_expr(y), ctx.emit_expr(x)])
74
+
75
+ @staticmethod
76
+ def atanh(x: Any, *, ctx: Transpiler) -> Expr:
77
+ return _math_call("atanh", [ctx.emit_expr(x)])
78
+
79
+ @staticmethod
80
+ def cbrt(x: Any, *, ctx: Transpiler) -> Expr:
81
+ return _math_call("cbrt", [ctx.emit_expr(x)])
82
+
83
+ @staticmethod
84
+ def ceil(x: Any, *, ctx: Transpiler) -> Expr:
85
+ return _math_call("ceil", [ctx.emit_expr(x)])
86
+
87
+ @staticmethod
88
+ def copysign(x: Any, y: Any, *, ctx: Transpiler) -> Expr:
89
+ # Math.sign(y) * Math.abs(x)
90
+ return Binary(
91
+ _math_call("sign", [ctx.emit_expr(y)]),
92
+ "*",
93
+ _math_call("abs", [ctx.emit_expr(x)]),
94
+ )
95
+
96
+ @staticmethod
97
+ def cos(x: Any, *, ctx: Transpiler) -> Expr:
98
+ return _math_call("cos", [ctx.emit_expr(x)])
99
+
100
+ @staticmethod
101
+ def cosh(x: Any, *, ctx: Transpiler) -> Expr:
102
+ return _math_call("cosh", [ctx.emit_expr(x)])
103
+
104
+ @staticmethod
105
+ def degrees(x: Any, *, ctx: Transpiler) -> Expr:
106
+ # x * (180 / PI)
107
+ return Binary(
108
+ ctx.emit_expr(x),
109
+ "*",
110
+ Binary(Literal(180), "/", _math_prop("PI")),
111
+ )
112
+
113
+ @staticmethod
114
+ def exp(x: Any, *, ctx: Transpiler) -> Expr:
115
+ return _math_call("exp", [ctx.emit_expr(x)])
116
+
117
+ @staticmethod
118
+ def exp2(x: Any, *, ctx: Transpiler) -> Expr:
119
+ # 2 ** x
120
+ return Binary(Literal(2), "**", ctx.emit_expr(x))
121
+
122
+ @staticmethod
123
+ def expm1(x: Any, *, ctx: Transpiler) -> Expr:
124
+ return _math_call("expm1", [ctx.emit_expr(x)])
125
+
126
+ @staticmethod
127
+ def fabs(x: Any, *, ctx: Transpiler) -> Expr:
128
+ return _math_call("abs", [ctx.emit_expr(x)])
129
+
130
+ @staticmethod
131
+ def floor(x: Any, *, ctx: Transpiler) -> Expr:
132
+ return _math_call("floor", [ctx.emit_expr(x)])
133
+
134
+ @staticmethod
135
+ def fmod(x: Any, y: Any, *, ctx: Transpiler) -> Expr:
136
+ return Binary(ctx.emit_expr(x), "%", ctx.emit_expr(y))
137
+
138
+ @staticmethod
139
+ def hypot(*coords: Any, ctx: Transpiler) -> Expr:
140
+ return _math_call("hypot", [ctx.emit_expr(c) for c in coords])
141
+
142
+ @staticmethod
143
+ def isclose(
144
+ a: Any, b: Any, *, rel_tol: Any = 1e-9, abs_tol: Any = 0.0, ctx: Transpiler
145
+ ) -> Expr:
146
+ # abs(a - b) <= max(rel_tol * max(abs(a), abs(b)), abs_tol)
147
+ a_expr = ctx.emit_expr(a)
148
+ b_expr = ctx.emit_expr(b)
149
+ rel_tol_expr = ctx.emit_expr(rel_tol)
150
+ abs_tol_expr = ctx.emit_expr(abs_tol)
151
+
152
+ abs_diff = _math_call("abs", [Binary(a_expr, "-", b_expr)])
153
+ max_abs = _math_call(
154
+ "max", [_math_call("abs", [a_expr]), _math_call("abs", [b_expr])]
155
+ )
156
+ rel_bound = Binary(rel_tol_expr, "*", max_abs)
157
+ max_bound = _math_call("max", [rel_bound, abs_tol_expr])
158
+ return Binary(abs_diff, "<=", max_bound)
159
+
160
+ @staticmethod
161
+ def isfinite(x: Any, *, ctx: Transpiler) -> Expr:
162
+ return _number_call("isFinite", [ctx.emit_expr(x)])
163
+
164
+ @staticmethod
165
+ def isinf(x: Any, *, ctx: Transpiler) -> Expr:
166
+ # !Number.isFinite(x) && !Number.isNaN(x)
167
+ x_expr = ctx.emit_expr(x)
168
+ return Binary(
169
+ Unary("!", _number_call("isFinite", [x_expr])),
170
+ "&&",
171
+ Unary("!", _number_call("isNaN", [x_expr])),
172
+ )
173
+
174
+ @staticmethod
175
+ def isnan(x: Any, *, ctx: Transpiler) -> Expr:
176
+ return _number_call("isNaN", [ctx.emit_expr(x)])
177
+
178
+ @staticmethod
179
+ def isqrt(n: Any, *, ctx: Transpiler) -> Expr:
180
+ return _math_call("floor", [_math_call("sqrt", [ctx.emit_expr(n)])])
181
+
182
+ @staticmethod
183
+ def ldexp(x: Any, i: Any, *, ctx: Transpiler) -> Expr:
184
+ # x * (2 ** i)
185
+ return Binary(
186
+ ctx.emit_expr(x),
187
+ "*",
188
+ Binary(Literal(2), "**", ctx.emit_expr(i)),
189
+ )
190
+
191
+ @staticmethod
192
+ def log(value: Any, base: Any = None, *, ctx: Transpiler) -> Expr:
193
+ if base is None:
194
+ return _math_call("log", [ctx.emit_expr(value)])
195
+ return Binary(
196
+ _math_call("log", [ctx.emit_expr(value)]),
197
+ "/",
198
+ _math_call("log", [ctx.emit_expr(base)]),
199
+ )
200
+
201
+ @staticmethod
202
+ def log10(x: Any, *, ctx: Transpiler) -> Expr:
203
+ return _math_call("log10", [ctx.emit_expr(x)])
204
+
205
+ @staticmethod
206
+ def log1p(x: Any, *, ctx: Transpiler) -> Expr:
207
+ return _math_call("log1p", [ctx.emit_expr(x)])
208
+
209
+ @staticmethod
210
+ def log2(x: Any, *, ctx: Transpiler) -> Expr:
211
+ return _math_call("log2", [ctx.emit_expr(x)])
212
+
213
+ @staticmethod
214
+ def pow(x: Any, y: Any, *, ctx: Transpiler) -> Expr:
215
+ return _math_call("pow", [ctx.emit_expr(x), ctx.emit_expr(y)])
216
+
217
+ @staticmethod
218
+ def radians(x: Any, *, ctx: Transpiler) -> Expr:
219
+ # x * (PI / 180)
220
+ return Binary(
221
+ ctx.emit_expr(x),
222
+ "*",
223
+ Binary(_math_prop("PI"), "/", Literal(180)),
224
+ )
225
+
226
+ @staticmethod
227
+ def remainder(x: Any, y: Any, *, ctx: Transpiler) -> Expr:
228
+ # x - round(x/y) * y
229
+ x_expr = ctx.emit_expr(x)
230
+ y_expr = ctx.emit_expr(y)
231
+ n = _math_call("round", [Binary(x_expr, "/", y_expr)])
232
+ return Binary(x_expr, "-", Binary(n, "*", y_expr))
233
+
234
+ @staticmethod
235
+ def sin(x: Any, *, ctx: Transpiler) -> Expr:
236
+ return _math_call("sin", [ctx.emit_expr(x)])
237
+
238
+ @staticmethod
239
+ def sinh(x: Any, *, ctx: Transpiler) -> Expr:
240
+ return _math_call("sinh", [ctx.emit_expr(x)])
241
+
242
+ @staticmethod
243
+ def sqrt(x: Any, *, ctx: Transpiler) -> Expr:
244
+ return _math_call("sqrt", [ctx.emit_expr(x)])
245
+
246
+ @staticmethod
247
+ def tan(x: Any, *, ctx: Transpiler) -> Expr:
248
+ return _math_call("tan", [ctx.emit_expr(x)])
249
+
250
+ @staticmethod
251
+ def tanh(x: Any, *, ctx: Transpiler) -> Expr:
252
+ return _math_call("tanh", [ctx.emit_expr(x)])
253
+
254
+ @staticmethod
255
+ def trunc(x: Any, *, ctx: Transpiler) -> Expr:
256
+ return _math_call("trunc", [ctx.emit_expr(x)])
257
+
258
+ @staticmethod
259
+ def fma(x: Any, y: Any, z: Any, *, ctx: Transpiler) -> Expr:
260
+ # (x * y) + z
261
+ return Binary(
262
+ Binary(ctx.emit_expr(x), "*", ctx.emit_expr(y)),
263
+ "+",
264
+ ctx.emit_expr(z),
265
+ )
@@ -0,0 +1,5 @@
1
+ """Pulse module transpilation.
2
+
3
+ Submodules:
4
+ - tags: HTML tag functions -> JSX elements
5
+ """
@@ -0,0 +1,250 @@
1
+ """HTML tag function transpilation to JSX elements.
2
+
3
+ This module provides transpilation from pulse.dom.tags (like div, span, etc.)
4
+ to JSX elements. Tag functions can be called with props and children:
5
+
6
+ # Python
7
+ div("Hello", className="container")
8
+
9
+ # JavaScript
10
+ <div className="container">Hello</div>
11
+ """
12
+
13
+ from __future__ import annotations
14
+
15
+ import ast
16
+ from dataclasses import dataclass
17
+ from typing import Any, final, override
18
+
19
+ from pulse.components.for_ import emit_for
20
+ from pulse.transpiler.nodes import (
21
+ Element,
22
+ Expr,
23
+ Literal,
24
+ Node,
25
+ Prop,
26
+ Spread,
27
+ spread_dict,
28
+ )
29
+ from pulse.transpiler.py_module import PyModule
30
+ from pulse.transpiler.transpiler import Transpiler
31
+
32
+
33
+ @dataclass(slots=True, frozen=True)
34
+ class TagExpr(Expr):
35
+ """Expr that creates JSX elements when called.
36
+
37
+ Represents a tag function like `div`, `span`, etc.
38
+ When called, produces an Element with props from kwargs and children from args.
39
+ """
40
+
41
+ tag: str
42
+
43
+ @override
44
+ def emit(self, out: list[str]) -> None:
45
+ out.append(f'"{self.tag}"')
46
+
47
+ @override
48
+ def render(self):
49
+ return self.tag
50
+
51
+ @override
52
+ def transpile_call(
53
+ self,
54
+ args: list[ast.expr],
55
+ keywords: list[ast.keyword],
56
+ ctx: Transpiler,
57
+ ) -> Expr:
58
+ """Handle tag calls: positional args are children, kwargs are props.
59
+
60
+ Spread (**expr) is supported for prop spreading.
61
+ """
62
+ # Build children from positional args
63
+ children: list[Node] = []
64
+ for a in args:
65
+ children.append(ctx.emit_expr(a))
66
+
67
+ # Build props from kwargs
68
+ props: list[tuple[str, Prop] | Spread] = []
69
+ key: str | Expr | None = None
70
+ for kw in keywords:
71
+ if kw.arg is None:
72
+ # **spread syntax
73
+ props.append(spread_dict(ctx.emit_expr(kw.value)))
74
+ else:
75
+ k = kw.arg
76
+ prop_value = ctx.emit_expr(kw.value)
77
+ if k == "key":
78
+ # Accept any expression as key for transpilation
79
+ if isinstance(prop_value, Literal) and isinstance(
80
+ prop_value.value, str
81
+ ):
82
+ key = prop_value.value # Optimize string literals
83
+ else:
84
+ key = prop_value # Keep as expression
85
+ else:
86
+ props.append((k, prop_value))
87
+
88
+ return Element(
89
+ tag=self.tag,
90
+ props=props if props else None,
91
+ children=children if children else None,
92
+ key=key,
93
+ )
94
+
95
+ # -------------------------------------------------------------------------
96
+ # Python dunder methods: allow natural syntax in @javascript functions
97
+ # -------------------------------------------------------------------------
98
+
99
+ @override
100
+ def __call__(self, *args: Any, **kwargs: Any): # pyright: ignore[reportIncompatibleMethodOverride]
101
+ """Allow calling TagExpr objects in Python code.
102
+
103
+ Returns a placeholder Element for type checking. The actual transpilation
104
+ happens via transpile_call when the transpiler processes the AST.
105
+ """
106
+ return Element(tag=self.tag, props=None, children=None, key=None)
107
+
108
+
109
+ @final
110
+ class PulseTags(PyModule):
111
+ """Provides transpilation for pulse.dom.tags to JSX elements."""
112
+
113
+ # Regular tags
114
+ a = TagExpr("a")
115
+ abbr = TagExpr("abbr")
116
+ address = TagExpr("address")
117
+ article = TagExpr("article")
118
+ aside = TagExpr("aside")
119
+ audio = TagExpr("audio")
120
+ b = TagExpr("b")
121
+ bdi = TagExpr("bdi")
122
+ bdo = TagExpr("bdo")
123
+ blockquote = TagExpr("blockquote")
124
+ body = TagExpr("body")
125
+ button = TagExpr("button")
126
+ canvas = TagExpr("canvas")
127
+ caption = TagExpr("caption")
128
+ cite = TagExpr("cite")
129
+ code = TagExpr("code")
130
+ colgroup = TagExpr("colgroup")
131
+ data = TagExpr("data")
132
+ datalist = TagExpr("datalist")
133
+ dd = TagExpr("dd")
134
+ del_ = TagExpr("del")
135
+ details = TagExpr("details")
136
+ dfn = TagExpr("dfn")
137
+ dialog = TagExpr("dialog")
138
+ div = TagExpr("div")
139
+ dl = TagExpr("dl")
140
+ dt = TagExpr("dt")
141
+ em = TagExpr("em")
142
+ fieldset = TagExpr("fieldset")
143
+ figcaption = TagExpr("figcaption")
144
+ figure = TagExpr("figure")
145
+ footer = TagExpr("footer")
146
+ form = TagExpr("form")
147
+ h1 = TagExpr("h1")
148
+ h2 = TagExpr("h2")
149
+ h3 = TagExpr("h3")
150
+ h4 = TagExpr("h4")
151
+ h5 = TagExpr("h5")
152
+ h6 = TagExpr("h6")
153
+ head = TagExpr("head")
154
+ header = TagExpr("header")
155
+ hgroup = TagExpr("hgroup")
156
+ html = TagExpr("html")
157
+ i = TagExpr("i")
158
+ iframe = TagExpr("iframe")
159
+ ins = TagExpr("ins")
160
+ kbd = TagExpr("kbd")
161
+ label = TagExpr("label")
162
+ legend = TagExpr("legend")
163
+ li = TagExpr("li")
164
+ main = TagExpr("main")
165
+ map_ = TagExpr("map")
166
+ mark = TagExpr("mark")
167
+ menu = TagExpr("menu")
168
+ meter = TagExpr("meter")
169
+ nav = TagExpr("nav")
170
+ noscript = TagExpr("noscript")
171
+ object_ = TagExpr("object")
172
+ ol = TagExpr("ol")
173
+ optgroup = TagExpr("optgroup")
174
+ option = TagExpr("option")
175
+ output = TagExpr("output")
176
+ p = TagExpr("p")
177
+ picture = TagExpr("picture")
178
+ pre = TagExpr("pre")
179
+ progress = TagExpr("progress")
180
+ q = TagExpr("q")
181
+ rp = TagExpr("rp")
182
+ rt = TagExpr("rt")
183
+ ruby = TagExpr("ruby")
184
+ s = TagExpr("s")
185
+ samp = TagExpr("samp")
186
+ script = TagExpr("script")
187
+ section = TagExpr("section")
188
+ select = TagExpr("select")
189
+ small = TagExpr("small")
190
+ span = TagExpr("span")
191
+ strong = TagExpr("strong")
192
+ style = TagExpr("style")
193
+ sub = TagExpr("sub")
194
+ summary = TagExpr("summary")
195
+ sup = TagExpr("sup")
196
+ table = TagExpr("table")
197
+ tbody = TagExpr("tbody")
198
+ td = TagExpr("td")
199
+ template = TagExpr("template")
200
+ textarea = TagExpr("textarea")
201
+ tfoot = TagExpr("tfoot")
202
+ th = TagExpr("th")
203
+ thead = TagExpr("thead")
204
+ time = TagExpr("time")
205
+ title = TagExpr("title")
206
+ tr = TagExpr("tr")
207
+ u = TagExpr("u")
208
+ ul = TagExpr("ul")
209
+ var = TagExpr("var")
210
+ video = TagExpr("video")
211
+
212
+ # Self-closing tags
213
+ area = TagExpr("area")
214
+ base = TagExpr("base")
215
+ br = TagExpr("br")
216
+ col = TagExpr("col")
217
+ embed = TagExpr("embed")
218
+ hr = TagExpr("hr")
219
+ img = TagExpr("img")
220
+ input = TagExpr("input")
221
+ link = TagExpr("link")
222
+ meta = TagExpr("meta")
223
+ param = TagExpr("param")
224
+ source = TagExpr("source")
225
+ track = TagExpr("track")
226
+ wbr = TagExpr("wbr")
227
+
228
+ # SVG tags
229
+ svg = TagExpr("svg")
230
+ circle = TagExpr("circle")
231
+ ellipse = TagExpr("ellipse")
232
+ g = TagExpr("g")
233
+ line = TagExpr("line")
234
+ path = TagExpr("path")
235
+ polygon = TagExpr("polygon")
236
+ polyline = TagExpr("polyline")
237
+ rect = TagExpr("rect")
238
+ text = TagExpr("text")
239
+ tspan = TagExpr("tspan")
240
+ defs = TagExpr("defs")
241
+ clipPath = TagExpr("clipPath")
242
+ mask = TagExpr("mask")
243
+ pattern = TagExpr("pattern")
244
+ use = TagExpr("use")
245
+
246
+ # React fragment
247
+ fragment = TagExpr("")
248
+
249
+ # For component - maps to array.map()
250
+ For = emit_for
@@ -0,0 +1,63 @@
1
+ """Python typing module transpilation - mostly no-ops for type hints."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import ast
6
+ from dataclasses import dataclass
7
+ from typing import final, override
8
+
9
+ from pulse.transpiler.nodes import Expr
10
+ from pulse.transpiler.py_module import PyModule
11
+ from pulse.transpiler.transpiler import Transpiler
12
+
13
+
14
+ @dataclass(slots=True)
15
+ class TypeHint(Expr):
16
+ """A type hint that should never be emitted directly.
17
+
18
+ Used for typing constructs like Any that can be passed to cast() but
19
+ shouldn't appear in generated code.
20
+ """
21
+
22
+ name: str
23
+
24
+ @override
25
+ def emit(self, out: list[str]) -> None:
26
+ raise TypeError(
27
+ f"Type hint '{self.name}' cannot be emitted as JavaScript. "
28
+ + "It should only be used with typing.cast() or similar."
29
+ )
30
+
31
+ @override
32
+ def render(self):
33
+ raise TypeError(
34
+ f"Type hint '{self.name}' cannot be rendered. "
35
+ + "It should only be used with typing.cast() or similar."
36
+ )
37
+
38
+ @override
39
+ def transpile_subscript(self, key: ast.expr, ctx: Transpiler) -> Expr:
40
+ # List[int], Optional[str], etc. -> still a type hint
41
+ return TypeHint(f"{self.name}[...]")
42
+
43
+
44
+ @final
45
+ class PyTyping(PyModule):
46
+ """Provides transpilation for Python typing functions."""
47
+
48
+ # Type constructs used with cast() - error if emitted directly
49
+ Any = TypeHint("Any")
50
+ Optional = TypeHint("Optional")
51
+ Union = TypeHint("Union")
52
+ List = TypeHint("List")
53
+ Dict = TypeHint("Dict")
54
+ Set = TypeHint("Set")
55
+ Tuple = TypeHint("Tuple")
56
+ FrozenSet = TypeHint("FrozenSet")
57
+ Type = TypeHint("Type")
58
+ Callable = TypeHint("Callable")
59
+
60
+ @staticmethod
61
+ def cast(_type: ast.expr, val: ast.expr, *, ctx: Transpiler) -> Expr:
62
+ """cast(T, val) -> val (type cast is a no-op at runtime)."""
63
+ return ctx.emit_expr(val)