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.
Files changed (84) hide show
  1. pulse/__init__.py +542 -562
  2. pulse/_examples.py +29 -0
  3. pulse/app.py +0 -14
  4. pulse/cli/cmd.py +96 -80
  5. pulse/cli/dependencies.py +10 -41
  6. pulse/cli/folder_lock.py +3 -3
  7. pulse/cli/helpers.py +40 -67
  8. pulse/cli/logging.py +102 -0
  9. pulse/cli/packages.py +16 -0
  10. pulse/cli/processes.py +40 -23
  11. pulse/codegen/codegen.py +70 -35
  12. pulse/codegen/js.py +2 -4
  13. pulse/codegen/templates/route.py +94 -146
  14. pulse/component.py +115 -0
  15. pulse/components/for_.py +1 -1
  16. pulse/components/if_.py +1 -1
  17. pulse/components/react_router.py +16 -22
  18. pulse/{html → dom}/events.py +1 -1
  19. pulse/{html → dom}/props.py +6 -6
  20. pulse/{html → dom}/tags.py +11 -11
  21. pulse/dom/tags.pyi +480 -0
  22. pulse/form.py +7 -6
  23. pulse/hooks/init.py +1 -13
  24. pulse/js/__init__.py +37 -41
  25. pulse/js/__init__.pyi +22 -2
  26. pulse/js/_types.py +5 -3
  27. pulse/js/array.py +121 -38
  28. pulse/js/console.py +9 -9
  29. pulse/js/date.py +22 -19
  30. pulse/js/document.py +8 -4
  31. pulse/js/error.py +12 -14
  32. pulse/js/json.py +4 -3
  33. pulse/js/map.py +17 -7
  34. pulse/js/math.py +2 -2
  35. pulse/js/navigator.py +4 -4
  36. pulse/js/number.py +8 -8
  37. pulse/js/object.py +9 -13
  38. pulse/js/promise.py +25 -9
  39. pulse/js/regexp.py +6 -6
  40. pulse/js/set.py +20 -8
  41. pulse/js/string.py +7 -7
  42. pulse/js/weakmap.py +6 -6
  43. pulse/js/weakset.py +6 -6
  44. pulse/js/window.py +17 -14
  45. pulse/messages.py +1 -4
  46. pulse/react_component.py +3 -1001
  47. pulse/render_session.py +74 -66
  48. pulse/renderer.py +311 -238
  49. pulse/routing.py +1 -10
  50. pulse/transpiler/__init__.py +84 -114
  51. pulse/transpiler/builtins.py +661 -343
  52. pulse/transpiler/errors.py +78 -2
  53. pulse/transpiler/function.py +463 -133
  54. pulse/transpiler/id.py +18 -0
  55. pulse/transpiler/imports.py +230 -325
  56. pulse/transpiler/js_module.py +218 -209
  57. pulse/transpiler/modules/__init__.py +16 -13
  58. pulse/transpiler/modules/asyncio.py +45 -26
  59. pulse/transpiler/modules/json.py +12 -8
  60. pulse/transpiler/modules/math.py +161 -216
  61. pulse/transpiler/modules/pulse/__init__.py +5 -0
  62. pulse/transpiler/modules/pulse/tags.py +231 -0
  63. pulse/transpiler/modules/typing.py +33 -28
  64. pulse/transpiler/nodes.py +1607 -923
  65. pulse/transpiler/py_module.py +118 -95
  66. pulse/transpiler/react_component.py +51 -0
  67. pulse/transpiler/transpiler.py +593 -437
  68. pulse/transpiler/vdom.py +255 -0
  69. {pulse_framework-0.1.51.dist-info → pulse_framework-0.1.53.dist-info}/METADATA +1 -1
  70. pulse_framework-0.1.53.dist-info/RECORD +120 -0
  71. pulse/html/tags.pyi +0 -470
  72. pulse/transpiler/constants.py +0 -110
  73. pulse/transpiler/context.py +0 -26
  74. pulse/transpiler/ids.py +0 -16
  75. pulse/transpiler/modules/re.py +0 -466
  76. pulse/transpiler/modules/tags.py +0 -268
  77. pulse/transpiler/utils.py +0 -4
  78. pulse/vdom.py +0 -599
  79. pulse_framework-0.1.51.dist-info/RECORD +0 -119
  80. /pulse/{html → dom}/__init__.py +0 -0
  81. /pulse/{html → dom}/elements.py +0 -0
  82. /pulse/{html → dom}/svg.py +0 -0
  83. {pulse_framework-0.1.51.dist-info → pulse_framework-0.1.53.dist-info}/WHEEL +0 -0
  84. {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 typing import Any as _Any
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.errors import JSCompilationError
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
- class JSTypeHint(JSExpr):
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) -> str:
25
- raise JSCompilationError(
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 emit_subscript(self, indices: list[_Any]) -> JSExpr:
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
- args = ", ".join("..." for _ in indices)
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 = 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")
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: _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)
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)