extract_demos.py 5.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142
  1. #!/usr/bin/env python3
  2. """extract_demos.py - build the m2 gtk-demo catalog from the GTK C demo sources.
  3. Reads the per-demo `.c` files under gtk/demos/gtk-demo and emits
  4. `demos/DemoCatalog.def` / `demos/DemoCatalog.mod`: the metadata (name,
  5. title, keywords, description) of every demo, so the Modula-2 browser can
  6. list and search them.
  7. Usage:
  8. tools/extract_demos.py [--src gtk/demos/gtk-demo] [--out demos]
  9. """
  10. import argparse
  11. import glob
  12. import os
  13. import re
  14. import sys
  15. MAX_NAME = 48
  16. MAX_TITLE = 80
  17. MAX_KEYWORDS = 256
  18. MAX_DESCRIPTION = 512
  19. def m2_str(text, limit):
  20. """Escape a Python string for a Modula-2 string literal of bounded size."""
  21. text = " ".join(text.split())
  22. if len(text) > limit:
  23. text = text[:limit]
  24. text = text.encode("ascii", "replace").decode("ascii")
  25. text = text.replace("\\", " ").replace('"', "'")
  26. return text
  27. def parse(path):
  28. src = open(path, encoding="utf-8", errors="replace").read()
  29. name = os.path.basename(path)[:-2]
  30. title = ""
  31. keywords = ""
  32. description = ""
  33. m = re.match(r"\ufeff?\s*/\*(.*?)\*/", src, re.S)
  34. if m:
  35. lines = [l.strip(" *\t") for l in m.group(1).splitlines()]
  36. lines = [l for l in lines if l]
  37. if lines:
  38. title = lines[0]
  39. desc = []
  40. for l in lines[1:]:
  41. if l.startswith("#Keywords:"):
  42. keywords = l.split(":", 1)[1].strip()
  43. else:
  44. desc.append(l)
  45. description = " ".join(desc)
  46. # the do_<name> function defines the demo id used by the C registry
  47. ids = re.findall(r"\bdo_([a-z0-9_]+)\s*\(", src)
  48. if ids:
  49. name = ids[0]
  50. return name, title, keywords, description
  51. def main():
  52. ap = argparse.ArgumentParser()
  53. ap.add_argument("--src", default="gtk/demos/gtk-demo")
  54. ap.add_argument("--out", default="demos")
  55. args = ap.parse_args()
  56. files = sorted(glob.glob(os.path.join(args.src, "*.c")))
  57. entries = []
  58. for f in files:
  59. name, title, kw, desc = parse(f)
  60. if name == "main":
  61. continue
  62. entries.append((name, title or name.replace("_", " "), kw, desc))
  63. entries.sort(key=lambda e: e[1].lower())
  64. os.makedirs(args.out, exist_ok=True)
  65. with open(os.path.join(args.out, "DemoCatalog.def"), "w") as f:
  66. f.write(
  67. 'DEFINITION MODULE DemoCatalog ;\n\n'
  68. '(*\n'
  69. ' m2-GTK4 - the catalogue of gtk-demo demos, generated from the\n'
  70. ' GTK C demo sources by tools/extract_demos.py. Provides the\n'
  71. ' title/keywords metadata used by the demo browser.\n'
  72. '*)\n\n'
  73. 'EXPORT UNQUALIFIED DemoCatalogCount, DemoCatalogGet;\n\n'
  74. 'PROCEDURE DemoCatalogCount () : CARDINAL;\n\n'
  75. '(* copy the metadata of demo i into the caller buffers *)\n'
  76. 'PROCEDURE DemoCatalogGet (i: CARDINAL; VAR name, title, keywords,\n'
  77. ' description: ARRAY OF CHAR);\n\n'
  78. 'END DemoCatalog.\n'
  79. )
  80. with open(os.path.join(args.out, "DemoCatalog.mod"), "w") as f:
  81. f.write("IMPLEMENTATION MODULE DemoCatalog ;\n\n")
  82. f.write("TYPE\n Entry = RECORD\n")
  83. f.write(" name: ARRAY [0..%d] OF CHAR;\n" % (MAX_NAME - 1))
  84. f.write(" title: ARRAY [0..%d] OF CHAR;\n" % (MAX_TITLE - 1))
  85. f.write(" keywords: ARRAY [0..%d] OF CHAR;\n" % (MAX_KEYWORDS - 1))
  86. f.write(" description: ARRAY [0..%d] OF CHAR;\n" % (MAX_DESCRIPTION - 1))
  87. f.write(" END;\n\n")
  88. f.write("VAR\n entries: ARRAY [0..%d] OF Entry;\n n: CARDINAL;\n\n" % (len(entries) - 1))
  89. f.write(
  90. "PROCEDURE Copy (VAR dst: ARRAY OF CHAR; src: ARRAY OF CHAR);\n"
  91. "VAR i: CARDINAL;\n"
  92. "BEGIN\n"
  93. " i := 0;\n"
  94. " WHILE (i <= HIGH(dst)) AND (i <= HIGH(src)) AND (src[i] # 0C) DO\n"
  95. " dst[i] := src[i]; INC(i)\n"
  96. " END;\n"
  97. " IF i <= HIGH(dst) THEN dst[i] := 0C END\n"
  98. "END Copy;\n\n"
  99. "PROCEDURE DemoCatalogCount () : CARDINAL;\nBEGIN\n RETURN n\nEND DemoCatalogCount;\n\n"
  100. "PROCEDURE DemoCatalogGet (i: CARDINAL; VAR name, title, keywords,\n"
  101. " description: ARRAY OF CHAR);\n"
  102. "BEGIN\n"
  103. " IF i < n THEN\n"
  104. " Copy(name, entries[i].name);\n"
  105. " Copy(title, entries[i].title);\n"
  106. " Copy(keywords, entries[i].keywords);\n"
  107. " Copy(description, entries[i].description)\n"
  108. " ELSE\n"
  109. " name[0] := 0C; title[0] := 0C; keywords[0] := 0C;\n"
  110. " description[0] := 0C\n"
  111. " END\n"
  112. "END DemoCatalogGet;\n\n"
  113. "BEGIN\n"
  114. " n := %d;\n" % len(entries)
  115. )
  116. for i, (name, title, kw, desc) in enumerate(entries):
  117. f.write(' entries[%d].name := "%s";\n' % (i, m2_str(name, MAX_NAME - 1)))
  118. f.write(' entries[%d].title := "%s";\n' % (i, m2_str(title, MAX_TITLE - 1)))
  119. f.write(' entries[%d].keywords := "%s";\n' % (i, m2_str(kw, MAX_KEYWORDS - 1)))
  120. f.write(' entries[%d].description := "%s";\n' % (i, m2_str(desc, MAX_DESCRIPTION - 1)))
  121. f.write("END DemoCatalog.\n")
  122. sys.stderr.write("extract_demos: %d demos -> %s/DemoCatalog.{def,mod}\n"
  123. % (len(entries), args.out))
  124. if __name__ == "__main__":
  125. main()