Compare commits

82 Commits

Author SHA1 Message Date
9b030b24f4 feat: added parent context 2025-04-16 16:42:21 +02:00
44b651cace feat: move meta values to article 2025-04-16 16:39:06 +02:00
eb2edc3710 feat: removed card templates 2025-04-16 16:17:57 +02:00
f63497090b Revert "feat: removed redundand templates"
This reverts commit 215142dee4.
2025-04-16 16:17:13 +02:00
215142dee4 feat: removed redundand templates 2025-04-16 16:09:18 +02:00
e391f64cc5 feat: removed redundand methods 2025-04-16 16:08:25 +02:00
4216d153fd feat: removed redundand methods 2025-04-16 16:08:11 +02:00
9241ecfee8 feat: added dynamic children cards in templates 2025-04-16 16:07:20 +02:00
4bc7a6d980 feat: added article translation templating 2025-04-16 16:01:38 +02:00
b50833f19f feat: dynamic translation cards 2025-04-16 15:57:17 +02:00
f5cccc30dc feat: dynamic translation cards 2025-04-16 15:56:34 +02:00
8a8f02c5bd feat: implemented code to use with jinja 2025-04-16 15:45:06 +02:00
292d71edc5 feat: added proper content 2025-04-16 15:26:43 +02:00
c9fb8fda93 feat: building recursive context structures 2025-04-16 14:26:50 +02:00
1fae03e70b feat: properly recursively building context 2025-04-16 14:20:51 +02:00
6ed94db8cf feat: added translations 2025-04-16 14:17:45 +02:00
02a7c29dba feat: add flat context to article 2025-04-16 14:05:16 +02:00
cf8e2955c2 feat: initializing location 2025-04-16 13:23:30 +02:00
7aa06b7f83 feat: added meta context 2025-04-16 13:15:27 +02:00
f7a690405b feat: add language context 2025-04-16 13:09:46 +02:00
93ea11cd0e feat: removed weird article cards 2025-04-16 12:30:54 +02:00
263281df3c feat: renamed ArticleOverview to article 2025-04-16 12:18:44 +02:00
amnesia
b953933e6f added jinja to deps 2025-04-15 23:41:03 +02:00
7db84492b5 feat: hastly implementation of article cards 2025-04-15 17:48:21 +02:00
c7cd5e0601 feat: renamed overview_card to translation_card 2025-04-15 17:31:06 +02:00
c05f8fad35 feat: renamed overview_card to translation_card 2025-04-15 17:30:57 +02:00
b9c7535812 feat: added article card template 2025-04-15 17:29:25 +02:00
cdcd3bce1f fix: temlate typo 2025-04-15 17:28:12 +02:00
6eb02cc95f feat: added possibility for child pages cards 2025-04-15 17:26:09 +02:00
2454d26d6b feat: better timestamps 2025-04-15 17:15:55 +02:00
8054a4a983 fix: preview html fuckup 2025-04-15 17:13:04 +02:00
afefce0a11 feat: added datetime 2025-04-15 17:08:37 +02:00
334c82d098 feat: added date to terminal 2025-04-15 17:04:57 +02:00
ddd536c958 feat: added date to values 2025-04-15 16:54:01 +02:00
ae5ba0d044 feat: writing article config values to class 2025-04-15 16:48:00 +02:00
83133218a4 feat: writing article config values to class 2025-04-15 16:47:15 +02:00
d412d983bd feat: cleaned the logs and copy static 2025-04-15 16:34:43 +02:00
9859229101 feat: added nice config functionality 2025-04-15 16:21:05 +02:00
dc8f9d91e7 feat: added toml files 2025-04-15 15:26:48 +02:00
f3a86b8070 feat: added toml dependency 2025-04-15 15:20:39 +02:00
b814434c48 feat: added toml dependency 2025-04-15 15:13:08 +02:00
ba05ecdd94 feat: renamed pages 2025-04-15 13:12:36 +02:00
1751158cbd feat: added logger 2025-04-15 13:11:59 +02:00
c45cebe497 feat: properly building additional keywords 2025-04-15 13:00:34 +02:00
dc61a7ee92 fix: location position copy instead of reference 2025-04-15 12:47:56 +02:00
d70a0a8630 feat: slight rewrite 2025-04-15 12:43:09 +02:00
4f37283e68 fix: stupid ahh typo bug 2025-04-15 12:21:49 +02:00
0e948e7f55 feat: small refactoring for building of urls 2025-04-15 12:19:45 +02:00
79c95a9ddb feat: small refactoring for building of urls 2025-04-15 12:16:37 +02:00
0ca0fb90d6 feat: added type hints 2025-04-15 12:10:21 +02:00
603a7f5942 feat: filling an article lake 2025-04-15 12:09:28 +02:00
63f8541a82 feat: refactor type dict 2025-04-15 12:01:10 +02:00
dc5af8da28 feat: refactor type dict 2025-04-15 11:59:04 +02:00
3fdbf13d95 Merge branch 'main' of ssh://gitea.elara.ws:2222/Hazel/STSG 2025-04-15 11:55:57 +02:00
7e4640e7d9 feat: added inline type dict 2025-04-15 11:55:43 +02:00
amnesia
8239b3ea6d added src 2025-04-15 11:50:56 +02:00
amnesia
3f0049dfdb feat: recursive pages 2025-04-14 21:06:52 +02:00
amnesia
a113a13318 fix: added bs4 to dep 2025-04-14 19:29:39 +02:00
3926277f8f feat: move relevant card to the top 2025-04-14 17:35:59 +02:00
69b3efd78f feat: move relevant card to the top 2025-04-14 17:35:11 +02:00
2b80c90a19 feat: added lang attribute to reduce barriers 2025-04-14 16:59:18 +02:00
37d28a3542 feat: added priority to languages 2025-04-14 16:46:12 +02:00
3e692dd74f feat: added priority to languages 2025-04-14 16:45:16 +02:00
3e6884ad8a feat: added priority to config 2025-04-14 16:11:12 +02:00
8e57af5a78 feat: added article title 2025-04-14 16:07:25 +02:00
37974c418b feat: added article home overview 2025-04-14 16:04:40 +02:00
2dc1dfef98 feat: added article home overview 2025-04-14 15:58:56 +02:00
c36deab71a feat: added kurdish placeholder 2025-04-14 15:50:16 +02:00
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
amnesia
523402d3ad feat: added additional data 2025-04-11 12:08:24 +02:00
d2d2628e56 feat: added language info 2025-04-10 17:49:59 +02:00
7a3aec9872 feat: added a go back 2025-04-10 17:06:45 +02:00
bfa9cb1527 feat: added a go back 2025-04-10 17:05:10 +02:00
4b307d7896 feat: overview 2025-04-10 17:03:00 +02:00
b1a4d8e6ae feat: added example texts 2025-04-10 16:48:12 +02:00
fa512cfb0e feat: added example texts 2025-04-10 16:47:47 +02:00
3b042b718f feat: layed out language code class 2025-04-10 16:46:44 +02:00
e84828092c feat: storing languages 2025-04-10 16:28:55 +02:00
948f3ca0ab feat: storing languages 2025-04-10 16:28:46 +02:00
f6af26ef54 feat: added markdown conversion 2025-04-10 16:26:01 +02:00
b28bb33412 feat: added recursively copying static files 2025-04-10 15:58:01 +02:00
18 changed files with 1471 additions and 76 deletions

