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 ADDED
@@ -0,0 +1,34 @@
1
+ from __future__ import annotations
2
+
3
+ from dataclasses import dataclass, field
4
+
5
+ from hypie.hs_ast.features import Feature
6
+ from hypie.commands import Set
7
+
8
+
9
+ @dataclass
10
+ class Hs:
11
+ features: tuple[Feature] = field(default_factory=lambda: [])
12
+
13
+ def render(self):
14
+ return "\n".join(f.render() for f in self.features)
15
+
16
+ def add_features(self, *feats: Hs | Feature):
17
+ features = []
18
+
19
+ for f in feats:
20
+ if isinstance(f, Hs):
21
+ features.extend(f.features)
22
+ elif isinstance(f, (Feature, Set)):
23
+ features.append(f)
24
+ self.features = tuple([*self.features, *features])
25
+ return self
26
+
27
+ def __str__(self):
28
+ return self.render()
29
+
30
+ __html__ = __str__
31
+
32
+
33
+ def hs(*features):
34
+ return Hs(features)
@@ -0,0 +1,148 @@
1
+ import importlib
2
+ import pathlib
3
+ import sys
4
+ import inspect
5
+
6
+ import argparse
7
+ from pathlib import Path
8
+
9
+ from hypie.experimental.components import Component
10
+ from hypie.experimental.templates import Template
11
+ from hypie.experimental.client_components import ClientComponent
12
+ from hypie.experimental.hyperscript import HyperScript
13
+
14
+ import watchfiles
15
+
16
+
17
+ def find_components_register_artifacts(in_path, out_path, out_files_prefix=""):
18
+ IGNORE_LIST = [".venv", ".pyc", ".pyo"]
19
+ MODULES = set()
20
+
21
+ COMPONENTS: set[Component] = set()
22
+ TEMPLATES: set[Template] = set()
23
+ CLIENT_COMPONENTS: set[ClientComponent] = set()
24
+ HYPERSCRIPT: set[HyperScript] = set()
25
+
26
+ path = pathlib.Path(in_path).resolve()
27
+ if not path.exists():
28
+ raise Exception("Input path does not exist")
29
+ # print(path, path.parent.resolve())
30
+ outdir = pathlib.Path(out_path).resolve()
31
+ if outdir.is_file():
32
+ raise Exception("--output must be a dir")
33
+ outdir.mkdir(exist_ok=True, parents=True)
34
+ # print(outdir)
35
+ sys.path.insert(0, str(path.parent.resolve()))
36
+ path_parts_len = len(path.parts)
37
+ python_files = pathlib.Path(path).glob("**/*.py")
38
+ for file_path in python_files:
39
+ if any(p in file_path.parts for p in IGNORE_LIST):
40
+ continue
41
+ module_name_parts = list(file_path.parts[path_parts_len - 1 : -1]) + [
42
+ file_path.stem
43
+ ]
44
+ module_name = ".".join(module_name_parts)
45
+ mod = importlib.import_module(module_name)
46
+ MODULES.add(module_name)
47
+ for _, obj in inspect.getmembers(mod):
48
+ if hasattr(obj, "_hs_dsl_component") and obj._hs_dsl_component == True:
49
+ COMPONENTS.add(obj)
50
+ elif hasattr(obj, "_hs_dsl_template") and obj._hs_dsl_template == True:
51
+ TEMPLATES.add(obj)
52
+ elif (
53
+ hasattr(obj, "_hs_dsl_client_component")
54
+ and obj._hs_dsl_client_component == True
55
+ ):
56
+ CLIENT_COMPONENTS.add(obj)
57
+ elif (
58
+ hasattr(obj, "_hs_dsl_hyperscript") and obj._hs_dsl_hyperscript == True
59
+ ):
60
+ HYPERSCRIPT.add(obj)
61
+
62
+ # create css files
63
+ styles = []
64
+ scripts = []
65
+ hs_files = []
66
+ html = []
67
+ print(
68
+ f"Done Proccessing: {len(COMPONENTS)} Component, {len(TEMPLATES)} Templates, {len(CLIENT_COMPONENTS)} Client Components {len(HYPERSCRIPT)} HS Scripts"
69
+ )
70
+ for c in COMPONENTS:
71
+ style = c.generate_style(with_style_tags=False)
72
+ if style:
73
+ styles.append(style)
74
+
75
+ for c_comp in CLIENT_COMPONENTS:
76
+ template = c_comp.register_template()
77
+ if template:
78
+ html.append(str(template))
79
+
80
+ for h in HYPERSCRIPT:
81
+ script = h.register_hyperscript()
82
+ if script:
83
+ hs_files.append(script)
84
+
85
+ style_contents = "\n".join(styles)
86
+ if style_contents:
87
+ with open(outdir / f"{out_files_prefix}_hypie_styles.css", "w") as f:
88
+ f.write(style_contents)
89
+
90
+ html_contents = "\n".join(html)
91
+ if html_contents:
92
+ with open(outdir / f"{out_files_prefix}_hypie_templates.html", "w") as f:
93
+ f.write(html_contents)
94
+
95
+ hs_contents = "\n".join(h.render() for h in hs_files)
96
+ if hs_contents:
97
+ with open(outdir / f"{out_files_prefix}_hypie_hyperscript._hs", "w") as f:
98
+ f.write(hs_contents)
99
+
100
+ # clean up
101
+ sys.path.pop(0)
102
+ for mod_name in MODULES:
103
+ del sys.modules[mod_name]
104
+
105
+
106
+ def main():
107
+ cli_parser = argparse.ArgumentParser(prog="hypie")
108
+
109
+ subparsers = cli_parser.add_subparsers(dest="command", required=True)
110
+ build_parser = subparsers.add_parser("build", help="Build components")
111
+ build_parser.add_argument(
112
+ "--input", "-i", type=Path, required=True, help="Path to components dir"
113
+ )
114
+ build_parser.add_argument(
115
+ "--output", "-o", type=Path, required=True, help="Output path for file"
116
+ )
117
+ build_parser.add_argument(
118
+ "--prefix",
119
+ "-p",
120
+ type=str,
121
+ required=False,
122
+ default="",
123
+ help="output files prefix",
124
+ )
125
+ build_parser.add_argument(
126
+ "--watch",
127
+ "-w",
128
+ action="store_true",
129
+ required=False,
130
+ default=False,
131
+ help="watch dir and re-run build",
132
+ )
133
+ args = cli_parser.parse_args()
134
+
135
+ if args.command == "build":
136
+ find_components_register_artifacts(
137
+ out_files_prefix=args.prefix, in_path=args.input, out_path=args.output
138
+ )
139
+ if args.watch:
140
+ print("[hypie]: waiting for changes...")
141
+ for _ in watchfiles.watch(args.input):
142
+ print("[hypie]: detected changes, re-running build...")
143
+ find_components_register_artifacts(
144
+ out_files_prefix=args.prefix,
145
+ in_path=args.input,
146
+ out_path=args.output,
147
+ )
148
+ print("[hypie]: waiting for changes...")
hypie/commands.py ADDED
@@ -0,0 +1,220 @@
1
+ from typing import overload
2
+
3
+ from hypie.hs_ast.commands import *
4
+ from hypie.hs_ast.expressions import *
5
+ from hypie.events import Event
6
+ from hypie.experimental.templates import Template
7
+ # from hypie.experimental.client_components import ClientComponent
8
+
9
+
10
+ @overload
11
+ def add(*class_refs, to: Expr = None, when: Expr = None): ...
12
+
13
+
14
+ @overload
15
+ def add(*attr_ref, to: Expr = None, when: Expr = None): ...
16
+
17
+
18
+ def add(*exprs: Expr, to: Expr = None, when: Expr = None):
19
+ if len(exprs) == 1 and not isinstance(exprs[0], ClassDOMLiteral):
20
+ return Add(attr_ref=exprs[0], to=to, when=when)
21
+ else:
22
+ return Add(class_refs=exprs, to=to, when=when)
23
+
24
+
25
+ @overload
26
+ def remove(*class_refs, from_: Expr = None, when: Expr = None): ...
27
+
28
+
29
+ @overload
30
+ def remove(attr_ref, from_: Expr = None, when: Expr = None): ...
31
+
32
+
33
+ def remove(*exprs: Expr, from_: Expr = None, when: Expr = None):
34
+ if len(exprs) == 1 and not isinstance(exprs[0], ClassDOMLiteral):
35
+ return Remove(attr_ref=exprs[0], from_=from_, when=when)
36
+ else:
37
+ return Remove(class_refs=exprs, from_=from_, when=when)
38
+
39
+
40
+ def take(
41
+ *class_refs,
42
+ from_: Expr = None,
43
+ giving: Expr = None,
44
+ for_: Expr = None,
45
+ ):
46
+ return Take(class_refs=class_refs, from_=from_, giving=giving, for_=for_)
47
+
48
+
49
+ def call(expr: str, /):
50
+ # using variable literal as it puts the expr as is
51
+ return Call(VariableLiteral(expr))
52
+
53
+
54
+ def fetch(
55
+ url: str, /, as_: FetchAs = None, with_: FetchOptions = None, throw: bool = True
56
+ ):
57
+ # # print(pattern.search(url))
58
+ # if pattern.search(url):
59
+ # url = f"`{url}`"
60
+ return Fetch(url=str(url), as_=as_, with_=with_, throw=throw)
61
+
62
+
63
+ def halt_event(
64
+ halt_bubbling: bool = True, halt_default: bool = True, exit_handler: bool = False
65
+ ):
66
+ return HaltEvent(
67
+ halt_bubbling=halt_bubbling,
68
+ halt_default=halt_default,
69
+ exit_handler=exit_handler,
70
+ )
71
+
72
+
73
+ def if_(condition: Expr, /):
74
+ return If(condition=condition)
75
+
76
+
77
+ def log(*exprs):
78
+ return Log(exprs=tuple(coerce_python_type_to_hs(e) for e in exprs))
79
+
80
+
81
+ def send(event_name: str, bind: dict = None, to: Expr = None):
82
+ if isinstance(event_name, Event):
83
+ _event = event_name
84
+ event_name = _event.event_name
85
+ bind = _event._bounded_args
86
+ event = EventLiteral(event_name=event_name, bind=bind)
87
+ return SendEvent(kind="send", event=event, target=to)
88
+
89
+
90
+ def trigger(event_name: str | Event, bind: dict = None, on: Expr = None):
91
+ if isinstance(event_name, type) and issubclass(event_name, Event):
92
+ _event = event_name()
93
+ event_name = _event.event_name
94
+ bind = _event._bounded_args
95
+ elif isinstance(event_name, Event):
96
+ _event = event_name
97
+ event_name = _event.event_name
98
+ bind = _event._bounded_args
99
+ event = EventLiteral(event_name=event_name, bind=bind)
100
+ return SendEvent(kind="trigger", event=event, target=on)
101
+
102
+
103
+ def repeat(
104
+ for_: VariableLiteral = None,
105
+ in_: Expr = None,
106
+ forever: bool = False,
107
+ number_of_times: int = None,
108
+ while_: Expr = None,
109
+ until: Expr = None,
110
+ at_least_once: bool = False,
111
+ ):
112
+ return Repeat(
113
+ loop_var=for_,
114
+ loop_expression=in_,
115
+ forever_condition=forever,
116
+ while_condition=while_,
117
+ until_condition=until,
118
+ at_least_once=at_least_once,
119
+ number_times=number_of_times,
120
+ )
121
+
122
+
123
+ def set_(set_expr: VariableLiteral, /, to: Expr):
124
+ return Set(
125
+ set_expr=set_expr,
126
+ to_expr=coerce_python_type_to_hs(wrap_if_not_literal(to)),
127
+ )
128
+
129
+
130
+ def put(expr: Expr, /, placement: PUT_PLACEMENTS, target: Expr):
131
+ if (
132
+ hasattr(expr, "_hs_dsl_client_component")
133
+ and expr._hs_dsl_client_component == True
134
+ ):
135
+ # expr = TemplateLiteral(expr.__html__())
136
+ tag = f"<{expr.component_name}/>"
137
+ # f:python_to_hs(getattr(self, f)) for f in self.field_names
138
+ commands = [make(tag)]
139
+ # for f in expr.field_names
140
+ return [
141
+ make(tag),
142
+ [
143
+ set_(
144
+ VariableLiteral(f"result's @{f}"),
145
+ to=VariableLiteral(f"`'${{{getattr(expr, f)}}}'`"),
146
+ )
147
+ for f in expr.field_names
148
+ ],
149
+ Put(expr=VariableLiteral("result"), placement=placement, target=target),
150
+ ]
151
+ return Put(expr=expr, placement=placement, target=target)
152
+
153
+
154
+ @overload
155
+ def toggle(*class_refs: ClassDOMLiteral, on: Expr = None): ...
156
+
157
+
158
+ @overload
159
+ def toggle(attr_ref: AttrLiteral, /, on: Expr = None): ...
160
+
161
+
162
+ def toggle(*exprs: Expr, on: Expr = None):
163
+ if len(exprs) == 1 and not isinstance(exprs[0], ClassDOMLiteral):
164
+ return Toggle(attr_ref=exprs[0], on=on)
165
+ else:
166
+ return Toggle(class_refs=exprs, on=on)
167
+
168
+
169
+ def scroll(
170
+ target: Expr,
171
+ /,
172
+ by: int,
173
+ direction: SCROLL_DIRS = None,
174
+ transition: SCROLL_TRANSITIONS = None,
175
+ ):
176
+ return Scroll(by=by, target=target, direction=direction, transition=transition)
177
+
178
+
179
+ def scroll_to(
180
+ to: Expr,
181
+ /,
182
+ vertical_direction: SCROLL_TO_V_DIRS = None,
183
+ horizontal_direction: SCROLL_TO_H_DIRS = None,
184
+ offset: int = None,
185
+ in_: Expr = None,
186
+ transition: SCROLL_TRANSITIONS = None,
187
+ ):
188
+ return ScrollTo(
189
+ to=to,
190
+ vertical_direction=vertical_direction,
191
+ horizontal_direction=horizontal_direction,
192
+ offset=offset,
193
+ in_=in_,
194
+ transition=transition,
195
+ )
196
+
197
+
198
+ def morph(expr: Expr, /, to: Expr):
199
+ return Morph(expr=expr, to=to)
200
+
201
+
202
+ def wait(time: TimeLiteral):
203
+ return Wait(time=time)
204
+
205
+
206
+ def focus(expr: Expr, /):
207
+ return Focus(expr=expr)
208
+
209
+
210
+ def make(object_name: str, /, *from_: Expr | list[Expr], called: str = None):
211
+ return Make(object_name=object_name, from_=from_, called=called)
212
+
213
+
214
+ def render(template: Template):
215
+ if isinstance(template, Template):
216
+ return Render(
217
+ template_id=template.id,
218
+ with_={f: getattr(template, f) for f in template.field_names},
219
+ )
220
+ raise Exception("render only accepts Template objects")
hypie/dom_position.py ADDED
@@ -0,0 +1,39 @@
1
+ from hypie.hs_ast.expressions import *
2
+
3
+
4
+ def next_(
5
+ css_selector: CSSExpr,
6
+ from_: Expr = None,
7
+ within: Expr = None,
8
+ with_wrapping: bool = False,
9
+ ):
10
+ return RelativetDOM(
11
+ kind="next",
12
+ css_selector=css_selector,
13
+ from_=from_,
14
+ within=within,
15
+ with_wrapping=with_wrapping,
16
+ )
17
+
18
+
19
+ def previous(
20
+ css_selector: CSSExpr,
21
+ from_: Expr = None,
22
+ within: Expr = None,
23
+ with_wrapping: bool = False,
24
+ ):
25
+ return RelativetDOM(
26
+ kind="previous",
27
+ css_selector=css_selector,
28
+ from_=from_,
29
+ within=within,
30
+ with_wrapping=with_wrapping,
31
+ )
32
+
33
+
34
+ def closest(css_selector: CSSExpr, to: Expr = None):
35
+ return ClosestDOM(kind="closest", css_selector=css_selector, to=to)
36
+
37
+
38
+ def closest_parent(css_selector: CSSExpr, to: Expr = None):
39
+ return ClosestDOM(kind="closest parent", css_selector=css_selector, to=to)
hypie/events.py ADDED
@@ -0,0 +1,77 @@
1
+ from hypie.literals import var, Expr, TimeLiteral
2
+ from typing import Literal, dataclass_transform
3
+ from dataclasses import fields, dataclass, is_dataclass
4
+
5
+
6
+ @dataclass
7
+ class EventSpec:
8
+ filter: Expr = (None,)
9
+ count: int = (None,)
10
+ from_: Expr | Literal["elsewhere"] = (None,)
11
+ in_: Expr = (None,)
12
+ debounce: TimeLiteral = (None,)
13
+ throttle: TimeLiteral = (None,)
14
+
15
+
16
+ @dataclass_transform()
17
+ class Event:
18
+ event_spec: EventSpec = None
19
+ event_name: str = None
20
+ event_args: list = None
21
+
22
+ def __init_subclass__(cls):
23
+ if not is_dataclass(cls):
24
+ dataclass_cls = dataclass(cls)
25
+ else:
26
+ dataclass_cls = cls
27
+ dataclass_cls.event_name = cls.__name__
28
+ args = []
29
+ for f in fields(dataclass_cls):
30
+ setattr(cls, f.name, var(f"{cls.event_name}__{f.name}"))
31
+ args.append(f"{cls.event_name}__{f.name}")
32
+ cls.event_args = args
33
+
34
+ @property
35
+ def _bounded_args(self):
36
+ # if not TEMPLATE_CONTEXT.get():
37
+ return {
38
+ f"{self.event_name}__{f.name}": getattr(self, f.name) for f in fields(self)
39
+ }
40
+ # template
41
+ # out = {}
42
+ # for f in fields(self):
43
+ # v = getattr(self, f.name)
44
+ # if isinstance(v, VariableLiteral):
45
+ # out[f"{self.event_name}__{f.name}"] = VariableLiteral(f"${{{v.render()}}}")
46
+ # else:
47
+ # out[f"{self.event_name}__{f.name}"] = v
48
+
49
+ # return out
50
+
51
+ @classmethod
52
+ def with_spec(
53
+ cls,
54
+ filter=None,
55
+ count: int = None,
56
+ from_: Expr | Literal["elsewhere"] = None,
57
+ in_: Expr = None,
58
+ debounce: TimeLiteral = None,
59
+ throttle: TimeLiteral = None,
60
+ ):
61
+ spec = EventSpec(
62
+ filter=filter,
63
+ count=count,
64
+ from_=from_,
65
+ in_=in_,
66
+ debounce=debounce,
67
+ throttle=throttle,
68
+ )
69
+ return type(cls)(
70
+ cls.__name__,
71
+ (cls,),
72
+ {
73
+ "event_spec": spec,
74
+ "__qualname__": cls.__qualname__,
75
+ "__module__": cls.__module__,
76
+ },
77
+ )
@@ -0,0 +1,78 @@
1
+ import dataclasses
2
+ from typing import dataclass_transform
3
+
4
+ import htpy
5
+
6
+ from hypie import hs
7
+ from hypie.literals import var
8
+ from hypie.hs_ast.helpers import (
9
+ kebab_case_name,
10
+ htpy_to_tree,
11
+ tranform_tree,
12
+ tree_to_htpy,
13
+ )
14
+ from hypie.hs_ast.expressions import VarTemplateString, python_to_hs
15
+ from hypie.commands import set_
16
+
17
+
18
+ @dataclass_transform()
19
+ class ClientComponent:
20
+ field_names = None
21
+
22
+ def __init_subclass__(cls):
23
+ dataclasses.dataclass(cls)
24
+ cls._hs_dsl_client_component = True
25
+ for f in dataclasses.fields(cls):
26
+ setattr(cls, f.name, var(f"^{f.name}"))
27
+ setattr(cls, "field_names", [f.name for f in dataclasses.fields(cls)])
28
+ cls.component_name = kebab_case_name(cls.__name__)
29
+ if "-" not in cls.component_name:
30
+ cls.component_name += "-view"
31
+ cls.script = hs()
32
+ for f in dataclasses.fields(cls):
33
+ cls.script.add_features(set_(var(f"^{f.name}"), to=var(f"attrs.{f.name}")))
34
+
35
+ @classmethod
36
+ def template(cls):
37
+ pass
38
+
39
+ @classmethod
40
+ def register_template(cls):
41
+ template = cls.template()
42
+ tree = htpy_to_tree(template)
43
+ transformed_tree = tranform_tree(tree, rules={"text": cls.handle_text_nodes})
44
+ transformed_htpy = tree_to_htpy(transformed_tree)
45
+ return htpy.script(
46
+ type="text/hyperscript-template", component=cls.component_name, _=cls.script
47
+ )[transformed_htpy]
48
+
49
+ @staticmethod
50
+ def handle_text_nodes(text):
51
+ # print(text)
52
+ exp_template = VarTemplateString(text)
53
+ if not exp_template.interpolations:
54
+ return text
55
+ return exp_template.transform(interp_func=lambda x: "${" + x + "}")
56
+
57
+ def __html__(self):
58
+ attrs = {f: python_to_hs(getattr(self, f)) for f in self.field_names}
59
+ # print(attrs)
60
+ return htpy.Element(name=self.component_name)(**attrs)
61
+
62
+ def encode(self, encoding: str = "utf-8", errors: str = "strict") -> bytes:
63
+ return str(self).encode(encoding, errors)
64
+
65
+ def __str__(self):
66
+ return str(self.__html__())
67
+
68
+
69
+ class Modal(ClientComponent):
70
+ message: str = "some test"
71
+ todo_id: int = 1
72
+
73
+ @classmethod
74
+ def template(cls):
75
+ return htpy.div[
76
+ # "hello",
77
+ cls.todo_id
78
+ ]