pulse-framework 0.1.46__py3-none-any.whl → 0.1.48__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 (73) hide show
  1. pulse/__init__.py +9 -23
  2. pulse/app.py +6 -25
  3. pulse/cli/processes.py +1 -0
  4. pulse/codegen/codegen.py +43 -88
  5. pulse/codegen/js.py +35 -5
  6. pulse/codegen/templates/route.py +341 -254
  7. pulse/form.py +1 -1
  8. pulse/helpers.py +51 -27
  9. pulse/hooks/core.py +2 -2
  10. pulse/hooks/effects.py +1 -1
  11. pulse/hooks/init.py +2 -1
  12. pulse/hooks/setup.py +1 -1
  13. pulse/hooks/stable.py +2 -2
  14. pulse/hooks/states.py +2 -2
  15. pulse/html/props.py +3 -2
  16. pulse/html/tags.py +135 -0
  17. pulse/html/tags.pyi +4 -0
  18. pulse/js/__init__.py +110 -0
  19. pulse/js/__init__.pyi +95 -0
  20. pulse/js/_types.py +297 -0
  21. pulse/js/array.py +253 -0
  22. pulse/js/console.py +47 -0
  23. pulse/js/date.py +113 -0
  24. pulse/js/document.py +138 -0
  25. pulse/js/error.py +139 -0
  26. pulse/js/json.py +62 -0
  27. pulse/js/map.py +84 -0
  28. pulse/js/math.py +66 -0
  29. pulse/js/navigator.py +76 -0
  30. pulse/js/number.py +54 -0
  31. pulse/js/object.py +173 -0
  32. pulse/js/promise.py +150 -0
  33. pulse/js/regexp.py +54 -0
  34. pulse/js/set.py +109 -0
  35. pulse/js/string.py +35 -0
  36. pulse/js/weakmap.py +50 -0
  37. pulse/js/weakset.py +45 -0
  38. pulse/js/window.py +199 -0
  39. pulse/messages.py +22 -3
  40. pulse/proxy.py +21 -8
  41. pulse/react_component.py +167 -14
  42. pulse/reactive_extensions.py +5 -5
  43. pulse/render_session.py +144 -34
  44. pulse/renderer.py +80 -115
  45. pulse/routing.py +1 -18
  46. pulse/transpiler/__init__.py +131 -0
  47. pulse/transpiler/builtins.py +731 -0
  48. pulse/transpiler/constants.py +110 -0
  49. pulse/transpiler/context.py +26 -0
  50. pulse/transpiler/errors.py +2 -0
  51. pulse/transpiler/function.py +250 -0
  52. pulse/transpiler/ids.py +16 -0
  53. pulse/transpiler/imports.py +409 -0
  54. pulse/transpiler/js_module.py +274 -0
  55. pulse/transpiler/modules/__init__.py +30 -0
  56. pulse/transpiler/modules/asyncio.py +38 -0
  57. pulse/transpiler/modules/json.py +20 -0
  58. pulse/transpiler/modules/math.py +320 -0
  59. pulse/transpiler/modules/re.py +466 -0
  60. pulse/transpiler/modules/tags.py +268 -0
  61. pulse/transpiler/modules/typing.py +59 -0
  62. pulse/transpiler/nodes.py +1216 -0
  63. pulse/transpiler/py_module.py +119 -0
  64. pulse/transpiler/transpiler.py +938 -0
  65. pulse/transpiler/utils.py +4 -0
  66. pulse/vdom.py +112 -6
  67. {pulse_framework-0.1.46.dist-info → pulse_framework-0.1.48.dist-info}/METADATA +1 -1
  68. pulse_framework-0.1.48.dist-info/RECORD +119 -0
  69. pulse/codegen/imports.py +0 -204
  70. pulse/css.py +0 -155
  71. pulse_framework-0.1.46.dist-info/RECORD +0 -80
  72. {pulse_framework-0.1.46.dist-info → pulse_framework-0.1.48.dist-info}/WHEEL +0 -0
  73. {pulse_framework-0.1.46.dist-info → pulse_framework-0.1.48.dist-info}/entry_points.txt +0 -0
