hypie 0.1.1.dev2__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.
- hypie/__init__.py +34 -0
- hypie/cli/build_components.py +148 -0
- hypie/commands.py +220 -0
- hypie/dom_position.py +39 -0
- hypie/events.py +77 -0
- hypie/experimental/client_components.py +78 -0
- hypie/experimental/components.py +135 -0
- hypie/experimental/hyperscript.py +35 -0
- hypie/experimental/templates.py +50 -0
- hypie/features.py +46 -0
- hypie/hs_ast/commands.py +478 -0
- hypie/hs_ast/constants.py +1 -0
- hypie/hs_ast/contexts.py +3 -0
- hypie/hs_ast/expressions.py +719 -0
- hypie/hs_ast/features.py +181 -0
- hypie/hs_ast/helpers.py +85 -0
- hypie/literals.py +76 -0
- hypie-0.1.1.dev2.dist-info/METADATA +816 -0
- hypie-0.1.1.dev2.dist-info/RECORD +22 -0
- hypie-0.1.1.dev2.dist-info/WHEEL +4 -0
- hypie-0.1.1.dev2.dist-info/entry_points.txt +3 -0
- hypie-0.1.1.dev2.dist-info/licenses/LICENSE +7 -0
hypie/hs_ast/commands.py
ADDED
|
@@ -0,0 +1,478 @@
|
|
|
1
|
+
from typing import Literal, TypedDict, Iterable
|
|
2
|
+
from dataclasses import dataclass, field
|
|
3
|
+
from textwrap import indent
|
|
4
|
+
|
|
5
|
+
from hypie.hs_ast.expressions import *
|
|
6
|
+
from hypie.hs_ast.constants import *
|
|
7
|
+
from hypie.hs_ast import helpers
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class Command:
|
|
11
|
+
def render(self):
|
|
12
|
+
pass
|
|
13
|
+
|
|
14
|
+
def __html__(self):
|
|
15
|
+
return self.render()
|
|
16
|
+
|
|
17
|
+
# __html__ = __str__
|
|
18
|
+
__str__ = __html__
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
@dataclass
|
|
22
|
+
class Add(Command):
|
|
23
|
+
class_refs: tuple[CSSExpr] = None
|
|
24
|
+
attr_ref: AttrLiteral = None
|
|
25
|
+
to: Expr | None = None
|
|
26
|
+
when: Expr | None = None
|
|
27
|
+
|
|
28
|
+
def render(self):
|
|
29
|
+
rendered = "add"
|
|
30
|
+
if self.class_refs:
|
|
31
|
+
for c in self.class_refs:
|
|
32
|
+
rendered += f" {c.render()}"
|
|
33
|
+
else:
|
|
34
|
+
rendered += f" {self.attr_ref.render()}"
|
|
35
|
+
if self.to:
|
|
36
|
+
rendered += f" to {self.to.render()}"
|
|
37
|
+
if self.when:
|
|
38
|
+
rendered += f" {self.when.render()}"
|
|
39
|
+
return rendered
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
@dataclass
|
|
43
|
+
class Call(Command):
|
|
44
|
+
expr: Expr
|
|
45
|
+
|
|
46
|
+
def render(self):
|
|
47
|
+
rendered = f"call {self.expr.render()}"
|
|
48
|
+
return rendered
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
type FetchAs = Literal["HTML", "JSON", "Text", "Stream", "Response"]
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
class FetchOptions(TypedDict, total=False):
|
|
55
|
+
method: Literal["GET", "POST", "PUT", "DELETE", "PATCH", "HEAD", "OPTIONS"]
|
|
56
|
+
headers: dict[str, str]
|
|
57
|
+
body: str
|
|
58
|
+
credentials: Literal["omit", "same-origin", "include"]
|
|
59
|
+
cache: Literal[
|
|
60
|
+
"default", "no-store", "reload", "no-cache", "force-cache", "only-if-cached"
|
|
61
|
+
]
|
|
62
|
+
mode: Literal["cors", "no-cors", "same-origin"]
|
|
63
|
+
timeout: str
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
@dataclass
|
|
67
|
+
class Fetch(Command):
|
|
68
|
+
url: str | StringLiteral
|
|
69
|
+
as_: FetchAs = None
|
|
70
|
+
with_: FetchOptions = None
|
|
71
|
+
throw: bool = True
|
|
72
|
+
|
|
73
|
+
def render(self):
|
|
74
|
+
rendered = "fetch"
|
|
75
|
+
url_template = VarTemplateString(self.url)
|
|
76
|
+
if not url_template.interpolations:
|
|
77
|
+
url = self.url
|
|
78
|
+
else:
|
|
79
|
+
url = url_template.transform(
|
|
80
|
+
interp_func=lambda x: "${" + x + "}",
|
|
81
|
+
join_func=lambda parts: "`" + "".join(p for p in parts) + "`",
|
|
82
|
+
)
|
|
83
|
+
rendered += f" {url}"
|
|
84
|
+
if self.as_:
|
|
85
|
+
rendered += f" as {self.as_}"
|
|
86
|
+
if self.with_:
|
|
87
|
+
with_ = python_to_hs(self.with_)
|
|
88
|
+
rendered += f" with {with_}"
|
|
89
|
+
if not self.throw:
|
|
90
|
+
rendered += " don't throw"
|
|
91
|
+
return rendered
|
|
92
|
+
|
|
93
|
+
# def _default_fetch_options() -> FetchOptions:
|
|
94
|
+
# return FetchOptions(
|
|
95
|
+
# method="GET"
|
|
96
|
+
# )
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
@dataclass
|
|
100
|
+
class HaltEvent(Command):
|
|
101
|
+
halt_bubbling: bool = True
|
|
102
|
+
halt_default: bool = True
|
|
103
|
+
exit_handler: bool = False
|
|
104
|
+
|
|
105
|
+
def render(self):
|
|
106
|
+
if self.halt_bubbling and self.halt_default and self.exit_handler:
|
|
107
|
+
return "halt"
|
|
108
|
+
elif self.halt_bubbling and self.halt_default and not self.exit_handler:
|
|
109
|
+
return "halt the event"
|
|
110
|
+
elif self.halt_bubbling and not self.exit_handler:
|
|
111
|
+
return "halt the event's bubbling"
|
|
112
|
+
elif self.halt_bubbling and self.exit_handler:
|
|
113
|
+
return "halt bubbling"
|
|
114
|
+
elif self.halt_default and not self.exit_handler:
|
|
115
|
+
return "halt the event's default"
|
|
116
|
+
else:
|
|
117
|
+
return "halt default"
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
# has block
|
|
121
|
+
@dataclass
|
|
122
|
+
class If(Command):
|
|
123
|
+
condition: Expr
|
|
124
|
+
commands: list[Command] = field(default_factory=lambda: [])
|
|
125
|
+
else_commands: list[Command] = field(default_factory=lambda: [])
|
|
126
|
+
|
|
127
|
+
def render(self):
|
|
128
|
+
lines = []
|
|
129
|
+
header = f"if {self.condition.render()}"
|
|
130
|
+
lines.append(header)
|
|
131
|
+
for c in self.commands:
|
|
132
|
+
lines.append(indent(c.render(), INDENT, lambda _: True))
|
|
133
|
+
if self.else_commands:
|
|
134
|
+
lines.append("else")
|
|
135
|
+
for c in self.else_commands:
|
|
136
|
+
lines.append(indent(c.render(), INDENT, lambda _: True))
|
|
137
|
+
lines.append("end")
|
|
138
|
+
return "\n".join(lines)
|
|
139
|
+
|
|
140
|
+
def __getitem__(self, commands: Command):
|
|
141
|
+
if not isinstance(commands, Iterable):
|
|
142
|
+
commands = (commands,)
|
|
143
|
+
self.commands = [
|
|
144
|
+
c for c in helpers.flatten_iterable(commands) if isinstance(c, Command)
|
|
145
|
+
]
|
|
146
|
+
return self
|
|
147
|
+
|
|
148
|
+
@property
|
|
149
|
+
def else_(self):
|
|
150
|
+
if not self.commands:
|
|
151
|
+
raise ValueError("Can't start an else block without if path")
|
|
152
|
+
if self.else_commands:
|
|
153
|
+
raise ValueError("Can not have two else blocks")
|
|
154
|
+
else_ = _Else(self)
|
|
155
|
+
return else_
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
@dataclass
|
|
159
|
+
class _Else:
|
|
160
|
+
if_cmd: If
|
|
161
|
+
|
|
162
|
+
def __getitem__(self, commands):
|
|
163
|
+
if not isinstance(commands, Iterable):
|
|
164
|
+
commands = (commands,)
|
|
165
|
+
self.if_cmd.else_commands = [
|
|
166
|
+
c for c in helpers.flatten_iterable(commands) if isinstance(c, Command)
|
|
167
|
+
]
|
|
168
|
+
return self.if_cmd
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
@dataclass
|
|
172
|
+
class Log(Command):
|
|
173
|
+
exprs: tuple[Expr]
|
|
174
|
+
|
|
175
|
+
def render(self):
|
|
176
|
+
rendered_exprs = ", ".join(
|
|
177
|
+
[wrap_if_not_literal(e).render() for e in self.exprs]
|
|
178
|
+
)
|
|
179
|
+
return f"log {rendered_exprs}"
|
|
180
|
+
|
|
181
|
+
def _wrap_as_template(self, string: str):
|
|
182
|
+
return f"`{string}`"
|
|
183
|
+
|
|
184
|
+
|
|
185
|
+
@dataclass
|
|
186
|
+
class Remove(Command):
|
|
187
|
+
class_refs: tuple[CSSExpr] = None
|
|
188
|
+
attr_ref: AttrLiteral = None
|
|
189
|
+
from_: Expr | None = None
|
|
190
|
+
when: Expr | None = None
|
|
191
|
+
|
|
192
|
+
def render(self):
|
|
193
|
+
rendered = "remove"
|
|
194
|
+
if self.class_refs:
|
|
195
|
+
for c in self.class_refs:
|
|
196
|
+
rendered += f" {c.render()}"
|
|
197
|
+
else:
|
|
198
|
+
rendered += f" {self.attr_ref.render()}"
|
|
199
|
+
if self.from_:
|
|
200
|
+
rendered += f" from {self.from_.render()}"
|
|
201
|
+
if self.when:
|
|
202
|
+
rendered += f" {self.when.render()}"
|
|
203
|
+
return rendered
|
|
204
|
+
|
|
205
|
+
|
|
206
|
+
@dataclass
|
|
207
|
+
class Take(Command):
|
|
208
|
+
class_refs: tuple[CSSExpr]
|
|
209
|
+
# with_: CSSExpr = None
|
|
210
|
+
from_: Expr = None
|
|
211
|
+
giving: Expr = None
|
|
212
|
+
for_: Expr = None
|
|
213
|
+
|
|
214
|
+
def render(self):
|
|
215
|
+
# print(self.giving)
|
|
216
|
+
rendered = "take"
|
|
217
|
+
for c in self.class_refs:
|
|
218
|
+
rendered += f" {c.render()}"
|
|
219
|
+
if self.from_:
|
|
220
|
+
rendered += f" from {self.from_.render()}"
|
|
221
|
+
if self.giving is not None:
|
|
222
|
+
rendered += f" giving {coerce_python_type_to_hs(self.giving).render()}"
|
|
223
|
+
if self.for_:
|
|
224
|
+
rendered += f" for {self.for_.render()}"
|
|
225
|
+
return rendered
|
|
226
|
+
|
|
227
|
+
|
|
228
|
+
@dataclass
|
|
229
|
+
class SendEvent(Command):
|
|
230
|
+
kind: Literal["send", "trigger"]
|
|
231
|
+
event: EventLiteral
|
|
232
|
+
target: Expr = None
|
|
233
|
+
|
|
234
|
+
def render(self):
|
|
235
|
+
if self.kind == "send":
|
|
236
|
+
rendered = f"send {self.event.render()}"
|
|
237
|
+
if self.target:
|
|
238
|
+
rendered += f" to {self.target.render()}"
|
|
239
|
+
else:
|
|
240
|
+
rendered = f"trigger {self.event.render()}"
|
|
241
|
+
if self.target:
|
|
242
|
+
rendered += f" on {self.target.render()}"
|
|
243
|
+
return rendered
|
|
244
|
+
|
|
245
|
+
|
|
246
|
+
type SCROLL_DIRS = Literal["up", "down", "left", "right"]
|
|
247
|
+
type SCROLL_TO_V_DIRS = Literal["top", "middle", "bottom"]
|
|
248
|
+
type SCROLL_TO_H_DIRS = Literal["left", "center", "right"]
|
|
249
|
+
type SCROLL_TRANSITIONS = Literal["smoothly", "instantly"]
|
|
250
|
+
|
|
251
|
+
|
|
252
|
+
@dataclass
|
|
253
|
+
class Scroll(Command):
|
|
254
|
+
by: int
|
|
255
|
+
target: Expr = None
|
|
256
|
+
direction: SCROLL_DIRS = None
|
|
257
|
+
transition: SCROLL_TRANSITIONS = None
|
|
258
|
+
|
|
259
|
+
def render(self):
|
|
260
|
+
rendered = "scroll"
|
|
261
|
+
if self.target:
|
|
262
|
+
rendered += f" {self.target.render()}"
|
|
263
|
+
if self.direction:
|
|
264
|
+
rendered += f" {self.direction}"
|
|
265
|
+
rendered += f" by {self.by}px"
|
|
266
|
+
if self.transition:
|
|
267
|
+
rendered += f" {self.transition}"
|
|
268
|
+
return rendered
|
|
269
|
+
|
|
270
|
+
|
|
271
|
+
@dataclass
|
|
272
|
+
class ScrollTo(Command):
|
|
273
|
+
to: Expr
|
|
274
|
+
vertical_direction: SCROLL_TO_V_DIRS = None
|
|
275
|
+
horizontal_direction: SCROLL_TO_H_DIRS = None
|
|
276
|
+
offset: int = None
|
|
277
|
+
in_: Expr = None
|
|
278
|
+
transition: Literal["smoothly", "instantly"] = None
|
|
279
|
+
|
|
280
|
+
def render(self):
|
|
281
|
+
rendered = "scroll to"
|
|
282
|
+
if self.vertical_direction:
|
|
283
|
+
rendered += f" {self.vertical_direction}"
|
|
284
|
+
if self.horizontal_direction:
|
|
285
|
+
rendered += f" {self.horizontal_direction}"
|
|
286
|
+
if self.vertical_direction or self.horizontal_direction:
|
|
287
|
+
rendered += " of"
|
|
288
|
+
rendered += f" {self.to.render()}"
|
|
289
|
+
if self.offset:
|
|
290
|
+
sign = "+"
|
|
291
|
+
if self.offset <= 0:
|
|
292
|
+
sign = "-"
|
|
293
|
+
rendered += f" {sign} {abs(self.offset)}px"
|
|
294
|
+
if self.in_:
|
|
295
|
+
rendered += f" in {self.in_.render()}"
|
|
296
|
+
if self.transition:
|
|
297
|
+
rendered += f" {self.transition}"
|
|
298
|
+
return rendered
|
|
299
|
+
|
|
300
|
+
|
|
301
|
+
@dataclass
|
|
302
|
+
class Toggle(Command):
|
|
303
|
+
class_refs: tuple[ClassDOMLiteral] = None
|
|
304
|
+
|
|
305
|
+
# TODO: ...
|
|
306
|
+
attr_ref: AttrLiteral = None
|
|
307
|
+
on: Expr = None
|
|
308
|
+
|
|
309
|
+
def render(self):
|
|
310
|
+
# print(self.class_refs)
|
|
311
|
+
if self.class_refs:
|
|
312
|
+
rendered = "toggle " + " ".join(c.render() for c in self.class_refs)
|
|
313
|
+
else:
|
|
314
|
+
rendered = f"toggle {self.attr.render()}"
|
|
315
|
+
if self.on:
|
|
316
|
+
rendered += f" on {self.on.render()}"
|
|
317
|
+
return rendered
|
|
318
|
+
|
|
319
|
+
|
|
320
|
+
@dataclass
|
|
321
|
+
class ToggleBetween(Command): ...
|
|
322
|
+
|
|
323
|
+
|
|
324
|
+
@dataclass
|
|
325
|
+
class Set(Command):
|
|
326
|
+
set_expr: Expr
|
|
327
|
+
to_expr: Expr
|
|
328
|
+
|
|
329
|
+
def render(self):
|
|
330
|
+
return f"set {self.set_expr.render()} to {self.to_expr.render()}"
|
|
331
|
+
|
|
332
|
+
|
|
333
|
+
@dataclass
|
|
334
|
+
class Repeat(Command):
|
|
335
|
+
commands: tuple[Command] = None
|
|
336
|
+
loop_var: VariableLiteral = None
|
|
337
|
+
index_var: VariableLiteral = None
|
|
338
|
+
loop_expression: Expr = None
|
|
339
|
+
while_condition: Expr = None
|
|
340
|
+
until_condition: Expr = None
|
|
341
|
+
number_times: NumberLiteral = None
|
|
342
|
+
forever_condition: bool = None
|
|
343
|
+
at_least_once: bool = False
|
|
344
|
+
|
|
345
|
+
def __getitem__(self, commands: Command):
|
|
346
|
+
if not isinstance(commands, Iterable):
|
|
347
|
+
commands = (commands,)
|
|
348
|
+
self.commands = helpers.flatten_iterable(commands)
|
|
349
|
+
return self
|
|
350
|
+
|
|
351
|
+
def render(self):
|
|
352
|
+
lines = []
|
|
353
|
+
header = ""
|
|
354
|
+
end = ""
|
|
355
|
+
cond = self.while_condition or self.until_condition
|
|
356
|
+
stop = "stop " if isinstance(cond, EventLiteral) else ""
|
|
357
|
+
|
|
358
|
+
if self.loop_expression:
|
|
359
|
+
if self.loop_var:
|
|
360
|
+
header = f"repeat for {self.loop_var.render()} in {self.loop_expression.render()}"
|
|
361
|
+
else:
|
|
362
|
+
header = f"repeat in {self.loop_expression.render()}"
|
|
363
|
+
elif self.while_condition:
|
|
364
|
+
if self.at_least_once:
|
|
365
|
+
header = "repeat"
|
|
366
|
+
end = f"while {stop}{self.while_condition.render()}"
|
|
367
|
+
else:
|
|
368
|
+
header = f"repeat while {stop}{self.while_condition.render()}"
|
|
369
|
+
elif self.until_condition:
|
|
370
|
+
if self.at_least_once:
|
|
371
|
+
header = "repeat"
|
|
372
|
+
end = f"until {stop}{self.until_condition.render()}"
|
|
373
|
+
else:
|
|
374
|
+
header = f"repeat until {stop}{self.until_condition.render()}"
|
|
375
|
+
elif self.forever_condition:
|
|
376
|
+
header = "repeat forever"
|
|
377
|
+
elif self.number_times:
|
|
378
|
+
header = f"repeat {self.number_times} times"
|
|
379
|
+
# not bottom-tested check
|
|
380
|
+
if self.index_var and not self.at_least_once:
|
|
381
|
+
header += f" index {self.index_var.render()}"
|
|
382
|
+
|
|
383
|
+
lines.append(header)
|
|
384
|
+
for c in self.commands:
|
|
385
|
+
lines.append(indent(c.render(), INDENT, lambda _: True))
|
|
386
|
+
if end:
|
|
387
|
+
lines.append(end)
|
|
388
|
+
lines.append("end")
|
|
389
|
+
# print(lines)
|
|
390
|
+
return "\n".join(lines)
|
|
391
|
+
|
|
392
|
+
|
|
393
|
+
class Continue(Command):
|
|
394
|
+
def render(self):
|
|
395
|
+
return "continue"
|
|
396
|
+
|
|
397
|
+
|
|
398
|
+
class Break(Command):
|
|
399
|
+
def render(self):
|
|
400
|
+
return "break"
|
|
401
|
+
|
|
402
|
+
|
|
403
|
+
class Exit(Command):
|
|
404
|
+
def render(self):
|
|
405
|
+
return "exit"
|
|
406
|
+
|
|
407
|
+
|
|
408
|
+
type PUT_PLACEMENTS = Literal["into", "before", "at start of", "at end of", "after"]
|
|
409
|
+
|
|
410
|
+
|
|
411
|
+
@dataclass
|
|
412
|
+
class Put(Command):
|
|
413
|
+
expr: Expr
|
|
414
|
+
placement: PUT_PLACEMENTS
|
|
415
|
+
target: Expr
|
|
416
|
+
|
|
417
|
+
def render(self):
|
|
418
|
+
return f"put {coerce_python_type_to_hs(self.expr).render()} {self.placement} {self.target.render()}"
|
|
419
|
+
|
|
420
|
+
|
|
421
|
+
@dataclass
|
|
422
|
+
class Morph(Command):
|
|
423
|
+
expr: Expr
|
|
424
|
+
to: Expr
|
|
425
|
+
|
|
426
|
+
def render(self):
|
|
427
|
+
return f"morph {self.expr.render()} to {coerce_python_type_to_hs(self.to).render()}"
|
|
428
|
+
|
|
429
|
+
|
|
430
|
+
@dataclass
|
|
431
|
+
class Wait(Command):
|
|
432
|
+
# events: list[EventSpecLiteral | str] = None
|
|
433
|
+
time: TimeLiteral = None
|
|
434
|
+
|
|
435
|
+
def render(self):
|
|
436
|
+
return f"wait {self.time.render()}"
|
|
437
|
+
|
|
438
|
+
|
|
439
|
+
@dataclass
|
|
440
|
+
class Focus(Command):
|
|
441
|
+
expr: Expr = None
|
|
442
|
+
|
|
443
|
+
def render(self):
|
|
444
|
+
rendered = "focus"
|
|
445
|
+
if self.expr:
|
|
446
|
+
rendered += f" {self.expr.render()}"
|
|
447
|
+
return rendered
|
|
448
|
+
|
|
449
|
+
|
|
450
|
+
@dataclass
|
|
451
|
+
class Make(Command):
|
|
452
|
+
object_name: str | Expr
|
|
453
|
+
from_: Iterable[Expr] = None
|
|
454
|
+
called: str = None
|
|
455
|
+
|
|
456
|
+
def render(self):
|
|
457
|
+
rendered = f"make a {self.object_name.render() if isinstance(self.object_name, Expr) else self.object_name}"
|
|
458
|
+
if self.from_:
|
|
459
|
+
from_ = self.from_
|
|
460
|
+
rendered += f" from {', '.join(coerce_python_type_to_hs(e).render() for e in from_)}"
|
|
461
|
+
if self.called:
|
|
462
|
+
rendered += f" called {self.called}"
|
|
463
|
+
return rendered
|
|
464
|
+
|
|
465
|
+
|
|
466
|
+
@dataclass
|
|
467
|
+
class Render(Command):
|
|
468
|
+
template_id: str
|
|
469
|
+
with_: dict = None
|
|
470
|
+
|
|
471
|
+
def render(self):
|
|
472
|
+
rendered = f"render #{self.template_id}"
|
|
473
|
+
if self.with_:
|
|
474
|
+
values = ", ".join(
|
|
475
|
+
[f"{k}: {python_to_hs(v)}" for k, v in self.with_.items()]
|
|
476
|
+
)
|
|
477
|
+
rendered += f" with {values}"
|
|
478
|
+
return rendered
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
INDENT = " " * 2
|
hypie/hs_ast/contexts.py
ADDED