Compare commits

...

3 Commits

Author SHA1 Message Date
74980d2c1b cleanup 2025-08-22 09:26:18 -04:00
61fac50e20 working! 2025-08-21 13:39:30 -04:00
dd440fa779 nice 2025-08-21 11:53:23 -04:00
10 changed files with 195 additions and 243 deletions

101
css.py
View File

@ -1,24 +1,6 @@
from dataclasses import dataclass
from typing import Optional, Tuple, Union, Dict, List
import random
css_final:List[str] = []
css_lines:List[str] = []
def css_class(css_context:str):
global css_lines
output = f'{css_context}{{ {"".join(css_lines)} }}'
css_lines = []
css_final.append(output)
def css_write(str:str):
css_lines.append(str)
def css_dump():
global css_final
output = "".join(css_final)
css_final = []
return output
class Unit():
unit = "px"
def __init__(self, amount:float):
@ -41,11 +23,9 @@ class Cluster():
map:Dict[str, str] = {}
def __init__(self):
self.key = random.randrange(1000, 5000)
pass
def __repr__(self)->str:
print("repr called on cluster")
#todo: flesh this out / provide overrides for each inheriting class
return self.render()
def render(self)->str:
@ -54,7 +34,7 @@ class Cluster():
value = self.__getattribute__(self.map[key])
if value:
output.append(f'{key}:{value};')
return "\n".join(output)
return "".join(output)
@dataclass
class Font(Cluster):
@ -83,49 +63,78 @@ class XForm(Space):
class Chainer():
def __init__(self) -> None:
self.accessed = 0
self.size = 0
self.dump_id:int = -1
self.log:List[str] = []
pass
def __getitem__(self, args:Union[Cluster, Tuple[Cluster, ...]]):
if isinstance(args, tuple):
children = f'\n'.join(k.render() for k in args)
print("bracket access", args)
styledata = "".join(c.render() for c in args) if isinstance(args, tuple) else args.render()
if self.size:
self.log.append(f'\n@media(min-width:{self.size}px){{ {styledata} }}')
else:
children = args.render()
if self.accessed:
css_write(f'@media(max-width:{self.accessed}px){{ {children} }}')
else:
css_write(children)
self.accessed = 0
self.log.append(styledata)
return self
def __call__(self, key:int):
self.accessed = key
print("call access", key)
self.size = key
return self
def dump(self, log:List[str], fullName:str, dump_id:int):
if self.dump_id != dump_id:
log.append(f'.{fullName} {{\n {"".join(self.log)} \n}}')
self.dump_id = dump_id
self.log = []
self.size = 0
class Kickoff():
def __getitem__(self, args:Union[Cluster, Tuple[Cluster, ...]]):
return Chainer().__getitem__(args)
def __call__(self, key:int):
return Chainer().__call__(key)
CSS = Kickoff()
CSS = Chainer()
##########################
class Writer:
dump_id = 0
log:List[str] = []
@classmethod
def start(cls):
cls.dump_id += 1
cls.log.clear()
print("Writer reset, dump_id:", cls.dump_id)
@classmethod
def capture(cls, fullName:str, chainer:Chainer):
chainer.dump(cls.log, fullName, Writer.dump_id)
@classmethod
def dump(cls) -> str:
output = "\n".join(cls.log)
cls.start() # Reset after dumping
return output
class _SHEET(type):
last_accessed:Optional[str] = None
def __getattribute__(cls, name:str):
if name != "__name__" and name != "__module__":
css_class(f'.{cls.__module__}.{cls.__name__}.{name}')
print("class name access", name)
value = super().__getattribute__(name)
if isinstance(value, Chainer):
fullName = f'{cls.__module__}_{cls.__name__}_{name}'
Writer.capture(fullName, value)
return fullName
return super().__getattribute__(name)
class SHEET(metaclass=_SHEET):
pass
class UserMadeSheet(SHEET):
Paragraph=CSS[
Font( size=PX(10) ),
](512)[
Font( size=PX(18), family="sans" ),
](1024)[
Font( size=PX(120) ),
]
Anchor=CSS[
Font( size=PX(123) )
]

33
example.html Normal file
View File

