14 Commits

Author SHA1 Message Date
Hazel Noack
a4aa73b1aa added todo 2025-05-22 16:46:49 +02:00
Hazel Noack
73e89ca513 properly added license everywhere 2025-05-22 16:41:05 +02:00
Hazel Noack
a1900a77e4 added year to context 2025-05-22 16:33:25 +02:00
Hazel Noack
258d062ff4 credited the license author 2025-05-22 16:19:22 +02:00
Hazel Noack
8fee8d879e credited the license author 2025-05-22 16:18:32 +02:00
Hazel Noack
373cade0a7 added license to website 2025-05-22 16:14:36 +02:00
Hazel Noack
5196913266 added license 2025-05-22 16:11:29 +02:00
Hazel Noack
d3ff002901 added breadcrumbs 2025-05-22 16:08:36 +02:00
Hazel Noack
08e7e343f5 removed header from translated cards as it is in the preview 2025-05-22 15:52:16 +02:00
Hazel Noack
433e2f6023 couldnt add multiple translations anymore 2025-05-22 15:50:51 +02:00
Hazel Noack
2690f0af87 edited templates 2025-05-22 15:47:46 +02:00
Hazel Noack
f2a3d7ada4 refactoring article context building 2025-05-22 13:43:34 +02:00
Hazel Noack
753de66e08 feat: started cleaning up context 2025-05-22 12:59:50 +02:00
Hazel Noack
036d5fb30a feat: started cleaning up context 2025-05-22 12:56:40 +02:00
15 changed files with 435 additions and 120 deletions

57
LICENSE Normal file
View File

