crieur 2.0.2__py3-none-any.whl

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.
@@ -0,0 +1,24 @@
1
+ {% extends "base.html" %}
2
+
3
+ {% block content %}
4
+ {% for numero in numeros|reverse %}
5
+ <div class="aside">
6
+ <hgroup>
7
+ <h2><a href="{{ base_url }}numero/{{ numero.slug }}/">{{ numero.title_f }}</a></h2>
8
+ <p class="muted"><small>{{ numero.date.strftime('%d %B %Y') }}</small></p>
9
+ </hgroup>
10
+ <ul>
11
+ {% for article in numero.articles %}
12
+ <li>
13
+ <a href="{{ base_url }}{{ article.url }}">{{ article.title_f }}</a>
14
+ par
15
+ {% for author in article.authors -%}
16
+ <em>{{ author.forname }} {{ author.surname }}{%- if not loop.last -%}, {% endif %}</em>
17
+ {%- endfor %}
18
+ <nobr><span class="muted"><small>({{ article.date.strftime('%d %B %Y') }})</small></span></nobr>
19
+ </li>
20
+ {% endfor %}
21
+ </ul>
22
+ </div>
23
+ {% endfor %}
24
+ {% endblock content %}
crieur/typography.py ADDED
@@ -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
crieur/utils.py ADDED
@@ -0,0 +1,56 @@
1
+ import re
2
+ from pathlib import Path
3
+
4
+ html_comments_re = re.compile(r"<!--.*?-->")
5
+ html_tags_re = re.compile(r"<[^>]*>")
6
+
7
+
8
+ def strip_html_comments(value):
9
+ if not value:
10
+ return ""
11
+ return html_comments_re.sub("", value)
12
+
13
+
14
+ def strip_html_tags(value):
15
+ if not value:
16
+ return ""
17
+ return html_tags_re.sub("", value)
18
+
19
+
20
+ def each_file_from(source_dir, pattern="*.html", exclude=None):
21
+ """Walk across the `source_dir` and return the html file paths."""
22
+ for path in _each_path_from(source_dir, pattern=pattern, exclude=exclude):
23
+ if path.is_file():
24
+ yield path
25
+
26
+
27
+ def each_folder_from(source_dir, exclude=None):
28
+ """Walk across the `source_dir` and return the folder paths."""
29
+ for path in _each_path_from(source_dir, exclude=exclude):
30
+ if path.is_dir():
31
+ yield path
32
+
33
+
34
+ def _each_path_from(source_dir, pattern="*", exclude=None):
35
+ for path in sorted(Path(source_dir).glob(pattern)):
36
+ if exclude is not None and path.name in exclude:
37
+ continue
38
+ yield path
39
+
40
+
41
+ def neighborhood(iterable, first=None, last=None):
42
+ """
43
+ Yield the (index, previous, current, next) items given an iterable.
44
+
45
+ You can specify a `first` and/or `last` item for bounds.
46
+ """
47
+ index = 1
48
+ iterator = iter(iterable)
49
+ previous = first
50
+ current = next(iterator) # Throws StopIteration if empty.
51
+ for next_ in iterator:
52
+ yield (index, previous, current, next_)
53
+ previous = current
54
+ index += 1
55
+ current = next_
56
+ yield (index, previous, current, last)