@ -0,0 +1,33 @@
<!DOCTYPE html><html>
<head>
<style>
.user_styles_UserMadeSheet_Anchor {
font-size:123px;
}
.user_styles_UserMadeSheet_Paragraph {
font-size:10px;
@media(min-width:512px){ font-family:sans;font-size:18px; }
@media(min-width:1024px){ font-size:120px; }
}
</style>
</head>
<body>
<div>
<h1>
Welcome, seth!
</h1>
<a href="www.site.com" class="user_styles_UserMadeSheet_Anchor">
<img src="image.png"/>
</a>
<p class="user_styles_UserMadeSheet_Paragraph">
hello and lorem ipsum dolor sit amet, consectetur adipiscing elit.
Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi
ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit
in voluptate velit esse cillum dolore eu fugiat nulla pariatur.
Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia
deserunt mollit anim id est laborum.
</p>
</div>
</body>
</html>

View File

@ -1,138 +0,0 @@
from typing import TypedDict, Union, Literal, Tuple, Optional, cast
import re
# --- Units and Sizes ---
MediaQueryCheck = Literal["max-width", "min-width", "max-height", "min-height"]
Unit = Literal["px", "em", "rem", "%", "vh", "vw"]
Size = Tuple[float, Unit]
MediaQuery = Tuple[MediaQueryCheck, float, Unit]
# --- Colors ---
RGB = Tuple[float, float, float]
RGBA = Tuple[float, float, float, float]
Color = Union[RGB, RGBA]
CSSValue = Union[Size, Color, str]
# --- Positions ---
PositionKeyword = Literal["static", "relative", "absolute", "fixed", "sticky"]
# --- Display ---
Display = Literal["none", "block", "inline", "inline-block", "flex", "grid"]
# --- Alignment ---
TextAlign = Literal["left", "right", "center", "justify", "start", "end"]
# --- Overflow ---
Overflow = Literal["visible", "hidden", "scroll", "auto", "clip"]
# --- Border Style ---
BorderStyle = Literal["none", "solid", "dashed", "dotted", "double", "groove", "ridge", "inset", "outset"]
# --- Length / Size or keyword ---
AutoSize = Union[Size, Literal["auto"]]
class CSS(TypedDict, total=False):
color: Color
backgroundColor: Color
fontSize: Size
margin: Union[AutoSize, Tuple[AutoSize, AutoSize], Tuple[AutoSize, AutoSize, AutoSize, AutoSize]]
padding: Union[AutoSize, Tuple[AutoSize, AutoSize], Tuple[AutoSize, AutoSize, AutoSize, AutoSize]]
width: AutoSize
height: AutoSize
display: Display
position: PositionKeyword
top: AutoSize
left: AutoSize
right: AutoSize
bottom: AutoSize
textAlign: TextAlign
overflow: Overflow
borderColor: Color
borderWidth: Size
borderStyle: BorderStyle
opacity: float # 0.0 to 1.0
zIndex: int
lineHeight: Union[float, Size]
def Styles(
color: Optional[Color] = None,
backgroundColor: Optional[Color] = None,
fontSize: Optional[Size] = None,
margin: Optional[Union[AutoSize, Tuple[AutoSize, AutoSize], Tuple[AutoSize, AutoSize, AutoSize, AutoSize]]] = None,
padding: Optional[Union[AutoSize, Tuple[AutoSize, AutoSize], Tuple[AutoSize, AutoSize, AutoSize, AutoSize]]] = None,
width: Optional[AutoSize] = None,
height: Optional[AutoSize] = None,
display: Optional[Display] = None,
position: Optional[PositionKeyword] = None,
top: Optional[AutoSize] = None,
left: Optional[AutoSize] = None,
right: Optional[AutoSize] = None,
bottom: Optional[AutoSize] = None,
textAlign: Optional[TextAlign] = None,
overflow: Optional[Overflow] = None,
borderColor: Optional[Color] = None,
borderWidth: Optional[Size] = None,
borderStyle: Optional[BorderStyle] = None,
opacity: Optional[float] = None,
zIndex: Optional[int] = None,
lineHeight: Optional[Union[float, Size]] = None,
) -> CSS:
"""
Function alternative to create a block of CSS styles.
"""
return cast(CSS, {k: v for k, v in locals().items() if v is not None})
def Sheet(mq:MediaQuery, *css:CSS):
"""
Serialize a CSS sheet with a media query and multiple CSS dictionaries.
"""
lines:list[str] = []
lines.append(f"@media ({mq[0]}: {mq[1]}{mq[2]}) {{")
def camel_to_kebab(name):
# Insert hyphen before capital letters and convert to lowercase
s1 = re.sub(r'(.)([A-Z][a-z]+)', r'\1-\2', name)
kebab = re.sub(r'([a-z0-9])([A-Z])', r'\1-\2', s1).lower()
return kebab
def concat(joiner:str, tuple:Union[RGB, RGBA, Size])->str:
return joiner.join(str(item) for item in tuple)
merged:CSS = {}
for d in css:
merged = {**merged, **d}
for key, value in merged.items():
text = value
if isinstance(value, tuple):
size = len(value)
if size < 3:
text = concat("", value)
elif size == 3:
text = "rgb(" + concat(", ", value) + ")"
elif size == 4:
text = "rgba(" + concat(", ", value) + ")"
lines.append(f" {camel_to_kebab(key)}: {text};")
lines.append("}")
return "\n".join(lines)
test = Sheet(
("max-width", 1024, "px"),
{
"fontSize":(16, "px"),
"color":(255, 0, 0),
"backgroundColor":(0, 255, 0),
"display":"block",
},
Styles(
color=(0, 0, 255),
backgroundColor=(255, 255, 0),
)
)
print(test)

