STSG/stsg/build.py

244 lines
7.2 KiB
Python

from __future__ import annotations
import logging
import shutil
from pathlib import Path
import os
import markdown
from typing import Optional, Union, Dict, Generator, List
from .config import SOURCE_DIRECTORY, DIST_DIRECTORY, LANGUAGE_INFORMATION, ARTICLE_PREVIEW_LENGTH
logger = logging.getLogger("stsg.build")
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 '%s'", src, dst)
dst.mkdir(parents=True, exist_ok=True)
for root, dirs, files in os.walk(src):
root_path = Path(root)
if any(part.startswith(".") for part in root_path.parts):
continue
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 = root_path / file
dest_file = dest_dir / file
shutil.copy2(src_file, dest_file)
class CustomLanguageCode:
def __init__(self, language_code: str):
self.language_code = language_code
def __repr__(self) -> str:
return f"{self.language_code}"
def _get_additional_data(self) -> dict:
parsed_language_code = self.language_code.lower().replace("-", "_")
if parsed_language_code in LANGUAGE_INFORMATION:
return LANGUAGE_INFORMATION[parsed_language_code]
parsed_language_code = parsed_language_code.split("_")[0]
if parsed_language_code in LANGUAGE_INFORMATION:
return LANGUAGE_INFORMATION[parsed_language_code]
return {}
@property
def flag(self) -> str:
return self._get_additional_data()["flag"]
@property
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 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: CustomPath, template: Optional[Template] = None):
template_dir = root.get_child("_templates", force_directory=True)
if template_dir is not None:
template = Template(template_dir)
if template is None:
logger.error("Didn't find template for %d", root)
return
for current_path in root.iterdir():
if current_path.name.startswith("_") or current_path.name == "static":
continue
if current_path.source_path.is_file():
template.build_article(Article(current_path))
continue
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(CustomPath(Path()))