1
.gitignore vendored
View File

@@ -174,3 +174,4 @@ cython_debug/
.pypirc
dist
context.json

View File

@@ -1,7 +1,11 @@
[project]
name = "stsg"
dependencies = [
"watchdog~=6.0.0"
"watchdog~=6.0.0",
"markdown~=3.3.6",
"bs4~=0.0.2",
"toml~0.10.2",
"jinja2~=3.1.6",
]
dynamic = []
authors = []

75
src/articles/en.md Normal file
View File

@@ -0,0 +1,75 @@
# 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!

View File

@@ -0,0 +1,57 @@
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.

View File

@@ -0,0 +1,75 @@
# 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!

View File

@@ -0,0 +1,35 @@
# Navê Gotarê example
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.

2
src/articles/index.toml Normal file
View File

@@ -0,0 +1,2 @@
name="index_page"
datetime="2024-04-15 13:45:12.123456"

38
src/articles/ku.md Normal file
View File

@@ -0,0 +1,38 @@
# Navê Gotarê
[{article_title:example}]({article_url:example})
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.

View File

@@ -1 +0,0 @@

View File

@@ -1 +0,0 @@

View File

@@ -0,0 +1,98 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>{{slug}}</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">
{% if translations|length %}
<div class="container content">
<div class="column is-half is-offset-one-quarter">
<h1>Translations</h1>
</div>
<div class="column is-half is-offset-one-quarter">
{% for t in translations %}
<div class="card mb-4" lang="{{t.language.code}}">
<a href="{{t.url}}" hreflang="{{t.language.code}}" class="card mb-4" style="color: inherit; text-decoration: none;">
<div class="card-content">
<p class="title">{{t.language.flag}} {{t.title}} </p>
<p class="content">
{{t.preview}}
<br />
<time datetime="{{iso_date}}">{{date}}</time>
</p>
</div>
</a>
</div>
{% endfor %}
</div>
</div>
{% endif %}
{% if children|length %}
<div class="container content">
<div class="column is-half is-offset-one-quarter">
<h1>Child Articles</h1>
</div>
<div class="column is-half is-offset-one-quarter">
{% for c in children %}
<div class="card mb-4" >
<a href="{{c.url}}" class="card mb-4" style="color: inherit; text-decoration: none;">
<div class="card-content">
<p class="title">{{c.slug}} </p>
<p class="content">
<time datetime="{{iso_date}}">{{date}}</time>
</p>
</div>
</a>
</div>
{% endfor %}
</div>
</div>
{% endif %}
</section>
<!-- Footer -->
<footer class="footer">
<div class="content has-text-centered">
<p><strong>STSG</strong> by Hazel. &copy; 2025</p>
</div>
</footer>
</body>
<script>
document.addEventListener("DOMContentLoaded", function () {
const userLang = navigator.language || navigator.userLanguage;
// Normalize and check if the language is not English or German
if (!["en", "de", "de-DE"].includes(userLang)) {
// Try to find a matching card by language attribute
const cardToMove =
document.querySelector(`.card[lang^="${userLang.replace("_", "-").toLowerCase()}"]`) ||
document.querySelector(`.card[lang^="${userLang.split("-")[0]}"]`);
if (cardToMove) {
const container = cardToMove.parentNode;
container.insertBefore(cardToMove, container.firstChild);
}
}
});
</script>
</html>