64
pyx.py
View File

@ -1,14 +1,17 @@
from typing import List, Tuple, Union, Optional
from css import Chainer, Writer
from dataclasses import dataclass
Attributes = dict[str, Union[str, bool, None]]
Stringy = Union[str, Tuple[str, ...]]
Renderable = Union[str, 'Branch', 'Leaf']
TAGGED = Union[Tuple[Chainer], Chainer]
def MakeAttrs(css:Optional[Stringy], id:Optional[str], attributes: Attributes) -> str:
def MakeAttrs(css:TAGGED, id:Optional[str], attributes: Attributes) -> str:
if not attributes:
attributes = {}
if css:
attributes['class'] = " ".join(css) if isinstance(css, (list, tuple)) else css
attributes['class'] = " ".join(css) if isinstance(css, tuple) else css
if id:
attributes['id'] = id
attrList: List[str] = []
@ -24,47 +27,54 @@ class Leaf():
self.name = name
self.attrs = None
def props(self, css:Optional[Stringy], id:Optional[str], attributes: Attributes):
def props(self, css:Optional[TAGGED], id:Optional[str], attributes: Attributes):
clone = self.__class__(self.name)
clone.attrs = MakeAttrs(css, id, attributes)
return clone
def __call__(self, css:Optional[str] = None, id:Optional[str] = None):
def __call__(self, css:Optional[TAGGED] = None, id:Optional[str] = None):
return self.props(css, id, {})
@classmethod
def template(cls, name:str, attrs:Optional[str] = None):
return f'<{name}{attrs or ""}/>'
def __repr__(self) -> str:
return f'<{self.name}{self.attrs or ""}/>'
return __class__.template(self.name, self.attrs)
class Branch(Leaf):
name:str = ""
def __init__(self, name:Optional[str] = None):
if name:
self.name = name
def __init__(self, name:str):
self.name = name
self.attrs = None
@classmethod
def template(cls, name:str, attrs:Optional[str] = None, children:Optional[str] = None):
return f'<{name}{attrs or ""}>\n{children}\n</{name}>'
def __repr__(self) -> str:
return f'<{self.name}{self.attrs or ""}></{self.name}>'
return self.__class__.template(self.name, self.attrs)
def __getitem__(self, key:Union[Renderable, Tuple[Renderable, ...]]) -> str:
if isinstance(key, tuple):
children = f'\n'.join(str(k) for k in key)
else:
children = str(key)
return f'<{self.name}{self.attrs or ""}>\n{children}\n</{self.name}>'
def __getitem__(self, key:Union[Renderable, Tuple[Renderable, ...]]) -> Renderable:
children = f'\n'.join(str(k) for k in key) if isinstance(key, tuple) else str(key)
return self.__class__.template(self.name, self.attrs, children)
class IMGTag(Leaf):
name = "img"
def __call__(self, css:Optional[Stringy] = None, id:Optional[str] = None, src:Optional[str] = None):
def __call__(self, css:Optional[TAGGED] = None, id:Optional[str] = None, src:Optional[str] = None):
return self.props(css, id, {"src":src})
class ATag(Branch):
name = "a"
def __call__(self, css:Optional[Stringy] = None, id:Optional[str] = None, href:Optional[str] = None, target:Optional[str] = None):
def __call__(self, css:Optional[TAGGED] = None, id:Optional[str] = None, href:Optional[str] = None, target:Optional[str] = None):
return self.props(css, id, {"href":href, "target":target})
class HTMLTag(Branch):
@classmethod
def template(cls, name:str, attrs:Optional[str] = None, children:Optional[str] = None):
return f'<!DOCTYPE html><{name}{attrs or ""}>\n{children}\n</{name}>'
IMG = IMGTag()
A = ATag()
IMG = IMGTag("img")
A = ATag("a")
BR = Leaf("br")
HR = Leaf("hr")
DIV = Branch("div")
@ -77,3 +87,13 @@ H3 = Branch("h3")
H4 = Branch("h4")
SECTION = Branch("section")
MAIN = Branch("main")
HTML = HTMLTag("html")
HEAD = Branch("head")
BODY = Branch("body")
STYLE = Branch("style")
@dataclass
class Capture:
def __init__(self, html:Renderable):
self.html = html
self.css = Writer.dump()

