"""Regenere DOCS/kanban.html depuis DOCS/kanban/.
python3 DOCS/tools/build_kanban.py
Les compteurs de colonne, le bandeau de chiffres cles et le « Fait recemment »
sont calcules a partir du nombre de fichiers : ils ne peuvent plus se
desynchroniser du contenu reel.
Ajouter un chantier : creer un .md dans kanban/cards//
Deplacer un chantier : deplacer le fichier vers un autre dossier de colonne
Clore un chantier : deplacer le fichier vers kanban/done/ (ne garder que « title »)
L'ordre d'affichage suit le prefixe numerique du nom de fichier (pas de 10).
"""
import io
import json
import os
import sys
DOCS = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
KANBAN = os.path.join(DOCS, "kanban")
CARDS_DIR = os.path.join(KANBAN, "cards")
DONE_DIR = os.path.join(KANBAN, "done")
TPL = os.path.join(DOCS, "kanban-template.html")
OUT = os.path.join(DOCS, "kanban.html")
def read_item(path):
raw = io.open(path, encoding="utf-8", newline="").read().replace("\r\n", "\n")
if not raw.startswith("---\n") or "\n---\n" not in raw:
sys.exit("%s : frontmatter manquant ou mal ferme" % os.path.basename(path))
front, body = raw[4:].split("\n---\n", 1)
item = {"body": body.strip(), "tags": [], "flag": None, "horizon": None}
for line in front.strip().split("\n"):
key, _, value = line.partition(":")
key, value = key.strip(), value.strip()
if key == "tags":
item["tags"] = [t.strip() for t in value.split(",") if t.strip()]
elif key == "flag":
level, _, text = value.partition("|")
item["flag"] = {"level": level.strip(), "text": text.strip()}
else:
item[key] = value
if not item.get("title"):
sys.exit("%s : champ « title » manquant" % os.path.basename(path))
return item
def md_files(folder):
return [os.path.join(folder, f) for f in sorted(os.listdir(folder)) if f.endswith(".md")]
def render_card(card, path):
for required in ("area", "src"):
if not card.get(required):
sys.exit("%s : champ « %s » manquant" % (os.path.basename(path), required))
attrs = ' data-area="%s"' % card["area"]
if card["horizon"]:
attrs += ' data-horizon="%s"' % card["horizon"]
meta = "".join('%s' % t for t in card["tags"])
if card["flag"]:
meta += '%s' % (card["flag"]["level"], card["flag"]["text"])
body = "\n ".join(card["body"].split("\n"))
return (
' \n'
' %s
\n'
" %s
\n"
" %s\n"
' %s\n'
" "
) % (attrs, meta, card["title"], body, card["src"])
board = json.load(io.open(TPL.replace("kanban-template.html", "kanban/board.json"), encoding="utf-8"))
sections, counts = [], []
for col in board["columns"]:
paths = md_files(os.path.join(CARDS_DIR, col["dir"]))
cards = [render_card(read_item(p), p) for p in paths]
counts.append((col, len(cards)))
sections.append(
" \n"
' \n'
' %s
%d
\n'
' \n\n%s\n\n
\n'
" " % (col["comment"], col["stripe"], col["title"], len(cards), "\n\n".join(cards))
)
done_items = [read_item(p) for p in md_files(DONE_DIR)]
done_html = "\n\n".join(
' \n %s\n %s\n
'
% (it["title"], "\n ".join(it["body"].split("\n")))
for it in done_items
)
done = board["done"]
done_section = (
'\n %s
\n %s
\n \n\n%s\n\n
\n '
% (done["heading"], done["intro"], done_html)
)
stats = [
' %d%s
'
% ((" " + col["statClass"]) if col["statClass"] else "", n, col["title"])
for col, n in counts
]
stats.append(
' %d%s
'
% (done["statClass"], len(done_items), done["statLabel"])
)
html = io.open(TPL, encoding="utf-8", newline="").read().replace("\r\n", "\n")
html = html.replace("{{INTRO}}", "\n".join(" %s
" % p for p in board["intro"]))
html = html.replace("{{STAMP}}", "Mise à jour · %s
\n %s" % (board["updated"], board["stampNote"]))
html = html.replace(
"{{SUMMARY}}", ' ' % "\n".join(stats)
)
html = html.replace("{{BOARD}}", '\n\n%s\n\n
' % "\n\n".join(sections))
html = html.replace("{{DONE}}", done_section)
io.open(OUT, "w", encoding="utf-8", newline="\n").write(html)
print("kanban.html : %d o" % os.path.getsize(OUT))
print("%d chantiers ouverts + %d clos" % (sum(n for _, n in counts), len(done_items)))
for col, n in counts:
print(" %-14s %2d" % (col["title"], n))