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