Compare commits
7 Commits
ded054147b
...
8e64e48097
Author | SHA1 | Date | |
---|---|---|---|
8e64e48097 | |||
fab58de71d | |||
0b7bfddd64 | |||
9e2ee75927 | |||
3503d1d42f | |||
6d1ea43089 | |||
d68cfe15fe |
2
.gitignore
vendored
2
.gitignore
vendored
@ -172,3 +172,5 @@ cython_debug/
|
||||
|
||||
# PyPI configuration file
|
||||
.pypirc
|
||||
|
||||
dist
|
5
.vscode/settings.json
vendored
Normal file
5
.vscode/settings.json
vendored
Normal file
@ -0,0 +1,5 @@
|
||||
{
|
||||
"cSpell.words": [
|
||||
"stsg"
|
||||
]
|
||||
}
|
0
dist/example_page_1/en/index.html
vendored
0
dist/example_page_1/en/index.html
vendored
0
dist/example_page_2/de/index.html
vendored
0
dist/example_page_2/de/index.html
vendored
30
pyproject.toml
Normal file
30
pyproject.toml
Normal 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
|
||||
|
@ -0,0 +1 @@
|
||||
|
@ -0,0 +1 @@
|
||||
|
37
src/index.html
Normal file
37
src/index.html
Normal 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. © 2025</p>
|
||||
</div>
|
||||
</footer>
|
||||
</body>
|
||||
</html>
|
21551
src/static/bulma.css
vendored
Normal file
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
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
99
stsg/__main__.py
Normal 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
84
stsg/build.py
Normal 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
9
stsg/config.py
Normal 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"
|
Loading…
x
Reference in New Issue
Block a user