"""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%s\n
' % "\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))