crieur 1.7.0__tar.gz → 1.9.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of crieur might be problematic. Click here for more details.
- {crieur-1.7.0 → crieur-1.9.0}/PKG-INFO +2 -1
- {crieur-1.7.0 → crieur-1.9.0}/crieur/__init__.py +1 -1
- {crieur-1.7.0 → crieur-1.9.0}/crieur/generator.py +9 -1
- {crieur-1.7.0 → crieur-1.9.0}/crieur/models.py +22 -6
- crieur-1.9.0/crieur/typography.py +93 -0
- {crieur-1.7.0 → crieur-1.9.0}/pyproject.toml +1 -0
- {crieur-1.7.0 → crieur-1.9.0}/.gitignore +0 -0
- {crieur-1.7.0 → crieur-1.9.0}/LICENSE +0 -0
- {crieur-1.7.0 → crieur-1.9.0}/README.md +0 -0
- {crieur-1.7.0 → crieur-1.9.0}/crieur/__main__.py +0 -0
- {crieur-1.7.0 → crieur-1.9.0}/crieur/cli.py +0 -0
- {crieur-1.7.0 → crieur-1.9.0}/crieur/statics/pico.css +0 -0
- {crieur-1.7.0 → crieur-1.9.0}/crieur/templates/article.html +0 -0
- {crieur-1.7.0 → crieur-1.9.0}/crieur/templates/author.html +0 -0
- {crieur-1.7.0 → crieur-1.9.0}/crieur/templates/base.html +0 -0
- {crieur-1.7.0 → crieur-1.9.0}/crieur/templates/homepage.html +0 -0
- {crieur-1.7.0 → crieur-1.9.0}/crieur/templates/keyword.html +0 -0
- {crieur-1.7.0 → crieur-1.9.0}/crieur/templates/numero.html +0 -0
- {crieur-1.7.0 → crieur-1.9.0}/crieur/utils.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: crieur
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.9.0
|
|
4
4
|
Summary: A Static Revue Generator.
|
|
5
5
|
Project-URL: Homepage, https://gitlab.huma-num.fr/ecrinum/crieur
|
|
6
6
|
Project-URL: Issues, https://gitlab.huma-num.fr/ecrinum/crieur/-/issues
|
|
@@ -693,6 +693,7 @@ Requires-Dist: pillow
|
|
|
693
693
|
Requires-Dist: python-slugify
|
|
694
694
|
Requires-Dist: pytz>=2025.2
|
|
695
695
|
Requires-Dist: pyyaml>=6.0.2
|
|
696
|
+
Requires-Dist: regex>=2024.11.6
|
|
696
697
|
Requires-Dist: tzdata>=2025.2
|
|
697
698
|
Requires-Dist: unidecode
|
|
698
699
|
Provides-Extra: dev
|
|
@@ -11,10 +11,12 @@ from jinja2 import FileSystemLoader
|
|
|
11
11
|
from slugify import slugify
|
|
12
12
|
|
|
13
13
|
from . import VERSION
|
|
14
|
+
from .typography import typographie
|
|
14
15
|
from .utils import neighborhood
|
|
15
16
|
|
|
16
17
|
locale.setlocale(locale.LC_ALL, "")
|
|
17
|
-
|
|
18
|
+
mistune_plugins = ["footnotes", "superscript", "table"]
|
|
19
|
+
md = mistune.create_markdown(plugins=mistune_plugins, escape=False)
|
|
18
20
|
|
|
19
21
|
|
|
20
22
|
def slugify_(value):
|
|
@@ -25,6 +27,11 @@ def markdown(value):
|
|
|
25
27
|
return md(value) if value else ""
|
|
26
28
|
|
|
27
29
|
|
|
30
|
+
def typography(value):
|
|
31
|
+
value = value.replace("\\ ", " ")
|
|
32
|
+
return typographie(value) if value else ""
|
|
33
|
+
|
|
34
|
+
|
|
28
35
|
def generate_html(
|
|
29
36
|
title, base_url, numeros, keywords, authors, extra_vars, target_path, templates_path
|
|
30
37
|
):
|
|
@@ -35,6 +42,7 @@ def generate_html(
|
|
|
35
42
|
)
|
|
36
43
|
environment.filters["slugify"] = slugify_
|
|
37
44
|
environment.filters["markdown"] = markdown
|
|
45
|
+
environment.filters["typography"] = typography
|
|
38
46
|
|
|
39
47
|
extra_vars = json.loads(extra_vars) if extra_vars else {}
|
|
40
48
|
|
|
@@ -10,8 +10,23 @@ from PIL import Image, UnidentifiedImageError
|
|
|
10
10
|
from slugify import slugify
|
|
11
11
|
from yaml.composer import ComposerError
|
|
12
12
|
|
|
13
|
+
from .generator import mistune_plugins
|
|
14
|
+
from .typography import typographie
|
|
13
15
|
|
|
14
|
-
|
|
16
|
+
|
|
17
|
+
class FrenchTypographyRenderer(mistune.HTMLRenderer):
|
|
18
|
+
"""Apply French typographic rules to text."""
|
|
19
|
+
|
|
20
|
+
def text(self, text):
|
|
21
|
+
text = text.replace("\\ ", " ")
|
|
22
|
+
return typographie(super().text(text), html=True)
|
|
23
|
+
|
|
24
|
+
def block_html(self, html):
|
|
25
|
+
html = html.replace("\\ ", " ")
|
|
26
|
+
return typographie(super().block_html(html), html=True)
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
class ImgsWithSizesRenderer(FrenchTypographyRenderer):
|
|
15
30
|
"""Renders images as <figure>s and add sizes."""
|
|
16
31
|
|
|
17
32
|
def __init__(
|
|
@@ -21,7 +36,7 @@ class ImgsWithSizesRenderer(mistune.HTMLRenderer):
|
|
|
21
36
|
base_url=None,
|
|
22
37
|
article=None,
|
|
23
38
|
):
|
|
24
|
-
super().__init__()
|
|
39
|
+
super().__init__(escape, allow_harmful_protocols)
|
|
25
40
|
self._base_url = base_url
|
|
26
41
|
self._article = article
|
|
27
42
|
|
|
@@ -31,7 +46,7 @@ class ImgsWithSizesRenderer(mistune.HTMLRenderer):
|
|
|
31
46
|
return text
|
|
32
47
|
return super().paragraph(text)
|
|
33
48
|
|
|
34
|
-
def image(self,
|
|
49
|
+
def image(self, text, url, title=None):
|
|
35
50
|
if self._article.images_path is None:
|
|
36
51
|
print(f"Image with URL `{url}` is discarded.")
|
|
37
52
|
return ""
|
|
@@ -42,7 +57,7 @@ class ImgsWithSizesRenderer(mistune.HTMLRenderer):
|
|
|
42
57
|
print(f"`{full_path}` is not a valid image.")
|
|
43
58
|
return ""
|
|
44
59
|
width, height = image.size
|
|
45
|
-
caption = f"<figcaption>{
|
|
60
|
+
caption = f"<figcaption>{text}</figcaption>" if text else ""
|
|
46
61
|
full_url = f"{self._base_url}{self._article.url}{url}"
|
|
47
62
|
return dedent(
|
|
48
63
|
f"""\
|
|
@@ -54,7 +69,7 @@ class ImgsWithSizesRenderer(mistune.HTMLRenderer):
|
|
|
54
69
|
width="{width}" height="{height}"
|
|
55
70
|
loading="lazy"
|
|
56
71
|
decoding="async"
|
|
57
|
-
alt="{
|
|
72
|
+
alt="{text}">
|
|
58
73
|
</a>
|
|
59
74
|
{caption}
|
|
60
75
|
</figure>
|
|
@@ -121,7 +136,8 @@ class Numero(YAMLWizard):
|
|
|
121
136
|
base_url=base_url,
|
|
122
137
|
article=loaded_article,
|
|
123
138
|
),
|
|
124
|
-
plugins=
|
|
139
|
+
plugins=mistune_plugins,
|
|
140
|
+
escape=False,
|
|
125
141
|
)
|
|
126
142
|
loaded_article.content_html = md(loaded_article.content_md)
|
|
127
143
|
loaded_articles.append(loaded_article)
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import html.entities
|
|
2
|
+
import unicodedata
|
|
3
|
+
from dataclasses import dataclass
|
|
4
|
+
|
|
5
|
+
import regex # pour le support de "\p{}"
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
@dataclass
|
|
9
|
+
class Caractere:
|
|
10
|
+
unicode: str
|
|
11
|
+
html: str
|
|
12
|
+
|
|
13
|
+
def __init__(self, name: str):
|
|
14
|
+
self.unicode = unicodedata.lookup(name)
|
|
15
|
+
codepoint = ord(self.unicode)
|
|
16
|
+
html_name = html.entities.codepoint2name.get(codepoint, f"#{codepoint}")
|
|
17
|
+
self.html = f"&{html_name};"
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
ESPACE_INSECABLE = Caractere(name="NO-BREAK SPACE")
|
|
21
|
+
ESPACE_FINE_INSECABLE = Caractere(name="NARROW NO-BREAK SPACE")
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def assemble_regexes(*regexes):
|
|
25
|
+
return "|".join(regexes)
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def build_regex(avant, apres):
|
|
29
|
+
# \p{} permet de reconnaître un caractère par sa catégorie Unicode
|
|
30
|
+
# "Zs" est la catégorie "Separator, space".
|
|
31
|
+
return (
|
|
32
|
+
rf"((?P<avant>{avant})"
|
|
33
|
+
+ rf"(\p{{Zs}}|{ESPACE_INSECABLE.html})"
|
|
34
|
+
+ rf"(?P<apres>{apres}))"
|
|
35
|
+
+ r"(?!(.(?!<svg))*<\/svg>)"
|
|
36
|
+
)
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
RE_ESPACE_FINE_INSECABLE = regex.compile(
|
|
40
|
+
assemble_regexes(
|
|
41
|
+
build_regex(r"\w?", r"[;\?!]"), # Ponctuations doubles.
|
|
42
|
+
build_regex(
|
|
43
|
+
r"\d", r"([ghj]|min|sec|images|mm|hab|kg|mg|µg|L|km|°C|GHz)(\b|$)"
|
|
44
|
+
), # Unités.
|
|
45
|
+
build_regex(r"\d", r"(Mo|Ko|Go|Mb|Kb|Gb)(\b|$)"), # Tailles de fichiers.
|
|
46
|
+
build_regex(r"\d", r"%"), # Pourcentages.
|
|
47
|
+
build_regex(r"\d", r"€"), # Symboles monétaires.
|
|
48
|
+
build_regex(r"\d", r"\d"), # Séparateurs de milliers.
|
|
49
|
+
)
|
|
50
|
+
)
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def insere_espaces_fines_insecables(texte):
|
|
54
|
+
return RE_ESPACE_FINE_INSECABLE.sub(
|
|
55
|
+
r"\g<avant>" + ESPACE_FINE_INSECABLE.unicode + r"\g<apres>", texte
|
|
56
|
+
)
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
RE_ESPACE_INSECABLE = regex.compile(
|
|
60
|
+
assemble_regexes(
|
|
61
|
+
build_regex(r"\w?", r":"), # Deux points.
|
|
62
|
+
build_regex(r"«", r""), # Guillemets en chevrons.
|
|
63
|
+
build_regex(r"", r"»"), # Guillemets en chevrons.
|
|
64
|
+
build_regex(
|
|
65
|
+
rf"\b(\d|{ESPACE_FINE_INSECABLE.html})+", r"(?!\d)\w"
|
|
66
|
+
), # Nombre suivi de lettres.
|
|
67
|
+
build_regex(r"(M\.|Mme)", r"\w"), # Titres (Monsieur, Madame).
|
|
68
|
+
)
|
|
69
|
+
)
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def insere_espaces_insecables(texte):
|
|
73
|
+
return RE_ESPACE_INSECABLE.sub(
|
|
74
|
+
r"\g<avant>" + ESPACE_INSECABLE.unicode + r"\g<apres>", texte
|
|
75
|
+
)
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def encode_espaces_insecables_en_html(texte):
|
|
79
|
+
for caractere in (ESPACE_INSECABLE, ESPACE_FINE_INSECABLE):
|
|
80
|
+
texte = texte.replace(caractere.unicode, caractere.html)
|
|
81
|
+
return texte
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
def typographie(texte, html=False):
|
|
85
|
+
"""
|
|
86
|
+
Utilise les espaces insécables fines ou normales lorsque c’est approprié
|
|
87
|
+
|
|
88
|
+
https://fr.wikipedia.org/wiki/Espace_ins%C3%A9cable#En_France
|
|
89
|
+
"""
|
|
90
|
+
res = insere_espaces_fines_insecables(insere_espaces_insecables(texte))
|
|
91
|
+
if html:
|
|
92
|
+
res = encode_espaces_insecables_en_html(res)
|
|
93
|
+
return res
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|