@@ -0,0 +1,57 @@
# 🏳️‍🌈 Opinionated Queer License v1.2
© Copyright {Licensor}
## Permissions
The creators of this Work (“The Licensor”) grant permission
to any person, group or legal entity that doesn't violate the prohibitions below (“The User”),
to do everything with this Work that would otherwise infringe their copyright or any patent claims,
subject to the following conditions:
## Obligations
The User must give appropriate credit to the Licensor,
provide a copy of this license or a (clickable, if the medium allows) link to
[oql.avris.it/license/v1.2](https://oql.avris.it/license/v1.2),
and indicate whether and what kind of changes were made.
The User may do so in any reasonable manner,
but not in any way that suggests the Licensor endorses the User or their use.
## Prohibitions
No one may use this Work for prejudiced or bigoted purposes, including but not limited to:
racism, xenophobia, queerphobia, queer exclusionism, homophobia, transphobia, enbyphobia, misogyny.
No one may use this Work to inflict or facilitate violence or abuse of human rights,
as defined in either of the following documents:
[Universal Declaration of Human Rights](https://www.un.org/en/about-us/universal-declaration-of-human-rights),
[European Convention on Human Rights](https://prd-echr.coe.int/web/echr/european-convention-on-human-rights)
along with the rulings of the [European Court of Human Rights](https://www.echr.coe.int/).
No law enforcement, carceral institutions, immigration enforcement entities, military entities or military contractors
may use the Work for any reason. This also applies to any individuals employed by those entities.
No business entity where the ratio of pay (salaried, freelance, stocks, or other benefits)
between the highest and lowest individual in the entity is greater than 50 : 1
may use the Work for any reason.
No private business run for profit with more than a thousand employees
may use the Work for any reason.
Unless the User has made substantial changes to the Work,
or uses it only as a part of a new work (eg. as a library, as a part of an anthology, etc.),
they are prohibited from selling the Work.
That prohibition includes processing the Work with machine learning models.
## Sanctions
If the Licensor notifies the User that they have not complied with the rules of the license,
they can keep their license by complying within 30 days after the notice.
If they do not do so, their license ends immediately.
## Warranty
This Work is provided “as is”, without warranty of any kind, express or implied.
The Licensor will not be liable to anyone for any damages related to the Work or this license,
under any kind of legal claim as far as the law allows.

View File

@@ -1,4 +1,15 @@
# Installing
<a href="https://oql.avris.it/license/v1.2" target="_blank" rel="noopener"><img src="https://badgers.space/badge/License/OQL/pink" alt="License: OQL" style="vertical-align: middle;"/></a>
# STSG
## Planned features
- [ ] auto uploading to ftp and other online services using a plugin system
- [ ] build a git wiki in a specified languages instead of a static website
- [ ] hosting the documentation somehow
- [ ] multiple templates + a way to choose templates
## Installing
```sh
python3 -m venv .venv
@@ -6,7 +17,7 @@ source .venv/bin/activate
pip install -e .
```
# Execute
## Execute
To start a local http server in the dist folder you can simply do:
@@ -24,13 +35,3 @@ stsg
stsg_dev
```
# build favicon
```
cd src/static/assets
inkscape -w 16 -h 16 -o 16.png logo.svg
inkscape -w 32 -h 32 -o 32.png logo.svg
inkscape -w 48 -h 48 -o 48.png logo.svg
convert 16.png 32.png 48.png ../icon.ico
```

View File

@@ -13,6 +13,9 @@ readme = "README.md"
requires-python = ">=3.8"
classifiers = []
version = "0.0.0"
license-files = [
"LICENSE"
]
[project.scripts]
stsg = "stsg.__main__:build"

View File

@@ -6,4 +6,4 @@ Dies ist ein Static Site Generator mit fokus auf Nutzer:innenfreundlichkeit und
Die Templates sind komplet anpassbar, trotzdem empfehle ich [Bulma](https://bulma.io/) als CSS-Framework zu verwenden.
Polizei und das Millitär jeglichen Staates dürfen dieses Tool nicht verwenden.
Polizei und das Millitär jeglichen Staates dürfen dieses Tool nicht verwenden. Für mehr Info zur Nutzung lest [die Lizenz]({{license.url}}).

View File

@@ -6,4 +6,4 @@ This is a static-site-generator with focus on ease of use and making accessible
The templates are completely customizable, but I still reccomend using [bulma](https://bulma.io/) as css-framework.
Cops and Millitary of any state are stricly prohibited from using this tool.
Cops and Millitary of any state are stricly prohibited from using this tool. For more information about the usage of that tool, read [our license]({{license.url}}).

View File

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

View File

@@ -0,0 +1,57 @@
# 🏳️‍🌈 Opinionated Queer License v1.2
© Copyright {Licensor}
## Permissions
The creators of this Work (“The Licensor”) grant permission
to any person, group or legal entity that doesn't violate the prohibitions below (“The User”),
to do everything with this Work that would otherwise infringe their copyright or any patent claims,
subject to the following conditions:
## Obligations
The User must give appropriate credit to the Licensor,
provide a copy of this license or a (clickable, if the medium allows) link to
[oql.avris.it/license/v1.2](https://oql.avris.it/license/v1.2),
and indicate whether and what kind of changes were made.
The User may do so in any reasonable manner,
but not in any way that suggests the Licensor endorses the User or their use.
## Prohibitions
No one may use this Work for prejudiced or bigoted purposes, including but not limited to:
racism, xenophobia, queerphobia, queer exclusionism, homophobia, transphobia, enbyphobia, misogyny.
No one may use this Work to inflict or facilitate violence or abuse of human rights,
as defined in either of the following documents:
[Universal Declaration of Human Rights](https://www.un.org/en/about-us/universal-declaration-of-human-rights),
[European Convention on Human Rights](https://prd-echr.coe.int/web/echr/european-convention-on-human-rights)
along with the rulings of the [European Court of Human Rights](https://www.echr.coe.int/).
No law enforcement, carceral institutions, immigration enforcement entities, military entities or military contractors
may use the Work for any reason. This also applies to any individuals employed by those entities.
No business entity where the ratio of pay (salaried, freelance, stocks, or other benefits)
between the highest and lowest individual in the entity is greater than 50 : 1
may use the Work for any reason.
No private business run for profit with more than a thousand employees
may use the Work for any reason.
Unless the User has made substantial changes to the Work,
or uses it only as a part of a new work (eg. as a library, as a part of an anthology, etc.),
they are prohibited from selling the Work.
That prohibition includes processing the Work with machine learning models.
## Sanctions
If the Licensor notifies the User that they have not complied with the rules of the license,
they can keep their license by complying within 30 days after the notice.
If they do not do so, their license ends immediately.
## Warranty
This Work is provided “as is”, without warranty of any kind, express or implied.
The Licensor will not be liable to anyone for any damages related to the Work or this license,
under any kind of legal claim as far as the law allows.

View File

@@ -0,0 +1,57 @@
# 🏳️‍🌈 Opinionated Queer License v1.2
© Copyright {Licensor}
## Permissions
The creators of this Work (“The Licensor”) grant permission
to any person, group or legal entity that doesn't violate the prohibitions below (“The User”),
to do everything with this Work that would otherwise infringe their copyright or any patent claims,
subject to the following conditions:
## Obligations
The User must give appropriate credit to the Licensor,
provide a copy of this license or a (clickable, if the medium allows) link to
[oql.avris.it/license/v1.2](https://oql.avris.it/license/v1.2),
and indicate whether and what kind of changes were made.
The User may do so in any reasonable manner,
but not in any way that suggests the Licensor endorses the User or their use.
## Prohibitions
No one may use this Work for prejudiced or bigoted purposes, including but not limited to:
racism, xenophobia, queerphobia, queer exclusionism, homophobia, transphobia, enbyphobia, misogyny.
No one may use this Work to inflict or facilitate violence or abuse of human rights,
as defined in either of the following documents:
[Universal Declaration of Human Rights](https://www.un.org/en/about-us/universal-declaration-of-human-rights),
[European Convention on Human Rights](https://prd-echr.coe.int/web/echr/european-convention-on-human-rights)
along with the rulings of the [European Court of Human Rights](https://www.echr.coe.int/).
No law enforcement, carceral institutions, immigration enforcement entities, military entities or military contractors
may use the Work for any reason. This also applies to any individuals employed by those entities.
No business entity where the ratio of pay (salaried, freelance, stocks, or other benefits)
between the highest and lowest individual in the entity is greater than 50 : 1
may use the Work for any reason.
No private business run for profit with more than a thousand employees
may use the Work for any reason.
Unless the User has made substantial changes to the Work,
or uses it only as a part of a new work (eg. as a library, as a part of an anthology, etc.),
they are prohibited from selling the Work.
That prohibition includes processing the Work with machine learning models.
## Sanctions
If the Licensor notifies the User that they have not complied with the rules of the license,
they can keep their license by complying within 30 days after the notice.
If they do not do so, their license ends immediately.
## Warranty
This Work is provided “as is”, without warranty of any kind, express or implied.
The Licensor will not be liable to anyone for any damages related to the Work or this license,
under any kind of legal claim as far as the law allows.

View File

@@ -0,0 +1 @@
author="Andrea Vos"

View File

@@ -4,7 +4,7 @@
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<link rel="icon" type="image/x-icon" href="/static/icon.ico">
<title>{{slug}}</title>
<title>{{name}}</title>
<link rel="stylesheet" href="/static/bulma.min.css" />
<link rel="stylesheet" href="/static/style.css" />
</head>
@@ -16,11 +16,24 @@
>
<div class="navbar-brand">
<a class="navbar-item" href="#">
<strong>Static Translated Site Generator</strong>
<strong>{{name}}</strong>
</a>
</div>
</nav>
{% if breadcrumbs|length %}
<!-- Breadcrumbs Section -->
<nav class="breadcrumb is-small has-succeeds-separator p-3" aria-label="breadcrumbs">
<ul>
{% for b in breadcrumbs %}
<li class="{{'is-active' if b.slug == slug}}">
<a {{'aria-current="page"' if b.slug == slug}} href="{{b.url}}">{{b.name}}</a>
</li>
{% endfor %}
</ul>
</nav>
{% endif %}
<section class="section">
{% if translations|length %}
<div class="container content">
@@ -33,7 +46,7 @@
<div class="card mb-4" lang="{{t.language.code}}" style="height: 100%;">
<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="title">{{t.language.flag}} {{t.name}}</p>
<hr />
<p class="content">
{{t.preview}}
@@ -41,7 +54,7 @@
</div>
<div class="card-footer">
<time class="card-footer-item" datetime="{{iso_date}}">{{date}}</time>
<time class="card-footer-item" datetime="{{t.iso_date}}">{{t.date}}</time>
</div>
</a>
</div>
@@ -62,15 +75,15 @@
<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="title">{{c.name}} </p>
<hr />
<p class="content is-flex is-flex-direction-column" style="gap: 10px;">
{% for ct in c.translations %}
<a href="{{ct.url}}" hreflang="{{ct.language.code}}">{{ct.language.flag}}: {{ct.title}}</a>
<a href="{{ct.url}}" hreflang="{{ct.language.code}}">{{ct.language.flag}}: {{ct.name}}</a>
{% endfor %}
</p>
<hr />
<time datetime="{{iso_date}}">{{date}}</time>
<time datetime="{{c.iso_date}}">{{c.date}}</time>
</div>
</a>
</div>
@@ -83,7 +96,7 @@
<!-- Footer -->
<footer class="footer">
<div class="content has-text-centered">
<p><strong>STSG</strong> by Hazel. &copy; 2025</p>
<p><strong>{{name}}</strong> by {{author}}. &copy; {{year}}</p>
</div>
</footer>
</body>

View File

@@ -4,7 +4,7 @@
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<link rel="icon" type="image/x-icon" href="/static/icon.ico">
<title>{{title}}</title>
<title>{{name}}</title>
<link rel="stylesheet" href="/static/bulma.min.css" />
<link rel="stylesheet" href="/static/style.css" />
</head>
@@ -18,11 +18,24 @@
<div class="navbar-brand">
<a class="navbar-item" href="{{article_url}}">
<strong>{{language.flag}} {{title}}</strong>
<time datetime="{{meta.iso_date}}">{{meta.date}}</time>
<time datetime="{{iso_date}}">{{date}}</time>
</a>
</div>
</nav>
{% if breadcrumbs|length %}
<!-- Breadcrumbs Section -->
<nav class="breadcrumb is-small has-succeeds-separator p-3" aria-label="breadcrumbs">
<ul>
{% for b in breadcrumbs %}
<li class="{{'is-active' if b.slug == slug}}">
<a {{'aria-current="page"' if b.slug == slug}} href="{{b.url}}">{{b.name}}</a>
</li>
{% endfor %}
</ul>
</nav>
{% endif %}
<!-- Main Content -->
<section class="section">
<div class="content">
@@ -39,15 +52,13 @@
<div class="card mb-4" >
<a href="{{c.url}}" hreflang="{{c.language.code}}" class="card mb-4" style="color: inherit; text-decoration: none;">
<div class="card-content">
<p class="title">{{c.title}}</p>
<hr />
<p class="content">
{{c.preview}}
</p>
</div>
<div class="card-footer">
<time class="card-footer-item" datetime="{{c.meta.iso_date}}">{{c.meta.date}}</time>
<time class="card-footer-item" datetime="{{c.iso_date}}">{{c.date}}</time>
</div>
</a>
</div>
@@ -61,7 +72,7 @@
<!-- Footer -->
<footer class="footer">
<div class="content has-text-centered">
<p><strong>STSG</strong> by Hazel. &copy; 2025</p>
<p><strong>{{name}}</strong> by {{author}}. &copy; {{year}}</p>
</div>
</footer>
</body>

View File

@@ -1,3 +1,5 @@
fall_back_to_overview_in_translation = false
[setup]
source_directory = "src"
dist_directory = "dist"

View File

@@ -1,4 +1,7 @@
class config:
default_author = "anonymous"
fall_back_to_overview_in_translation = True
class setup:
source_directory = "src"
dist_directory = "dist"

View File

@@ -6,26 +6,17 @@ import os
from markdown2 import markdown
from typing import Optional, Union, Dict, Generator, List, DefaultDict, Any, TypedDict, Set
from bs4 import BeautifulSoup
from collections import defaultdict
from collections import defaultdict, UserList
import toml
from datetime import datetime
import jinja2
from functools import cached_property
from .definitions import *
from . import config
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)
return fallback
def shorten_text_and_clean(html_string, max_length=config.formatting.preview_length):
soup = BeautifulSoup(html_string, 'html.parser')
@@ -120,23 +111,69 @@ class LanguageDict(dict):
LANGUAGES = LanguageDict()
def compile_cross_article_context(cross_article_context):
title = cross_article_context["title"]
url = cross_article_context["url"]
def add_html_link(c):
name = c["name"]
url = c["url"]
cross_article_context["link"] = f'<a href="{url}">{title}</a>'
c["link"] = f'<a href="{url}">{name}</a>'
class ArticleTranslationContext(TypedDict):
slug: str
name: str
datetime: str
author: str
url: str
def get_translated_articles(articles: List[Article], language_code: str = None) -> List[Union[ArticleTranslation, Article]]:
result = {}
for a in articles:
if a.slug in result:
continue
if language_code is None:
result[a.slug] = a
continue
if not config.fall_back_to_overview_in_translation and language_code not in a.article_translations_map:
continue
result[a.slug] = a.article_translations_map.get(language_code, a)
class ArticleList(UserList):
def __init__(self, iterable):
super().__init__(item for item in iterable)
self.used_slugs = set()
def append(self, a: Union[Article, str]):
if isinstance(a, str):
a = ARTICLE_LAKE[a]
if a.slug in self.used_slugs:
return
self.used_slugs.add(a.slug)
self.data.append(a)
def extend(self, other):
for a in other:
self.append(a)
def get_translated(self, language_code: str) -> ArticleList[Union[ArticleTranslation, Article]]:
res = ArticleList([])
for a in self:
if not config.fall_back_to_overview_in_translation and language_code not in a.article_translations_map:
continue
res.append(a.article_translations_map.get(language_code, a))
return res
@property
def context(self) -> List[Union[ArticleContext, ArticleTranslationContext]]:
return [a.context for a in self]
class ArticleTranslation:
article: Article
slug: str = property(fget=lambda self: self.article.slug)
file: Path
@cached_property
@@ -183,31 +220,34 @@ class ArticleTranslation:
self.article = article
self.file = file
self.context = {}
self.cross_article_context = TRANSLATED_CROSS_ARTICLE_CONTEXT[self.language_code][self.article.slug] = {}
self.context = TRANSLATED_CROSS_ARTICLE_CONTEXT[self.language_code][self.article.slug] = {}
@cached_property
def name(self) -> str:
soup = BeautifulSoup(self.html_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.article.name
def __init_context__(self):
self.context["meta"] = self.article.context_shared
self.context["slug"] = self.article.slug
self.context["name"] = self.name
self.context["url"] = self.url
add_html_link(self.context)
self.context["date"] = self.article.modified_at.strftime(config.formatting.datetime_format)
self.context["year"] = str(self.article.modified_at.year)
self.context["iso_date"] = self.article.modified_at.isoformat()
self.context["author"] = self.article.author
self.context["language"] = LANGUAGES[self.language_code]
self.context["article_url"] = self.article.url
self.context["title"] = get_first_header_content(self.html_content, fallback=LANGUAGES[self.language_code]["native_name"])
self.cross_article_context.update(self.article.context_shared)
self.cross_article_context["title"] = self.context["title"]
self.cross_article_context["article_url"] = self.article.url
self.cross_article_context["url"] = self.url
compile_cross_article_context(self.cross_article_context)
# get children
self.context["children"] = [
c.article_translations_map[self.language_code].context for c in self.article.child_articles
if self.language_code in c.article_translations_map
]
self.linked_context = self.context["linked"] = []
self.related_context = self.context["related"] = []
self.context["children"] = self.article.child_articles.get_translated(self.language_code).context
self.context["breadcrumbs"] = ArticleList(self.article.article_path).get_translated(self.language_code).context
def __init_content_context__(self):
template = jinja2.Template(self.html_content)
@@ -221,17 +261,16 @@ class ArticleTranslation:
template.environment.context_class = jinja2.runtime.Context
accessed_keys = template.environment.accessed_keys
for key in accessed_keys:
a = ARTICLE_LAKE[key]
if self.language_code in a.article_translations_map:
self.linked_context.append(a.article_translations_map[self.language_code].context)
self.related_context.extend(self.linked_context)
self.related_context.extend(self.context["children"])
for key in accessed_keys:
self.article.linked_articles.append(key)
self.context["content"] = self.html_content
self.context["preview"] = get_preview_text(html_string=self.html_content)
self.context["linked"] = self.article.linked_articles.get_translated(self.language_code).context
self.context["related"] = self.article.related_articles.get_translated(self.language_code).context
def build(self):
self.dist_path.mkdir(parents=True, exist_ok=True)
@@ -239,20 +278,6 @@ class ArticleTranslation:
f.write(TEMPLATE["article_translation"].render(self.context))
class ArticleConfig(TypedDict):
slug: str
name: str
datetime: str
author: str
class ArticleContext(TypedDict):
slug: str
name: str
datetime: str
author: str
url: str
class Article:
directory: Path
@@ -288,8 +313,18 @@ class Article:
return Path(config.setup.dist_directory, *self.slug_path)
context: ArticleContext
context_shared: Dict[str, Any]
cross_article_context: Dict[str, Any]
child_articles: ArticleList[Article]
article_translations_list: List[ArticleTranslation]
article_translations_map: Dict[str, ArticleTranslation]
linked_articles: ArticleList[Article]
@cached_property
def related_articles(self) -> ArticleList[Article]:
res = ArticleList(self.child_articles)
res.extend(self.linked_articles)
return res
def __init__(self, directory: Path, article_path: Optional[List[str]] = None, is_root: bool = False, parent: Optional[Article] = None):
self.directory = directory
@@ -297,16 +332,16 @@ class Article:
self.article_path: List[Article] = article_path or []
self.article_path.append(self)
self.context: ArticleContext = {}
self.context_shared = {}
self.cross_article_context = CROSS_ARTICLE_CONTEXT[self.slug] = {}
self.context = CROSS_ARTICLE_CONTEXT[self.slug] = {}
ARTICLE_LAKE[self.slug] = self
self.linked_articles = ArticleList([])
# build the tree
self.child_articles: List[Article] = []
self.article_translations_list: List[ArticleTranslation] = []
self.article_translations_map: Dict[str, ArticleTranslation] = {}
self.child_articles = ArticleList([])
self.article_translations_list = []
self.article_translations_map = {}
for c in self.directory.iterdir():
if c.name == "index.toml":
@@ -327,41 +362,52 @@ class Article:
logger.info("found %s at %s with the translations %s", self.slug, ".".join(list(self.slug_path)), ",".join(self.article_translations_map.keys()))
@cached_property
def modified_at(self) -> datetime:
if "iso_date" in self.config:
return datetime.fromisoformat(self.config["iso_date"])
"""
TODO
scann every article file and use the youngest article file
"""
return datetime.fromtimestamp(self.directory.stat().st_mtime)
@cached_property
def author(self) -> str:
return self.config.get("author", config.default_author)
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)
self.cross_article_context.update(self.context_shared)
self.cross_article_context["title"] = self.context_shared["slug"]
self.cross_article_context["article_url"] = self.context_shared["url"]
compile_cross_article_context(self.cross_article_context)
self.context["slug"] = self.slug
self.context["name"] = self.name
self.context["url"] = self.url
add_html_link(self.context)
self.context["date"] = self.modified_at.strftime(config.formatting.datetime_format)
self.context["year"] = str(self.modified_at.year)
self.context["iso_date"] = self.modified_at.isoformat()
self.context["author"] = self.author
# recursive context structures
translation_list = self.context["translations"] = []
child_article_list = self.context["children"] = []
self.context["translations"] = [c.context for c in self.article_translations_list]
self.context["children"] = self.child_articles.context
self.context["breadcrumbs"] = [b.context for b in self.article_path]
for lang, article in self.article_translations_map.items():
self.context[lang] = article.context
for article_translation in self.article_translations_list:
self.context[article_translation.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 __init_content_context__(self):
for at in self.article_translations_list:
at.__init_content_context__()
self.context["linked"] = self.linked_articles.context
self.context["related"] = self.related_articles.context
for a in self.child_articles:
a.__init_content_context__()
@@ -387,9 +433,9 @@ class ContextDict(jinja2.runtime.Context):
# GLOBALS
logger = logging.getLogger("stsg.build")
ARTICLE_LAKE: Dict[str, Article] = {}
CROSS_ARTICLE_CONTEXT: Dict[str, Dict[str, Any]] = {}
TRANSLATED_CROSS_ARTICLE_CONTEXT: Dict[str, Dict[str, Dict[str, Any]]] = defaultdict(dict)
ARTICLE_LAKE: Dict[str, Article] = {}
ARTICLE_REFERENCE_VALUES: DefaultDict[str, Dict[str, str]] = defaultdict(dict)
@@ -406,6 +452,7 @@ def build():
tree.__init_context__()
tree.__init_content_context__()
"""
import json
with Path("context.json").open("w") as f:
json.dump(tree.context, f, indent=4)
@@ -413,6 +460,7 @@ def build():
json.dump(CROSS_ARTICLE_CONTEXT, f, indent=4)
with Path("t_cross_article_context.json").open("w") as f:
json.dump(TRANSLATED_CROSS_ARTICLE_CONTEXT, f, indent=4)
"""
logger.info("dumping page tree...")
tree.build()

62
stsg/definitions.py Normal file
View File

@@ -0,0 +1,62 @@
from __future__ import annotations
from typing import TypedDict, List, Union
class ArticleConfig(TypedDict):
slug: str
name: str
iso_date: str
author: str
class ArticleContext(TypedDict):
slug: str
name: str
url: str
link: str
date: str
year: str
iso_date: str
author: str
translations: List[ArticleTranslationContext]
children: List[ArticleContext]
breadcrumbs: List[ArticleContext]
linked: List[ArticleContext]
related: List[ArticleContext]
class TypedLanguage(TypedDict):
flag: str
name: str
native_name: str
priority: int
code: str
class ArticleTranslationContext(TypedDict):
slug: str
name: str
url: str
link: str
date: str
year: str
iso_date: str
author: str
language: TypedLanguage
article_url: str
"""
The type Union[ArticleTranslationContext, ArticleContext] exist,
because if the article it is linked to doesn't exist in the same languages it uses the overview instead.
If you dislike this behavior set:
config.fall_back_to_overview_in_translation = False
"""
children: List[Union[ArticleTranslationContext, ArticleContext]]
breadcrumbs: List[Union[ArticleTranslationContext, ArticleContext]]
# you can't use these within the markdown text itself
content: str
preview: str
linked: List[Union[ArticleTranslationContext, ArticleContext]]
related: List[Union[ArticleTranslationContext, ArticleContext]]