Nouvelle version de Zola et petites corrections
This commit is contained in:
1 parent
7e09d12345
commit
58bcc189ab
9 files changed
+92
-22
No files matched your search
@@ -0,0 +1 @@
|
|||||||
|
FRONTEND_PORT=8002
|
||||||
@@ -1,3 +1,5 @@
|
|||||||
|
.env
|
||||||
|
|
||||||
# Site généré par Zola
|
# Site généré par Zola
|
||||||
src/public/
|
src/public/
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,6 @@
|
|||||||
lescarnetsdakkadien.netig.net {
|
lescarnetsdakkadien.netig.net {
|
||||||
|
encode zstd gzip
|
||||||
|
|
||||||
@assets path /main.css /giallo.css /images/* /fonts/*
|
@assets path /main.css /giallo.css /images/* /fonts/*
|
||||||
@documents {
|
@documents {
|
||||||
not path /main.css /giallo.css /images/* /fonts/*
|
not path /main.css /giallo.css /images/* /fonts/*
|
||||||
@@ -7,6 +9,7 @@ lescarnetsdakkadien.netig.net {
|
|||||||
header {
|
header {
|
||||||
Content-Security-Policy "default-src 'self'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; object-src 'none'"
|
Content-Security-Policy "default-src 'self'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; object-src 'none'"
|
||||||
Referrer-Policy "strict-origin-when-cross-origin"
|
Referrer-Policy "strict-origin-when-cross-origin"
|
||||||
|
Permissions-Policy "camera=(), geolocation=(), microphone=()"
|
||||||
Strict-Transport-Security "max-age=31536000"
|
Strict-Transport-Security "max-age=31536000"
|
||||||
X-Content-Type-Options "nosniff"
|
X-Content-Type-Options "nosniff"
|
||||||
X-Frame-Options "DENY"
|
X-Frame-Options "DENY"
|
||||||
|
|||||||
+6
-6
@@ -4,13 +4,13 @@ COPY scripts/check-content.py scripts/_schema.py ./scripts/
|
|||||||
COPY src ./src
|
COPY src ./src
|
||||||
RUN python3 scripts/check-content.py
|
RUN python3 scripts/check-content.py
|
||||||
|
|
||||||
FROM ghcr.io/getzola/zola:v0.23.1 AS builder
|
FROM ghcr.io/getzola/zola:v0.23.6 AS build
|
||||||
WORKDIR /project
|
WORKDIR /site
|
||||||
COPY --from=validator /project/src ./
|
COPY --from=validator /project/src /site
|
||||||
RUN ["/zola", "build"]
|
RUN ["zola", "build", "--output-dir", "/public"]
|
||||||
|
|
||||||
FROM ghcr.io/static-web-server/static-web-server:2
|
FROM ghcr.io/static-web-server/static-web-server:2.44.0
|
||||||
|
COPY --from=build /public /public
|
||||||
ENV SERVER_ROOT=/public \
|
ENV SERVER_ROOT=/public \
|
||||||
SERVER_CACHE_CONTROL_HEADERS=false \
|
SERVER_CACHE_CONTROL_HEADERS=false \
|
||||||
SERVER_SECURITY_HEADERS=false
|
SERVER_SECURITY_HEADERS=false
|
||||||
COPY --from=builder /project/public /public
|
|
||||||
+6
-5
@@ -29,7 +29,7 @@ Les commandes publiques sont :
|
|||||||
|
|
||||||
- `new-content.py` : création d’un brouillon ;
|
- `new-content.py` : création d’un brouillon ;
|
||||||
- `update-content.py` : mise à jour de l’horodatage `updated` ;
|
- `update-content.py` : mise à jour de l’horodatage `updated` ;
|
||||||
- `check-content.py` : validation structurelle des fiches.
|
- `check-content.py` : validation des noms et de la structure des fiches.
|
||||||
|
|
||||||
`_schema.py` est leur module commun et ne se lance pas directement.
|
`_schema.py` est leur module commun et ne se lance pas directement.
|
||||||
|
|
||||||
@@ -424,6 +424,9 @@ Zola génère `giallo.css`, tandis que le HTML rendu utilise les classes corresp
|
|||||||
|
|
||||||
Pour chaque fiche, il vérifie :
|
Pour chaque fiche, il vérifie :
|
||||||
|
|
||||||
|
- le nom de fichier en Unicode NFC ;
|
||||||
|
- les identifiants en minuscules avec lettres Unicode, chiffres et tirets ;
|
||||||
|
- la forme `mzl-NNN.md` pour les signes ;
|
||||||
- la lecture en UTF-8 ;
|
- la lecture en UTF-8 ;
|
||||||
- la présence d’un front matter délimité par `+++` ;
|
- la présence d’un front matter délimité par `+++` ;
|
||||||
- la validité du TOML ;
|
- la validité du TOML ;
|
||||||
@@ -453,11 +456,9 @@ Le validateur accepte les chaînes et tableaux vides. Il ne contrôle pas :
|
|||||||
|
|
||||||
- la qualité ou l’exactitude linguistique des valeurs ;
|
- la qualité ou l’exactitude linguistique des valeurs ;
|
||||||
- les champs supplémentaires ;
|
- les champs supplémentaires ;
|
||||||
- la forme des noms de fichiers ;
|
- la correspondance entre le numéro MZL du front matter et le nom du fichier.
|
||||||
- la correspondance entre le numéro MZL et le nom du fichier ;
|
|
||||||
- la normalisation Unicode des fichiers existants.
|
|
||||||
|
|
||||||
Zola reste responsable des liens, taxonomies et templates. Le générateur reste responsable de produire des noms de fichiers valides et des squelettes complets.
|
Zola reste responsable des liens, taxonomies et templates. Le générateur et le validateur appliquent les mêmes conventions de nommage ; le générateur reste responsable de produire des squelettes complets.
|
||||||
|
|
||||||
## Vérification avant publication
|
## Vérification avant publication
|
||||||
|
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
|
|
||||||
Site public : <https://lescarnetsdakkadien.netig.net>
|
Site public : <https://lescarnetsdakkadien.netig.net>
|
||||||
|
|
||||||
Le site est généré avec **Zola 0.23.1** et **Tera 2** à partir de fichiers Markdown. Il est entièrement statique : aucune base de données, aucune interface d’administration et aucune exécution côté serveur ne sont nécessaires.
|
Le site est généré avec **Zola 0.23.6** et **Tera 2** à partir de fichiers Markdown. Il est entièrement statique : aucune base de données, aucune interface d’administration et aucun code applicatif côté serveur ne sont nécessaires.
|
||||||
|
|
||||||
## Contenu
|
## Contenu
|
||||||
|
|
||||||
@@ -21,7 +21,7 @@ La page d’accueil présente le dernier article et les trois fiches les plus r
|
|||||||
Pour le développement local :
|
Pour le développement local :
|
||||||
|
|
||||||
- Python 3.11 ou ultérieur ;
|
- Python 3.11 ou ultérieur ;
|
||||||
- Zola 0.23.1 ;
|
- Zola 0.23.6 ;
|
||||||
- Git, si le dépôt est versionné avec Git.
|
- Git, si le dépôt est versionné avec Git.
|
||||||
|
|
||||||
Pour le déploiement décrit ici :
|
Pour le déploiement décrit ici :
|
||||||
@@ -83,7 +83,7 @@ Les contenus sont stockés dans `src/content/`. Trois commandes accompagnent leu
|
|||||||
|
|
||||||
- `new-content.py` crée un brouillon complet pour une section donnée ;
|
- `new-content.py` crée un brouillon complet pour une section donnée ;
|
||||||
- `update-content.py` actualise son horodatage `updated` ;
|
- `update-content.py` actualise son horodatage `updated` ;
|
||||||
- `check-content.py` vérifie la présence et le type des champs prévus.
|
- `check-content.py` vérifie les noms de fichiers ainsi que la présence et le type des champs prévus.
|
||||||
|
|
||||||
Le module `_schema.py` centralise les modèles utilisés par le générateur et le validateur ; il n’est pas exécuté directement.
|
Le module `_schema.py` centralise les modèles utilisés par le générateur et le validateur ; il n’est pas exécuté directement.
|
||||||
|
|
||||||
@@ -93,6 +93,7 @@ Les commandes, conventions et modèles de front matter sont détaillés dans le
|
|||||||
|
|
||||||
```text
|
```text
|
||||||
.
|
.
|
||||||
|
├── .env.example
|
||||||
├── Caddyfile.example
|
├── Caddyfile.example
|
||||||
├── Dockerfile
|
├── Dockerfile
|
||||||
├── compose.yml
|
├── compose.yml
|
||||||
@@ -128,8 +129,20 @@ Les commandes, conventions et modèles de front matter sont détaillés dans le
|
|||||||
L’image est construite en trois étapes :
|
L’image est construite en trois étapes :
|
||||||
|
|
||||||
1. validation des fiches avec Python ;
|
1. validation des fiches avec Python ;
|
||||||
2. génération du site avec Zola 0.23.1 ;
|
2. génération du site avec Zola 0.23.6 ;
|
||||||
3. copie des seuls fichiers publics dans un serveur HTTP statique.
|
3. copie des seuls fichiers publics dans static-web-server 2.44.0.
|
||||||
|
|
||||||
|
Créer la configuration locale à partir de l’exemple :
|
||||||
|
|
||||||
|
```sh
|
||||||
|
cp .env.example .env
|
||||||
|
```
|
||||||
|
|
||||||
|
Le seul réglage nécessaire est le port HTTP publié sur l’hôte :
|
||||||
|
|
||||||
|
```dotenv
|
||||||
|
FRONTEND_PORT=8002
|
||||||
|
```
|
||||||
|
|
||||||
Vérifier puis démarrer le service :
|
Vérifier puis démarrer le service :
|
||||||
|
|
||||||
@@ -138,12 +151,14 @@ docker compose config
|
|||||||
docker compose up -d --build
|
docker compose up -d --build
|
||||||
```
|
```
|
||||||
|
|
||||||
Le conteneur est exposé uniquement sur l’interface locale de l’hôte :
|
Par défaut, le conteneur est exposé uniquement sur l’interface locale de l’hôte :
|
||||||
|
|
||||||
```text
|
```text
|
||||||
http://127.0.0.1:8002
|
http://127.0.0.1:8002
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Le port peut être changé dans `.env`. Si Caddy est utilisé, adapter alors aussi le port de `reverse_proxy` dans `Caddyfile.example` ou dans la configuration de l’hôte.
|
||||||
|
|
||||||
Pour mettre à jour le déploiement :
|
Pour mettre à jour le déploiement :
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
@@ -163,10 +178,11 @@ Caddy prend en charge le domaine public, HTTPS, les en-têtes de sécurité et l
|
|||||||
|
|
||||||
[`Caddyfile.example`](Caddyfile.example) fournit le bloc correspondant au port publié par `compose.yml`. Il applique :
|
[`Caddyfile.example`](Caddyfile.example) fournit le bloc correspondant au port publié par `compose.yml`. Il applique :
|
||||||
|
|
||||||
|
- la compression `zstd` ou `gzip` selon le client ;
|
||||||
- `Cache-Control: no-cache` aux documents ;
|
- `Cache-Control: no-cache` aux documents ;
|
||||||
- un cache navigateur d’une journée aux feuilles de style, images et polices ;
|
- un cache navigateur d’une journée aux feuilles de style, images et polices ;
|
||||||
- une CSP compatible avec les ressources locales du site ;
|
- une CSP compatible avec les ressources locales du site ;
|
||||||
- HSTS, `Referrer-Policy`, `X-Content-Type-Options` et `X-Frame-Options`.
|
- HSTS, `Referrer-Policy`, `Permissions-Policy`, `X-Content-Type-Options` et `X-Frame-Options`.
|
||||||
|
|
||||||
Le fichier peut être importé au niveau principal du Caddyfile de l’hôte ou servir de modèle pour un bloc existant. Sa syntaxe peut être contrôlée avec :
|
Le fichier peut être importé au niveau principal du Caddyfile de l’hôte ou servir de modèle pour un bloc existant. Sa syntaxe peut être contrôlée avec :
|
||||||
|
|
||||||
|
|||||||
+2
-2
@@ -3,6 +3,6 @@ name: les-carnets-d-akkadien
|
|||||||
services:
|
services:
|
||||||
zola:
|
zola:
|
||||||
build: .
|
build: .
|
||||||
ports:
|
|
||||||
- "127.0.0.1:8002:80"
|
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
|
ports:
|
||||||
|
- "127.0.0.1:${FRONTEND_PORT:-8002}:80"
|
||||||
@@ -1,8 +1,10 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
"""Vérifie que chaque fiche contient les champs prévus pour son modèle."""
|
"""Vérifie que chaque fiche contient les champs prévus pour son modèle."""
|
||||||
|
|
||||||
|
import re
|
||||||
import sys
|
import sys
|
||||||
import tomllib
|
import tomllib
|
||||||
|
import unicodedata
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Any
|
from typing import Any
|
||||||
@@ -25,6 +27,8 @@ COMMON_FIELDS = {
|
|||||||
"draft": bool,
|
"draft": bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
MZL_FILENAME_RE = re.compile(r"mzl-([0-9]{3})")
|
||||||
|
|
||||||
|
|
||||||
class Validator:
|
class Validator:
|
||||||
def __init__(self) -> None:
|
def __init__(self) -> None:
|
||||||
@@ -68,6 +72,46 @@ def read_front_matter(
|
|||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def check_filename(
|
||||||
|
path: Path,
|
||||||
|
section: str,
|
||||||
|
validator: Validator,
|
||||||
|
) -> None:
|
||||||
|
stem = path.stem
|
||||||
|
normalized = unicodedata.normalize("NFC", stem)
|
||||||
|
if stem != normalized:
|
||||||
|
validator.error(path, "nom de fichier non normalisé en Unicode NFC")
|
||||||
|
|
||||||
|
if section == "signes":
|
||||||
|
match = MZL_FILENAME_RE.fullmatch(stem)
|
||||||
|
if match is None or not 1 <= int(match.group(1)) <= 999:
|
||||||
|
validator.error(path, "un signe doit être nommé mzl-NNN.md (001 à 999)")
|
||||||
|
return
|
||||||
|
|
||||||
|
allow_leading_hyphen = section == "vocabulaire"
|
||||||
|
body = stem[1:] if stem.startswith("-") else stem
|
||||||
|
if stem.startswith("-") and not allow_leading_hyphen:
|
||||||
|
validator.error(
|
||||||
|
path,
|
||||||
|
"seul le vocabulaire accepte un nom commençant par un tiret",
|
||||||
|
)
|
||||||
|
return
|
||||||
|
|
||||||
|
if (
|
||||||
|
not body
|
||||||
|
or stem in {".", "..", "_index"}
|
||||||
|
or stem != stem.lower()
|
||||||
|
or stem.endswith("-")
|
||||||
|
or "--" in stem
|
||||||
|
or not all(character.isalnum() or character == "-" for character in body)
|
||||||
|
):
|
||||||
|
validator.error(
|
||||||
|
path,
|
||||||
|
"nom de fichier invalide : utiliser des lettres Unicode minuscules, "
|
||||||
|
"des chiffres et des tirets",
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def check_type(
|
def check_type(
|
||||||
value: Any,
|
value: Any,
|
||||||
expected_type: type,
|
expected_type: type,
|
||||||
@@ -189,6 +233,8 @@ def main() -> int:
|
|||||||
validator.error(path, f"section inconnue : {section}")
|
validator.error(path, f"section inconnue : {section}")
|
||||||
continue
|
continue
|
||||||
|
|
||||||
|
check_filename(path, section, validator)
|
||||||
|
|
||||||
result = read_front_matter(path, validator)
|
result = read_front_matter(path, validator)
|
||||||
if result is None:
|
if result is None:
|
||||||
continue
|
continue
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
{% extends "base.html" %}
|
{% extends "base.html" %}
|
||||||
{% set term_label = term.name | replace(from="-", to=" ") if taxonomy.name != "lectures" else term.name %}
|
{% block title %}
|
||||||
{% block title %}{% if taxonomy.name == "natures" %}Mots : {{ term_label }}{% elif taxonomy.name == "lectures" %}Lecture « {{ term_label }} »{% elif taxonomy.name == "genres" %}Textes : {{ term_label }}{% elif taxonomy.name == "themes" %}Grammaire : {{ term_label }}{% else %}{{ term_label }}{% endif %} · {{ config.title }}{% endblock title %}
|
{% set term_label = term.name | replace(from="-", to=" ") if taxonomy.name != "lectures" else term.name %}{% if taxonomy.name == "natures" %}Mots : {{ term_label }}{% elif taxonomy.name == "lectures" %}Lecture « {{ term_label }} »{% elif taxonomy.name == "genres" %}Textes : {{ term_label }}{% elif taxonomy.name == "themes" %}Grammaire : {{ term_label }}{% else %}{{ term_label }}{% endif %} · {{ config.title }}{% endblock title %}
|
||||||
{% block content %}
|
{% block content %}
|
||||||
|
{% set term_label = term.name | replace(from="-", to=" ") if taxonomy.name != "lectures" else term.name %}
|
||||||
{% set heading = term_label %}{% set description = "Entrées classées dans cet index." %}{% set back_url = get_url(path=taxonomy.name, trailing_slash=true) %}{% set back_label = "Retour à l’index" %}
|
{% set heading = term_label %}{% set description = "Entrées classées dans cet index." %}{% set back_url = get_url(path=taxonomy.name, trailing_slash=true) %}{% set back_label = "Retour à l’index" %}
|
||||||
{% if taxonomy.name == "natures" %}
|
{% if taxonomy.name == "natures" %}
|
||||||
{% set heading = "Mots : " ~ term_label %}{% set description = "Fiches de vocabulaire classées comme « " ~ term_label ~ " »." %}{% set back_label = "Toutes les natures grammaticales" %}
|
{% set heading = "Mots : " ~ term_label %}{% set description = "Fiches de vocabulaire classées comme « " ~ term_label ~ " »." %}{% set back_label = "Toutes les natures grammaticales" %}
|
||||||
|
|||||||
Reference in new issue
Block a user