@@ -0,0 +1,268 @@
1
+ """HTML tag function transpilation to JSX elements.
2
+
3
+ This module provides transpilation from pulse.html.tags (like div, span, etc.)
4
+ to JSX elements. Tag functions can be called with props and subscripted with children:
5
+
6
+ # Python
7
+ div(className="container")[span("Hello"), p("World")]
8
+
9
+ # JavaScript
10
+ <div className="container"><span>Hello</span><p>World</p></div>
11
+ """
12
+
13
+ # pyright: reportUnannotatedClassAttribute=false
14
+
15
+ from __future__ import annotations
16
+
17
+ from collections.abc import Sequence
18
+ from dataclasses import dataclass, field
19
+ from typing import Any, ClassVar, override
20
+
21
+ from pulse.transpiler.errors import JSCompilationError
22
+ from pulse.transpiler.nodes import (
23
+ JSArray,
24
+ JSExpr,
25
+ JSSpread,
26
+ JSString,
27
+ JSXElement,
28
+ JSXFragment,
29
+ JSXProp,
30
+ JSXSpreadProp,
31
+ )
32
+ from pulse.transpiler.py_module import PyModule
33
+
34
+
35
+ def _flatten_children(items: list[Any], out: list[JSExpr | JSXElement | str]) -> None:
36
+ """Flatten arrays and handle spreads in children list."""
37
+ for it in items:
38
+ # Convert raw values first
39
+ it = JSExpr.of(it) if not isinstance(it, JSExpr) else it
40
+ if isinstance(it, JSArray):
41
+ _flatten_children(list(it.elements), out)
42
+ elif isinstance(it, JSSpread):
43
+ # Spread children: pass the inner expression; React handles arrays
44
+ out.append(it.expr)
45
+ elif isinstance(it, JSString):
46
+ out.append(it.value)
47
+ else:
48
+ out.append(it)
49
+
50
+
51
+ def _build_jsx_props(kwargs: dict[str, Any]) -> list[JSXProp | JSXSpreadProp]:
52
+ """Build JSX props list from kwargs dict.
53
+
54
+ Kwargs maps:
55
+ - "propName" -> value for named props
56
+ - "__spread_N" -> JSSpread(expr) for spread props
57
+
58
+ Dict order is preserved, so iteration order matches source order.
59
+ """
60
+ props: list[JSXProp | JSXSpreadProp] = []
61
+
62
+ for key, value in kwargs.items():
63
+ if isinstance(value, JSSpread):
64
+ # Spread prop: {...expr}
65
+ props.append(JSXSpreadProp(value.expr))
66
+ else:
67
+ # Named prop - convert to JSExpr
68
+ props.append(JSXProp(key, JSExpr.of(value)))
69
+
70
+ return props
71
+
72
+
73
+ def _create_tag_function(tag_name: str):
74
+ """Create a tag function that returns JSXCallExpr when called."""
75
+
76
+ @staticmethod
77
+ def tag_func(*args: Any, **kwargs: Any) -> JSExpr:
78
+ """Tag function that creates JSXCallExpr with props and children."""
79
+ props_list = _build_jsx_props(kwargs)
80
+ children_list: list[JSExpr | JSXElement | str] = []
81
+ _flatten_children(list(args), children_list)
82
+ return JSXCallExpr(tag_name, tuple(props_list), tuple(children_list))
83
+
84
+ return tag_func
85
+
86
+
87
+ @dataclass
88
+ class JSXCallExpr(JSExpr):
89
+ """Expression representing a called tag with props (but possibly more children).
90
+
91
+ When subscripted, adds children to produce the final JSXElement.
92
+ Calling again is an error (can't call an element).
93
+ """
94
+
95
+ tag_name: str
96
+ props: Sequence[JSXProp | JSXSpreadProp] = field(default_factory=tuple)
97
+ children: Sequence[str | JSExpr | JSXElement] = field(default_factory=tuple)
98
+ is_jsx: ClassVar[bool] = True
99
+
100
+ @override
101
+ def emit(self) -> str:
102
+ # Emit as final JSXElement
103
+ return JSXElement(self.tag_name, self.props, self.children).emit()
104
+
105
+ @override
106
+ def emit_subscript(self, indices: list[Any]) -> JSExpr:
107
+ """Handle tag(props...)[children] -> JSXElement."""
108
+ extra_children: list[JSExpr | JSXElement | str] = []
109
+ _flatten_children(indices, extra_children)
110
+ all_children = list(self.children) + extra_children
111
+ return JSXElement(self.tag_name, self.props, all_children)
112
+
113
+ @override
114
+ def emit_call(self, args: list[Any], kwargs: dict[str, Any]) -> JSExpr:
115
+ """Calling an already-called tag is an error."""
116
+ from pulse.transpiler.errors import JSCompilationError
117
+
118
+ raise JSCompilationError(
119
+ f"Cannot call JSX element <{self.tag_name}> - already called. "
120
+ + "Use subscript for children: tag(props...)[children]"
121
+ )
122
+
123
+
124
+ class PyTags(PyModule):
125
+ """Provides transpilation for pulse.html.tags to JSX elements."""
126
+
127
+ # Regular tags - each is a function that returns JSXCallExpr when called
128
+ a = _create_tag_function("a")
129
+ abbr = _create_tag_function("abbr")
130
+ address = _create_tag_function("address")
131
+ article = _create_tag_function("article")
132
+ aside = _create_tag_function("aside")
133
+ audio = _create_tag_function("audio")
134
+ b = _create_tag_function("b")
135
+ bdi = _create_tag_function("bdi")
136
+ bdo = _create_tag_function("bdo")
137
+ blockquote = _create_tag_function("blockquote")
138
+ body = _create_tag_function("body")
139
+ button = _create_tag_function("button")
140
+ canvas = _create_tag_function("canvas")
141
+ caption = _create_tag_function("caption")
142
+ cite = _create_tag_function("cite")
143
+ code = _create_tag_function("code")
144
+ colgroup = _create_tag_function("colgroup")
145
+ data = _create_tag_function("data")
146
+ datalist = _create_tag_function("datalist")
147
+ dd = _create_tag_function("dd")
148
+ del_ = _create_tag_function("del")
149
+ details = _create_tag_function("details")
150
+ dfn = _create_tag_function("dfn")
151
+ dialog = _create_tag_function("dialog")
152
+ div = _create_tag_function("div")
153
+ dl = _create_tag_function("dl")
154
+ dt = _create_tag_function("dt")
155
+ em = _create_tag_function("em")
156
+ fieldset = _create_tag_function("fieldset")
157
+ figcaption = _create_tag_function("figcaption")
158
+ figure = _create_tag_function("figure")
159
+ footer = _create_tag_function("footer")
160
+ form = _create_tag_function("form")
161
+ h1 = _create_tag_function("h1")
162
+ h2 = _create_tag_function("h2")
163
+ h3 = _create_tag_function("h3")
164
+ h4 = _create_tag_function("h4")
165
+ h5 = _create_tag_function("h5")
166
+ h6 = _create_tag_function("h6")
167
+ head = _create_tag_function("head")
168
+ header = _create_tag_function("header")
169
+ hgroup = _create_tag_function("hgroup")
170
+ html = _create_tag_function("html")
171
+ i = _create_tag_function("i")
172
+ iframe = _create_tag_function("iframe")
173
+ ins = _create_tag_function("ins")
174
+ kbd = _create_tag_function("kbd")
175
+ label = _create_tag_function("label")
176
+ legend = _create_tag_function("legend")
177
+ li = _create_tag_function("li")
178
+ main = _create_tag_function("main")
179
+ map_ = _create_tag_function("map")
180
+ mark = _create_tag_function("mark")
181
+ menu = _create_tag_function("menu")
182
+ meter = _create_tag_function("meter")
183
+ nav = _create_tag_function("nav")
184
+ noscript = _create_tag_function("noscript")
185
+ object_ = _create_tag_function("object")
186
+ ol = _create_tag_function("ol")
187
+ optgroup = _create_tag_function("optgroup")
188
+ option = _create_tag_function("option")
189
+ output = _create_tag_function("output")
190
+ p = _create_tag_function("p")
191
+ picture = _create_tag_function("picture")
192
+ pre = _create_tag_function("pre")
193
+ progress = _create_tag_function("progress")
194
+ q = _create_tag_function("q")
195
+ rp = _create_tag_function("rp")
196
+ rt = _create_tag_function("rt")
197
+ ruby = _create_tag_function("ruby")
198
+ s = _create_tag_function("s")
199
+ samp = _create_tag_function("samp")
200
+ script = _create_tag_function("script")
201
+ section = _create_tag_function("section")
202
+ select = _create_tag_function("select")
203
+ small = _create_tag_function("small")
204
+ span = _create_tag_function("span")
205
+ strong = _create_tag_function("strong")
206
+ style = _create_tag_function("style")
207
+ sub = _create_tag_function("sub")
208
+ summary = _create_tag_function("summary")
209
+ sup = _create_tag_function("sup")
210
+ table = _create_tag_function("table")
211
+ tbody = _create_tag_function("tbody")
212
+ td = _create_tag_function("td")
213
+ template = _create_tag_function("template")
214
+ textarea = _create_tag_function("textarea")
215
+ tfoot = _create_tag_function("tfoot")
216
+ th = _create_tag_function("th")
217
+ thead = _create_tag_function("thead")
218
+ time = _create_tag_function("time")
219
+ title = _create_tag_function("title")
220
+ tr = _create_tag_function("tr")
221
+ u = _create_tag_function("u")
222
+ ul = _create_tag_function("ul")
223
+ var = _create_tag_function("var")
224
+ video = _create_tag_function("video")
225
+
226
+ # Self-closing tags
227
+ area = _create_tag_function("area")
228
+ base = _create_tag_function("base")
229
+ br = _create_tag_function("br")
230
+ col = _create_tag_function("col")
231
+ embed = _create_tag_function("embed")
232
+ hr = _create_tag_function("hr")
233
+ img = _create_tag_function("img")
234
+ input = _create_tag_function("input")
235
+ link = _create_tag_function("link")
236
+ meta = _create_tag_function("meta")
237
+ param = _create_tag_function("param")
238
+ source = _create_tag_function("source")
239
+ track = _create_tag_function("track")
240
+ wbr = _create_tag_function("wbr")
241
+
242
+ # SVG tags
243
+ svg = _create_tag_function("svg")
244
+ circle = _create_tag_function("circle")
245
+ ellipse = _create_tag_function("ellipse")
246
+ g = _create_tag_function("g")
247
+ line = _create_tag_function("line")
248
+ path = _create_tag_function("path")
249
+ polygon = _create_tag_function("polygon")
250
+ polyline = _create_tag_function("polyline")
251
+ rect = _create_tag_function("rect")
252
+ text = _create_tag_function("text")
253
+ tspan = _create_tag_function("tspan")
254
+ defs = _create_tag_function("defs")
255
+ clipPath = _create_tag_function("clipPath")
256
+ mask = _create_tag_function("mask")
257
+ pattern = _create_tag_function("pattern")
258
+ use = _create_tag_function("use")
259
+
260
+ # React fragment
261
+ @staticmethod
262
+ def fragment(*args: Any, **kwargs: Any) -> JSExpr:
263
+ """Fragment function that creates JSXFragment with children."""
264
+ if kwargs:
265
+ raise JSCompilationError("React fragments cannot have props")
266
+ children_list: list[JSExpr | JSXElement | str] = []
267
+ _flatten_children(list(args), children_list)
268
+ return JSXFragment(children_list)
@@ -0,0 +1,59 @@
1
+ """Python typing module transpilation - mostly no-ops for type hints."""
2
+
3
+ from typing import Any as _Any
4
+ from typing import final, override
5
+
6
+ from pulse.transpiler.errors import JSCompilationError
7
+ from pulse.transpiler.nodes import JSExpr
8
+ from pulse.transpiler.py_module import PyModule
9
+
10
+
11
+ class JSTypeHint(JSExpr):
12
+ """A type hint that should never be emitted directly.
13
+
14
+ Used for typing constructs like Any that can be passed to cast() but
15
+ shouldn't appear in generated code.
16
+ """
17
+
18
+ name: str
19
+
20
+ def __init__(self, name: str) -> None:
21
+ self.name = name
22
+
23
+ @override
24
+ def emit(self) -> str:
25
+ raise JSCompilationError(
26
+ f"Type hint '{self.name}' cannot be emitted as JavaScript. "
27
+ + "It should only be used with typing.cast() or similar."
28
+ )
29
+
30
+ @override
31
+ def emit_subscript(self, indices: list[_Any]) -> JSExpr:
32
+ # List[int], Optional[str], etc. -> still a type hint
33
+ args = ", ".join("..." for _ in indices)
34
+ return JSTypeHint(f"{self.name}[{args}]")
35
+
36
+
37
+ @final
38
+ class PyTyping(PyModule):
39
+ """Provides transpilation for Python typing functions."""
40
+
41
+ # Type constructs used with cast() - error if emitted directly
42
+ Any = JSTypeHint("Any")
43
+ Optional = JSTypeHint("Optional")
44
+ Union = JSTypeHint("Union")
45
+ List = JSTypeHint("List")
46
+ Dict = JSTypeHint("Dict")
47
+ Set = JSTypeHint("Set")
48
+ Tuple = JSTypeHint("Tuple")
49
+ FrozenSet = JSTypeHint("FrozenSet")
50
+ Type = JSTypeHint("Type")
51
+ Callable = JSTypeHint("Callable")
52
+
53
+ @staticmethod
54
+ def cast(_type: _Any, val: _Any) -> JSExpr:
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)