#!/usr/bin/env python3 """extract_demos.py - build the m2 gtk-demo catalog from the GTK C demo sources. Reads the per-demo `.c` files under gtk/demos/gtk-demo and emits `demos/DemoCatalog.def` / `demos/DemoCatalog.mod`: the metadata (name, title, keywords, description) of every demo, so the Modula-2 browser can list and search them. Usage: tools/extract_demos.py [--src gtk/demos/gtk-demo] [--out demos] """ import argparse import glob import os import re import sys MAX_NAME = 48 MAX_TITLE = 80 MAX_KEYWORDS = 256 MAX_DESCRIPTION = 512 def m2_str(text, limit): """Escape a Python string for a Modula-2 string literal of bounded size.""" text = " ".join(text.split()) if len(text) > limit: text = text[:limit] text = text.encode("ascii", "replace").decode("ascii") text = text.replace("\\", " ").replace('"', "'") return text def parse(path): src = open(path, encoding="utf-8", errors="replace").read() name = os.path.basename(path)[:-2] title = "" keywords = "" description = "" m = re.match(r"\ufeff?\s*/\*(.*?)\*/", src, re.S) if m: lines = [l.strip(" *\t") for l in m.group(1).splitlines()] lines = [l for l in lines if l] if lines: title = lines[0] desc = [] for l in lines[1:]: if l.startswith("#Keywords:"): keywords = l.split(":", 1)[1].strip() else: desc.append(l) description = " ".join(desc) # the do_ function defines the demo id used by the C registry ids = re.findall(r"\bdo_([a-z0-9_]+)\s*\(", src) if ids: name = ids[0] return name, title, keywords, description def main(): ap = argparse.ArgumentParser() ap.add_argument("--src", default="gtk/demos/gtk-demo") ap.add_argument("--out", default="demos") args = ap.parse_args() files = sorted(glob.glob(os.path.join(args.src, "*.c"))) entries = [] for f in files: name, title, kw, desc = parse(f) if name == "main": continue entries.append((name, title or name.replace("_", " "), kw, desc)) entries.sort(key=lambda e: e[1].lower()) os.makedirs(args.out, exist_ok=True) with open(os.path.join(args.out, "DemoCatalog.def"), "w") as f: f.write( 'DEFINITION MODULE DemoCatalog ;\n\n' '(*\n' ' m2-GTK4 - the catalogue of gtk-demo demos, generated from the\n' ' GTK C demo sources by tools/extract_demos.py. Provides the\n' ' title/keywords metadata used by the demo browser.\n' '*)\n\n' 'EXPORT UNQUALIFIED DemoCatalogCount, DemoCatalogGet;\n\n' 'PROCEDURE DemoCatalogCount () : CARDINAL;\n\n' '(* copy the metadata of demo i into the caller buffers *)\n' 'PROCEDURE DemoCatalogGet (i: CARDINAL; VAR name, title, keywords,\n' ' description: ARRAY OF CHAR);\n\n' 'END DemoCatalog.\n' ) with open(os.path.join(args.out, "DemoCatalog.mod"), "w") as f: f.write("IMPLEMENTATION MODULE DemoCatalog ;\n\n") f.write("TYPE\n Entry = RECORD\n") f.write(" name: ARRAY [0..%d] OF CHAR;\n" % (MAX_NAME - 1)) f.write(" title: ARRAY [0..%d] OF CHAR;\n" % (MAX_TITLE - 1)) f.write(" keywords: ARRAY [0..%d] OF CHAR;\n" % (MAX_KEYWORDS - 1)) f.write(" description: ARRAY [0..%d] OF CHAR;\n" % (MAX_DESCRIPTION - 1)) f.write(" END;\n\n") f.write("VAR\n entries: ARRAY [0..%d] OF Entry;\n n: CARDINAL;\n\n" % (len(entries) - 1)) f.write( "PROCEDURE Copy (VAR dst: ARRAY OF CHAR; src: ARRAY OF CHAR);\n" "VAR i: CARDINAL;\n" "BEGIN\n" " i := 0;\n" " WHILE (i <= HIGH(dst)) AND (i <= HIGH(src)) AND (src[i] # 0C) DO\n" " dst[i] := src[i]; INC(i)\n" " END;\n" " IF i <= HIGH(dst) THEN dst[i] := 0C END\n" "END Copy;\n\n" "PROCEDURE DemoCatalogCount () : CARDINAL;\nBEGIN\n RETURN n\nEND DemoCatalogCount;\n\n" "PROCEDURE DemoCatalogGet (i: CARDINAL; VAR name, title, keywords,\n" " description: ARRAY OF CHAR);\n" "BEGIN\n" " IF i < n THEN\n" " Copy(name, entries[i].name);\n" " Copy(title, entries[i].title);\n" " Copy(keywords, entries[i].keywords);\n" " Copy(description, entries[i].description)\n" " ELSE\n" " name[0] := 0C; title[0] := 0C; keywords[0] := 0C;\n" " description[0] := 0C\n" " END\n" "END DemoCatalogGet;\n\n" "BEGIN\n" " n := %d;\n" % len(entries) ) for i, (name, title, kw, desc) in enumerate(entries): f.write(' entries[%d].name := "%s";\n' % (i, m2_str(name, MAX_NAME - 1))) f.write(' entries[%d].title := "%s";\n' % (i, m2_str(title, MAX_TITLE - 1))) f.write(' entries[%d].keywords := "%s";\n' % (i, m2_str(kw, MAX_KEYWORDS - 1))) f.write(' entries[%d].description := "%s";\n' % (i, m2_str(desc, MAX_DESCRIPTION - 1))) f.write("END DemoCatalog.\n") sys.stderr.write("extract_demos: %d demos -> %s/DemoCatalog.{def,mod}\n" % (len(entries), args.out)) if __name__ == "__main__": main()