View File

@@ -1,10 +1,10 @@
<!DOCTYPE html>
<html>
<html lang="{article_language_code}">
<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" />
<title>{{language.flag}} {{title}}</title>
<link rel="stylesheet" href="/static/bulma.min.css" />
</head>
<body>
<!-- Header (Navbar) -->
@@ -14,16 +14,17 @@
aria-label="main navigation"
>
<div class="navbar-brand">
<a class="navbar-item" href="#">
<strong>Static Translated Site Generator</strong>
<a class="navbar-item" href="{{article_url}}">
<strong>{{language.flag}} {{title}}</strong>
<time datetime="{{meta.iso_date}}">{{meta.date}}</time>
</a>
</div>
</nav>
<!-- Main Content -->
<section class="section">
<div class="container">
<content />
<div class="content">
{{content}}
</div>
</section>

25
stsg.toml Normal file
View File

@@ -0,0 +1,25 @@
[setup]
source_directory = "src"
dist_directory = "dist"
[formatting]
article_preview_length = 400
datetime_format = "%d. %B %Y"
default_language = "de"
[languages]
[languages.de]
native_name = "Schland"
priority = 100
[languages.en]
priority = 90
[languages.ar_sy]
priority = 50
[languages.ku]
priority = 49
[languages.tr]
priority = 60

View File

