Compare commits

...

7 Commits

14 changed files with 21823 additions and 1 deletions

2
.gitignore vendored
View File

@ -172,3 +172,5 @@ cython_debug/
# PyPI configuration file
.pypirc
dist

5
.vscode/settings.json vendored Normal file
View File

@ -0,0 +1,5 @@
{
"cSpell.words": [
"stsg"
]
}

View File

View File

30
pyproject.toml Normal file
View File

@ -0,0 +1,30 @@
[project]
name = "stsg"
dependencies = [
"watchdog~=6.0.0"
]
dynamic = []
authors = []
readme = "README.md"
requires-python = ">=3.10"
classifiers = []
version = "0.0.0"
[project.scripts]
stsg = "stsg.__main__:build"
stsg_dev = "stsg.__main__:hot_reload"
[build-system]
requires = ["hatchling", "hatch-requirements-txt"]
build-backend = "hatchling.build"
[tool.hatch.build.targets.sdist]
include = ["stsg/*.py"]
[tool.hatch.build.targets.wheel]
packages = ["stsg"]
[tool.hatch.metadata]
allow-direct-references = true

View File

@ -0,0 +1 @@

View File

@ -0,0 +1 @@

37
src/index.html Normal file
View File

@ -0,0 +1,37 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>STSG</title>
<link rel="stylesheet" href="static/bulma.min.css" />
</head>
<body>
<!-- Header (Navbar) -->
<nav
class="navbar is-primary"
role="navigation"
aria-label="main navigation"
>
<div class="navbar-brand">
<a class="navbar-item" href="#">
<strong>Static Translated Site Generator</strong>
</a>
</div>
</nav>
<!-- Main Content -->
<section class="section">
<div class="container">
<content />
</div>
</section>
<!-- Footer -->
<footer class="footer">
<div class="content has-text-centered">
<p><strong>STSG</strong> by Hazel. &copy; 2025</p>
</div>
</footer>
</body>
</html>

21551
src/static/bulma.css vendored Normal file

File diff suppressed because it is too large Load Diff

3
src/static/bulma.min.css vendored Normal file

File diff suppressed because one or more lines are too long

99
stsg/__main__.py Normal file
View File

@ -0,0 +1,99 @@
import logging
from watchdog.observers import Observer
from watchdog.events import FileSystemEventHandler
import time
import os
import subprocess
from .config import SOURCE_DIRECTORY, CODE_DIRECTORY
from .build import build as complete_build
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger("stsg")
def build():
complete_build()
class MarkdownChangeHandler(FileSystemEventHandler):
def on_modified(self, event):
if event.is_directory:
return
filename = os.path.basename(event.src_path)
if filename.startswith('.') or filename.endswith(('~', '.tmp', '.swp')):
return
logger.info("%s changed, building", event.src_path)
build()
class PythonChangeHandler(FileSystemEventHandler):
def __init__(self, command):
self.logger = logging.getLogger("stsg.hot_reload")
self.env = os.environ.copy()
self.env["IS_CHILD"] = "true"
self.command = command
self.process = self.start_process()
def start_process(self):
self.logger.info("Starting process...")
return subprocess.Popen(self.command, env=self.env)
def restart_process(self):
self.logger.info("Restarting process...")
self.process.kill()
self.process = self.start_process()
def on_modified(self, event):
if event.is_directory:
return
filename = os.path.basename(event.src_path)
if filename.startswith('.') or filename.endswith(('~', '.tmp', '.swp')):
return
if event.src_path.endswith(".py"):
self.logger.info(f"Detected change: {event.src_path}")
self.restart_process()
def stop_process(self):
if self.process:
self.process.terminate()
def build_on_change():
build()
observer = Observer()
observer.schedule(MarkdownChangeHandler(), path=SOURCE_DIRECTORY, recursive=True)
observer.start()
try:
while True:
time.sleep(1) # Keeps the thread alive
except KeyboardInterrupt:
observer.stop()
observer.join()
def hot_reload():
if os.environ.get("IS_CHILD") == "true":
build_on_change()
return
observer = Observer()
observer.schedule(PythonChangeHandler(["stsg_dev"]), path=CODE_DIRECTORY, recursive=True)
observer.start()
try:
while True:
time.sleep(1) # Keeps the thread alive
except KeyboardInterrupt:
observer.stop()
observer.join()

84
stsg/build.py Normal file
View File

@ -0,0 +1,84 @@
import logging
import shutil
from pathlib import Path
import os
from .config import SOURCE_DIRECTORY, DIST_DIRECTORY, STATIC_DIRECTORY
logger = logging.getLogger("stsg.build")
def copy_static():
src = str(Path(SOURCE_DIRECTORY, STATIC_DIRECTORY))
dst = str(Path(DIST_DIRECTORY, STATIC_DIRECTORY))
if not os.path.exists(src):
logger.warn("The static folder '%s' wasn't defined.", src)
return
logger.info("copying static files from '%s' to '%r'", src, dst)
os.makedirs(dst, exist_ok=True)
for root, dirs, files in os.walk(src):
if any(p.startswith(".") for p in Path(root).parts):
continue
# Compute relative path from the source root
rel_path = os.path.relpath(root, src)
dest_dir = os.path.join(dst, rel_path)
os.makedirs(dest_dir, exist_ok=True)
for file in files:
if file.startswith("."):
continue
src_file = os.path.join(root, file)
dest_file = os.path.join(dest_dir, file)
shutil.copy2(src_file, dest_file)
class Context:
def __init__(self, root: str = SOURCE_DIRECTORY):
self.file = Path(root, "index.html")
current_root = Path(root)
while current_root.parts and self.file is None:
current_file = Path(current_root, "index.html")
if current_file.exists() and current_file.is_file:
self.file = current_file
current_root = current_root.parent
if self.file is None:
logger.error("couldn't find context for %s", root)
exit(1)
logger.info("%s found context %r", root, str(self.file))
def get_text(self, placeholder_values: dict):
text = self.file.read_text()
for key, value in placeholder_values.items():
text = text.replace(f"<{key}/>", value)
text = text.replace(f"<{key} />", value)
return text
def build():
logger.info("building static page")
copy_static()
context = Context()
# debug
t = context.get_text({
"content": """
<h1 class="title">Hello World</h1>
<p class="subtitle">My first website with <strong>Bulma</strong>!</p>
"""
})
with Path(DIST_DIRECTORY, "index.html").open("w") as f:
f.write(t)

9
stsg/config.py Normal file
View File

@ -0,0 +1,9 @@
SOURCE_DIRECTORY = "src"
DIST_DIRECTORY = "dist"
# relative to SOURCE_DIRECTORY / DIST_DIRECTORY
STATIC_DIRECTORY = "static"
# FOR DEVELOPMENT
CODE_DIRECTORY = "stsg"