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.
@@ -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
@@ -0,0 +1,3 @@
1
+ from contextvars import ContextVar
2
+
3
+ TEMPLATE_CONTEXT = ContextVar("TEMPLATE_CONTEXT", default=False)