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
|
@@ -0,0 +1,719 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from dataclasses import dataclass, field
|
|
4
|
+
from typing import Literal, get_args
|
|
5
|
+
import json
|
|
6
|
+
from functools import wraps
|
|
7
|
+
|
|
8
|
+
from textwrap import indent
|
|
9
|
+
|
|
10
|
+
import re
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def python_to_hs(obj):
|
|
14
|
+
if isinstance(obj, Expr):
|
|
15
|
+
return obj.render()
|
|
16
|
+
if isinstance(obj, bool):
|
|
17
|
+
return "true" if obj else "false"
|
|
18
|
+
elif obj is None:
|
|
19
|
+
return "null"
|
|
20
|
+
elif isinstance(obj, (int, float)):
|
|
21
|
+
return f"{obj}"
|
|
22
|
+
elif isinstance(obj, str):
|
|
23
|
+
exp_template = VarTemplateString(obj)
|
|
24
|
+
if not exp_template.interpolations:
|
|
25
|
+
return repr(obj)
|
|
26
|
+
return exp_template.transform(
|
|
27
|
+
interp_func=lambda x: "${" + x + "}",
|
|
28
|
+
join_func=lambda parts: "`" + "".join(parts) + "`",
|
|
29
|
+
)
|
|
30
|
+
elif isinstance(obj, dict):
|
|
31
|
+
lines = []
|
|
32
|
+
for k, v in obj.items():
|
|
33
|
+
l = f"{k}: {python_to_hs(v)}"
|
|
34
|
+
lines.append(indent(l, " ", lambda _: True))
|
|
35
|
+
return "{\n" + ",\n".join(lines) + "\n}"
|
|
36
|
+
elif isinstance(obj, list):
|
|
37
|
+
lines = []
|
|
38
|
+
for v in obj:
|
|
39
|
+
l = python_to_hs(v)
|
|
40
|
+
lines.append(indent(l, " ", lambda _: True))
|
|
41
|
+
return "[\n" + ",\n".join(lines) + "\n]"
|
|
42
|
+
else:
|
|
43
|
+
return python_to_hs(str(obj))
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def coerce_python_type_to_hs(v):
|
|
47
|
+
match v:
|
|
48
|
+
case bool():
|
|
49
|
+
return BooleanLiteral(v)
|
|
50
|
+
case int() | float():
|
|
51
|
+
return NumberLiteral(v)
|
|
52
|
+
case str():
|
|
53
|
+
return StringLiteral(v)
|
|
54
|
+
case list():
|
|
55
|
+
return ListLiteral(v)
|
|
56
|
+
case dict():
|
|
57
|
+
return ObjectLiteral(v)
|
|
58
|
+
case None:
|
|
59
|
+
return NullLiteral()
|
|
60
|
+
case _:
|
|
61
|
+
return v
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def coerce_arguments(func):
|
|
65
|
+
@wraps(func)
|
|
66
|
+
def wrapped(*args, **kwargs):
|
|
67
|
+
new_args = []
|
|
68
|
+
for a in args:
|
|
69
|
+
new_args.append(coerce_python_type_to_hs(a))
|
|
70
|
+
new_kwargs = {}
|
|
71
|
+
for k, v in kwargs.items():
|
|
72
|
+
new_kwargs[k] = coerce_python_type_to_hs(v)
|
|
73
|
+
return func(*new_args, **new_kwargs)
|
|
74
|
+
|
|
75
|
+
return wrapped
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def wrap_if_not_literal(v):
|
|
79
|
+
if isinstance(v, Expr) and not isinstance(v, get_args(Literals.__value__)):
|
|
80
|
+
return Parentheses(v)
|
|
81
|
+
return v
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
def wrap_expressions(func):
|
|
85
|
+
@wraps(func)
|
|
86
|
+
def wrapped(*args, **kwargs):
|
|
87
|
+
new_args = []
|
|
88
|
+
for a in args:
|
|
89
|
+
new_args.append(wrap_if_not_literal(a))
|
|
90
|
+
new_kwargs = {}
|
|
91
|
+
for k, v in kwargs.items():
|
|
92
|
+
new_kwargs[k] = wrap_if_not_literal(v)
|
|
93
|
+
return func(*new_args, **new_kwargs)
|
|
94
|
+
|
|
95
|
+
return wrapped
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
PATTERN = re.compile(r"\?VAR:<(.*?)>\?")
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
class VarTemplateString:
|
|
102
|
+
def __init__(self, source: str):
|
|
103
|
+
self.pattern = PATTERN
|
|
104
|
+
self.source = source
|
|
105
|
+
self.strings = []
|
|
106
|
+
self.interpolations = []
|
|
107
|
+
matches = re.finditer(self.pattern, self.source)
|
|
108
|
+
last_idx = 0
|
|
109
|
+
if matches:
|
|
110
|
+
for m in matches:
|
|
111
|
+
end_idx = m.span()[0]
|
|
112
|
+
s = self.source[last_idx:end_idx]
|
|
113
|
+
self.strings.append(s)
|
|
114
|
+
self.interpolations.append(m.group(1))
|
|
115
|
+
last_idx = m.span()[1]
|
|
116
|
+
# print(last_idx, len(source), print(source[last_idx]))
|
|
117
|
+
# if last_idx <= len(source) - 1:
|
|
118
|
+
self.strings.append(self.source[last_idx:])
|
|
119
|
+
|
|
120
|
+
else:
|
|
121
|
+
self.strings = source
|
|
122
|
+
|
|
123
|
+
def transform(
|
|
124
|
+
self,
|
|
125
|
+
string_func=lambda x: x,
|
|
126
|
+
interp_func=lambda x: x,
|
|
127
|
+
join_func=lambda parts: "".join(p for p in parts if p != ""),
|
|
128
|
+
):
|
|
129
|
+
parts = [string_func(self.strings[0])]
|
|
130
|
+
for i, inter in enumerate(self.interpolations):
|
|
131
|
+
parts.append(interp_func(inter))
|
|
132
|
+
parts.append(string_func(self.strings[i + 1]))
|
|
133
|
+
return join_func(parts)
|
|
134
|
+
|
|
135
|
+
def __str__(self):
|
|
136
|
+
return f"VarTemplateString(strings={self.strings}, interpolations={self.interpolations})"
|
|
137
|
+
|
|
138
|
+
__repr__ = __str__
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
class Expr:
|
|
142
|
+
def __html__(self):
|
|
143
|
+
return "${" + self.render() + "}"
|
|
144
|
+
|
|
145
|
+
def __str__(self):
|
|
146
|
+
return f"?VAR:<{self.render()}>?"
|
|
147
|
+
|
|
148
|
+
# __html__ = __str__
|
|
149
|
+
|
|
150
|
+
def render(self):
|
|
151
|
+
pass
|
|
152
|
+
|
|
153
|
+
# def __format__(self, spec):
|
|
154
|
+
# return "${" + self.render() + "}"
|
|
155
|
+
|
|
156
|
+
# property access
|
|
157
|
+
def __getattr__(self, name: str):
|
|
158
|
+
# htpy attempts to check for this, which makes __html__ fail
|
|
159
|
+
if name == "iter_chunks":
|
|
160
|
+
raise AttributeError()
|
|
161
|
+
return PropertyAccess(self, property=name)
|
|
162
|
+
|
|
163
|
+
def prop(self, name: str):
|
|
164
|
+
return PropertyAccess(self, property=name)
|
|
165
|
+
|
|
166
|
+
def __getitem__(self, name: str | Expr):
|
|
167
|
+
return PropertyAccessBrackets(self, property=name)
|
|
168
|
+
|
|
169
|
+
# binary
|
|
170
|
+
@coerce_arguments
|
|
171
|
+
@wrap_expressions
|
|
172
|
+
def __add__(self, other: Expr):
|
|
173
|
+
return BinaryOp(kind="add", left=self, right=other)
|
|
174
|
+
|
|
175
|
+
@coerce_arguments
|
|
176
|
+
@wrap_expressions
|
|
177
|
+
def __sub__(self, other: Expr):
|
|
178
|
+
return BinaryOp(kind="subtract", left=self, right=other)
|
|
179
|
+
|
|
180
|
+
@coerce_arguments
|
|
181
|
+
@wrap_expressions
|
|
182
|
+
def __mul__(self, other: Expr):
|
|
183
|
+
return BinaryOp(kind="multiply", left=self, right=other)
|
|
184
|
+
|
|
185
|
+
@coerce_arguments
|
|
186
|
+
@wrap_expressions
|
|
187
|
+
def __mod__(self, other: Expr):
|
|
188
|
+
return BinaryOp(kind="mod", left=self, right=other)
|
|
189
|
+
|
|
190
|
+
@coerce_arguments
|
|
191
|
+
@wrap_expressions
|
|
192
|
+
def __gt__(self, other):
|
|
193
|
+
return BinaryOp(kind="gt", left=self, right=other)
|
|
194
|
+
|
|
195
|
+
@coerce_arguments
|
|
196
|
+
@wrap_expressions
|
|
197
|
+
def __ge__(self, other):
|
|
198
|
+
return BinaryOp(kind="gte", left=self, right=other)
|
|
199
|
+
|
|
200
|
+
@coerce_arguments
|
|
201
|
+
@wrap_expressions
|
|
202
|
+
def __lt__(self, other):
|
|
203
|
+
return BinaryOp(kind="lt", left=self, right=other)
|
|
204
|
+
|
|
205
|
+
@coerce_arguments
|
|
206
|
+
@wrap_expressions
|
|
207
|
+
def __le__(self, other):
|
|
208
|
+
return BinaryOp(kind="lte", left=self, right=other)
|
|
209
|
+
|
|
210
|
+
@coerce_arguments
|
|
211
|
+
@wrap_expressions
|
|
212
|
+
def __ne__(self, other):
|
|
213
|
+
return BinaryOp(kind="ne", left=self, right=other)
|
|
214
|
+
|
|
215
|
+
@coerce_arguments
|
|
216
|
+
@wrap_expressions
|
|
217
|
+
def and_(self, other: Expr):
|
|
218
|
+
return BinaryOp(kind="and", left=self, right=other)
|
|
219
|
+
|
|
220
|
+
@coerce_arguments
|
|
221
|
+
@wrap_expressions
|
|
222
|
+
def or_(self, other: Expr):
|
|
223
|
+
return BinaryOp(kind="or", left=self, right=other)
|
|
224
|
+
|
|
225
|
+
@coerce_arguments
|
|
226
|
+
@wrap_expressions
|
|
227
|
+
def matches(self, other: Expr):
|
|
228
|
+
return BinaryOp(kind="matches", left=self, right=other)
|
|
229
|
+
|
|
230
|
+
@coerce_arguments
|
|
231
|
+
@wrap_expressions
|
|
232
|
+
def does_not_match(self, other: Expr):
|
|
233
|
+
return BinaryOp(kind="does not match", left=self, right=other)
|
|
234
|
+
|
|
235
|
+
@coerce_arguments
|
|
236
|
+
@wrap_expressions
|
|
237
|
+
# collection
|
|
238
|
+
def where(self, condition: Expr):
|
|
239
|
+
return Where(collection=self, condition=condition)
|
|
240
|
+
|
|
241
|
+
@coerce_arguments
|
|
242
|
+
@wrap_expressions
|
|
243
|
+
def first(self):
|
|
244
|
+
return PositionalCollection(kind="first", collection=self)
|
|
245
|
+
|
|
246
|
+
@coerce_arguments
|
|
247
|
+
@wrap_expressions
|
|
248
|
+
def random(self):
|
|
249
|
+
return PositionalCollection(kind="random", collection=self)
|
|
250
|
+
|
|
251
|
+
@coerce_arguments
|
|
252
|
+
@wrap_expressions
|
|
253
|
+
def last(self):
|
|
254
|
+
return PositionalCollection(kind="last", collection=self)
|
|
255
|
+
|
|
256
|
+
@wrap_expressions
|
|
257
|
+
def __or__(self, type: Literal["JSON", "JSONString"]):
|
|
258
|
+
return TypeConversion(coerce_python_type_to_hs(self), type)
|
|
259
|
+
|
|
260
|
+
@wrap_expressions
|
|
261
|
+
def __ror__(type, self: Literal["JSON", "JSONString"]):
|
|
262
|
+
return TypeConversion(coerce_python_type_to_hs(self), type)
|
|
263
|
+
|
|
264
|
+
# @coerce_arguments
|
|
265
|
+
@wrap_expressions
|
|
266
|
+
def as_(self, type: Literal["JSON", "JSONString"]):
|
|
267
|
+
return TypeConversion(self, type)
|
|
268
|
+
|
|
269
|
+
|
|
270
|
+
################## BASIC TYPES ##################
|
|
271
|
+
|
|
272
|
+
|
|
273
|
+
@dataclass
|
|
274
|
+
class StringLiteral(Expr):
|
|
275
|
+
value: str
|
|
276
|
+
|
|
277
|
+
def render(self):
|
|
278
|
+
return python_to_hs(self.value)
|
|
279
|
+
|
|
280
|
+
|
|
281
|
+
@dataclass
|
|
282
|
+
class TemplateLiteral(Expr):
|
|
283
|
+
value: str
|
|
284
|
+
|
|
285
|
+
def render(self):
|
|
286
|
+
return f"`{self.value}`"
|
|
287
|
+
|
|
288
|
+
|
|
289
|
+
@dataclass
|
|
290
|
+
class NumberLiteral(Expr):
|
|
291
|
+
value: int | float
|
|
292
|
+
|
|
293
|
+
def render(self):
|
|
294
|
+
return python_to_hs(self.value)
|
|
295
|
+
|
|
296
|
+
|
|
297
|
+
@dataclass
|
|
298
|
+
class BooleanLiteral(Expr):
|
|
299
|
+
value: Literal[True, False]
|
|
300
|
+
|
|
301
|
+
def render(self):
|
|
302
|
+
return python_to_hs(self.value)
|
|
303
|
+
|
|
304
|
+
|
|
305
|
+
@dataclass
|
|
306
|
+
class NullLiteral(Expr):
|
|
307
|
+
def render(self):
|
|
308
|
+
return python_to_hs(self.value)
|
|
309
|
+
|
|
310
|
+
|
|
311
|
+
@dataclass
|
|
312
|
+
class ListLiteral(Expr):
|
|
313
|
+
value: list
|
|
314
|
+
|
|
315
|
+
def render(self):
|
|
316
|
+
return python_to_hs(self.value)
|
|
317
|
+
|
|
318
|
+
|
|
319
|
+
@dataclass
|
|
320
|
+
class ObjectLiteral(Expr):
|
|
321
|
+
value: dict
|
|
322
|
+
|
|
323
|
+
def render(self):
|
|
324
|
+
return python_to_hs(self.value)
|
|
325
|
+
|
|
326
|
+
|
|
327
|
+
################## DOM ##################
|
|
328
|
+
# class
|
|
329
|
+
@dataclass
|
|
330
|
+
class ClassDOMLiteral(Expr):
|
|
331
|
+
class_: str
|
|
332
|
+
|
|
333
|
+
def render(self):
|
|
334
|
+
exp_template = VarTemplateString(self.class_)
|
|
335
|
+
if not exp_template.interpolations:
|
|
336
|
+
return f".{self.class_}"
|
|
337
|
+
out = exp_template.transform(
|
|
338
|
+
string_func=lambda x: repr(x),
|
|
339
|
+
join_func=lambda parts: ".{"
|
|
340
|
+
+ " + ".join(p for p in parts if p != repr(""))
|
|
341
|
+
+ "}",
|
|
342
|
+
)
|
|
343
|
+
# print(exp_template)
|
|
344
|
+
# print(out)
|
|
345
|
+
return out
|
|
346
|
+
|
|
347
|
+
def in_(self, other: Expr):
|
|
348
|
+
return InDOM(self, other)
|
|
349
|
+
|
|
350
|
+
|
|
351
|
+
# query
|
|
352
|
+
@dataclass
|
|
353
|
+
class QueryDOMLiteral(Expr):
|
|
354
|
+
query: str
|
|
355
|
+
|
|
356
|
+
def render(self):
|
|
357
|
+
exp_template = VarTemplateString(self.query)
|
|
358
|
+
# print(exp_template)
|
|
359
|
+
if not exp_template.interpolations:
|
|
360
|
+
return f"<{self.query}/>"
|
|
361
|
+
out = exp_template.transform(
|
|
362
|
+
string_func=lambda x: x,
|
|
363
|
+
interp_func=lambda x: "${" + x + "}",
|
|
364
|
+
join_func=lambda parts: "<" + "".join(parts) + "/>",
|
|
365
|
+
)
|
|
366
|
+
# print(exp_template)
|
|
367
|
+
# print(out)
|
|
368
|
+
return out
|
|
369
|
+
|
|
370
|
+
def in_(self, other: Expr):
|
|
371
|
+
return InDOM(self, other)
|
|
372
|
+
|
|
373
|
+
|
|
374
|
+
# id
|
|
375
|
+
@dataclass
|
|
376
|
+
class IdDOMLiteral(Expr):
|
|
377
|
+
id_: str
|
|
378
|
+
|
|
379
|
+
def render(self):
|
|
380
|
+
exp_template = VarTemplateString(self.id_)
|
|
381
|
+
if not exp_template.interpolations:
|
|
382
|
+
return f"#{self.id_}"
|
|
383
|
+
out = exp_template.transform(
|
|
384
|
+
string_func=lambda x: repr(x),
|
|
385
|
+
join_func=lambda parts: "#{"
|
|
386
|
+
+ " + ".join(p for p in parts if p != repr(""))
|
|
387
|
+
+ "}",
|
|
388
|
+
)
|
|
389
|
+
# print(exp_template)
|
|
390
|
+
# print(out)
|
|
391
|
+
return out
|
|
392
|
+
|
|
393
|
+
def in_(self, other: Expr):
|
|
394
|
+
return InDOM(self, other)
|
|
395
|
+
|
|
396
|
+
|
|
397
|
+
@dataclass
|
|
398
|
+
class AttrLiteral(Expr):
|
|
399
|
+
attr_name: str
|
|
400
|
+
attr_value: str = None
|
|
401
|
+
|
|
402
|
+
def render(self):
|
|
403
|
+
rendered = f"@{self.attr_name}"
|
|
404
|
+
if self.attr_value:
|
|
405
|
+
rendered += f"='{json.dumps(self.attr_value)}'"
|
|
406
|
+
return rendered
|
|
407
|
+
|
|
408
|
+
|
|
409
|
+
type CSSExpr = IdDOMLiteral | QueryDOMLiteral | ClassDOMLiteral
|
|
410
|
+
|
|
411
|
+
|
|
412
|
+
################## VARIABLE ##################
|
|
413
|
+
@dataclass
|
|
414
|
+
class VariableLiteral(Expr):
|
|
415
|
+
symbol: str
|
|
416
|
+
|
|
417
|
+
def render(self):
|
|
418
|
+
return f"{self.symbol}"
|
|
419
|
+
|
|
420
|
+
|
|
421
|
+
@dataclass
|
|
422
|
+
class TemplatedVariableLiteral(Expr):
|
|
423
|
+
symbol: str
|
|
424
|
+
|
|
425
|
+
def render(self):
|
|
426
|
+
return "${" + self.symbol + "}"
|
|
427
|
+
|
|
428
|
+
def __html__(self):
|
|
429
|
+
return self.render()
|
|
430
|
+
|
|
431
|
+
def __str__(self):
|
|
432
|
+
return self.render()
|
|
433
|
+
|
|
434
|
+
|
|
435
|
+
################## Time ##################
|
|
436
|
+
type TIME_RES = Literal["s", "ms"]
|
|
437
|
+
|
|
438
|
+
|
|
439
|
+
@dataclass
|
|
440
|
+
class TimeLiteral(Expr):
|
|
441
|
+
time: int
|
|
442
|
+
resolution: TIME_RES = "ms"
|
|
443
|
+
|
|
444
|
+
def render(self):
|
|
445
|
+
return f"{self.time}{self.resolution}"
|
|
446
|
+
|
|
447
|
+
|
|
448
|
+
################## Event ##################
|
|
449
|
+
@dataclass
|
|
450
|
+
class EventLiteral(Expr):
|
|
451
|
+
event_name: str
|
|
452
|
+
bind: dict[str, Expr] = field(default_factory=lambda: {})
|
|
453
|
+
|
|
454
|
+
def render(self):
|
|
455
|
+
rendered = f"{self.event_name}"
|
|
456
|
+
if self.bind:
|
|
457
|
+
rendered += "("
|
|
458
|
+
parts = []
|
|
459
|
+
for k, v in self.bind.items():
|
|
460
|
+
parts.append(f"{k}:{coerce_python_type_to_hs(v).render()}")
|
|
461
|
+
rendered += ", ".join(parts) + ")"
|
|
462
|
+
return rendered
|
|
463
|
+
|
|
464
|
+
|
|
465
|
+
@dataclass
|
|
466
|
+
class EventSpecLiteral(Expr):
|
|
467
|
+
event_name: str
|
|
468
|
+
args: list[str] = field(default_factory=lambda: [])
|
|
469
|
+
filter: Expr = None
|
|
470
|
+
count: int | tuple = None
|
|
471
|
+
from_: Expr | Literal["elsewhere"] = None
|
|
472
|
+
in_: Expr = None
|
|
473
|
+
debounce: TimeLiteral = None
|
|
474
|
+
throttle: TimeLiteral = None
|
|
475
|
+
|
|
476
|
+
def render(self):
|
|
477
|
+
rendered = f"{self.event_name}"
|
|
478
|
+
if self.args:
|
|
479
|
+
rendered += "("
|
|
480
|
+
parts = []
|
|
481
|
+
for a in self.args:
|
|
482
|
+
parts.append(a)
|
|
483
|
+
rendered += ", ".join(parts)
|
|
484
|
+
rendered += ")"
|
|
485
|
+
if self.filter:
|
|
486
|
+
rendered += f"[{self.filter.render()}]"
|
|
487
|
+
if self.count:
|
|
488
|
+
rendered += f" {self.count}"
|
|
489
|
+
if self.from_:
|
|
490
|
+
from_ = self.from_.render() if isinstance(self.from_, Expr) else self.from_
|
|
491
|
+
rendered += f" from {from_}"
|
|
492
|
+
if self.in_:
|
|
493
|
+
rendered += f" in {self.in_.render()}"
|
|
494
|
+
if self.debounce:
|
|
495
|
+
rendered += f" debounced at {self.debounce.render()}"
|
|
496
|
+
elif self.throttle:
|
|
497
|
+
rendered += f" throttled at {self.throttle.render()}"
|
|
498
|
+
return rendered
|
|
499
|
+
|
|
500
|
+
|
|
501
|
+
################## DOM POSITONING ##################
|
|
502
|
+
@dataclass
|
|
503
|
+
class InDOM(Expr):
|
|
504
|
+
css_selector: CSSExpr
|
|
505
|
+
target: CSSExpr
|
|
506
|
+
|
|
507
|
+
def render(self):
|
|
508
|
+
return f"{self.css_selector.render()} in {self.target.render()}"
|
|
509
|
+
|
|
510
|
+
|
|
511
|
+
@dataclass
|
|
512
|
+
class RelativetDOM(Expr):
|
|
513
|
+
kind: Literal["next", "previous"]
|
|
514
|
+
css_selector: CSSExpr
|
|
515
|
+
from_: Expr | None = None
|
|
516
|
+
within: Expr | None = None
|
|
517
|
+
with_wrapping: bool = False
|
|
518
|
+
|
|
519
|
+
def render(self):
|
|
520
|
+
rendered = f"{self.kind} {self.css_selector.render()}"
|
|
521
|
+
if self.from_:
|
|
522
|
+
rendered += f" from {self.from_.render()}"
|
|
523
|
+
if self.within:
|
|
524
|
+
rendered += f" within {self.within.render()}"
|
|
525
|
+
if self.with_wrapping:
|
|
526
|
+
rendered += " with wrapping"
|
|
527
|
+
return rendered
|
|
528
|
+
|
|
529
|
+
|
|
530
|
+
@dataclass
|
|
531
|
+
class ClosestDOM(Expr):
|
|
532
|
+
kind: Literal["closest", "closest parent"]
|
|
533
|
+
css_selector: CSSExpr
|
|
534
|
+
to: Expr | None = None
|
|
535
|
+
|
|
536
|
+
def render(self):
|
|
537
|
+
rendered = f"{self.kind} {self.css_selector.render()}"
|
|
538
|
+
if self.to:
|
|
539
|
+
rendered += f" {self.to.render()}"
|
|
540
|
+
return rendered
|
|
541
|
+
|
|
542
|
+
|
|
543
|
+
type Literals = (
|
|
544
|
+
VariableLiteral
|
|
545
|
+
| CSSExpr.__value__
|
|
546
|
+
| ObjectLiteral
|
|
547
|
+
| ListLiteral
|
|
548
|
+
| NumberLiteral
|
|
549
|
+
| StringLiteral
|
|
550
|
+
| BooleanLiteral
|
|
551
|
+
| NullLiteral
|
|
552
|
+
| TemplateLiteral
|
|
553
|
+
| EventLiteral
|
|
554
|
+
| AttrLiteral
|
|
555
|
+
| Parentheses
|
|
556
|
+
| PropertyAccess
|
|
557
|
+
| PropertyAccessBrackets
|
|
558
|
+
| AsType
|
|
559
|
+
)
|
|
560
|
+
|
|
561
|
+
|
|
562
|
+
################## COLLECTIONS ##################
|
|
563
|
+
@dataclass
|
|
564
|
+
class PositionalCollection(Expr):
|
|
565
|
+
kind: Literal["first", "last", "random"]
|
|
566
|
+
collection: Expr
|
|
567
|
+
# preposition: Literal["in", "of", "from"] = "in"
|
|
568
|
+
|
|
569
|
+
def render(self):
|
|
570
|
+
return f"{self.kind} {self.collection.render()}"
|
|
571
|
+
|
|
572
|
+
|
|
573
|
+
@dataclass
|
|
574
|
+
class Where(Expr):
|
|
575
|
+
collection: Expr
|
|
576
|
+
condition: Expr
|
|
577
|
+
|
|
578
|
+
def render(self):
|
|
579
|
+
return f"{self.collection.render()} where {self.condition.render()}"
|
|
580
|
+
|
|
581
|
+
|
|
582
|
+
@dataclass
|
|
583
|
+
class MappedTo(Expr):
|
|
584
|
+
collection: Expr
|
|
585
|
+
expr: Expr
|
|
586
|
+
|
|
587
|
+
def render(self):
|
|
588
|
+
return f"{self.collection.render()} mapped to {self.expr.render()}"
|
|
589
|
+
|
|
590
|
+
|
|
591
|
+
################## PROPERTY ACCESS ##################
|
|
592
|
+
@dataclass
|
|
593
|
+
class PropertyAccess(Expr):
|
|
594
|
+
expr: Expr
|
|
595
|
+
property: str
|
|
596
|
+
|
|
597
|
+
def render(self):
|
|
598
|
+
if isinstance(self.expr, VariableLiteral) and self.expr.symbol == "me":
|
|
599
|
+
return f"my {self.property}"
|
|
600
|
+
if isinstance(self.expr, PropertyAccessBrackets):
|
|
601
|
+
return f"{self.expr.render()}.{self.property}"
|
|
602
|
+
return f"{self.expr.render()}'s {self.property}"
|
|
603
|
+
|
|
604
|
+
|
|
605
|
+
@dataclass
|
|
606
|
+
class PropertyAccessBrackets(Expr):
|
|
607
|
+
expr: Expr
|
|
608
|
+
property: Expr | str
|
|
609
|
+
|
|
610
|
+
def render(self):
|
|
611
|
+
rendered_property = (
|
|
612
|
+
self.property.render()
|
|
613
|
+
if isinstance(self.property, Expr)
|
|
614
|
+
else repr(self.property)
|
|
615
|
+
)
|
|
616
|
+
return f"{self.expr.render()}[{rendered_property}]"
|
|
617
|
+
|
|
618
|
+
|
|
619
|
+
################## OPS ##################
|
|
620
|
+
|
|
621
|
+
type MATH_BINARY_OPS = Literal["add", "subtract", "multiply", "divide", "mod"]
|
|
622
|
+
type LOGICAL_BINARY_OPS = Literal["and", "or"]
|
|
623
|
+
type BOOL_BINARY_OPS = Literal[
|
|
624
|
+
"eq",
|
|
625
|
+
"ne",
|
|
626
|
+
"gt",
|
|
627
|
+
"gte",
|
|
628
|
+
"lt",
|
|
629
|
+
"lte",
|
|
630
|
+
"matches",
|
|
631
|
+
"does not match",
|
|
632
|
+
"is",
|
|
633
|
+
"is not",
|
|
634
|
+
"is really",
|
|
635
|
+
"is not really",
|
|
636
|
+
"contains",
|
|
637
|
+
"does not contain",
|
|
638
|
+
"starts with",
|
|
639
|
+
"ends with",
|
|
640
|
+
"precedes",
|
|
641
|
+
"does not precede",
|
|
642
|
+
"follows",
|
|
643
|
+
"does not follow",
|
|
644
|
+
"in", # this can be used as a location restrictor for DOM literals
|
|
645
|
+
]
|
|
646
|
+
|
|
647
|
+
|
|
648
|
+
@dataclass
|
|
649
|
+
class BinaryOp(Expr):
|
|
650
|
+
kind: MATH_BINARY_OPS | LOGICAL_BINARY_OPS | BOOL_BINARY_OPS
|
|
651
|
+
left: Expr
|
|
652
|
+
right: Expr
|
|
653
|
+
|
|
654
|
+
def render(self):
|
|
655
|
+
op = None
|
|
656
|
+
match self.kind:
|
|
657
|
+
case "add":
|
|
658
|
+
op = "+"
|
|
659
|
+
case "subtract":
|
|
660
|
+
op = "-"
|
|
661
|
+
case "multiply":
|
|
662
|
+
op = "*"
|
|
663
|
+
case "lt":
|
|
664
|
+
op = "<"
|
|
665
|
+
case "lte":
|
|
666
|
+
op = "<="
|
|
667
|
+
case "gt":
|
|
668
|
+
op = ">"
|
|
669
|
+
case "gte":
|
|
670
|
+
op = ">="
|
|
671
|
+
case "eq":
|
|
672
|
+
op = "=="
|
|
673
|
+
case "ne":
|
|
674
|
+
op = "!="
|
|
675
|
+
case _:
|
|
676
|
+
op = self.kind
|
|
677
|
+
return f"{self.left.render()} {op} {self.right.render()}"
|
|
678
|
+
|
|
679
|
+
|
|
680
|
+
# MATH_Unary_OPS = ...
|
|
681
|
+
LOGICAL_Unary_OPS = ...
|
|
682
|
+
BOOL_Unary_OPS = Literal[
|
|
683
|
+
"ignoring case", "exists", "does not exist", "is empty", "is not empty"
|
|
684
|
+
]
|
|
685
|
+
|
|
686
|
+
|
|
687
|
+
@dataclass
|
|
688
|
+
class UnaryOp(Expr):
|
|
689
|
+
pass
|
|
690
|
+
|
|
691
|
+
|
|
692
|
+
################## PARENTHESES ##################
|
|
693
|
+
|
|
694
|
+
|
|
695
|
+
@dataclass
|
|
696
|
+
class Parentheses(Expr):
|
|
697
|
+
expr: Expr
|
|
698
|
+
|
|
699
|
+
def render(self):
|
|
700
|
+
return f"({self.expr.render()})"
|
|
701
|
+
|
|
702
|
+
|
|
703
|
+
################## TYPE CONVERSION ##################
|
|
704
|
+
@dataclass
|
|
705
|
+
class TypeConversion(Expr):
|
|
706
|
+
expr: Expr
|
|
707
|
+
type: Literal["JSON", "JSONString"] | Expr
|
|
708
|
+
|
|
709
|
+
def render(self):
|
|
710
|
+
return f"{self.expr.render()} as {self.type.render() if isinstance(self.type, Expr) else self.type}"
|
|
711
|
+
|
|
712
|
+
|
|
713
|
+
@dataclass
|
|
714
|
+
class AsType(Expr):
|
|
715
|
+
expr: Expr
|
|
716
|
+
type: Literal["JSON", "JSONString"]
|
|
717
|
+
|
|
718
|
+
def render(self):
|
|
719
|
+
return f"{python_to_hs(self.expr)} as {self.type}"
|