Compare commits

..

2 Commits

Author SHA1 Message Date
432f16ba08 feat: added a nice overview card site 2025-04-14 15:45:06 +02:00
ee26af8b35 feat: refactored the overview 2025-04-14 15:12:56 +02:00
5 changed files with 230 additions and 102 deletions

View File

@ -26,8 +26,7 @@
<a href="../">Go Back</a>
</div>
<div class="content">
<content />
{article_content}
</div>
</section>

View File

@ -0,0 +1,38 @@
<!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>
<section class="section">
<div class="container">
<div class="column is-half is-offset-one-quarter">
{overview_cards}
</div>
</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>

View File

@ -0,0 +1,8 @@
<div class="card mb-4">
<a href="{article_href}" class="card mb-4" style="color: inherit; text-decoration: none;">
<div class="card-content" href="{article_href}">
<p class="title">{article_language_flag} {article_language_name}</p>
<p class="content">{article_preview}</p>
</div>
</a>
</div>

View File

@ -1,97 +1,100 @@
from __future__ import annotations
import logging
import shutil
from pathlib import Path
import os
import markdown
from typing import Optional
from typing import Optional, Union, Dict, Generator, List
from .config import SOURCE_DIRECTORY, DIST_DIRECTORY, LANGUAGE_INFORMATION
from .config import SOURCE_DIRECTORY, DIST_DIRECTORY, LANGUAGE_INFORMATION, ARTICLE_PREVIEW_LENGTH
logger = logging.getLogger("stsg.build")
def copy_static(src, dst):
if not os.path.exists(src):
logger.warn("The static folder '%s' wasn't defined.", src)
class CustomPath:
def __init__(self, path: Path):
self.path = path
def __repr__(self) -> str:
return str(self.path)
@property
def source_path(self) -> Path:
return Path(SOURCE_DIRECTORY, self.path)
@property
def dist_path(self) -> Path:
return Path(DIST_DIRECTORY, self.path)
@property
def name(self) -> str:
return Path(self.path).name
@property
def parent(self) -> CustomPath:
return CustomPath(Path(self.path).parent)
@property
def stem(self) -> str:
return Path(self.path).stem
def iterdir(self) -> Generator[CustomPath, None, None]:
for p in self.source_path.iterdir():
yield CustomPath(Path(self.path, p.name))
def get_child(self, name: str, force_directory: bool = False, force_file: bool = False) -> Optional[CustomPath]:
child = Path(self.source_path, name)
if not child.exists():
return None
if force_directory and not child.is_dir():
return None
if force_file and not child.is_file():
return None
return CustomPath(Path(self.path, name))
def read_text(self) -> str:
return self.source_path.read_text()
def copy_static(path: CustomPath):
src = path.source_path
dst = path.dist_path
if not src.exists():
logger.warning("The static folder '%s' wasn't defined.", src)
return
logger.info("copying static files from '%s' to '%r'", src, dst)
logger.info("Copying static files from '%s' to '%s'", src, dst)
os.makedirs(dst, exist_ok=True)
dst.mkdir(parents=True, exist_ok=True)
for root, dirs, files in os.walk(src):
if any(p.startswith(".") for p in Path(root).parts):
continue
root_path = Path(root)
# Compute relative path from the source root
rel_path = os.path.relpath(root, src)
dest_dir = os.path.join(dst, rel_path)
if any(part.startswith(".") for part in root_path.parts):
continue
os.makedirs(dest_dir, exist_ok=True)
rel_path = root_path.relative_to(src)
dest_dir = dst / rel_path
dest_dir.mkdir(parents=True, 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 = None
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 convert_md(src: Path, dst: Path, context: Optional[Context] = None):
logger.info("converting %s", src)
html_content = markdown.markdown(src.read_text())
context = context or Context(str(src.parent))
full_page = context.get_text(content=html_content)
folder_dst = dst.parent / dst.name.replace(".md", "")
folder_dst.mkdir(parents=True, exist_ok=True)
with Path(folder_dst, "index.html").open("w") as f:
f.write(full_page)
src_file = root_path / file
dest_file = dest_dir / file
shutil.copy2(src_file, dest_file)
class CustomLanguageCode:
def __init__(self, file: Path):
self.file: Path = file
@property
def language_code(self) -> str:
return self.file.name.replace(".md", "")
@property
def relative_url(self) -> str:
return "/" + str(Path(self.file.parent, self.language_code))
def __init__(self, language_code: str):
self.language_code = language_code
def __repr__(self) -> str:
return f"{self.language_code}"
@ -115,45 +118,126 @@ class CustomLanguageCode:
def native_name(self) -> str:
return self._get_additional_data()["native_name"]
class Article(CustomPath):
def __init__(self, path: CustomPath):
super().__init__(path.path)
def get_content(self) -> str:
if self.name.endswith(".md"):
return markdown.markdown(self.read_text())
return self.read_text()
@property
def html_code(self) -> str:
return f'<ul><a href="{self.relative_url}"><bold>{self.flag} {self.native_name}</bold></a></ul>'
def article_directory(self) -> Path:
return self.dist_path.parent / self.stem
@property
def language_code(self) -> CustomLanguageCode:
return CustomLanguageCode(self.stem)
def get_article_keys(self) -> Dict[str, str]:
article_content = self.get_content()
return {
"article_content": article_content,
"article_preview": article_content[:ARTICLE_PREVIEW_LENGTH],
"article_href": "/" + str(self.path.parent / self.stem),
"article_language_name": self.language_code.native_name,
"article_language_code": self.language_code.language_code,
"article_language_flag": self.language_code.flag,
}
class Template:
def __init__(self, root: CustomPath):
self.root = root
self.articles: List[Article] = []
def copy(self):
return Template(root=self.root)
def _replace_keywords(self, text: str, **placeholder_values: str) -> str:
for key, value in placeholder_values.items():
text = text.replace("{" + key + "}", value)
return text
def get_article_template(self) -> str:
article = self.root.get_child("article.html", force_file=True)
return article.source_path.read_text() if article else "{article_content}"
def build_article(self, path: Article):
logger.info("converting %s", path)
article_text = self._replace_keywords(
self.get_article_template(),
**path.get_article_keys(),
)
path.article_directory.mkdir(parents=True, exist_ok=True)
with Path(path.article_directory, "index.html").open("w") as f:
f.write(article_text)
self.articles.append(path)
def get_overview_card_template(self) -> str:
overview_card = self.root.get_child("overview_card.html", force_file=True)
return overview_card.source_path.read_text() if overview_card else "<a href=\"{article_href}\"> {article_language_flag} {article_language_name} </a>"
def get_overview_template(self) -> str:
overview = self.root.get_child("overview.html", force_file=True)
return overview.source_path.read_text() if overview else "{overview_cards}"
def build_overview(self, root: CustomPath):
if not len(self.articles):
return
overview_card_template = self.get_overview_card_template()
overview_cards = "\n".join(self._replace_keywords(
overview_card_template,
**a.get_article_keys(),
) for a in self.articles)
overview_text = self._replace_keywords(
self.get_overview_template(),
overview_cards = overview_cards,
)
with Path(root.dist_path, "index.html").open("w") as f:
f.write(overview_text)
def walk_directory(root):
src_path = Path(SOURCE_DIRECTORY, root)
dst_path = Path(DIST_DIRECTORY, root)
context = Context(src_path)
language_codes_found = []
def walk_directory(root: CustomPath, template: Optional[Template] = None):
template_dir = root.get_child("_templates", force_directory=True)
if template_dir is not None:
template = Template(template_dir)
for current_full_path in src_path.iterdir():
current_name = Path(current_full_path).name
current_dst = Path(dst_path, current_name)
current_src = Path(src_path, current_name)
if template is None:
logger.error("Didn't find template for %d", root)
return
if current_name == "static":
copy_static(current_src, current_dst)
continue
if current_name.endswith(".md"):
convert_md(current_src, current_dst, context=context)
language_codes_found.append(CustomLanguageCode(Path(root, current_name)))
for current_path in root.iterdir():
if current_path.name.startswith("_") or current_path.name == "static":
continue
if current_src.is_dir():
walk_directory(Path(root, current_full_path.name))
if current_path.source_path.is_file():
template.build_article(Article(current_path))
continue
content = f"""
<li>
{''.join(l.html_code for l in language_codes_found)}
</li>
"""
with Path(dst_path, "index.html").open("w") as f:
f.write(context.get_text(content=content))
if current_path.source_path.is_dir():
walk_directory(current_path, template=template.copy())
template.build_overview(root=root)
static_dir = root.get_child("static", force_directory=True)
if static_dir:
copy_static(static_dir)
def build():
logger.info("building static page")
walk_directory("")
walk_directory(CustomPath(Path()))

View File

@ -1,11 +1,10 @@
SOURCE_DIRECTORY = "src"
DIST_DIRECTORY = "dist"
# relative to SOURCE_DIRECTORY / DIST_DIRECTORY
STATIC_DIRECTORY = "static"
# config template stuff
ARTICLE_PREVIEW_LENGTH = 200
# FOR DEVELOPMENT
CODE_DIRECTORY = "stsg"
# LANGUAGE INFORMATION