This commit is contained in:
Seth Trowbridge 2025-08-21 13:39:30 -04:00
parent dd440fa779
commit 61fac50e20
8 changed files with 107 additions and 182 deletions

25
css.py
View File

@ -1,6 +1,5 @@
from dataclasses import dataclass
from typing import Optional, Tuple, Union, Dict, List
import random
class Unit():
unit = "px"
@ -24,7 +23,6 @@ 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")
@ -101,9 +99,6 @@ class Kickoff():
CSS = Kickoff()
def TAG(*styles:Chainer):
for item in styles:
print(item)
##########################
@ -121,6 +116,12 @@ class Writer:
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):
def __getattribute__(cls, name:str):
@ -137,17 +138,3 @@ class _SHEET(type):
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!
</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)

29
pyx.py
View File

@ -1,14 +1,17 @@
from typing import List, Tuple, Union, Optional
from typing import Any, 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,12 +27,12 @@ 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, {})
def __repr__(self) -> str:
@ -46,7 +49,7 @@ class Branch(Leaf):
def __repr__(self) -> str:
return f'<{self.name}{self.attrs or ""}></{self.name}>'
def __getitem__(self, key:Union[Renderable, Tuple[Renderable, ...]]) -> str:
def __getitem__(self, key:Union[Renderable, Tuple[Renderable, ...]]) -> Renderable:
if isinstance(key, tuple):
children = f'\n'.join(str(k) for k in key)
else:
@ -55,12 +58,12 @@ class Branch(Leaf):
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})
IMG = IMGTag()
@ -77,3 +80,13 @@ H3 = Branch("h3")
H4 = Branch("h4")
SECTION = Branch("section")
MAIN = Branch("main")
HTML = Branch("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()

View File

@ -1,5 +0,0 @@
from css import UserMadeSheet, TAG, Writer
print(TAG(UserMadeSheet.Paragraph))
print(Writer.log)

View File

@ -1,12 +0,0 @@
from pyx import DIV, H1, H2, P, SPAN, BR, IMG, A
print(
DIV
[
H1["Welcome!"],
A(css="www.site.com", href="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) )
]

32
user_template.py Normal file
View File

@ -0,0 +1,32 @@
from pyx import DIV, H1, P, IMG, A, HTML, HEAD, BODY, STYLE, Capture
from user_styles import UserMadeSheet
output = Capture(
DIV
[
H1["Welcome!"],
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."""
]
]
)
with open("example.html", "w") as file:
file.write(f'<!doctype html>{HTML[
HEAD[
STYLE[ output.css ]
],
BODY[ output.html ]
]}')