En ces temps de confinement, voici une commande snippets.py
qui permet d’interpréter des fichiers texte combinant explications et bouts de code Python.
Voir le docstring
pour la syntaxe très simple des fichiers snippets. Exécutez
./snippets.py -
pour lancer l’interprétation.
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
snippets combiner du texte libre avec des snippets python
Les fichiers d'entrée sont des fichiers texte incluant des bouts de code
Python terminés par une ligne de la forme `### n` où n est le nombre de lignes
du code Python qui précède ce marqueur
Le hello world d'un fichier snippets
print("hello", "snippets")
### 1
exemple de boucle sur un triplet
triplet = 1, 2, 3
for v in triplet:
print(v)
### 3
Python c'est super, n'est-ce pas ?
Python = True
great = True
it = False
res = Python is great is not it
print("résultat", res)
### 5
Les lignes d'explication (hors bout de code) sont imprimées telle que.
Pour un bout de code (snippet), le programme imprime le code et les résultats
de son évaluation par l'interpréteur Python, le tout encadré par les chaînes
`begin_code`et `end_code`.
try:
import aujourd
now = aujourd.hui
except ImportError:
from datetime import date
now = date.today()
print("C'est fini pour aujourd'hui", now)
### 7
Au revoir!
"""
from itertools import groupby
_marker = '###'
def n_marker(line, marker=None):
"""retourne
0 si la ligne ne commence pas par "###"
n si la ligne est de la forme '### n'
1 si le nombre n n'est pas indiqué
"""
if marker is None:
marker = _marker
if not line.startswith(marker):
return 0
n = 1
nc = len(marker)
line = line[nc:]
words = line.split()
if words:
n = int(words[0])
return n
def drop_trailing(lines):
"""drop newlines and trailing spaces in iterable `lines`
"""
for line in lines:
yield line.rstrip()
def decoupe(lines, marker=None):
"""generator function which yields all lines outside of a snippet
snippet code yields a single code line `marker + '\n'.join(code_part)`
"""
if marker is None:
marker = _marker
# lines = iter(lines)
lines = drop_trailing(lines)
bloc = []
for upto, g in groupby(lines, n_marker):
if upto:
if not bloc:
print("successive marker lines", marker, "upto", upto)
continue
for line in bloc[:-upto]:
yield line
code_part = bloc[-upto:]
code = marker + '\n'.join(code_part)
yield code
bloc = []
else:
bloc.extend(g)
# all the lines upto the marker line excluded
if bloc: # trailing bloc after last snippet
for line in bloc:
yield line
def _test_decoupe(lines=None, marker=None, code_begin='# code'):
"""imprime la sortie de decoupe
"""
if not lines:
lines = __doc__.splitlines()
if not lines[-1]:
lines.pop()
if marker is None:
marker = _marker
nc = len(marker)
for line in decoupe(lines, marker):
if not line.startswith(marker):
print(line)
else:
if code_begin:
print(code_begin)
print(line[nc:])
def with_snippets(lines, marker=None, begin_code="```python", end_code="```"):
"""Process a snippet iterable
`lines` iterable au format snippets avec des marqueurs '### [n]'
"""
if marker is None:
marker = _marker
nc = len(marker)
for line in decoupe(lines, marker):
if not line.startswith(marker):
print(line)
else:
if begin_code:
print(begin_code)
line = line[nc:]
print(line)
cod = compile(line, "<stdin>", "exec")
eval(cod, globals())
if end_code:
print(end_code)
if __name__ == "__main__":
import argparse
parser = argparse.ArgumentParser(
description="""interpréter de fichiers texte contenant des snippets \
de code python terminés par une ligne `marqueur` N""",
epilog="""\
Si le seul fichier donné est '-', %(prog)s interprète snippets.__doc__ \n
Pour lister le code brut de snippets.__doc__ tapez \n
"%(prog)s --marker '#NOT_FOUND' -"
""")
parser.add_argument(
"--marker", dest="marker", default="###",
help="marqueur de fin de snippet" " '###' par défaut")
parser.add_argument(
"--begin_code", dest="begin_code", default="```python",
help="balise de début de snippet," " '```python' par défaut")
parser.add_argument(
"--end_code", dest="end_code", default="```",
help="balise de fin de snippet," " '```' par défaut")
parser.add_argument(
"fnames", nargs="+", metavar="fname", type=str,
help="Nom de fichier à interprèter")
args = parser.parse_args()
_marker = args.marker
fnames = args.fnames
if fnames and fnames[0] == '-':
del fnames[0] # compensate argparse behaviour
if not fnames:
g_lines = __doc__.splitlines()
with_snippets(g_lines, begin_code=args.begin_code, end_code=args.end_code)
# _test_decoupe()
else:
for fname in fnames:
if len(fnames) > 1:
print("Fichier", fname, '\n')
with open(fname) as fin:
with_snippets(fin, begin_code=args.begin_code, end_code=args.end_code)
# vim: set shiftwidth=4 softtabstop=4 expandtab tabstop=4:
Pour les curieux ce module n’utilise que l’interpréteur Python et la fonction groupby
de itertools
.
Cordialement,
Regards,
Mit freundlichen Grüßen,
مع تحياتي الخالصة
F. Petitjean
Ingénieur civil du Génie Maritime.
« Moi, lorsque je n’ai rien à dire, je veux qu’on le sache. » (R. Devos)
« Celui qui, parti de rien, n’est arrivé nulle part n’a de merci à dire à personne !! »
Pierre Dac