diff --git a/src/_templates/article.html b/src/_templates/article.html deleted file mode 100644 index 51e9b94..0000000 --- a/src/_templates/article.html +++ /dev/null @@ -1,37 +0,0 @@ - - - - - - {article_language_flag} {article_title} - - - - - - - -
-
- {article_content} -
-
- - - - - diff --git a/src/_templates/overview.html b/src/_templates/overview.html deleted file mode 100644 index 77022d4..0000000 --- a/src/_templates/overview.html +++ /dev/null @@ -1,55 +0,0 @@ - - - - - - STSG - - - - - - -
-
-
{overview_cards}
-
-
- - - - - - - diff --git a/src/_templates/overview_card.html b/src/_templates/overview_card.html deleted file mode 100644 index 3261dcb..0000000 --- a/src/_templates/overview_card.html +++ /dev/null @@ -1,8 +0,0 @@ -
- -
-

{article_language_flag} {article_title}

-

{article_preview}

-
-
-
\ No newline at end of file diff --git a/src/example/de-DE.md b/src/example/de-DE.md deleted file mode 100644 index 5c6361a..0000000 --- a/src/example/de-DE.md +++ /dev/null @@ -1,57 +0,0 @@ -meow - -# Abstrahere reddita celebrare in ossa - -## Usque de celebrabant puer - -Lorem markdownum, nec ora et vero me nec natae suadent. Nec damno ignorat -propiore aliquid temptata decipienda habetur. Vulnera lacrimis aequoreo madidos, -copia uvae, herbosaque quoque, per harenas, canos fui monstro Peleus. - -Et fuge cum liquidum puer Herculis arentis, tantum caudaque et generi vilior, in -rubore. Caeli modo palmis, suo tria accipe visus non similis qui remittat -retentus porrigit fluxit ubi testis. - -> Sulphura et color reliquit dextera: quid summa continuere obductos egesto -> moriens **fluentia vult iunctamque**, mihi patres spiro. Est Iovis **imperat -> quem est** putavi annis *omnis*; flumina, leporem constabat grave pelagi, -> insiluit, igne invito? Auctor circuit quod. - -## Rhoetus gravis - -Parari modo in sustulerat: ora sic verba meruit, uti. Pignora citus facto -amplectimur cupido amentes foedantem multoque datura. -[Quid](http://www.prima.net/se) ex tanti armaque exhibita descenderat! - -1. Alis ima -2. Mirantur nive sit -3. Maris ut adlabimur humana fine quam vultusque - -## A idonea miserum - -Montes tibi deorum igitur. Poterat nunc porrigitur perdidimus *sidereos animi -praesepia* nihil praeferri functa, in **Pleias removete** oculos sollemni -Tatius: **modo**. Inde dedit! Atque in matrem spinae foret ponti quam dixit, -aras. Gladii addendum fiducia magno, se quo fata humo esse **tellure corpora -discederet** sucis manibus, parentem ante, Iovem. - -Laqueique honore sequentia tyranni Harpalos, paelex, foedat tempestiva nomen. -Sit ter indiciumque requirit utrumque in nil et *suspectus*, quaerite patriam -nec facta [securi](http://heuqua.io/adhaut). Confessa ut per sit nostro futura -metaque oblitae fameque exit adspiciens. Morte flectere invidiam certe cum -vixque *nubes clamor viderunt* praeceps infamis collo percussis axem plena saxum -urbes ferre undae. Totoque utuntur ore lupus inplet sibila ullam, qui corpore -*contermina aera*? - -Convocat ipse abeunt sententia concolor a Auguste epops solent iubet qui -aequora. In illi, solvente resecare violentam nescio accipit multarum [aureus -exspectatus](http://oris-tibi.io/nasci) ergo ora. Positis **inquit**, sit iamque -hederae ulterius, pontus linguae matutina terra sic isse Graium passosque -sanguinis secus petit! - -Adfore dei volucrum Lydas; [hoc](http://similemnescitve.net/ipsamquee) quam -[superosque](http://www.quoqueabit.com/cumqueiam.html) caligine vulnera quoque -corpus foedaque mentis qui nectare, fatendo sensit! Rursus nulli miraris nuda -*Acrisio*. Cum modo, satis dissuadet luce; cum *freta* ab et diu, labor *tenuata -ieiunia*? Caesosque thalamique precor, dedit nulla loca [arce -et](http://viscera-rerum.com/mixta) tinguit aera. diff --git a/src/example/en.md b/src/example/en.md deleted file mode 100644 index 750f00b..0000000 --- a/src/example/en.md +++ /dev/null @@ -1,75 +0,0 @@ -# Populumque avidosque Sparte quoque auctore equidem - -## Sunt aevis - -Lorem markdownum turbavere prisca Aeacidae morando esse. Quam Styga spectata, -pariter Iove iunctis exercere Solis? Atlantis possit succurrere quam! - - if (stationRecord < ctp(rup, columnBase, dtd)) { - impact_qbe_symbolic(bank_c(exploit_gnutella_social), inbox); - marketing = telnetWebmasterFpu; - circuitSoapDns(dac, ieee); - } else { - ebook.spider(wildcard_publishing_memory.tcpDisk(encoding, 48149), -1 + - copyright_flash_icmp, superscalar_cluster + kofficeIsp); - reimage.mac = dslWebmail; - kilobyteVariable.margin_keylogger = dvd + microcomputer; - } - font_switch(servlet, file(1, protocol) * skinTouchscreen, - wheel_station_computer); - var startRom = bit(php_touchscreen_icio); - var unicode_hover_tiger = command(scan_install_cd(ruby(mail_chipset, web, - clientMemeSoftware), optical, wirelessMegahertz), oasis); - -## Est nec locumque anxia et - -Maligno puppes potuit petit. **Ipse regnat venit** tangit mitti opibus est unus -spectacula erat! Bacche qui dedit in ardet Phrygiis Liternum ipso ille Orphei -Canentem *ut*, parenti terrae! Frondibus deus sine leoni frustraque lentus. Est -deos cum corripuit erat sibi concussit simul; suus tantum. - -Camenis Lucifer ex geniti sitis quem. Styga si Ceae nova media remugis: haerens -ridet, nam [Pindo](http://www.murra.com/argolicaemaximus) est tritis flamma -[dixit una licebit](http://www.sedare-mopsus.net/) Pelia perdite! Aura *aurea -mecum* una mirabantur mansit domum simul de Euboica altis vincula tenentis vires -sub, *Scythicae*. Mora sitis pocula. Ultimus idem triplices inquit. - -## Et atque ministris imagine fas tenuit fornacibus - -Adeunda suffundit ille: Bacchi moribundo et quam **cacumina videre Tamasenum**. -Gauderet in [non arbitrium caelo](http://www.madidusperiuraque.io/). - -## In corpora in micat Phoebus corque transitus - -Mihi in macies, ab avoque malorum decusque. Appellant expellam unus colore -exiguo, maior ara loqui sit vires. - - var uat = cardPrinterLocalhost.pppoe(display, operating_row_fsb( - scalable_lion, compilerHeuristicTweet - 22)); - var toslink_software = tokenPciPrompt.source_x_firmware(drive, - boxSdkDlc.servicesIcsDrive(certificate_cycle_illegal(resolution)), - outboxTftMap + rssTextZebibyte.flashImpactDisk(3, eup_ad)); - var compression = programWebPort; - if (intellectual_left_system) { - favorites(lossyDramDay + 7, upsSliTruncate, 2); - laptop.android = ocr_piracy(clean, flatbedRte - -1); - directx_file_cable = -1; - } else { - point_sink_controller(flash(filenameDataAccess, vleSoftwareListserv), - point_rate_cmos(control_soa_restore)); - javascript *= nic_format; - address = nameDcim(touchscreenLink.port_port(diskHeat, vaporware)); - } - -## Enim nunc solvi - -Est pudet citharae, corpus? Modo in armentaque pennisque videri aquarum, est -equos ne in vulnere domus; maris. Quodque quoque orbe omnes metus, sol -[putas](http://www.uno.com/deorumvolumine) marmore fuit secreta haut vobis, -faciendo, oro. - -Quid sequenti, supersunt quoque, sortite; in in tenetur vecta horriferamque -amabat. Vos nudae anni amor chelydri [Picus candentibus -et](http://pugnare.io/angulus.html) sonum, et sentibus geminato volucrem -mercibus bracchia, cum *posito* delubraque. Templa extrahit in totidem altera -Nioben, honoris sui, fibris! diff --git a/src/example/ku.md b/src/example/ku.md deleted file mode 100644 index 422ad0d..0000000 --- a/src/example/ku.md +++ /dev/null @@ -1,35 +0,0 @@ -# Navê Gotarê -Ev gotar, mînakek placeholder ya bi zimanê Kurdî ye. Nivîs, weşan û rûpelên nûçe di vê belgeyê de têne pêşniyar kirin. Ev metn ji bo testkirina layout an jî demo-yê hatiye çêkirin û çend bingehên cihanî yên naveroka gotarê nîşan dide. - -## Destpêk -Di destpêka gotarê de, dikarin rêvekên bingehîn û armancên mijarê vebêjin. Tu dikarî vê metnê bikar bînin ji bo şexsî an jî projeyên xwe: - -Lorem ipsum ji bo kurdî: -"Rojên nû û şevên hêja, ev nivîs têne çêkirin ku bi awayek nîşanbideke cîhan û hunermendî were afirandin." - -## Lêkolîn û Naverok -Di vê beşê de hinek îzahiyên naverokî yên placeholder hatine peyda kirin. Hûn dikarin bi rêza li jêr anînên mifteyî yên gotarê şop bikin: - -- **Mijar:** Di vê gotarê de, mijarên bi zor û hêja têne nîşandan. -- **Rêbaz:** Ew kesên ku ji ber vê gotarê şîrove û daxuyanî dikin, çavkaniyên xwe diguhezînin. -- **Agahî:** Di vê derbarê de, agahîyên cihanî, dîtin û têgihiştin hatine berhev kirin. - -Di bin vê şexsiyeta, hertişt di dema xwe de têgihiştin û anîn pêşiyê dayîn. - -## Şirove û Kod -Di vê paragrafa kêm-kêm de, em dikarin bingehên şiroveyê yên gotarê di kodê de jî şop bikin: - -```python -def peyama_kurdî(): - mesaj = "Her bijî Kurdistan! Rojên baş û serkeftin!" - return mesaj - -print(peyama_kurdî()) -``` - -Kodê li ser vê koda simplesa gotinê ye û dikare weşana nûçe an demo-yê projeyan bixebite. - -# Encama Gotarê - -Di encama gotarê de, hûn dikarin her çend beşên bingehîn yên nûçe, daxuyanî û şirove yên navekî bifikirin. -Bê guman, ev placeholder bi awayek qelew li ser çalakiya te ya malper, blog an jî her sedema dijîtal tê de karîger e. \ No newline at end of file diff --git a/stsg/build.py b/stsg/build.py index 3bb26be..ed39c67 100644 --- a/stsg/build.py +++ b/stsg/build.py @@ -7,255 +7,173 @@ import markdown from typing import Optional, Union, Dict, Generator, List from bs4 import BeautifulSoup -from .config import SOURCE_DIRECTORY, DIST_DIRECTORY, LANGUAGE_INFORMATION, ARTICLE_PREVIEW_LENGTH +from .config import SOURCE_DIRECTORY, DIST_DIRECTORY, LANGUAGE_INFORMATION, ARTICLE_PREVIEW_LENGTH, DEFAULT_LANGUAGE logger = logging.getLogger("stsg.build") -class CustomPath: - def __init__(self, path: Path): - self.path = path +def replace_values(template: str, values: Dict[str, str]) -> str: + for key, value in values.items(): + template = template.replace("{" + key + "}", value) - def __repr__(self) -> str: - return str(self.path) + return template - @property - def source_path(self) -> Path: - return Path(SOURCE_DIRECTORY, self.path) - @property - def dist_path(self) -> Path: - return Path(DIST_DIRECTORY, self.path) +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) - @property - def name(self) -> str: - return Path(self.path).name + return self.language_code.native_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)) +class Template: + def __init__(self, folder: Path): + self.folder = folder - def get_child(self, name: str, force_directory: bool = False, force_file: bool = False) -> Optional[CustomPath]: - child = Path(self.source_path, name) + 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() - if not child.exists(): - return None +TEMPLATE = Template(Path(SOURCE_DIRECTORY, "templates")) - if force_directory and not child.is_dir(): - return None - if force_file and not child.is_file(): - return None +class ArticleTranslation: + def __init__(self, file: Path, article_overview: ArticleOverview): + self.file = file + self.article_overview = article_overview + self.language_code = self.file.stem - return CustomPath(Path(self.path, name)) + self.article_content = self.file.read_text() + if self.file.suffix == ".md": + self.article_content = markdown.markdown(self.article_content) - def read_text(self) -> str: - return self.source_path.read_text() + self.url = "/" + self.language_code + self.article_overview.url + self.dist_path = Path(DIST_DIRECTORY, self.url.strip("/")) -def copy_static(path: CustomPath): - src = path.source_path - dst = path.dist_path + _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]] - if not src.exists(): - logger.warning("The static folder '%s' wasn't defined.", src) + 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) -> Dict[str, str]: + return { + "article_content": self.article_content, + "article_preview": self.article_content[:ARTICLE_PREVIEW_LENGTH] + "...", + "article_overview_url": self.article_overview.url, + "article_href": self.url, + "article_title": get_first_header_content(self.article_content), + "article_language_name": self.language_name, + "article_language_code": self.language_code, + "article_language_flag": self.language_flag, + } + + 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, url: str = ""): + self.directory = directory + self.name = self.directory.name + + self.url = url + "/" + self.name + self.dist_path = Path(DIST_DIRECTORY, self.url.strip("/")) + + self.child_articles: List[ArticleOverview] = [] + self.article_translations: List[ArticleTranslation] = [] + + for c in self.directory.iterdir(): + if c.is_file(): + self.article_translations.append(ArticleTranslation(c, self)) + elif c.is_dir(): + self.child_articles.append(ArticleOverview(c, self.url)) + + # 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) -> Dict[str, str]: + return { + "overview_cards": self.overview_cards, + "overview_slug": self.name, + } + + 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 '%s'", src, dst) + logger.info("copying static files from '%s' to '%r'", src, dst) - dst.mkdir(parents=True, exist_ok=True) + os.makedirs(dst, exist_ok=True) for root, dirs, files in os.walk(src): - root_path = Path(root) + if any(p.startswith(".") for p in Path(root).parts): + continue - if any(part.startswith(".") for part in root_path.parts): - continue + # Compute relative path from the source root + rel_path = os.path.relpath(root, src) + dest_dir = os.path.join(dst, rel_path) - rel_path = root_path.relative_to(src) - dest_dir = dst / rel_path - dest_dir.mkdir(parents=True, exist_ok=True) + os.makedirs(dest_dir, exist_ok=True) + for file in files: if file.startswith("."): continue - src_file = root_path / file - dest_file = dest_dir / file + src_file = os.path.join(root, file) + dest_file = os.path.join(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"] - - @property - def priority(self) -> int: - return self._get_additional_data().get("priority", 0) - - -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_first_header_content(self, 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 - - - 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_overview_href": "/" + str(self.path.parent), - "article_href": "/" + str(self.path.parent / self.stem), - "article_title": self.get_first_header_content(article_content), - "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 " {article_language_flag} {article_language_name} " - - 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() - - self.articles.sort(key=lambda a: a.language_code.priority, reverse=True) - 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())) + logger.info("building static page") + + copy_static() + tree = ArticleOverview(directory=Path(SOURCE_DIRECTORY, "pages")) + tree.build() \ No newline at end of file diff --git a/stsg/config.py b/stsg/config.py index 9e1b8ca..7be5336 100644 --- a/stsg/config.py +++ b/stsg/config.py @@ -756,3 +756,6 @@ LANGUAGE_INFORMATION = { "native_name": "isiZulu" } } + + +DEFAULT_LANGUAGE = LANGUAGE_INFORMATION["de"]