pulse-framework 0.1.51__py3-none-any.whl → 0.1.53__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.
- pulse/__init__.py +542 -562
- pulse/_examples.py +29 -0
- pulse/app.py +0 -14
- pulse/cli/cmd.py +96 -80
- pulse/cli/dependencies.py +10 -41
- pulse/cli/folder_lock.py +3 -3
- pulse/cli/helpers.py +40 -67
- pulse/cli/logging.py +102 -0
- pulse/cli/packages.py +16 -0
- pulse/cli/processes.py +40 -23
- pulse/codegen/codegen.py +70 -35
- pulse/codegen/js.py +2 -4
- pulse/codegen/templates/route.py +94 -146
- pulse/component.py +115 -0
- pulse/components/for_.py +1 -1
- pulse/components/if_.py +1 -1
- pulse/components/react_router.py +16 -22
- pulse/{html → dom}/events.py +1 -1
- pulse/{html → dom}/props.py +6 -6
- pulse/{html → dom}/tags.py +11 -11
- pulse/dom/tags.pyi +480 -0
- pulse/form.py +7 -6
- pulse/hooks/init.py +1 -13
- pulse/js/__init__.py +37 -41
- pulse/js/__init__.pyi +22 -2
- pulse/js/_types.py +5 -3
- pulse/js/array.py +121 -38
- pulse/js/console.py +9 -9
- pulse/js/date.py +22 -19
- pulse/js/document.py +8 -4
- pulse/js/error.py +12 -14
- pulse/js/json.py +4 -3
- pulse/js/map.py +17 -7
- pulse/js/math.py +2 -2
- pulse/js/navigator.py +4 -4
- pulse/js/number.py +8 -8
- pulse/js/object.py +9 -13
- pulse/js/promise.py +25 -9
- pulse/js/regexp.py +6 -6
- pulse/js/set.py +20 -8
- pulse/js/string.py +7 -7
- pulse/js/weakmap.py +6 -6
- pulse/js/weakset.py +6 -6
- pulse/js/window.py +17 -14
- pulse/messages.py +1 -4
- pulse/react_component.py +3 -1001
- pulse/render_session.py +74 -66
- pulse/renderer.py +311 -238
- pulse/routing.py +1 -10
- pulse/transpiler/__init__.py +84 -114
- pulse/transpiler/builtins.py +661 -343
- pulse/transpiler/errors.py +78 -2
- pulse/transpiler/function.py +463 -133
- pulse/transpiler/id.py +18 -0
- pulse/transpiler/imports.py +230 -325
- pulse/transpiler/js_module.py +218 -209
- pulse/transpiler/modules/__init__.py +16 -13
- pulse/transpiler/modules/asyncio.py +45 -26
- pulse/transpiler/modules/json.py +12 -8
- pulse/transpiler/modules/math.py +161 -216
- pulse/transpiler/modules/pulse/__init__.py +5 -0
- pulse/transpiler/modules/pulse/tags.py +231 -0
- pulse/transpiler/modules/typing.py +33 -28
- pulse/transpiler/nodes.py +1607 -923
- pulse/transpiler/py_module.py +118 -95
- pulse/transpiler/react_component.py +51 -0
- pulse/transpiler/transpiler.py +593 -437
- pulse/transpiler/vdom.py +255 -0
- {pulse_framework-0.1.51.dist-info → pulse_framework-0.1.53.dist-info}/METADATA +1 -1
- pulse_framework-0.1.53.dist-info/RECORD +120 -0
- pulse/html/tags.pyi +0 -470
- pulse/transpiler/constants.py +0 -110
- pulse/transpiler/context.py +0 -26
- pulse/transpiler/ids.py +0 -16
- pulse/transpiler/modules/re.py +0 -466
- pulse/transpiler/modules/tags.py +0 -268
- pulse/transpiler/utils.py +0 -4
- pulse/vdom.py +0 -599
- pulse_framework-0.1.51.dist-info/RECORD +0 -119
- /pulse/{html → dom}/__init__.py +0 -0
- /pulse/{html → dom}/elements.py +0 -0
- /pulse/{html → dom}/svg.py +0 -0
- {pulse_framework-0.1.51.dist-info → pulse_framework-0.1.53.dist-info}/WHEEL +0 -0
- {pulse_framework-0.1.51.dist-info → pulse_framework-0.1.53.dist-info}/entry_points.txt +0 -0
|
@@ -0,0 +1,231 @@
|
|
|
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.transpiler.nodes import Element, Expr, Literal, Node, Prop
|
|
20
|
+
from pulse.transpiler.py_module import PyModule
|
|
21
|
+
from pulse.transpiler.transpiler import Transpiler
|
|
22
|
+
from pulse.transpiler.vdom import VDOMNode
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
@dataclass(slots=True, frozen=True)
|
|
26
|
+
class TagExpr(Expr):
|
|
27
|
+
"""Expr that creates JSX elements when called.
|
|
28
|
+
|
|
29
|
+
Represents a tag function like `div`, `span`, etc.
|
|
30
|
+
When called, produces an Element with props from kwargs and children from args.
|
|
31
|
+
"""
|
|
32
|
+
|
|
33
|
+
tag: str
|
|
34
|
+
|
|
35
|
+
@override
|
|
36
|
+
def emit(self, out: list[str]) -> None:
|
|
37
|
+
out.append(f'"{self.tag}"')
|
|
38
|
+
|
|
39
|
+
@override
|
|
40
|
+
def render(self) -> VDOMNode:
|
|
41
|
+
return self.tag
|
|
42
|
+
|
|
43
|
+
@override
|
|
44
|
+
def transpile_call(
|
|
45
|
+
self,
|
|
46
|
+
args: list[ast.expr],
|
|
47
|
+
kwargs: dict[str, ast.expr],
|
|
48
|
+
ctx: Transpiler,
|
|
49
|
+
) -> Expr:
|
|
50
|
+
"""Handle tag calls: positional args are children, kwargs are props."""
|
|
51
|
+
# Build children from positional args
|
|
52
|
+
children: list[Node] = []
|
|
53
|
+
for a in args:
|
|
54
|
+
children.append(ctx.emit_expr(a))
|
|
55
|
+
|
|
56
|
+
# Build props from kwargs
|
|
57
|
+
props: dict[str, Prop] = {}
|
|
58
|
+
key: str | Expr | None = None
|
|
59
|
+
for k, v in kwargs.items():
|
|
60
|
+
prop_value = ctx.emit_expr(v)
|
|
61
|
+
if k == "key":
|
|
62
|
+
# Accept any expression as key for transpilation
|
|
63
|
+
if isinstance(prop_value, Literal) and isinstance(
|
|
64
|
+
prop_value.value, str
|
|
65
|
+
):
|
|
66
|
+
key = prop_value.value # Optimize string literals
|
|
67
|
+
else:
|
|
68
|
+
key = prop_value # Keep as expression
|
|
69
|
+
else:
|
|
70
|
+
props[k] = prop_value
|
|
71
|
+
|
|
72
|
+
return Element(
|
|
73
|
+
tag=self.tag,
|
|
74
|
+
props=props if props else None,
|
|
75
|
+
children=children if children else None,
|
|
76
|
+
key=key,
|
|
77
|
+
)
|
|
78
|
+
|
|
79
|
+
# -------------------------------------------------------------------------
|
|
80
|
+
# Python dunder methods: allow natural syntax in @javascript functions
|
|
81
|
+
# -------------------------------------------------------------------------
|
|
82
|
+
|
|
83
|
+
@override
|
|
84
|
+
def __call__(self, *args: Any, **kwargs: Any): # pyright: ignore[reportIncompatibleMethodOverride]
|
|
85
|
+
"""Allow calling TagExpr objects in Python code.
|
|
86
|
+
|
|
87
|
+
Returns a placeholder Element for type checking. The actual transpilation
|
|
88
|
+
happens via transpile_call when the transpiler processes the AST.
|
|
89
|
+
"""
|
|
90
|
+
return Element(tag=self.tag, props=None, children=None, key=None)
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
@final
|
|
94
|
+
class PulseTags(PyModule):
|
|
95
|
+
"""Provides transpilation for pulse.dom.tags to JSX elements."""
|
|
96
|
+
|
|
97
|
+
# Regular tags
|
|
98
|
+
a = TagExpr("a")
|
|
99
|
+
abbr = TagExpr("abbr")
|
|
100
|
+
address = TagExpr("address")
|
|
101
|
+
article = TagExpr("article")
|
|
102
|
+
aside = TagExpr("aside")
|
|
103
|
+
audio = TagExpr("audio")
|
|
104
|
+
b = TagExpr("b")
|
|
105
|
+
bdi = TagExpr("bdi")
|
|
106
|
+
bdo = TagExpr("bdo")
|
|
107
|
+
blockquote = TagExpr("blockquote")
|
|
108
|
+
body = TagExpr("body")
|
|
109
|
+
button = TagExpr("button")
|
|
110
|
+
canvas = TagExpr("canvas")
|
|
111
|
+
caption = TagExpr("caption")
|
|
112
|
+
cite = TagExpr("cite")
|
|
113
|
+
code = TagExpr("code")
|
|
114
|
+
colgroup = TagExpr("colgroup")
|
|
115
|
+
data = TagExpr("data")
|
|
116
|
+
datalist = TagExpr("datalist")
|
|
117
|
+
dd = TagExpr("dd")
|
|
118
|
+
del_ = TagExpr("del")
|
|
119
|
+
details = TagExpr("details")
|
|
120
|
+
dfn = TagExpr("dfn")
|
|
121
|
+
dialog = TagExpr("dialog")
|
|
122
|
+
div = TagExpr("div")
|
|
123
|
+
dl = TagExpr("dl")
|
|
124
|
+
dt = TagExpr("dt")
|
|
125
|
+
em = TagExpr("em")
|
|
126
|
+
fieldset = TagExpr("fieldset")
|
|
127
|
+
figcaption = TagExpr("figcaption")
|
|
128
|
+
figure = TagExpr("figure")
|
|
129
|
+
footer = TagExpr("footer")
|
|
130
|
+
form = TagExpr("form")
|
|
131
|
+
h1 = TagExpr("h1")
|
|
132
|
+
h2 = TagExpr("h2")
|
|
133
|
+
h3 = TagExpr("h3")
|
|
134
|
+
h4 = TagExpr("h4")
|
|
135
|
+
h5 = TagExpr("h5")
|
|
136
|
+
h6 = TagExpr("h6")
|
|
137
|
+
head = TagExpr("head")
|
|
138
|
+
header = TagExpr("header")
|
|
139
|
+
hgroup = TagExpr("hgroup")
|
|
140
|
+
html = TagExpr("html")
|
|
141
|
+
i = TagExpr("i")
|
|
142
|
+
iframe = TagExpr("iframe")
|
|
143
|
+
ins = TagExpr("ins")
|
|
144
|
+
kbd = TagExpr("kbd")
|
|
145
|
+
label = TagExpr("label")
|
|
146
|
+
legend = TagExpr("legend")
|
|
147
|
+
li = TagExpr("li")
|
|
148
|
+
main = TagExpr("main")
|
|
149
|
+
map_ = TagExpr("map")
|
|
150
|
+
mark = TagExpr("mark")
|
|
151
|
+
menu = TagExpr("menu")
|
|
152
|
+
meter = TagExpr("meter")
|
|
153
|
+
nav = TagExpr("nav")
|
|
154
|
+
noscript = TagExpr("noscript")
|
|
155
|
+
object_ = TagExpr("object")
|
|
156
|
+
ol = TagExpr("ol")
|
|
157
|
+
optgroup = TagExpr("optgroup")
|
|
158
|
+
option = TagExpr("option")
|
|
159
|
+
output = TagExpr("output")
|
|
160
|
+
p = TagExpr("p")
|
|
161
|
+
picture = TagExpr("picture")
|
|
162
|
+
pre = TagExpr("pre")
|
|
163
|
+
progress = TagExpr("progress")
|
|
164
|
+
q = TagExpr("q")
|
|
165
|
+
rp = TagExpr("rp")
|
|
166
|
+
rt = TagExpr("rt")
|
|
167
|
+
ruby = TagExpr("ruby")
|
|
168
|
+
s = TagExpr("s")
|
|
169
|
+
samp = TagExpr("samp")
|
|
170
|
+
script = TagExpr("script")
|
|
171
|
+
section = TagExpr("section")
|
|
172
|
+
select = TagExpr("select")
|
|
173
|
+
small = TagExpr("small")
|
|
174
|
+
span = TagExpr("span")
|
|
175
|
+
strong = TagExpr("strong")
|
|
176
|
+
style = TagExpr("style")
|
|
177
|
+
sub = TagExpr("sub")
|
|
178
|
+
summary = TagExpr("summary")
|
|
179
|
+
sup = TagExpr("sup")
|
|
180
|
+
table = TagExpr("table")
|
|
181
|
+
tbody = TagExpr("tbody")
|
|
182
|
+
td = TagExpr("td")
|
|
183
|
+
template = TagExpr("template")
|
|
184
|
+
textarea = TagExpr("textarea")
|
|
185
|
+
tfoot = TagExpr("tfoot")
|
|
186
|
+
th = TagExpr("th")
|
|
187
|
+
thead = TagExpr("thead")
|
|
188
|
+
time = TagExpr("time")
|
|
189
|
+
title = TagExpr("title")
|
|
190
|
+
tr = TagExpr("tr")
|
|
191
|
+
u = TagExpr("u")
|
|
192
|
+
ul = TagExpr("ul")
|
|
193
|
+
var = TagExpr("var")
|
|
194
|
+
video = TagExpr("video")
|
|
195
|
+
|
|
196
|
+
# Self-closing tags
|
|
197
|
+
area = TagExpr("area")
|
|
198
|
+
base = TagExpr("base")
|
|
199
|
+
br = TagExpr("br")
|
|
200
|
+
col = TagExpr("col")
|
|
201
|
+
embed = TagExpr("embed")
|
|
202
|
+
hr = TagExpr("hr")
|
|
203
|
+
img = TagExpr("img")
|
|
204
|
+
input = TagExpr("input")
|
|
205
|
+
link = TagExpr("link")
|
|
206
|
+
meta = TagExpr("meta")
|
|
207
|
+
param = TagExpr("param")
|
|
208
|
+
source = TagExpr("source")
|
|
209
|
+
track = TagExpr("track")
|
|
210
|
+
wbr = TagExpr("wbr")
|
|
211
|
+
|
|
212
|
+
# SVG tags
|
|
213
|
+
svg = TagExpr("svg")
|
|
214
|
+
circle = TagExpr("circle")
|
|
215
|
+
ellipse = TagExpr("ellipse")
|
|
216
|
+
g = TagExpr("g")
|
|
217
|
+
line = TagExpr("line")
|
|
218
|
+
path = TagExpr("path")
|
|
219
|
+
polygon = TagExpr("polygon")
|
|
220
|
+
polyline = TagExpr("polyline")
|
|
221
|
+
rect = TagExpr("rect")
|
|
222
|
+
text = TagExpr("text")
|
|
223
|
+
tspan = TagExpr("tspan")
|
|
224
|
+
defs = TagExpr("defs")
|
|
225
|
+
clipPath = TagExpr("clipPath")
|
|
226
|
+
mask = TagExpr("mask")
|
|
227
|
+
pattern = TagExpr("pattern")
|
|
228
|
+
use = TagExpr("use")
|
|
229
|
+
|
|
230
|
+
# React fragment
|
|
231
|
+
fragment = TagExpr("")
|
|
@@ -1,14 +1,19 @@
|
|
|
1
1
|
"""Python typing module transpilation - mostly no-ops for type hints."""
|
|
2
2
|
|
|
3
|
-
from
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import ast
|
|
6
|
+
from dataclasses import dataclass
|
|
4
7
|
from typing import final, override
|
|
5
8
|
|
|
6
|
-
from pulse.transpiler.
|
|
7
|
-
from pulse.transpiler.nodes import JSExpr
|
|
9
|
+
from pulse.transpiler.nodes import Expr
|
|
8
10
|
from pulse.transpiler.py_module import PyModule
|
|
11
|
+
from pulse.transpiler.transpiler import Transpiler
|
|
12
|
+
from pulse.transpiler.vdom import VDOMNode
|
|
9
13
|
|
|
10
14
|
|
|
11
|
-
|
|
15
|
+
@dataclass(slots=True)
|
|
16
|
+
class TypeHint(Expr):
|
|
12
17
|
"""A type hint that should never be emitted directly.
|
|
13
18
|
|
|
14
19
|
Used for typing constructs like Any that can be passed to cast() but
|
|
@@ -17,21 +22,24 @@ class JSTypeHint(JSExpr):
|
|
|
17
22
|
|
|
18
23
|
name: str
|
|
19
24
|
|
|
20
|
-
def __init__(self, name: str) -> None:
|
|
21
|
-
self.name = name
|
|
22
|
-
|
|
23
25
|
@override
|
|
24
|
-
def emit(self) ->
|
|
25
|
-
raise
|
|
26
|
+
def emit(self, out: list[str]) -> None:
|
|
27
|
+
raise TypeError(
|
|
26
28
|
f"Type hint '{self.name}' cannot be emitted as JavaScript. "
|
|
27
29
|
+ "It should only be used with typing.cast() or similar."
|
|
28
30
|
)
|
|
29
31
|
|
|
30
32
|
@override
|
|
31
|
-
def
|
|
33
|
+
def render(self) -> VDOMNode:
|
|
34
|
+
raise TypeError(
|
|
35
|
+
f"Type hint '{self.name}' cannot be rendered. "
|
|
36
|
+
+ "It should only be used with typing.cast() or similar."
|
|
37
|
+
)
|
|
38
|
+
|
|
39
|
+
@override
|
|
40
|
+
def transpile_subscript(self, key: ast.expr, ctx: Transpiler) -> Expr:
|
|
32
41
|
# List[int], Optional[str], etc. -> still a type hint
|
|
33
|
-
|
|
34
|
-
return JSTypeHint(f"{self.name}[{args}]")
|
|
42
|
+
return TypeHint(f"{self.name}[...]")
|
|
35
43
|
|
|
36
44
|
|
|
37
45
|
@final
|
|
@@ -39,21 +47,18 @@ class PyTyping(PyModule):
|
|
|
39
47
|
"""Provides transpilation for Python typing functions."""
|
|
40
48
|
|
|
41
49
|
# Type constructs used with cast() - error if emitted directly
|
|
42
|
-
Any =
|
|
43
|
-
Optional =
|
|
44
|
-
Union =
|
|
45
|
-
List =
|
|
46
|
-
Dict =
|
|
47
|
-
Set =
|
|
48
|
-
Tuple =
|
|
49
|
-
FrozenSet =
|
|
50
|
-
Type =
|
|
51
|
-
Callable =
|
|
50
|
+
Any = TypeHint("Any")
|
|
51
|
+
Optional = TypeHint("Optional")
|
|
52
|
+
Union = TypeHint("Union")
|
|
53
|
+
List = TypeHint("List")
|
|
54
|
+
Dict = TypeHint("Dict")
|
|
55
|
+
Set = TypeHint("Set")
|
|
56
|
+
Tuple = TypeHint("Tuple")
|
|
57
|
+
FrozenSet = TypeHint("FrozenSet")
|
|
58
|
+
Type = TypeHint("Type")
|
|
59
|
+
Callable = TypeHint("Callable")
|
|
52
60
|
|
|
53
61
|
@staticmethod
|
|
54
|
-
def cast(_type:
|
|
55
|
-
"""cast(T, val) -> val (type cast is a no-op at runtime).
|
|
56
|
-
|
|
57
|
-
The type argument is completely ignored - we don't try to convert it.
|
|
58
|
-
"""
|
|
59
|
-
return JSExpr.of(val)
|
|
62
|
+
def cast(_type: ast.expr, val: ast.expr, *, ctx: Transpiler) -> Expr:
|
|
63
|
+
"""cast(T, val) -> val (type cast is a no-op at runtime)."""
|
|
64
|
+
return ctx.emit_expr(val)
|