2023-02-07 11:27:20 +00:00
|
|
|
|
# sphinx-lint
|
|
|
|
|
|
2023-02-14 22:53:23 +00:00
|
|
|
|
|
|
|
|
|
<!-- .slide: data-background="static/2023-snakes.svg" -->
|
2023-02-07 11:27:20 +00:00
|
|
|
|
|
|
|
|
|
Un linter pour ta doc.
|
|
|
|
|
|
|
|
|
|
<br/>
|
|
|
|
|
|
2023-02-14 22:53:23 +00:00
|
|
|
|
Julien Palard
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
## Julien Palard
|
|
|
|
|
|
|
|
|
|
<!-- .slide: data-background="static/background.jpg" -->
|
|
|
|
|
|
|
|
|
|
- CPython core dev
|
|
|
|
|
- Sysadmin à l’AFPy
|
|
|
|
|
- hackinscience.org
|
2023-02-07 11:27:20 +00:00
|
|
|
|
|
2023-02-14 22:53:23 +00:00
|
|
|
|
Notes:
|
2023-02-07 11:27:20 +00:00
|
|
|
|
|
2023-02-14 22:53:23 +00:00
|
|
|
|
- Je m’occupe surtout de docs.python.org
|
|
|
|
|
- Formateur, indépendant, faites passer le mot :)
|
2023-02-07 11:27:20 +00:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# Pourquoi ?
|
|
|
|
|
|
2023-02-14 22:53:23 +00:00
|
|
|
|
Notes:
|
|
|
|
|
|
2023-02-07 11:27:20 +00:00
|
|
|
|
Trouver des fautes de frappe dans du rst, ni plus, ni moins.
|
|
|
|
|
|
|
|
|
|
## Depuis quand ?
|
|
|
|
|
|
|
|
|
|
```text
|
|
|
|
|
Author: Georg Brandl <georg@python.org>
|
|
|
|
|
Date: Sat Jan 3 20:30:15 2009 +0000
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
Dans `cpython` dans `Doc/tools/rstlint.py` !
|
|
|
|
|
|
2023-02-14 22:53:23 +00:00
|
|
|
|
Notes:
|
2023-02-07 11:27:20 +00:00
|
|
|
|
|
|
|
|
|
Oui oui, Georg, l'auteur de Sphinx-doc.
|
|
|
|
|
|
|
|
|
|
|
2023-02-14 22:53:23 +00:00
|
|
|
|
# La motivation
|
2023-02-07 11:27:20 +00:00
|
|
|
|
|
2023-02-14 22:53:23 +00:00
|
|
|
|
`make suspicious`
|
2023-02-07 11:27:20 +00:00
|
|
|
|
|
2023-02-14 22:53:23 +00:00
|
|
|
|
Bien connu pour ses faux positifs, n'est-ce pas ?
|
2023-02-07 11:27:20 +00:00
|
|
|
|
|
2023-02-14 22:53:23 +00:00
|
|
|
|
Notes:
|
2023-02-07 11:27:20 +00:00
|
|
|
|
|
2023-02-14 22:53:23 +00:00
|
|
|
|
Lui il date du portage de la doc de LaTeX à Sphinx, il était là pour
|
|
|
|
|
repérer les erreurs de portage. Il date donc de 2009 aussi.
|
2023-02-07 11:27:20 +00:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
## La motivation
|
|
|
|
|
|
|
|
|
|
Remplacer `make suspicious` par un outil qui n’aurait **aucun** faux positif.
|
|
|
|
|
|
2023-02-14 22:53:23 +00:00
|
|
|
|
Notes:
|
2023-02-07 11:27:20 +00:00
|
|
|
|
|
2023-02-14 22:53:23 +00:00
|
|
|
|
- Aucun faux positif == cool pour les contributeurs !
|
|
|
|
|
- Et qui serait rapide !
|
|
|
|
|
- Parler de la lenteur de `make suspicious`.
|
2023-02-07 11:27:20 +00:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
## La motivation
|
|
|
|
|
|
2023-02-14 22:53:23 +00:00
|
|
|
|
Sortir `rstlint.py` de `cpython/Doc/tools` pour que tout le monde puisse l'utiliser.
|
|
|
|
|
|
2023-02-07 11:27:20 +00:00
|
|
|
|
|
2023-02-14 22:53:23 +00:00
|
|
|
|
Notes: Et pour faciliter la contribution.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
## La motivation
|
|
|
|
|
|
|
|
|
|
![](static/2023-pyconfr-issue-open.png)
|
2023-02-07 11:27:20 +00:00
|
|
|
|
|
|
|
|
|
# Parsons du rst !
|
|
|
|
|
|
|
|
|
|
## Le reStructuredText
|
|
|
|
|
|
|
|
|
|
Le balisage :
|
|
|
|
|
|
|
|
|
|
```text
|
|
|
|
|
*En italique*
|
|
|
|
|
**En gras**
|
|
|
|
|
`Interprété` (dont le rôle peut changer)
|
|
|
|
|
``litéral``
|
|
|
|
|
Lien_
|
|
|
|
|
`Autre lien`_
|
|
|
|
|
`Encore un lien <https://...>`_
|
|
|
|
|
`Un lien anonyme <https://...>`__
|
|
|
|
|
```
|
|
|
|
|
|
2023-02-14 22:53:23 +00:00
|
|
|
|
Notes: Comme en Markdown, comme en Org.
|
2023-02-07 11:27:20 +00:00
|
|
|
|
|
|
|
|
|
## Le reStructuredText
|
|
|
|
|
|
|
|
|
|
Les textes interprétés :
|
|
|
|
|
|
|
|
|
|
```text
|
|
|
|
|
`while` C'est du texte interprété.
|
|
|
|
|
:keyword:`while` Du texte interprété avec un « role marker »
|
|
|
|
|
`while`:keyword: Ça lui donne un sens particulier.
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
## Le reStructuredText
|
|
|
|
|
|
|
|
|
|
Donc oui le balisage et le texte interprété peuvent jouer le même rôle :
|
|
|
|
|
|
|
|
|
|
```text
|
|
|
|
|
*ça* c'est pareil que :emphasis:`ça`.
|
|
|
|
|
**ça** c'est pareil que :strong:`ça`.
|
|
|
|
|
``ça`` c'est pareil que :literal:`ça`.
|
|
|
|
|
```
|
|
|
|
|
|
2023-02-14 22:53:23 +00:00
|
|
|
|
Notes:
|
2023-02-07 11:27:20 +00:00
|
|
|
|
|
|
|
|
|
Mais comme en Python on a plus de méthodes que d'opérateurs…
|
|
|
|
|
|
|
|
|
|
En rst on a plus de rôles que de balises !
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
## Le reStructuredText
|
|
|
|
|
|
|
|
|
|
Les directives :
|
|
|
|
|
|
|
|
|
|
```text
|
|
|
|
|
.. image:: Lenna.jpg
|
|
|
|
|
|
|
|
|
|
.. important::
|
2023-02-14 22:53:23 +00:00
|
|
|
|
Elle s’appelle *Lena Sjööblom*.
|
2023-02-07 11:27:20 +00:00
|
|
|
|
```
|
|
|
|
|
|
2023-02-14 22:53:23 +00:00
|
|
|
|
Notes:
|
2023-02-07 11:27:20 +00:00
|
|
|
|
|
|
|
|
|
Je prends un gros raccourci : les directives ne sont qu'un bout de
|
|
|
|
|
tout le balisage "explicite" comme les commentaires, les notes de bas
|
|
|
|
|
de page, ...
|
|
|
|
|
|
2023-02-14 22:53:23 +00:00
|
|
|
|
Retenez-bien que le corps d'une directive peut contenir du reStructuredText !
|
2023-02-07 11:27:20 +00:00
|
|
|
|
|
|
|
|
|
## Parsons du rst !
|
|
|
|
|
|
2023-02-14 22:53:23 +00:00
|
|
|
|
Jusque-là, c’est facile !
|
|
|
|
|
|
2023-02-07 11:27:20 +00:00
|
|
|
|
|
2023-02-14 22:53:23 +00:00
|
|
|
|
## Parsons du rst !
|
|
|
|
|
|
|
|
|
|
Maintenant, parsez ça, avec vos yeux :
|
2023-02-07 11:27:20 +00:00
|
|
|
|
|
|
|
|
|
```text
|
|
|
|
|
*2 * x *a **b *.txt*
|
|
|
|
|
```
|
2023-02-14 22:53:23 +00:00
|
|
|
|
Notes:
|
2023-02-07 11:27:20 +00:00
|
|
|
|
|
|
|
|
|
C'est tout en italique !
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
## Parsons du rst !
|
|
|
|
|
|
2023-02-14 22:53:23 +00:00
|
|
|
|
Vous voyez du balisage, vous, ici ?
|
2023-02-07 11:27:20 +00:00
|
|
|
|
|
|
|
|
|
```text
|
|
|
|
|
2 * x ** 2 + 3 * x ** 3
|
|
|
|
|
|
|
|
|
|
a || b
|
|
|
|
|
|
|
|
|
|
"*" '|' (*) [*] {*} <*> ‘*’
|
|
|
|
|
|
|
|
|
|
2*x a**b O(N**2) e**(x*y)
|
|
|
|
|
```
|
|
|
|
|
|
2023-02-14 22:53:23 +00:00
|
|
|
|
Notes:
|
2023-02-07 11:27:20 +00:00
|
|
|
|
|
|
|
|
|
Pas le parseur. Mais bon les règles sont bien documentées ♥
|
|
|
|
|
|
|
|
|
|
Mais on voit que les humains peuvent facilement faire des erreurs ici.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
## Parsons du rst !
|
|
|
|
|
|
|
|
|
|
Regardons du côté des directives :
|
|
|
|
|
|
|
|
|
|
```text
|
|
|
|
|
.. code-block:: python
|
|
|
|
|
|
2023-02-14 22:53:23 +00:00
|
|
|
|
def fib(n):
|
2023-02-07 11:27:20 +00:00
|
|
|
|
if n in (0, 1):
|
|
|
|
|
return 1
|
|
|
|
|
x = n // 2
|
2023-02-14 22:53:23 +00:00
|
|
|
|
return fib(x-1) * fib(n-x-1) + fib(x) * fib(n-x)
|
2023-02-07 11:27:20 +00:00
|
|
|
|
```
|
|
|
|
|
|
2023-02-14 22:53:23 +00:00
|
|
|
|
Notes:
|
2023-02-07 11:27:20 +00:00
|
|
|
|
|
|
|
|
|
Une directive peut avoir un corps, ici du code.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
## Parsons du rst !
|
|
|
|
|
|
|
|
|
|
Regardons du côté des directives :
|
|
|
|
|
|
|
|
|
|
```text
|
|
|
|
|
.. image:: Lenna.jpg
|
|
|
|
|
:height: 330px
|
|
|
|
|
:width: 330px
|
|
|
|
|
:scale: 50 %
|
|
|
|
|
:alt: Lenna Sjööblom
|
|
|
|
|
```
|
|
|
|
|
|
2023-02-14 22:53:23 +00:00
|
|
|
|
Notes:
|
2023-02-07 11:27:20 +00:00
|
|
|
|
|
|
|
|
|
Une directive peut avoir des options mais **pas de corps**.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
## Parsons du rst !
|
|
|
|
|
|
|
|
|
|
Regardons du côté des directives :
|
|
|
|
|
|
|
|
|
|
```text
|
|
|
|
|
.. csv-table:: Domaines d'organismes publics
|
|
|
|
|
:header: "name", "http_status", "https_status"
|
|
|
|
|
:widths: 20, 10, 10
|
|
|
|
|
|
|
|
|
|
cgf.pf, 200, 200
|
|
|
|
|
dun.fr, 200, 200
|
|
|
|
|
caf.pf, 200, 200
|
|
|
|
|
aze.fr, 200, 200
|
|
|
|
|
```
|
|
|
|
|
|
2023-02-14 22:53:23 +00:00
|
|
|
|
Notes:
|
2023-02-07 11:27:20 +00:00
|
|
|
|
|
|
|
|
|
Une directive peut avoir des options **et** un corps.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
## Quiz !
|
|
|
|
|
|
|
|
|
|
```text
|
|
|
|
|
.. code-block:: python
|
|
|
|
|
|
|
|
|
|
items = ["titi", "tata", "toto"]
|
|
|
|
|
print(*items)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
.. important::
|
|
|
|
|
|
|
|
|
|
items = ["titi", "tata", "toto"]
|
|
|
|
|
print(*items)
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
## Quiz !
|
|
|
|
|
|
|
|
|
|
```text
|
|
|
|
|
.. csv-table:: Domaines d'organismes publics
|
|
|
|
|
:header: "name", "http_status", "https_status"
|
|
|
|
|
:widths: 20, 10, 10
|
|
|
|
|
|
|
|
|
|
cgf.pf, 200, 200, OK
|
|
|
|
|
dun.fr, 200, 200, OK
|
|
|
|
|
|
|
|
|
|
.. csv-table:: Domaines d'organismes publics
|
|
|
|
|
:header: "name", "http_status", "https_status"
|
|
|
|
|
:widths: 20, 10, 10
|
|
|
|
|
|
|
|
|
|
caf.pf
|
|
|
|
|
aze.fr
|
|
|
|
|
```
|
|
|
|
|
|
2023-02-14 22:53:23 +00:00
|
|
|
|
Notes:
|
2023-02-07 11:27:20 +00:00
|
|
|
|
|
|
|
|
|
Les valeurs manquantes sont moins graves que les valeurs surnuméraires.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
## Ne parsons pas de rst...
|
|
|
|
|
|
|
|
|
|
De toutes façons `sphinx-lint` ne cherche pas du rst valide, il cherche du rst invalide !
|
|
|
|
|
|
|
|
|
|
|
2023-02-14 22:53:23 +00:00
|
|
|
|
## Mes amies les regex
|
|
|
|
|
|
|
|
|
|
```python
|
|
|
|
|
SIMPLENAME = r"(?:(?!_)\w)+(?:[-._+:](?:(?!_)\w)+)*"
|
|
|
|
|
ROLE_GLUED_WITH_WORD_RE = re.compile(
|
|
|
|
|
fr"(^|\s)(?<!:){SIMPLENAME}:`(?!`)")
|
|
|
|
|
ROLE_WITH_NO_BACKTICKS_RE = re.compile(
|
|
|
|
|
fr"(^|\s):{SIMPLENAME}:(?![`:])[^\s`]+(\s|$)")
|
|
|
|
|
ROLE_MISSING_RIGHT_COLON_RE = re.compile(
|
|
|
|
|
fr"(^|\s):{SIMPLENAME}`(?!`)")
|
|
|
|
|
ROLE_MISSING_CLOSING_BACKTICK_RE = re.compile(
|
|
|
|
|
fr"({ROLE_HEAD}`[^`]+?)[^`]*$")
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
Notes: Parfois lisibles.
|
|
|
|
|
|
|
|
|
|
## Mes amies les regex
|
|
|
|
|
|
|
|
|
|
```python
|
|
|
|
|
ASCII_BEFORE = r"""[-:/'"<(\[{]"""
|
|
|
|
|
UNICODE_BEFORE = r"[\p{Ps}\p{Pi}\p{Pf}\p{Pd}\p{Po}]"
|
|
|
|
|
ASCII_AFTER = r"""[-.,:;!?/'")\]}>]"""
|
|
|
|
|
UNICODE_AFTER = r"[\p{Pe}\p{Pi}\p{Pf}\p{Pd}\p{Po}]"
|
|
|
|
|
re.compile(
|
|
|
|
|
fr"(?<!\x00)(?<=^|\s|{ASCII_BEFORE}|{UNICODE_BEFORE})"
|
|
|
|
|
fr"(?P<inline_markup>{start_string}\S{QUOTE_PAIRS_NLB}"
|
|
|
|
|
fr".*?(?<=\S){end_string})"
|
|
|
|
|
fr"(?=$|\s|\x00|{ASCII_AFTER}|{UNICODE_AFTER})"
|
|
|
|
|
)
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
Notes: Parfois pas. Mais vous connaissez `re.VERBOSE` ?
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
## Mes amies les regex
|
|
|
|
|
|
|
|
|
|
```python
|
|
|
|
|
re.compile(
|
|
|
|
|
fr"""
|
|
|
|
|
(?<!\x00) # Both inline markup start-string and end-string
|
|
|
|
|
# must not be preceded by an unescaped backslash
|
|
|
|
|
(?<= # Inline markup start-strings must:
|
|
|
|
|
^| # start a text block
|
|
|
|
|
\s| # or be immediately preceded by whitespace,
|
|
|
|
|
{ASCII_BEFORE}| # one of the ASCII characters or a
|
|
|
|
|
{UNICODE_BEFORE} # similar non-ASCII punctuation char.
|
|
|
|
|
)
|
|
|
|
|
(?P<inline_markup>
|
|
|
|
|
{start_string} # Inline markup start
|
|
|
|
|
\S # Inline markup start-strings must be
|
|
|
|
|
# immediately followed by non-whitespace.
|
|
|
|
|
# The inline markup end-string must be
|
|
|
|
|
# separated by at least one character
|
|
|
|
|
# from the start-string.
|
|
|
|
|
{QUOTE_PAIRS_NEGATIVE_LOOKBEHIND}
|
|
|
|
|
.*?
|
|
|
|
|
(?<=\S) # Inline markup end-strings must be
|
|
|
|
|
# immediately preceded by non-whitespace.
|
|
|
|
|
{end_string} # Inline markup end
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
(?= # Inline markup end-strings must
|
|
|
|
|
$| # end a text block or
|
|
|
|
|
\s| # be immediately followed by whitespace,
|
|
|
|
|
\x00|
|
|
|
|
|
{ASCII_AFTER}| # one of the ASCII characters or a
|
|
|
|
|
{UNICODE_AFTER} # similar non-ASCII punctuation char.
|
|
|
|
|
)
|
|
|
|
|
""",
|
|
|
|
|
flags=re.VERBOSE | re.DOTALL,
|
|
|
|
|
)
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
Notes: Les commentaires sont un copié-collé de la spec !
|
|
|
|
|
|
|
|
|
|
|
2023-02-07 11:27:20 +00:00
|
|
|
|
# C’est utilisé ?
|
|
|
|
|
|
2023-02-14 22:53:23 +00:00
|
|
|
|
- CPython (forcément…), devguide, peps, traductions, ...
|
2023-02-07 11:27:20 +00:00
|
|
|
|
- neo4j-python-driver
|
|
|
|
|
- pandas
|
|
|
|
|
- Spyder IDE
|
|
|
|
|
- Sympy
|
|
|
|
|
- Sphinx !!!
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# Ça trouve des bugs ?
|
|
|
|
|
|
|
|
|
|
## Dans les PEPS
|
|
|
|
|
|
|
|
|
|
```diff
|
|
|
|
|
-`vcs <https://pythonhosted.org/vcs/>` library as
|
|
|
|
|
+`vcs <https://pythonhosted.org/vcs/>`_ library as
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
## Dans sympy
|
|
|
|
|
|
|
|
|
|
```diff
|
|
|
|
|
-... autoclass:: sympy.assumptions.predicates.order
|
|
|
|
|
+.. autoclass:: sympy.assumptions.predicates.order
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
```diff
|
|
|
|
|
-:data:``sympy.parsing.sympy_parser.transformations``
|
|
|
|
|
+:data:`sympy.parsing.sympy_parser.transformations`
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
```diff
|
|
|
|
|
- `KanesMethod`` objects.
|
|
|
|
|
+ ``KanesMethod`` objects.
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
## Dans la traduction
|
|
|
|
|
|
|
|
|
|
> I just run the tool against library/ as a whole and it spit out a loooot of messages, so I thought that maybe it didn't work with our po files.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
## Dans la traduction
|
|
|
|
|
|
|
|
|
|
> After closer inspecting two of these files I found that they were indeed proper errors
|
|
|
|
|
|
|
|
|
|
```diff
|
|
|
|
|
-"datetime`. :const:`MINYEAR` es` `1``."
|
|
|
|
|
+"datetime`. :const:`MINYEAR` es ``1``."
|
|
|
|
|
```
|
|
|
|
|
|
2023-02-14 22:53:23 +00:00
|
|
|
|
Notes:
|
2023-02-07 11:27:20 +00:00
|
|
|
|
|
|
|
|
|
Ils ont réparé plus de 300 erreurs !
|
|
|
|
|
|
2023-02-14 22:53:23 +00:00
|
|
|
|
# Et les perfs ?
|
|
|
|
|
|
|
|
|
|
```text
|
|
|
|
|
$ time make suspicious
|
|
|
|
|
real 1m10.416s
|
|
|
|
|
user 3m28.918s
|
|
|
|
|
sys 0m3.127s
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
```text
|
|
|
|
|
$ time make check # (c'est sphinx-lint derrière)
|
|
|
|
|
real 0m6.351s
|
|
|
|
|
user 0m43.478s
|
|
|
|
|
sys 0m0.227s
|
|
|
|
|
```
|
|
|
|
|
|
2023-02-07 11:27:20 +00:00
|
|
|
|
|
|
|
|
|
# Et les faux positifs ?
|
|
|
|
|
|
2023-02-14 22:53:23 +00:00
|
|
|
|
- `make suspicious` → 400
|
|
|
|
|
- `make checks` → 0
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
## Et les faux positifs ?
|
|
|
|
|
|
2023-02-07 11:27:20 +00:00
|
|
|
|
Ils se cachent bien :
|
|
|
|
|
|
|
|
|
|
- Aucun dans la doc de Sphinx, c’est un bon test (beaucoup de rst dans du rst).
|
|
|
|
|
- Aucun dans la doc de Python, c’est un bon test (286k lignes de rst).
|
|
|
|
|
|
|
|
|
|
|
2023-02-14 22:53:23 +00:00
|
|
|
|
## Et `make suspicious` ?
|
|
|
|
|
|
|
|
|
|
![](static/2023-pyconfr-issue-open.png)
|
|
|
|
|
![](static/2023-pyconfr-issue-close.png)
|
|
|
|
|
|
2023-02-07 11:27:20 +00:00
|
|
|
|
# OK, vendu !
|
|
|
|
|
|
|
|
|
|
Mais comment ça s’utilise ?
|
|
|
|
|
|
|
|
|
|
```text
|
|
|
|
|
$ python -m pip install sphinx-lint
|
|
|
|
|
$ sphinx-lint
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
# Bouh c’est pas packagé pour [distrib]
|
|
|
|
|
|
|
|
|
|
Bah, fais-le.
|
|
|
|
|
|
2023-02-14 22:53:23 +00:00
|
|
|
|
Notes: Pour Debian je veux bien le faire ;)
|
|
|
|
|
|
2023-02-07 11:27:20 +00:00
|
|
|
|
|
|
|
|
|
# Questions ?
|
|
|
|
|
|
|
|
|
|
- <s>Twitter : [@sizeof](https://twitter.com/sizeof)</s>
|
|
|
|
|
- Mastodon : [@mdk@mamot.fr](https://mamot.fr/@mdk)
|
|
|
|
|
- XMPP : mdk@chapril.org
|
2023-02-14 22:53:23 +00:00
|
|
|
|
- HTTP : https://mdk.fr (et https://discuss.afpy.org)
|
2023-02-07 11:27:20 +00:00
|
|
|
|
- SMTP : julien@python.org
|
2023-02-14 22:53:23 +00:00
|
|
|
|
- IRC : mdk sur irc.libera.chat (#python-fr, #afpy)
|
|
|
|
|
- Whatsapp : HaHa. Jamais.
|
2023-02-07 11:27:20 +00:00
|
|
|
|
- Instagram : Et puis quoi encore ?
|
2023-02-14 22:53:23 +00:00
|
|
|
|
- TikTok : Euh, bon, stop maintenant !?
|