@@ -0,0 +1,793 @@
class config:
class setup:
source_directory = "src"
dist_directory = "dist"
class formatting:
article_preview_length = 200
datetime_format = "%d. %B %Y"
fallback_language = "en"
languages = {
"af": {
"flag": "🇿🇦",
"name": "Afrikaans",
"native_name": "Afrikaans"
},
"am": {
"flag": "🇪🇹",
"name": "Amharic",
"native_name": "አማርኛ"
},
"an": {
"flag": "🇪🇸",
"name": "Aragonese",
"native_name": "aragonés"
},
"ar": {
"flag": "🇸🇦",
"name": "Arabic",
"native_name": "العربية"
},
"ar_ae": {
"flag": "🇦🇪",
"name": "Arabic (UAE)",
"native_name": "العربية (الإمارات)"
},
"ar_bh": {
"flag": "🇧🇭",
"name": "Arabic (Bahrain)",
"native_name": "العربية (البحرين)"
},
"ar_dz": {
"flag": "🇩🇿",
"name": "Arabic (Algeria)",
"native_name": "العربية (الجزائر)"
},
"ar_eg": {
"flag": "🇪🇬",
"name": "Arabic (Egypt)",
"native_name": "العربية (مصر)"
},
"ar_iq": {
"flag": "🇮🇶",
"name": "Arabic (Iraq)",
"native_name": "العربية (العراق)"
},
"ar_jo": {
"flag": "🇯🇴",
"name": "Arabic (Jordan)",
"native_name": "العربية (الأردن)"
},
"ar_kw": {
"flag": "🇰🇼",
"name": "Arabic (Kuwait)",
"native_name": "العربية (الكويت)"
},
"ar_lb": {
"flag": "🇱🇧",
"name": "Arabic (Lebanon)",
"native_name": "العربية (لبنان)"
},
"ar_ly": {
"flag": "🇱🇾",
"name": "Arabic (Libya)",
"native_name": "العربية (ليبيا)"
},
"ar_ma": {
"flag": "🇲🇦",
"name": "Arabic (Morocco)",
"native_name": "العربية (المغرب)"
},
"ar_om": {
"flag": "🇴🇲",
"name": "Arabic (Oman)",
"native_name": "العربية (عُمان)"
},
"ar_qa": {
"flag": "🇶🇦",
"name": "Arabic (Qatar)",
"native_name": "العربية (قطر)"
},
"ar_sa": {
"flag": "🇸🇦",
"name": "Arabic (Saudi Arabia)",
"native_name": "العربية (السعودية)"
},
"ar_sd": {
"flag": "🇸🇩",
"name": "Arabic (Sudan)",
"native_name": "العربية (السودان)"
},
"ar_sy": {
"flag": "🇸🇾",
"name": "Arabic (Syria)",
"native_name": "العربية (سوريا)",
},
"ar_tn": {
"flag": "🇹🇳",
"name": "Arabic (Tunisia)",
"native_name": "العربية (تونس)"
},
"ar_ye": {
"flag": "🇾🇪",
"name": "Arabic (Yemen)",
"native_name": "العربية (اليمن)"
},
"ars_ae": {
"flag": "🇦🇪",
"name": "Najdi Arabic (UAE)",
"native_name": "نَجْدِيّ"
},
"ars_arab_sa": {
"flag": "🇸🇦",
"name": "Najdi Arabic (Saudi Arabia, Arabic Script)",
"native_name": "نَجْدِيّ"
},
"ars_sa": {
"flag": "🇸🇦",
"name": "Najdi Arabic (Saudi Arabia)",
"native_name": "نَجْدِيّ"
},
"as": {
"flag": "🇮🇳",
"name": "Assamese",
"native_name": "অসমীয়া"
},
"az": {
"flag": "🇦🇿",
"name": "Azerbaijani",
"native_name": "Azərbaycan"
},
"be": {
"flag": "🇧🇾",
"name": "Belarusian",
"native_name": "Беларуская"
},
"bg": {
"flag": "🇧🇬",
"name": "Bulgarian",
"native_name": "Български"
},
"bm": {
"flag": "🇲🇱",
"name": "Bambara",
"native_name": "bamanankan"
},
"bn": {
"flag": "🇧🇩",
"name": "Bengali",
"native_name": "বাংলা"
},
"bn_in": {
"flag": "🇮🇳",
"name": "Bengali (India)",
"native_name": "বাংলা (ভারত)"
},
"br": {
"flag": "🏴",
"name": "Breton",
"native_name": "brezhoneg"
},
"bs": {
"flag": "🇧🇦",
"name": "Bosnian",
"native_name": "Bosanski"
},
"ca": {
"flag": "🇪🇸",
"name": "Catalan",
"native_name": "Català"
},
"crh": {
"flag": "🇺🇦",
"name": "Crimean Tatar",
"native_name": "qırımtatarca"
},
"cs": {
"flag": "🇨🇿",
"name": "Czech",
"native_name": "Čeština"
},
"cv": {
"flag": "🇷🇺",
"name": "Chuvash",
"native_name": "чӑваш чӗлхи"
},
"cy": {
"flag": "🏴",
"name": "Welsh",
"native_name": "Cymraeg"
},
"da": {
"flag": "🇩🇰",
"name": "Danish",
"native_name": "Dansk"
},
"de": {
"flag": "🇩🇪",
"name": "German",
"native_name": "Deutsch",
},
"de_at": {
"flag": "🇦🇹",
"name": "German (Austria)",
"native_name": "Deutsch (Österreich)"
},
"de_be": {
"flag": "🇧🇪",
"name": "German (Belgium)",
"native_name": "Deutsch (Belgien)"
},
"de_ch": {
"flag": "🇨🇭",
"name": "German (Switzerland)",
"native_name": "Deutsch (Schweiz)"
},
"dv": {
"flag": "🇲🇻",
"name": "Dhivehi",
"native_name": "ދިވެހި"
},
"dz": {
"flag": "🇧🇹",
"name": "Dzongkha",
"native_name": "རྫོང་ཁ"
},
"el": {
"flag": "🇬🇷",
"name": "Greek",
"native_name": "Ελληνικά"
},
"en": {
"flag": "🇺🇸",
"name": "English",
"native_name": "English",
},
"en_au": {
"flag": "🇦🇺",
"name": "English (Australia)",
"native_name": "English (Australia)"
},
"en_ca": {
"flag": "🇨🇦",
"name": "English (Canada)",
"native_name": "English (Canada)"
},
"en_gb": {
"flag": "🇬🇧",
"name": "English (UK)",
"native_name": "English (UK)",
},
"en_ie": {
"flag": "🇮🇪",
"name": "English (Ireland)",
"native_name": "English (Ireland)"
},
"en_in": {
"flag": "🇮🇳",
"name": "English (India)",
"native_name": "English (India)"
},
"en_nz": {
"flag": "🇳🇿",
"name": "English (New Zealand)",
"native_name": "English (New Zealand)"
},
"en_us": {
"flag": "🇺🇸",
"name": "English (US)",
"native_name": "English (US)",
},
"es": {
"flag": "🇪🇸",
"name": "Spanish",
"native_name": "Español"
},
"es_ar": {
"flag": "🇦🇷",
"name": "Spanish (Argentina)",
"native_name": "Español (Argentina)"
},
"es_mx": {
"flag": "🇲🇽",
"name": "Spanish (Mexico)",
"native_name": "Español (México)"
},
"et": {
"flag": "🇪🇪",
"name": "Estonian",
"native_name": "Eesti"
},
"fa": {
"flag": "🇮🇷",
"name": "Persian",
"native_name": "فارسی"
},
"ff": {
"flag": "🌍",
"name": "Fula",
"native_name": "Fulfulde"
},
"fi": {
"flag": "🇫🇮",
"name": "Finnish",
"native_name": "Suomi"
},
"fo": {
"flag": "🇫🇴",
"name": "Faroese",
"native_name": "føroyskt"
},
"fr": {
"flag": "🇫🇷",
"name": "French",
"native_name": "Français"
},
"fr_ca": {
"flag": "🇨🇦",
"name": "French (Canada)",
"native_name": "Français (Canada)"
},
"fr_ch": {
"flag": "🇨🇭",
"name": "French (Switzerland)",
"native_name": "Français (Suisse)"
},
"ga": {
"flag": "🇮🇪",
"name": "Irish",
"native_name": "Gaeilge"
},
"gl": {
"flag": "🇪🇸",
"name": "Galician",
"native_name": "Galego"
},
"gn": {
"flag": "🇵🇾",
"name": "Guarani",
"native_name": "Avañe'"
},
"gu": {
"flag": "🇮🇳",
"name": "Gujarati",
"native_name": "ગુજરાતી"
},
"ha": {
"flag": "🇳🇬",
"name": "Hausa",
"native_name": "هَوُسَ"
},
"he": {
"flag": "🇮🇱",
"name": "Hebrew",
"native_name": "עברית"
},
"hi": {
"flag": "🇮🇳",
"name": "Hindi",
"native_name": "हिन्दी"
},
"hr": {
"flag": "🇭🇷",
"name": "Croatian",
"native_name": "Hrvatski"
},
"ht": {
"flag": "🇭🇹",
"name": "Haitian Creole",
"native_name": "Kreyòl ayisyen"
},
"hu": {
"flag": "🇭🇺",
"name": "Hungarian",
"native_name": "Magyar"
},
"id": {
"flag": "🇮🇩",
"name": "Indonesian",
"native_name": "Bahasa Indonesia"
},
"io": {
"flag": "🌍",
"name": "Ido",
"native_name": "Ido"
},
"is": {
"flag": "🇮🇸",
"name": "Icelandic",
"native_name": "Íslenska"
},
"it": {
"flag": "🇮🇹",
"name": "Italian",
"native_name": "Italiano"
},
"ja": {
"flag": "🇯🇵",
"name": "Japanese",
"native_name": "日本語"
},
"jv_id": {
"flag": "🇮🇩",
"name": "Javanese (Indonesia)",
"native_name": "basa jawa"
},
"ka": {
"flag": "🇬🇪",
"name": "Georgian",
"native_name": "ქართული"
},
"kg": {
"flag": "🇨🇬",
"name": "Kongo",
"native_name": "KiKongo"
},
"kj": {
"flag": "🇳🇦",
"name": "Kuanyama",
"native_name": "Oshikwanyama"
},
"kk": {
"flag": "🇰🇿",
"name": "Kazakh",
"native_name": "Қазақ"
},
"kl": {
"flag": "🇬🇱",
"name": "Kalaallisut",
"native_name": "kalaallisut"
},
"km": {
"flag": "🇰🇭",
"name": "Khmer",
"native_name": "ខ្មែរ"
},
"ko": {
"flag": "🇰🇷",
"name": "Korean",
"native_name": "한국어"
},
"ks": {
"flag": "🇮🇳",
"name": "Kashmiri",
"native_name": "کٲشُر"
},
"ku": {
"flag": "🇮🇶",
"name": "Kurdish",
"native_name": "Kurdî",
},
"lo": {
"flag": "🇱🇦",
"name": "Lao",
"native_name": "ລາວ"
},
"lt": {
"flag": "🇱🇹",
"name": "Lithuanian",
"native_name": "Lietuvių"
},
"lv": {
"flag": "🇱🇻",
"name": "Latvian",
"native_name": "Latviešu"
},
"mg": {
"flag": "🇲🇬",
"name": "Malagasy",
"native_name": "Malagasy"
},
"mg_mg": {
"flag": "🇲🇬",
"name": "Malagasy (Madagascar)",
"native_name": "malagasy"
},
"mh": {
"flag": "🇲🇭",
"name": "Marshallese",
"native_name": "Kajin M̧ajeļ"
},
"mk": {
"flag": "🇲🇰",
"name": "Macedonian",
"native_name": "Македонски"
},
"mn_mn": {
"flag": "🇲🇳",
"name": "Mongolian (Mongolia)",
"native_name": "Монгол хэл"
},
"mr_in": {
"flag": "🇮🇳",
"name": "Marathi (India)",
"native_name": "मराठी"
},
"ms": {
"flag": "🇲🇾",
"name": "Malay",
"native_name": "Bahasa Melayu"
},
"my": {
"flag": "🇲🇲",
"name": "Burmese",
"native_name": "မြန်မာဘာသာ"
},
"na": {
"flag": "🇳🇷",
"name": "Nauruan",
"native_name": "Dorerin Naoero"
},
"nb": {
"flag": "🇳🇴",
"name": "Norwegian Bokmål",
"native_name": "Norsk Bokmål"
},
"ng": {
"flag": "🇳🇦",
"name": "Ndonga",
"native_name": "Oshindonga"
},
"nl": {
"flag": "🇳🇱",
"name": "Dutch",
"native_name": "Nederlands"
},
"om": {
"flag": "🇪🇹",
"name": "Oromo",
"native_name": "Afaan Oromoo"
},
"os": {
"flag": "🇷🇺",
"name": "Ossetian",
"native_name": "ирон æвзаг"
},
"pl": {
"flag": "🇵🇱",
"name": "Polish",
"native_name": "Polski"
},
"pt": {
"flag": "🇵🇹",
"name": "Portuguese",
"native_name": "Português"
},
"pt_br": {
"flag": "🇧🇷",
"name": "Portuguese (Brazil)",
"native_name": "Português (Brasil)"
},
"qu": {
"flag": "🇵🇪",
"name": "Quechua",
"native_name": "Runa Simi"
},
"ro": {
"flag": "🇷🇴",
"name": "Romanian",
"native_name": "Română"
},
"ru": {
"flag": "🇷🇺",
"name": "Russian",
"native_name": "Русский"
},
"rw": {
"flag": "🇷🇼",
"name": "Kinyarwanda",
"native_name": "Ikinyarwanda"
},
"sc": {
"flag": "🇮🇹",
"name": "Sardinian",
"native_name": "sardu"
},
"sg": {
"flag": "🇨🇫",
"name": "Sango",
"native_name": "yângâ tî sängö"
},
"sk": {
"flag": "🇸🇰",
"name": "Slovak",
"native_name": "Slovenčina"
},
"sl": {
"flag": "🇸🇮",
"name": "Slovenian",
"native_name": "Slovenščina"
},
"sm": {
"flag": "🇼🇸",
"name": "Samoan",
"native_name": "Gagana Samoa"
},
"sn": {
"flag": "🇿🇼",
"name": "Shona",
"native_name": "chiShona"
},
"so": {
"flag": "🇸🇴",
"name": "Somali",
"native_name": "Soomaaliga"
},
"sr": {
"flag": "🇷🇸",
"name": "Serbian",
"native_name": "Српски"
},
"ss": {
"flag": "🇸🇿",
"name": "Swati",
"native_name": "SiSwati"
},
"st": {
"flag": "🇱🇸",
"name": "Southern Sotho",
"native_name": "Sesotho"
},
"su_id": {
"flag": "🇮🇩",
"name": "Sundanese (Indonesia)",
"native_name": "basa sunda"
},
"sv": {
"flag": "🇸🇪",
"name": "Swedish",
"native_name": "Svenska"
},
"th": {
"flag": "🇹🇭",
"name": "Thai",
"native_name": "ไทย"
},
"tk": {
"flag": "🇹🇲",
"name": "Turkmen",
"native_name": "Türkmen"
},
"tn": {
"flag": "🇧🇼",
"name": "Tswana",
"native_name": "Setswana"
},
"to": {
"flag": "🇹🇴",
"name": "Tongan",
"native_name": "faka-Tonga"
},
"tr": {
"flag": "🇹🇷",
"name": "Turkish",
"native_name": "Türkçe",
},
"ts": {
"flag": "🇿🇦",
"name": "Tsonga",
"native_name": "Xitsonga"
},
"ts_zw": {
"flag": "🇿🇼",
"name": "Tsonga (Zimbabwe)",
"native_name": "xitsonga"
},
"ty": {
"flag": "🇵🇫",
"name": "Tahitian",
"native_name": "Reo Tahiti"
},
"uk": {
"flag": "🇺🇦",
"name": "Ukrainian",
"native_name": "Українська"
},
"ur": {
"flag": "🇵🇰",
"name": "Urdu",
"native_name": "اردو"
},
"uz": {
"flag": "🇺🇿",
"name": "Uzbek",
"native_name": "oʻzbek"
},
"ve": {
"flag": "🇿🇦",
"name": "Venda",
"native_name": "Tshivenda"
},
"vi": {
"flag": "🇻🇳",
"name": "Vietnamese",
"native_name": "Tiếng Việt"
},
"vo": {
"flag": "🌍",
"name": "Volapük",
"native_name": "Volapük"
},
"wa": {
"flag": "🇧🇪",
"name": "Walloon",
"native_name": "walon"
},
"xh": {
"flag": "🇿🇦",
"name": "Xhosa",
"native_name": "isiXhosa"
},
"yi": {
"flag": "🌍",
"name": "Yiddish",
"native_name": "ייִדיש"
},
"yo": {
"flag": "🇳🇬",
"name": "Yoruba",
"native_name": "Yorùbá"
},
"zh": {
"flag": "🇨🇳",
"name": "Chinese",
"native_name": "中文"
},
"zh_hk": {
"flag": "🇭🇰",
"name": "Chinese (Hong Kong)",
"native_name": "中文(香港)"
},
"zh_tw": {
"flag": "🇹🇼",
"name": "Chinese (Taiwan)",
"native_name": "中文(台灣)"
},
"zu": {
"flag": "🇿🇦",
"name": "Zulu",
"native_name": "isiZulu"
}
}
if True:
import toml
from pathlib import Path
def process_data_dict(data: dict, existing: dict):
for key, value in data.items():
if key not in existing:
existing[key] = value
continue
if isinstance(value, dict):
if isinstance(existing[key], dict):
process_data_dict(value, existing[key])
continue
existing[key] = value
def process_data(data: dict, obj):
for key, value in data.items():
if not hasattr(obj, key):
continue
existing = getattr(obj, key)
if isinstance(existing, dict):
process_data_dict(value, existing)
continue
if isinstance(value, dict):
process_data(value, getattr(obj, key))
continue
setattr(obj, key, value)
config_file = Path("stsg.toml")
if config_file.exists() and config_file.is_file():
with config_file.open("r") as f:
process_data(toml.loads(f.read()), config)

