Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -166,4 +166,7 @@ src/**/*.js
*.tsbuildinfo

examples/**/static/*.js
tests/**/static/*.js
tests/**/static/*.js

# Stubs should be generated on build, matching the pydom version
seamless/stubs/
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,11 @@ pip install python-seamless
## Usage

```python
from seamless import Div, H1, P, Component, StyleObject
from seamless import Div, H1, P, Component, StyleSheet

class MyComponent(Component):
def render(self):
root_style = StyleObject(color="#33343c")
root_style = StyleSheet(color="#33343c")
return Div(style=div_style)(
H1(
"Hello, World!",
Expand Down
357 changes: 104 additions & 253 deletions _auto_html.py
Original file line number Diff line number Diff line change
@@ -1,259 +1,110 @@
# %%
import inspect
import pathlib
import typing

HERE = pathlib.Path(__file__).parent
HTML_FILES = HERE / "seamless/html"

# with open(HERE / "__init__.py", "w") as f:
# items = []
# for file in HERE.iterdir():
# if file.name.startswith("_") or file.suffix != ".py":
# continue

# capitalized = file.stem.capitalize()
# items.append(capitalized)
# print(f"from .{file.stem} import {capitalized}", file=f)

# all = "\n".join(f'\t"{item}",' for item in items)
# print(f"\n__all__ = [\n{all}\n]", file=f)

import pydom
import pydom.element
import pydom.types.html

html_map = {
"A": "HTMLAnchorElement",
"Area": "HTMLAreaElement",
"Audio": "HTMLAudioElement",
"Br": "HTMLBRElement",
"Base": "HTMLBaseElement",
"Body": "HTMLBodyElement",
"Button": "HTMLButtonElement",
"Canvas": "HTMLCanvasElement",
"Caption": "HTMLTableCaptionElement",
"Col": "HTMLTableColElement",
"ColGroup": "HTMLTableColElement",
"Data": "HTMLDataElement",
"DataList": "HTMLDataListElement",
"Del": "HTMLModElement",
"Details": "HTMLDetailsElement",
"Dialog": "HTMLDialogElement",
"Div": "HTMLDivElement",
"Embed": "HTMLEmbedElement",
"FieldSet": "HTMLFieldSetElement",
"Form": "HTMLFormElement",
"H1": "HTMLHeadingElement",
"H2": "HTMLHeadingElement",
"H3": "HTMLHeadingElement",
"H4": "HTMLHeadingElement",
"H5": "HTMLHeadingElement",
"H6": "HTMLHeadingElement",
"Hr": "HTMLHRElement",
"Head": "HTMLHeadElement",
"Heading": "HTMLHeadingElement",
"Html": "HTMLHtmlElement",
"IFrame": "HTMLIFrameElement",
"Img": "HTMLImageElement",
"Input": "HTMLInputElement",
"Ins": "HTMLModElement",
"Li": "HTMLListItemElement",
"Label": "HTMLLabelElement",
"Legend": "HTMLLegendElement",
"Link": "HTMLLinkElement",
"Map": "HTMLMapElement",
"Meter": "HTMLMeterElement",
"Object": "HTMLObjectElement",
"Ol": "HTMLOrderedListElement",
"OptGroup": "HTMLOptGroupElement",
"Option": "HTMLOptionElement",
"Output": "HTMLOutputElement",
"P": "HTMLParagraphElement",
"Param": "HTMLParamElement",
"Picture": "HTMLPictureElement",
"Pre": "HTMLPreElement",
"Progress": "HTMLProgressElement",
"Q": "HTMLQuoteElement",
"Script": "HTMLScriptElement",
"Select": "HTMLSelectElement",
"Slot": "HTMLSlotElement",
"Source": "HTMLSourceElement",
"Span": "HTMLSpanElement",
"Style": "HTMLStyleElement",
"Table": "HTMLTableElement",
"TBody": "HTMLTableSectionElement",
"Td": "HTMLTableDataCellElement",
"TFoot": "HTMLTableSectionElement",
"Th": "HTMLTableHeaderCellElement",
"THead": "HTMLTableSectionElement",
"Tr": "HTMLTableRowElement",
"Template": "HTMLTemplateElement",
"TextArea": "HTMLTextAreaElement",
"Time": "HTMLTimeElement",
"Title": "HTMLTitleElement",
"Track": "HTMLTrackElement",
"Ul": "HTMLUnorderedListElement",
"Video": "HTMLVideoElement",
HERE = pathlib.Path(__file__).parent
BASE_STUBS_DIR = HERE / "_auto_html"
PYDOM_ROOT = pathlib.Path(pydom.html.__file__)
SEAMLESS_ROOT = HERE / "seamless/stubs"

pydom_types = {
x: getattr(pydom.types.html, x)
for x in dir(pydom.types.html)
if x.startswith("HTML")
}

html_classes = [
"A",
"Abbr",
"Address",
"Area",
"Article",
"Aside",
"Audio",
"B",
"Base",
"Bdi",
"Bdo",
"BlockQuote",
"Body",
"Br",
"Button",
"Canvas",
"Caption",
"Cite",
"Code",
"Col",
"ColGroup",
"Data",
"DataList",
"Dd",
"Del",
"Details",
"Dfn",
"Dialog",
"Div",
"Dl",
"Dt",
"Em",
"Embed",
"FieldSet",
"FigCaption",
"Figure",
"Footer",
"Form",
"H1",
"H2",
"H3",
"H4",
"H5",
"H6",
"Head",
"Header",
"HGroup",
"Hr",
"Html",
"I",
"IFrame",
"Img",
"Input",
"Ins",
"Kbd",
"Label",
"Legend",
"Li",
"Link",
"Main",
"Map",
"Mark",
"Menu",
"Meta",
"Meter",
"Nav",
"NoScript",
"Object",
"Ol",
"OptGroup",
"Option",
"Output",
"P",
"Param",
"Picture",
"Pre",
"Progress",
"Q",
"Rp",
"Rt",
"Ruby",
"S",
"Samp",
"Script",
"Search",
"Section",
"Select",
"Slot",
"Small",
"Source",
"Span",
"Strong",
"Style",
"Sub",
"Summary",
"Sup",
"Svg",
"Table",
"TBody",
"Td",
"Template",
"TextArea",
"TFoot",
"Th",
"THead",
"Time",
"Title",
"Tr",
"Track",
"U",
"Ul",
"Var",
"Video",
"Wbr",
]

inline_html_classes = [
"Area",
"Base",
"Br",
"Col",
"Embed",
"Hr",
"Img",
"Input",
"Link",
"Meta",
"Param",
"Source",
"Track",
"Wbr",
]

html_map = {key: html_map.get(key, "HTMLElementProps") for key in html_classes}

template = """from typing_extensions import Unpack
from pydom.element import Element

from ..types.html import {props_class_name}
from ..types import ChildType


class {class_name}(Element):
def __init__(self, *children: ChildType, **kwargs: Unpack[{props_class_name}]): ...
"""

inline_string = "\n inline = True"

lower_keys = {key.lower(): key for key in html_map}

for cls, props in html_map.items():
class_name = cls
class_name_lower = class_name.lower()
props_class_name = props
filename = f"{class_name_lower}.pyi"
inline = inline_string if class_name in inline_html_classes else ""

if class_name == "Meta":
continue

if class_name == "Del":
filename = "del_.pyi"

with open(HTML_FILES / filename, "w") as f:
f.write(template.format(class_name=class_name, class_name_lower=class_name_lower, props_class_name=props_class_name, inline=inline))
element_classes = filter(
lambda x: isinstance(x, type) and issubclass(x, pydom.element.Element),
[getattr(pydom.html, x) for x in dir(pydom.html)],
)


def copy_bases(root: pathlib.Path, to_root: pathlib.Path):
for file in root.rglob("*.pyi"):
new_file = to_root / file.relative_to(root)
new_file.parent.mkdir(parents=True, exist_ok=True)
new_file.write_text(file.read_text())


def init_kwargs_type(cls):
annotations = inspect.signature(cls.__init__).parameters
kwargs_hint = annotations.get("kwargs").annotation
inner = typing.get_args(kwargs_hint)
if len(inner) != 1:
return

inner = inner[0]

if isinstance(inner, typing.ForwardRef):
inner = inner.__forward_arg__

type = pydom_types.get(inner)
return type


def append_to_init(import_text: str, init_file: pathlib.Path):
if not init_file.exists():
init_file.touch()
lines = init_file.read_text().splitlines()
lines = [x for x in lines if x.strip()]
if import_text in lines:
return
lines.append(import_text)
init_file.write_text("\n".join(lines))


copy_bases(
BASE_STUBS_DIR,
SEAMLESS_ROOT,
)

for element_cls in element_classes:
try:
class_file = pathlib.Path(inspect.getfile(element_cls))

kwargs_annotations = init_kwargs_type(element_cls)
annotation_file = None
if kwargs_annotations:
annotation_file = pathlib.Path(inspect.getfile(kwargs_annotations))

element_out_file = SEAMLESS_ROOT / "html" / class_file.with_suffix(".pyi").name
text = class_file.read_text()
new_text = text.replace(
"from ..element import Element",
"from pydom.element import Element",
)
if annotation_file:
new_text = new_text.replace(
f"from ..types.html import {kwargs_annotations.__name__}",
f"from ..types.html.{annotation_file.stem} import {kwargs_annotations.__name__}",
)
new_text = new_text.replace(
"from ..types import ChildType", "from pydom.types import ChildType"
)

element_out_file.parent.mkdir(parents=True, exist_ok=True)
element_out_file.write_text(new_text)

if annotation_file:
out_annotation_file = (
SEAMLESS_ROOT / "types/html" / annotation_file.with_suffix(".pyi").name
)
out_annotation_file.parent.mkdir(parents=True, exist_ok=True)
new_text = annotation_file.read_text().replace(
"from pydom.types.html.", "from ."
)
out_annotation_file.write_text(new_text)

append_to_init(
f"from .{element_out_file.stem} import {element_cls.__name__} as {element_cls.__name__}",
SEAMLESS_ROOT / "html" / "__init__.pyi",
)

except Exception as e:
raise ValueError(f"Error with {element_cls}") from e
Loading
Loading