about summary refs log tree commit diff
path: root/pkgs/by-name
diff options
context:
space:
mode:
authorjopejoe1 <johannes@joens.email>2023-12-06 19:37:56 +0100
committerJörg Thalheim <Mic92@users.noreply.github.com>2023-12-13 21:28:44 +0100
commitc34ac183ca5b34670d421ab504b6412c15a0ac9a (patch)
tree345e1a7f78f05fcffb5017bb7c0d6656dbb35905 /pkgs/by-name
parent546c660ff673bb4e95af205fcbdade43a6e9abbe (diff)
downloadnixlib-c34ac183ca5b34670d421ab504b6412c15a0ac9a.tar
nixlib-c34ac183ca5b34670d421ab504b6412c15a0ac9a.tar.gz
nixlib-c34ac183ca5b34670d421ab504b6412c15a0ac9a.tar.bz2
nixlib-c34ac183ca5b34670d421ab504b6412c15a0ac9a.tar.lz
nixlib-c34ac183ca5b34670d421ab504b6412c15a0ac9a.tar.xz
nixlib-c34ac183ca5b34670d421ab504b6412c15a0ac9a.tar.zst
nixlib-c34ac183ca5b34670d421ab504b6412c15a0ac9a.zip
noto-fonts-monochrome-emoji: move out of noto-fonts file
Diffstat (limited to 'pkgs/by-name')
-rw-r--r--pkgs/by-name/no/noto-fonts-monochrome-emoji/noto-emoji.hashes.json7
-rw-r--r--pkgs/by-name/no/noto-fonts-monochrome-emoji/noto-emoji.json30
-rwxr-xr-xpkgs/by-name/no/noto-fonts-monochrome-emoji/noto-emoji.py183
-rw-r--r--pkgs/by-name/no/noto-fonts-monochrome-emoji/package.nix53
4 files changed, 273 insertions, 0 deletions
diff --git a/pkgs/by-name/no/noto-fonts-monochrome-emoji/noto-emoji.hashes.json b/pkgs/by-name/no/noto-fonts-monochrome-emoji/noto-emoji.hashes.json
new file mode 100644
index 000000000000..e9ad1c327f4f
--- /dev/null
+++ b/pkgs/by-name/no/noto-fonts-monochrome-emoji/noto-emoji.hashes.json
@@ -0,0 +1,7 @@
+{
+  "http://fonts.gstatic.com/s/notoemoji/v47/bMrnmSyK7YY-MEu6aWjPDs-ar6uWaGWuob_10jwvS-FGJCMY.ttf": "sha256-B8XBpYycOYBjrhjlnyiz42YukIoOjGTd3NN3EY00NiQ=",
+  "http://fonts.gstatic.com/s/notoemoji/v47/bMrnmSyK7YY-MEu6aWjPDs-ar6uWaGWuob-r0jwvS-FGJCMY.ttf": "sha256-Zfwh9q2GrL5Dwp+J/8Ddd2IXCaUXpQ7dE3CqgCMMyPs=",
+  "http://fonts.gstatic.com/s/notoemoji/v47/bMrnmSyK7YY-MEu6aWjPDs-ar6uWaGWuob-Z0jwvS-FGJCMY.ttf": "sha256-/O5b2DzM8g97NAdJgIC/RsQ7E5P7USKq7TXyDuUE3WQ=",
+  "http://fonts.gstatic.com/s/notoemoji/v47/bMrnmSyK7YY-MEu6aWjPDs-ar6uWaGWuob911TwvS-FGJCMY.ttf": "sha256-vrjB8GlhzWAe6jG/Srpy8R431VivNtWbCa5Uh4ATnmU=",
+  "http://fonts.gstatic.com/s/notoemoji/v47/bMrnmSyK7YY-MEu6aWjPDs-ar6uWaGWuob9M1TwvS-FGJCMY.ttf": "sha256-EbnZt8h4Lcl0yJoOKmXlF1nfcP5hZv7n4cEQ10yBkcg="
+}
diff --git a/pkgs/by-name/no/noto-fonts-monochrome-emoji/noto-emoji.json b/pkgs/by-name/no/noto-fonts-monochrome-emoji/noto-emoji.json
new file mode 100644
index 000000000000..c729634b9e1e
--- /dev/null
+++ b/pkgs/by-name/no/noto-fonts-monochrome-emoji/noto-emoji.json
@@ -0,0 +1,30 @@
+{
+  "kind": "webfonts#webfontList",
+  "items": [
+    {
+      "family": "Noto Emoji",
+      "variants": [
+        "300",
+        "regular",
+        "500",
+        "600",
+        "700"
+      ],
+      "subsets": [
+        "emoji"
+      ],
+      "version": "v47",
+      "lastModified": "2023-09-27",
+      "files": {
+        "300": "http://fonts.gstatic.com/s/notoemoji/v47/bMrnmSyK7YY-MEu6aWjPDs-ar6uWaGWuob_10jwvS-FGJCMY.ttf",
+        "regular": "http://fonts.gstatic.com/s/notoemoji/v47/bMrnmSyK7YY-MEu6aWjPDs-ar6uWaGWuob-r0jwvS-FGJCMY.ttf",
+        "500": "http://fonts.gstatic.com/s/notoemoji/v47/bMrnmSyK7YY-MEu6aWjPDs-ar6uWaGWuob-Z0jwvS-FGJCMY.ttf",
+        "600": "http://fonts.gstatic.com/s/notoemoji/v47/bMrnmSyK7YY-MEu6aWjPDs-ar6uWaGWuob911TwvS-FGJCMY.ttf",
+        "700": "http://fonts.gstatic.com/s/notoemoji/v47/bMrnmSyK7YY-MEu6aWjPDs-ar6uWaGWuob9M1TwvS-FGJCMY.ttf"
+      },
+      "category": "sans-serif",
+      "kind": "webfonts#webfont",
+      "menu": "http://fonts.gstatic.com/s/notoemoji/v47/bMrnmSyK7YY-MEu6aWjPDs-ar6uWaGWuob-r0gwuQeU.ttf"
+    }
+  ]
+}
diff --git a/pkgs/by-name/no/noto-fonts-monochrome-emoji/noto-emoji.py b/pkgs/by-name/no/noto-fonts-monochrome-emoji/noto-emoji.py
new file mode 100755
index 000000000000..9f1eadd95bca
--- /dev/null
+++ b/pkgs/by-name/no/noto-fonts-monochrome-emoji/noto-emoji.py
@@ -0,0 +1,183 @@
+#!/usr/bin/env nix-shell
+#! nix-shell -i "python3 -I" -p python3
+
+from contextlib import contextmanager
+from pathlib import Path
+from typing import Iterable, Optional
+from urllib import request
+
+import hashlib, json
+
+
+def getMetadata(apiKey: str, family: str = "Noto Emoji"):
+    '''Fetch the Google Fonts metadata for a given family.
+
+    An API key can be obtained by anyone with a Google account (🚮) from
+      `https://developers.google.com/fonts/docs/developer_api#APIKey`
+    '''
+    from urllib.parse import urlencode
+
+    with request.urlopen(
+            "https://www.googleapis.com/webfonts/v1/webfonts?" +
+            urlencode({ 'key': apiKey, 'family': family })
+    ) as req:
+        return json.load(req)
+
+def getUrls(metadata) -> Iterable[str]:
+    '''Fetch all files' URLs from Google Fonts' metadata.
+
+    The metadata must obey the API v1 schema, and can be obtained from:
+      https://www.googleapis.com/webfonts/v1/webfonts?key=${GOOGLE_FONTS_TOKEN}&family=${FAMILY}
+    '''
+    return ( url for i in metadata['items'] for _, url in i['files'].items() )
+
+
+def hashUrl(url: str, *, hash: str = 'sha256'):
+    '''Compute the hash of the data from HTTP GETing a given `url`.
+
+    The `hash` must be an algorithm name `hashlib.new` accepts.
+    '''
+    with request.urlopen(url) as req:
+        return hashlib.new(hash, req.read())
+
+
+def sriEncode(h) -> str:
+    '''Encode a hash in the SRI format.
+
+    Takes a `hashlib` object, and produces a string that
+    nixpkgs' `fetchurl` accepts as `hash` parameter.
+    '''
+    from base64 import b64encode
+    return f"{h.name}-{b64encode(h.digest()).decode()}"
+
+def validateSRI(sri: Optional[str]) -> Optional[str]:
+    '''Decode an SRI hash, return `None` if invalid.
+
+    This is not a full SRI hash parser, hash options aren't supported.
+    '''
+    from base64 import b64decode
+
+    if sri is None:
+        return None
+
+    try:
+        hashName, b64 = sri.split('-', 1)
+
+        h = hashlib.new(hashName)
+        digest = b64decode(b64, validate=True)
+        assert len(digest) == h.digest_size
+
+    except:
+        return None
+    else:
+        return sri
+
+
+def hashUrls(
+    urls: Iterable[str],
+    knownHashes: dict[str, str] = {},
+) -> dict[str, str]:
+    '''Generate a `dict` mapping URLs to SRI-encoded hashes.
+
+    The `knownHashes` optional parameter can be used to avoid
+    re-downloading files whose URL have not changed.
+    '''
+    return {
+        url: validateSRI(knownHashes.get(url)) or sriEncode(hashUrl(url))
+        for url in urls
+    }
+
+
+@contextmanager
+def atomicFileUpdate(target: Path):
+    '''Atomically replace the contents of a file.
+
+    Yields an open file to write into; upon exiting the context,
+    the file is closed and (atomically) replaces the `target`.
+
+    Guarantees that the `target` was either successfully overwritten
+    with new content and no exception was raised, or the temporary
+    file was cleaned up.
+    '''
+    from tempfile import mkstemp
+    fd, _p = mkstemp(
+        dir = target.parent,
+        prefix = target.name,
+    )
+    tmpPath = Path(_p)
+
+    try:
+        with open(fd, 'w') as f:
+            yield f
+
+        tmpPath.replace(target)
+
+    except Exception:
+        tmpPath.unlink(missing_ok = True)
+        raise
+
+
+if __name__ == "__main__":
+    from os import environ
+    from urllib.error import HTTPError
+
+    environVar = 'GOOGLE_FONTS_TOKEN'
+    currentDir = Path(__file__).parent
+    metadataPath = currentDir / 'noto-emoji.json'
+
+    try:
+        apiToken = environ[environVar]
+        metadata = getMetadata(apiToken)
+
+    except (KeyError, HTTPError) as exn:
+        # No API key in the environment, or the query was rejected.
+        match exn:
+            case KeyError if exn.args[0] == environVar:
+                print(f"No '{environVar}' in the environment, "
+                       "skipping metadata update")
+
+            case HTTPError if exn.getcode() == 403:
+                print("Got HTTP 403 (Forbidden)")
+                if apiToken != '':
+                    print("Your Google API key appears to be valid "
+                          "but does not grant access to the fonts API.")
+                    print("Aborting!")
+                    raise SystemExit(1)
+
+            case HTTPError if exn.getcode() == 400:
+                # Printing the supposed token should be fine, as this is
+                #  what the API returns on invalid tokens.
+                print(f"Got HTTP 400 (Bad Request), is this really an API token: '{apiToken}' ?")
+            case _:
+                # Unknown error, let's bubble it up
+                raise
+
+        # In that case just use the existing metadata
+        with metadataPath.open() as metadataFile:
+            metadata = json.load(metadataFile)
+
+        lastModified = metadata["items"][0]["lastModified"];
+        print(f"Using metadata from file, last modified {lastModified}")
+
+    else:
+        # If metadata was successfully fetched, validate and persist it
+        lastModified = metadata["items"][0]["lastModified"];
+        print(f"Fetched current metadata, last modified {lastModified}")
+        with atomicFileUpdate(metadataPath) as metadataFile:
+            json.dump(metadata, metadataFile, indent = 2)
+            metadataFile.write("\n")  # Pacify nixpkgs' dumb editor config check
+
+    hashPath = currentDir / 'noto-emoji.hashes.json'
+    try:
+        with hashPath.open() as hashFile:
+            hashes = json.load(hashFile)
+    except FileNotFoundError:
+        hashes = {}
+
+    with atomicFileUpdate(hashPath) as hashFile:
+        json.dump(
+            hashUrls(getUrls(metadata), knownHashes = hashes),
+            hashFile,
+            indent = 2,
+        )
+        hashFile.write("\n")  # Pacify nixpkgs' dumb editor config check
diff --git a/pkgs/by-name/no/noto-fonts-monochrome-emoji/package.nix b/pkgs/by-name/no/noto-fonts-monochrome-emoji/package.nix
new file mode 100644
index 000000000000..08f78b613efc
--- /dev/null
+++ b/pkgs/by-name/no/noto-fonts-monochrome-emoji/package.nix
@@ -0,0 +1,53 @@
+{ lib
+, stdenvNoCC
+, fetchurl
+}:
+
+# Metadata fetched from
+#  https://www.googleapis.com/webfonts/v1/webfonts?key=${GOOGLE_FONTS_TOKEN}&family=Noto+Emoji
+let
+  metadata = with builtins; head (fromJSON (readFile ./noto-emoji.json)).items;
+  urlHashes = with builtins; fromJSON (readFile ./noto-emoji.hashes.json);
+in
+stdenvNoCC.mkDerivation {
+  pname = "noto-fonts-monochrome-emoji";
+  version = "${lib.removePrefix "v" metadata.version}.${metadata.lastModified}";
+  preferLocalBuild = true;
+
+  dontUnpack = true;
+  srcs =
+    let
+      weightNames = {
+        "300" = "Light";
+        regular = "Regular";
+        "500" = "Medium";
+        "600" = "SemiBold";
+        "700" = "Bold";
+      };
+    in
+    lib.mapAttrsToList
+      (variant: url: fetchurl {
+        name = "NotoEmoji-${weightNames.${variant}}.ttf";
+        hash = urlHashes.${url};
+        inherit url;
+      })
+      metadata.files;
+
+  installPhase = ''
+    runHook preInstall
+    for src in $srcs; do
+      install -D $src $out/share/fonts/noto/$(stripHash $src)
+    done
+    runHook postInstall
+  '';
+
+  meta = {
+    description = "Monochrome emoji font";
+    homepage = "https://fonts.google.com/noto/specimen/Noto+Emoji";
+    license = [ lib.licenses.ofl ];
+    maintainers = [ lib.maintainers.nicoo ];
+
+    platforms = lib.platforms.all;
+    sourceProvenance = [ lib.sourceTypes.binaryBytecode ];
+  };
+}