View File

@@ -5,9 +5,14 @@ import time
import os
import subprocess
from .config import SOURCE_DIRECTORY, CODE_DIRECTORY
from . import config
from .build import build as complete_build
CODE_DIRECTORY = "stsg"
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger("stsg")
@@ -69,7 +74,7 @@ def build_on_change():
build()
observer = Observer()
observer.schedule(MarkdownChangeHandler(), path=SOURCE_DIRECTORY, recursive=True)
observer.schedule(MarkdownChangeHandler(), path=config.setup.source_directory, recursive=True)
observer.start()
try:
@@ -97,3 +102,4 @@ def hot_reload():
observer.stop()
observer.join()

View File

@@ -1,84 +1,280 @@
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, Any
from bs4 import BeautifulSoup
from collections import defaultdict
import toml
from datetime import datetime
import jinja2
from .config import SOURCE_DIRECTORY, DIST_DIRECTORY, STATIC_DIRECTORY
from . import config
logger = logging.getLogger("stsg.build")
def replace_values(template: str, values: Dict[str, str]) -> str:
for key, value in values.items():
template = template.replace("{" + key + "}", value)
return template
def copy_static():
src = str(Path(SOURCE_DIRECTORY, STATIC_DIRECTORY))
dst = str(Path(DIST_DIRECTORY, STATIC_DIRECTORY))
def get_first_header_content(content, fallback: str = ""):
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)
if not os.path.exists(src):
logger.warn("The static folder '%s' wasn't defined.", src)
return
return fallback
logger.info("copying static files from '%s' to '%r'", src, dst)
os.makedirs(dst, exist_ok=True)
def shorten_text_and_clean(html_string, max_length=config.formatting.article_preview_length):
soup = BeautifulSoup(html_string, 'html.parser')
for root, dirs, files in os.walk(src):
if any(p.startswith(".") for p in Path(root).parts):
# Keep track of total characters added
total_chars = 0
finished = False
# Function to recursively trim and clean text
def process_element(element):
nonlocal total_chars, finished
for child in list(element.children):
if finished:
child.extract()
continue
# Compute relative path from the source root
rel_path = os.path.relpath(root, src)
dest_dir = os.path.join(dst, rel_path)
if isinstance(child, str):
remaining = max_length - total_chars
if remaining <= 0:
child.extract()
finished = True
elif len(child) > remaining:
child.replace_with(child[:remaining] + '...')
total_chars = max_length
finished = True
else:
total_chars += len(child)
elif hasattr(child, 'children'):
process_element(child)
# Remove empty tags
if not child.text.strip():
child.decompose()
os.makedirs(dest_dir, exist_ok=True)
process_element(soup)
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)
return str(soup)
class Context:
def __init__(self, root: str = SOURCE_DIRECTORY):
self.file = Path(root, "index.html")
def stem_to_language_code(stem: str) -> str:
language_code = stem.lower().replace("-", "_")
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
if language_code in config.languages:
return language_code
current_root = current_root.parent
language_code = language_code.split("_")[0]
if language_code in config.languages:
return language_code
if self.file is None:
logger.error("couldn't find context for %s", root)
logger.error("Didn't recognize %s as a valid language code, add it to the config, or fix your structure.", stem)
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
class TemplateDict(dict):
def __init__(self, folder: Path):
self.folder = folder
super().__init__()
def __missing__(self, name: str) -> jinja2.Template:
f = self.folder / (name + ".html")
if not f.exists():
logger.error("no template with the name %s exists", name)
exit(1)
t = jinja2.Template(f.read_text())
self[name] = t
return t
TEMPLATE: Dict[str, jinja2.Template] = TemplateDict(Path(config.setup.source_directory, "templates"))
class LanguageDict(dict):
def __missing__(self, key: str):
if key not in config.languages:
raise KeyError(key)
lang_dict = config.languages[key]
lang_dict["priority"] = lang_dict.get("priority", 0)
elements = key.split("_")
if len(elements) > 1:
elements[-1] = elements[-1].upper()
lang_dict["code"] = "-".join(elements)
return lang_dict
LANGUAGES = LanguageDict()
class ArticleTranslation:
def __init__(self, file: Path, article: Article):
self.file = file
self.article = article
self.context: Dict[str, Any] = {}
# initializing the location of the article translation
self.language_code = stem_to_language_code(self.file.stem)
self.location_in_tree = [self.language_code, *self.article.location_in_tree]
self.url = "/" + "/".join(self.location_in_tree)
self.dist_path = Path(config.setup.dist_directory, *self.location_in_tree)
self.priority = LANGUAGES[self.language_code]["priority"]
self.real_language_code = LANGUAGES[self.language_code]["code"]
# TODO remove
self.article_content = self.file.read_text()
self.article_preview = self.article_content[:config.formatting.article_preview_length] + "..."
if self.file.suffix == ".md":
self.article_content = markdown.markdown(self.article_content)
self.article_preview = markdown.markdown(self.article_preview)
self.title = get_first_header_content(self.article_content, fallback="")
def __init_context__(self):
self.context["meta"] = self.article.context_shared
self.context["url"] = self.url
self.context["language"] = LANGUAGES[self.language_code]
self.context["article_url"] = self.article.url
html_content = self.file.read_text()
if self.file.suffix == ".md":
html_content = markdown.markdown(html_content)
self.context["title"] = get_first_header_content(html_content, fallback=LANGUAGES[self.language_code]["native_name"])
self.context["content"] = html_content
self.context["preview"] = shorten_text_and_clean(html_string=html_content)
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(TEMPLATE["article_translation"].render(self.context))
class Article:
def __init__(self, directory: Path, location_in_tree: Optional[List[str]] = None, is_root: bool = False, parent: Optional[Article] = None):
self.directory = directory
self.context: Dict[str, Any] = {}
self.context_shared: Dict[str, Any] = {}
if parent is not None:
self.context["parent"] = parent.context_shared
# initializing the config values of the article
config_file = self.directory / "index.toml"
self.config = toml.load(config_file) if config_file.exists() else {}
# initializing the location and slug of the article
self.slug = self.config.get("name", self.directory.name)
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.location_in_tree: List[str] = location_in_tree or []
if not is_root:
self.location_in_tree.append(self.slug)
self.url = "/" + "/".join(self.location_in_tree)
self.dist_path = Path(config.setup.dist_directory, *self.location_in_tree)
# build the tree
self.child_articles: List[Article] = []
self.article_translations_list: List[ArticleTranslation] = []
self.article_translations_map: Dict[str, ArticleTranslation] = {}
for c in self.directory.iterdir():
if c.name == "index.toml":
continue
if c.is_file():
at = ArticleTranslation(c, self)
self.article_translations_list.append(at)
self.article_translations_map[at.language_code] = at
elif c.is_dir():
self.child_articles.append(Article(
directory=c,
location_in_tree=self.location_in_tree.copy(),
parent=self,
))
self.article_translations_list.sort(key=lambda a: a.priority, reverse=True)
logger.info("found %s at %s with the translations %s", self.slug, ".".join(list(self.location_in_tree)), ",".join(self.article_translations_map.keys()))
def __init_context__(self):
self.context_shared["url"] = self.url
self.context_shared["slug"] = self.slug
modified_at = datetime.fromisoformat(self.config["datetime"]) if "datetime" in self.config else datetime.fromtimestamp(self.directory.stat().st_mtime)
self.context_shared["date"] = modified_at.strftime(config.formatting.datetime_format)
self.context_shared["iso_date"] = modified_at.isoformat()
self.context.update(self.context_shared)
# recursive context structures
translation_list = self.context["translations"] = []
child_article_list = self.context["children"] = []
for article_translation in self.article_translations_list:
self.context[article_translation.real_language_code] = article_translation.context
translation_list.append(article_translation.context)
for child_article in self.child_articles:
child_article_list.append(child_article.context)
# recursively build context
for at in self.article_translations_list:
at.__init_context__()
for a in self.child_articles:
a.__init_context__()
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(TEMPLATE["article"].render(self.context))
for at in self.article_translations_list:
at.build()
for ac in self.child_articles:
ac.build()
# GLOBALS
logger = logging.getLogger("stsg.build")
ARTICLE_LAKE: Dict[str, Article] = {}
ARTICLE_REFERENCE_VALUES: DefaultDict[str, Dict[str, str]] = defaultdict(dict)
def build():
logger.info("building static page")
copy_static()
logger.info("starting build process...")
context = Context()
logger.info("copying static folder...")
shutil.copytree(Path(config.setup.source_directory, "static"), Path(config.setup.dist_directory, "static"), dirs_exist_ok=True)
# 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)
logger.info("building page tree...")
tree = Article(directory=Path(config.setup.source_directory, "articles"), is_root=True)
logger.info("compiling tree context...")
tree.__init_context__()
import json
with Path("context.json").open("w") as f:
json.dump(tree.context, f, indent=4)
logger.info("dumping page tree...")
tree.build()

View File

@@ -1,9 +0,0 @@
SOURCE_DIRECTORY = "src"
DIST_DIRECTORY = "dist"
# relative to SOURCE_DIRECTORY / DIST_DIRECTORY
STATIC_DIRECTORY = "static"
# FOR DEVELOPMENT
CODE_DIRECTORY = "stsg"