12
route.py Normal file
View File

@ -0,0 +1,12 @@
from pyx import HTML, HEAD, BODY, STYLE, Capture
from user_template import ViewFunction
output = Capture(ViewFunction(name="seth"))
with open("example.html", "w") as file:
file.write(f'{HTML[
HEAD[
STYLE[ output.css ]
],
BODY[ output.html ]
]}')

View File

@ -1,21 +1,20 @@
from dataclasses import dataclass, field
from typing import Optional, Dict, Tuple, List
from typing import Callable, TypeVar, ParamSpec, Optional
P = ParamSpec('P') # captures the parameter types
R = TypeVar('R') # original return type
NewR = TypeVar('NewR') # new return type
def transform_return_type(func: Callable[P, R]) -> Callable[P, str]:
def wrapper(*args: P.args, **kwargs: P.kwargs) -> str:
# You'd do something meaningful here
result = func(*args, **kwargs)
return str(result) # Just an example transformation
return wrapper
@transform_return_type
def idk(test:Optional[str] = None)->int:
return 7
class Bracket():
def __init__(self, *args: str):
self.children = list(args)
@dataclass
class StyledTag(Bracket):
style: Optional[str] = None
other: Optional[str] = None
def Test(*children:str, css:Optional[str]=None, ):
print(css)
print(children)
Test("itme1", "item2", css="yo")
test = idk(test="hey")

View File

@ -1,3 +0,0 @@
from css import UserMadeSheet, css_dump
print(UserMadeSheet.Paragraph, UserMadeSheet.Anchor)
print(css_dump())

View File

@ -1,16 +0,0 @@
from pyx import DIV, H1, H2, P, SPAN, BR, IMG, A
print(
DIV
[
H1("Welcome!"),
P(),
IMG(id="header.png"),
A(css="test"),
A["other?"],
A(css="www.site.com")
[
IMG(id="image.png")
]
]
)

15
user_styles.py Normal file
View File

@ -0,0 +1,15 @@
from css import SHEET, CSS, Font, PX
class UserMadeSheet(SHEET):
Paragraph=CSS[
Font( size=PX(10) ),
](512)[
Font( size=PX(18), family="sans" ),
](1024)[
Font( size=PX(120) ),
]
Anchor=CSS[
Font( size=PX(123) )
]

21
user_template.py Normal file
View File

@ -0,0 +1,21 @@
from pyx import DIV, H1, P, IMG, A
from user_styles import UserMadeSheet
def ViewFunction(name:str):
return DIV[
H1[f'Welcome, {name}!'],
A(css=(UserMadeSheet.Anchor), href="www.site.com")
[
IMG(src="image.png")
],
P(css=UserMadeSheet.Paragraph)
[
"""hello and lorem ipsum dolor sit amet, consectetur adipiscing elit.
Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi
ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit
in voluptate velit esse cillum dolore eu fugiat nulla pariatur.
Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia
deserunt mollit anim id est laborum."""
]
]