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