Icons: simple-icons@16.35.0 (CC0), generated by go run ./internal/master/web/icons/gen, which downloads and verifies: https://registry.npmjs.org/simple-icons/-/simple-icons-16.35.0.tgz 20ebead4f6bc93464812a5e260d19fc4d84772e5eeff69a6fbea263ca2441c5f Output (deterministic): simple-icons.json.gz a8f3b504cc21207e8d55d50f3724630a470968da69451c731538589c4debc87b Fonts (@fontsource-variable 5.3.0, latin, SIL OFL 1.1), sha256: 4c8b93f431d462c696e12b9d6a033feb3394d36e66e781357c496b95d8a75e05 schibsted-grotesk-latin-wght-normal.woff2 18be452724bfdc236c074ca94a249a7f41a86752c7d04ab258ce9ed5651f6a7e jetbrains-mono-latin-wght-normal.woff2 15222412a95d70042e66e79bfb3b985653e0693e4d41703bc131bee9c5aa1aa7 OFL-schibsted-grotesk.txt 403581b69dac5cff4079205e01c6b467e56af449ecbd7247693ddb1baafa005b OFL-jetbrains-mono.txt from https://cdn.jsdelivr.net/npm/@fontsource-variable/{schibsted-grotesk,jetbrains-mono}@5.3.0/ Claude-Session: https://claude.ai/code/session_01GAZsp3P7Qto6JnhipxWUJQ
130 lines
3.2 KiB
Go
130 lines
3.2 KiB
Go
// Command gen builds simple-icons.json.gz, the icon data the icons package
|
|
// embeds. It downloads the pinned simple-icons npm tarball, checks its
|
|
// sha256 and writes a gzipped JSON object {slug: svg path data}. The output
|
|
// is deterministic. Run it from the repository root:
|
|
//
|
|
// go run ./internal/master/web/icons/gen
|
|
package main
|
|
|
|
import (
|
|
"archive/tar"
|
|
"bytes"
|
|
"compress/gzip"
|
|
"crypto/sha256"
|
|
"encoding/hex"
|
|
"encoding/json"
|
|
"fmt"
|
|
"io"
|
|
"net/http"
|
|
"os"
|
|
"path"
|
|
"regexp"
|
|
"strings"
|
|
)
|
|
|
|
const (
|
|
tarballURL = "https://registry.npmjs.org/simple-icons/-/simple-icons-16.35.0.tgz"
|
|
tarballSHA256 = "20ebead4f6bc93464812a5e260d19fc4d84772e5eeff69a6fbea263ca2441c5f"
|
|
outDir = "internal/master/web/icons"
|
|
)
|
|
|
|
var pathData = regexp.MustCompile(`<path d="([^"]+)"`)
|
|
|
|
func main() {
|
|
if err := run(); err != nil {
|
|
fmt.Fprintln(os.Stderr, "gen:", err)
|
|
os.Exit(1)
|
|
}
|
|
}
|
|
|
|
func run() error {
|
|
tgz, err := download(tarballURL)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if sum := sha256.Sum256(tgz); hex.EncodeToString(sum[:]) != tarballSHA256 {
|
|
return fmt.Errorf("sha256 mismatch for %s: got %x, want %s", tarballURL, sum, tarballSHA256)
|
|
}
|
|
icons, license, err := extract(tgz)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
var js bytes.Buffer
|
|
enc := json.NewEncoder(&js)
|
|
enc.SetEscapeHTML(false)
|
|
if err := enc.Encode(icons); err != nil { // map keys are written sorted
|
|
return err
|
|
}
|
|
var gz bytes.Buffer
|
|
zw, err := gzip.NewWriterLevel(&gz, gzip.BestCompression)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if _, err := zw.Write(js.Bytes()); err != nil { // the header has no name and a zero mtime
|
|
return err
|
|
}
|
|
if err := zw.Close(); err != nil {
|
|
return err
|
|
}
|
|
if err := os.WriteFile(path.Join(outDir, "simple-icons.json.gz"), gz.Bytes(), 0o644); err != nil {
|
|
return err
|
|
}
|
|
if err := os.WriteFile(path.Join(outDir, "LICENSE-simple-icons.md"), license, 0o644); err != nil {
|
|
return err
|
|
}
|
|
fmt.Printf("wrote %d icons\n", len(icons))
|
|
return nil
|
|
}
|
|
|
|
func download(url string) ([]byte, error) {
|
|
resp, err := http.Get(url)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer resp.Body.Close()
|
|
if resp.StatusCode != http.StatusOK {
|
|
return nil, fmt.Errorf("GET %s: %s", url, resp.Status)
|
|
}
|
|
return io.ReadAll(resp.Body)
|
|
}
|
|
|
|
// extract returns the path data of every package/icons/*.svg by slug, and
|
|
// package/LICENSE.md.
|
|
func extract(tgz []byte) (icons map[string]string, license []byte, err error) {
|
|
zr, err := gzip.NewReader(bytes.NewReader(tgz))
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
tr := tar.NewReader(zr)
|
|
icons = map[string]string{}
|
|
for {
|
|
h, err := tr.Next()
|
|
if err == io.EOF {
|
|
break
|
|
}
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
switch {
|
|
case h.Name == "package/LICENSE.md":
|
|
if license, err = io.ReadAll(tr); err != nil {
|
|
return nil, nil, err
|
|
}
|
|
case path.Dir(h.Name) == "package/icons" && strings.HasSuffix(h.Name, ".svg"):
|
|
svg, err := io.ReadAll(tr)
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
m := pathData.FindAllSubmatch(svg, -1)
|
|
if len(m) != 1 {
|
|
return nil, nil, fmt.Errorf("%s: want exactly one <path>, found %d", h.Name, len(m))
|
|
}
|
|
icons[strings.TrimSuffix(path.Base(h.Name), ".svg")] = string(m[0][1])
|
|
}
|
|
}
|
|
if license == nil || len(icons) == 0 {
|
|
return nil, nil, fmt.Errorf("tarball has no LICENSE.md or no icons")
|
|
}
|
|
return icons, license, nil
|
|
}
|