9.0 KiB
sphinx-lint
Un linter pour ta doc.
Julien Palard
Julien Palard
- CPython core dev
- Sysadmin à l’AFPy
- hackinscience.org
Notes:
- Je m’occupe surtout de docs.python.org
- Formateur, indépendant, faites passer le mot :)
Pourquoi ?
Notes:
Trouver des fautes de frappe dans du rst, ni plus, ni moins.
Depuis quand ?
Author: Georg Brandl <georg@python.org>
Date: Sat Jan 3 20:30:15 2009 +0000
Dans cpython
dans Doc/tools/rstlint.py
!
Notes:
Oui oui, Georg, l'auteur de Sphinx-doc.
La motivation
make suspicious
Bien connu pour ses faux positifs, n'est-ce pas ?
Notes:
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.
La motivation
Remplacer make suspicious
par un outil qui n’aurait aucun faux positif.
Notes:
- Aucun faux positif == cool pour les contributeurs !
- Et qui serait rapide !
- Parler de la lenteur de
make suspicious
.
La motivation
Sortir rstlint.py
de cpython/Doc/tools
pour que tout le monde puisse l'utiliser.
Notes: Et pour faciliter la contribution.
La motivation
Parsons du rst !
Le reStructuredText
Le balisage :
*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://...>`__
Notes: Comme en Markdown, comme en Org.
Le reStructuredText
Les textes interprétés :
`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 :
*ça* c'est pareil que :emphasis:`ça`.
**ça** c'est pareil que :strong:`ça`.
``ça`` c'est pareil que :literal:`ça`.
Notes:
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 :
.. image:: Lenna.jpg
.. important::
Elle s’appelle *Lena Sjööblom*.
Notes:
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, ...
Retenez-bien que le corps d'une directive peut contenir du reStructuredText !
Parsons du rst !
Jusque-là, c’est facile !
Parsons du rst !
Maintenant, parsez ça, avec vos yeux :
*2 * x *a **b *.txt*
Notes:
C'est tout en italique !
Parsons du rst !
Vous voyez du balisage, vous, ici ?
2 * x ** 2 + 3 * x ** 3
a || b
"*" '|' (*) [*] {*} <*> ‘*’
2*x a**b O(N**2) e**(x*y)
Notes:
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 :
.. code-block:: python
def fib(n):
if n in (0, 1):
return 1
x = n // 2
return fib(x-1) * fib(n-x-1) + fib(x) * fib(n-x)
Notes:
Une directive peut avoir un corps, ici du code.
Parsons du rst !
Regardons du côté des directives :
.. image:: Lenna.jpg
:height: 330px
:width: 330px
:scale: 50 %
:alt: Lenna Sjööblom
Notes:
Une directive peut avoir des options mais pas de corps.
Parsons du rst !
Regardons du côté des directives :
.. 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
Notes:
Une directive peut avoir des options et un corps.
Quiz !
.. code-block:: python
items = ["titi", "tata", "toto"]
print(*items)
.. important::
items = ["titi", "tata", "toto"]
print(*items)
Quiz !
.. 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
Notes:
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 !
Mes amies les regex
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
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
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 !
C’est utilisé ?
- CPython (forcément…), devguide, peps, traductions, ...
- neo4j-python-driver
- pandas
- Spyder IDE
- Sympy
- Sphinx !!!
Ça trouve des bugs ?
Dans les PEPS
-`vcs <https://pythonhosted.org/vcs/>` library as
+`vcs <https://pythonhosted.org/vcs/>`_ library as
Dans sympy
-... autoclass:: sympy.assumptions.predicates.order
+.. autoclass:: sympy.assumptions.predicates.order
-:data:``sympy.parsing.sympy_parser.transformations``
+:data:`sympy.parsing.sympy_parser.transformations`
- `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
-"datetime`. :const:`MINYEAR` es` `1``."
+"datetime`. :const:`MINYEAR` es ``1``."
Notes:
Ils ont réparé plus de 300 erreurs !
Et les perfs ?
$ time make suspicious
real 1m10.416s
user 3m28.918s
sys 0m3.127s
$ time make check # (c'est sphinx-lint derrière)
real 0m6.351s
user 0m43.478s
sys 0m0.227s
Et les faux positifs ?
make suspicious
→ 400make checks
→ 0
Et les faux positifs ?
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).
Et make suspicious
?
OK, vendu !
Mais comment ça s’utilise ?
$ python -m pip install sphinx-lint
$ sphinx-lint
Bouh c’est pas packagé pour [distrib]
Bah, fais-le.
Notes: Pour Debian je veux bien le faire ;)
Questions ?
Twitter : @sizeof- Mastodon : @mdk@mamot.fr
- XMPP : mdk@chapril.org
- HTTP : https://mdk.fr (et https://discuss.afpy.org)
- SMTP : julien@python.org
- IRC : mdk sur irc.libera.chat (#python-fr, #afpy)
- Whatsapp : HaHa. Jamais.
- Instagram : Et puis quoi encore ?
- TikTok : Euh, bon, stop maintenant !?