STSG/stsg/build.py

238 lines
8.1 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, DefaultDict
from bs4 import BeautifulSoup
from collections import defaultdict
from .config import SOURCE_DIRECTORY, DIST_DIRECTORY, LANGUAGE_INFORMATION, ARTICLE_PREVIEW_LENGTH, DEFAULT_LANGUAGE
def replace_values(template: str, values: Dict[str, str]) -> str:
for key, value in values.items():
template = template.replace("{" + key + "}", value)
return template
def get_first_header_content(content):
soup = BeautifulSoup(content, 'html.parser')
for level in range(1, 7):
header = soup.find(f'h{level}')
if header:
return header.get_text(strip=True)
return self.language_code.native_name
class Template:
def __init__(self, folder: Path):
self.folder = folder
self.article: str = (self.folder / "article.html").read_text()
self.overview: str = (self.folder / "overview.html").read_text()
self.overview_card: str = (self.folder / "overview_card.html").read_text()
class ArticleTranslation:
def __init__(self, file: Path, article_overview: ArticleOverview):
self.file = file
self.article_overview = article_overview
self.language_code = self.file.stem
self.article_content = self.file.read_text()
if self.file.suffix == ".md":
self.article_content = markdown.markdown(self.article_content)
self.title = get_first_header_content(self.article_content)
self.location_in_tree = [self.language_code, *self.article_overview.location_in_tree]
self.url = "/" + "/".join(self.location_in_tree)
self.dist_path = Path(DIST_DIRECTORY, *self.location_in_tree)
_language_info = DEFAULT_LANGUAGE
parsed_language_code = self.language_code.lower().replace("-", "_")
if parsed_language_code in LANGUAGE_INFORMATION:
_language_info = LANGUAGE_INFORMATION[parsed_language_code]
elif parsed_language_code.split("_")[0] in LANGUAGE_INFORMATION:
_language_info = LANGUAGE_INFORMATION[parsed_language_code.split("_")[0]]
self.language_name: str = _language_info["native_name"]
self.language_flag: str = _language_info["flag"]
self.priority: int = _language_info.get("priority", 0)
def build(self):
self.dist_path.mkdir(parents=True, exist_ok=True)
with Path(self.dist_path, "index.html").open("w") as f:
f.write(self.get_article())
def _get_values(self, return_foreign_articles: bool = True) -> Dict[str, str]:
r = {
"article_content": self.article_content,
"article_preview": self.article_content[:ARTICLE_PREVIEW_LENGTH] + "...",
"article_url": self.url,
"article_overview_url": self.article_overview.url,
"article_slug": self.article_overview.slug,
"article_title": self.title,
"article_language_name": self.language_name,
"article_language_code": self.language_code,
"article_language_flag": self.language_flag,
}
if return_foreign_articles:
r.update(ARTICLE_REFERENCE_VALUES[self.language_code])
return r
def get_article_values(self) -> Dict[str, str]:
res = {}
for key, value in self._get_values(return_foreign_articles=False).items():
res[key + ":" + self.article_overview.slug] = value
return res
def get_article(self) -> str:
global TEMPLATE
return replace_values(TEMPLATE.article, self._get_values())
def get_overview_card(self) -> str:
global TEMPLATE
return replace_values(TEMPLATE.overview_card, self._get_values())
class ArticleOverview:
def __init__(self, directory: Path, location_in_tree: Optional[List[str]] = None):
self.directory = directory
self.slug = self.directory.name
self.article_written = self.directory.stat().st_mtime
print(self.article_written)
self.location_in_tree: List[str] = location_in_tree or []
self.location_in_tree.append(self.slug)
self.url = "/" + "/".join(self.location_in_tree)
self.dist_path = Path(DIST_DIRECTORY, *self.location_in_tree)
if self.slug in ARTICLE_LAKE:
logger.error("two articles have the same name at %s and %r", ARTICLE_LAKE[self.slug].directory, self.directory)
exit(1)
ARTICLE_LAKE[self.slug] = self
self.child_articles: List[ArticleOverview] = []
self.article_translations: List[ArticleTranslation] = []
self.article_translations_map: Dict[str, ArticleTranslation] = {}
for c in self.directory.iterdir():
if c.is_file():
at = ArticleTranslation(c, self)
self.article_translations.append(at)
self.article_translations_map[at.language_code] = at
elif c.is_dir():
self.child_articles.append(ArticleOverview(
directory=c,
location_in_tree=self.location_in_tree.copy(),
))
# the tree is built
self.article_translations.sort(key=lambda a: a.priority, reverse=True)
self.overview_cards = "\n".join(a.get_overview_card() for a in self.article_translations)
def build(self):
# builds the tree structure to the dist directory
self.dist_path.mkdir(parents=True, exist_ok=True)
with Path(self.dist_path, "index.html").open("w") as f:
f.write(self.get_overview())
for at in self.article_translations:
at.build()
for ca in self.child_articles:
ca.build()
def _get_values(self, return_foreign_articles: bool = True) -> Dict[str, str]:
r = {
"article_url": self.url,
"article_title": self.slug,
"article_slug": self.slug,
"article_overview_url": self.url,
"article_overview_cards": self.overview_cards,
}
if return_foreign_articles:
r.update(ARTICLE_REFERENCE_VALUES[""])
return r
def get_article_values(self) -> Dict[str, str]:
res = {}
for key, value in self._get_values(return_foreign_articles=False).items():
res[key + ":" + self.slug] = value
return res
def get_overview(self) -> str:
global TEMPLATE
return replace_values(TEMPLATE.overview, self._get_values())
def copy_static():
src = str(Path(SOURCE_DIRECTORY, "static"))
dst = str(Path(DIST_DIRECTORY, "static"))
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)
# GLOBALS
logger = logging.getLogger("stsg.build")
TEMPLATE = Template(Path(SOURCE_DIRECTORY, "templates"))
ARTICLE_LAKE: Dict[str, ArticleOverview] = {}
ARTICLE_REFERENCE_VALUES: DefaultDict[str, Dict[str, str]] = defaultdict(dict)
def build():
logger.info("building static page")
copy_static()
tree = ArticleOverview(directory=Path(SOURCE_DIRECTORY, "articles"))
# build article reverence values
for article_overview in ARTICLE_LAKE.values():
logger.info("found article %s", article_overview.slug)
ARTICLE_REFERENCE_VALUES[""].update(article_overview.get_article_values())
for language_code, at in article_overview.article_translations_map.items():
ARTICLE_REFERENCE_VALUES[language_code].update(at.get_article_values())
tree.build()