gather.py 5.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180
  1. from github import Github, Repository
  2. from gftools.utils import download_file
  3. from zipfile import ZipFile
  4. from pathlib import Path
  5. import tempfile
  6. import os
  7. import json
  8. from io import BytesIO
  9. from pybars import Compiler, strlist
  10. import re
  11. import subprocess
  12. TESTING = False
  13. SPECIAL_REPOS = [
  14. "notofonts.github.io",
  15. "overview",
  16. ".github",
  17. ".allstar",
  18. "notobuilder",
  19. "noto-data-dev",
  20. "noto-docs",
  21. "noto-project-template",
  22. "notoglot-mini",
  23. ]
  24. def fonts_from_zip(zipfile, dst=None):
  25. """Unzip fonts. If not dst is given unzip as BytesIO objects"""
  26. fonts = []
  27. for filename in zipfile.namelist():
  28. if filename.endswith(".ttf") or filename.endswith(".otf"):
  29. if dst:
  30. target = os.path.join(dst, filename)
  31. zipfile.extract(filename, dst)
  32. fonts.append(target)
  33. else:
  34. fonts.append(BytesIO(zipfile.read(filename)))
  35. return fonts
  36. def tree_has_new_files():
  37. ls = subprocess.run(["git", "ls-files", "--others"], capture_output=True)
  38. return ls.stdout
  39. print("Fetching existing repos")
  40. g = Github(os.environ["GITHUB_TOKEN"])
  41. org = g.get_organization("notofonts")
  42. org_repos = org.get_repos()
  43. org_names = [r.name for r in org_repos]
  44. subprocess.run(["git", "config", "user.name", "actions-user"])
  45. subprocess.run(["git", "config", "user.email", "[email protected]"])
  46. to_push = []
  47. fontrepos = json.load(open("fontrepos.json"))
  48. if os.path.exists("state.json"):
  49. state = json.load(open("state.json"))
  50. else:
  51. state = {}
  52. results = {}
  53. for repo_name in org_names:
  54. if repo_name in SPECIAL_REPOS:
  55. continue
  56. repo = g.get_repo("notofonts/" + repo_name)
  57. if repo.archived:
  58. continue
  59. if repo_name not in fontrepos:
  60. print("Unknown repo %s; is it missing from fontrepos?" % repo_name)
  61. continue
  62. print(f"Gathering data for {repo_name}")
  63. repo = g.get_repo("notofonts/" + repo_name)
  64. results[repo_name] = {
  65. "title": repo.description,
  66. "tier": fontrepos[repo_name].get("tier", 3),
  67. "gh_url": "https://notofonts.github.io/" + repo_name,
  68. "repo_url": "https://www.github.com/notofonts/" + repo_name,
  69. }
  70. # Get issues
  71. results[repo_name]["issues"] = []
  72. for issue in repo.get_issues():
  73. results[repo_name]["issues"].append(
  74. {"title": issue.title, "number": issue.number, "url": issue.html_url}
  75. )
  76. if repo_name not in state:
  77. state[repo_name] = {}
  78. # Check for new releases
  79. releases = repo.get_releases()
  80. for release in sorted(
  81. releases, key=lambda r: r.published_at.isoformat() if r.published_at else ""
  82. ):
  83. m = re.match(r"^(.*)-(v[\d.]+)", release.tag_name)
  84. if not m:
  85. print(f"Unparsable release {release.tag_name} in {repo_name}")
  86. continue
  87. if release.draft:
  88. continue
  89. family, version = m[1], m[2]
  90. family = re.sub(r"([a-z])([A-Z])", r"\1 \2", family)
  91. if release.tag_name in state[repo_name].get("known_releases", []):
  92. continue
  93. assets = release.get_assets()
  94. if not assets:
  95. continue
  96. latest_asset = assets[0]
  97. state[repo_name].setdefault("known_releases", []).append(release.tag_name)
  98. family_thing = (
  99. state[repo_name].setdefault("families", {}).setdefault(family, {})
  100. )
  101. body = release.body
  102. if not body:
  103. tag_sha = repo.get_git_ref("tags/" + release.tag_name).object.sha
  104. try:
  105. body = repo.get_git_tag(tag_sha).message
  106. except Exception as e:
  107. print("Couldn't retrieve release message for %s" % release.tag_name)
  108. family_thing["latest_release"] = {
  109. "url": release.html_url,
  110. "version": version,
  111. "notes": body,
  112. }
  113. if release.published_at:
  114. family_thing["latest_release"][
  115. "published"
  116. ] = release.published_at.isoformat()
  117. try:
  118. z = ZipFile(download_file(latest_asset.browser_download_url))
  119. family_thing["files"] = []
  120. with tempfile.TemporaryDirectory() as tmpdir:
  121. fonts = fonts_from_zip(z, tmpdir)
  122. for font in fonts:
  123. newpath = Path("fonts/") / Path(font).relative_to(tmpdir)
  124. os.makedirs(newpath.parent, exist_ok=True)
  125. family_thing["files"].append(str(newpath))
  126. os.rename(font, newpath)
  127. if tree_has_new_files() and not TESTING:
  128. # Add it and tag it
  129. subprocess.run(["git", "add", "."])
  130. subprocess.run(["git", "commit", "-m", "Add " + release.tag_name])
  131. subprocess.run(["git", "tag", release.tag_name])
  132. to_push.append(release.tag_name)
  133. except Exception as e:
  134. print("Couldn't fetch download for %s" % latest_asset.browser_download_url)
  135. # Tweet about the new release or something
  136. results[repo_name]["families"] = state[repo_name].get("families", {})
  137. subprocess.run(["git", "push"])
  138. for tag in to_push:
  139. subprocess.run(["git", "push", "origin", tag])
  140. # Save state
  141. json.dump(state, open("state.json", "w"), indent=True, sort_keys=True)
  142. for result in results.values():
  143. for family in result.get("families", {}).values():
  144. newfiles = {"unhinted": [], "hinted": [], "full": []}
  145. for file in sorted(family.get("files", [])):
  146. if "unhinted" in file:
  147. newfiles["unhinted"].append(file)
  148. elif "hinted" in file:
  149. newfiles["hinted"].append(file)
  150. elif "full" in file:
  151. newfiles["full"].append(file)
  152. family["files"] = newfiles
  153. json.dump(results, open("docs/noto.json", "w"), indent=True, sort_keys=True)