Files
gitea-MIRROR/build/generate-emoji.go
wxiaoguang a165fdb63c refactor: lazy init global regexp variables (#40269)
Gitea binary is an all-in-one program.

It doesn't make sense to compile dozens or even hundreds of regexps for
git hook sub commands.
2026-11-07 11:56:56 +00:00

136 lines
3.2 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// Copyright 2020 The Gitea Authors. All rights reserved.
// Copyright 2015 Kenneth Shaw
// SPDX-License-Identifier: MIT
//go:build ignore
package main
import (
"bufio"
"errors"
"fmt"
"log"
"net/http"
"os"
"regexp"
"slices"
"strings"
"sync"
"gitea.dev/modules/json"
)
const (
emojiTestURL = "https://www.unicode.org/Public/17.0.0/emoji/emoji-test.txt"
jsonFile = "public/assets/emoji.json"
)
type emoji struct {
Emoji string `json:"emoji"`
Aliases []string `json:"aliases"`
description string
}
var slugRe = sync.OnceValue(func() *regexp.Regexp {
return regexp.MustCompile(`[^a-z0-9]+`)
})
func main() {
if err := generate(); err != nil {
log.Fatal(err)
}
}
func generate() error {
// the existing file is also the alias source, so existing aliases stay stable
existing, err := os.ReadFile(jsonFile)
if err != nil {
return err
}
var existingEmojis []*emoji
if err := json.Unmarshal(existing, &existingEmojis); err != nil {
return err
}
existingAliases := make(map[string][]string, len(existingEmojis))
for _, e := range existingEmojis {
existingAliases[e.Emoji] = e.Aliases
}
emojis, err := fetchEmojis(existingAliases)
if err != nil {
return err
}
lines := make([]string, len(emojis))
for i, e := range emojis {
line, err := json.Marshal(e)
if err != nil {
return err
}
lines[i] = string(line)
}
return os.WriteFile(jsonFile, []byte("[\n"+strings.Join(lines, ",\n")+"\n]\n"), 0o644)
}
func isSkinTone(r rune) bool {
return r >= 0x1f3fb && r <= 0x1f3ff
}
func fetchEmojis(existingAliases map[string][]string) ([]*emoji, error) {
res, err := http.Get(emojiTestURL)
if err != nil {
return nil, err
}
defer res.Body.Close()
if res.StatusCode != http.StatusOK {
return nil, fmt.Errorf("fetching %s: %s", emojiTestURL, res.Status)
}
var emojis []*emoji
scanner := bufio.NewScanner(res.Body)
for scanner.Scan() {
// e.g. "1F44D ; fully-qualified # 👍 E0.6 thumbs up"
_, rest, _ := strings.Cut(scanner.Text(), ";")
status, comment, _ := strings.Cut(rest, "#")
if strings.TrimSpace(status) != "fully-qualified" {
continue
}
fields := strings.SplitN(strings.TrimSpace(comment), " ", 3)
if strings.ContainsFunc(fields[0], isSkinTone) {
continue
}
emojis = append(emojis, &emoji{Emoji: fields[0], description: fields[2]})
}
if err := scanner.Err(); err != nil {
return nil, err
}
var errs []error
aliasOwners := map[string]string{}
for _, e := range emojis {
e.Aliases = existingAliases[e.Emoji]
if e.Aliases == nil {
e.Aliases = []string{strings.Trim(slugRe().ReplaceAllString(strings.ReplaceAll(strings.ToLower(e.description), "’", ""), "_"), "_")}
}
delete(existingAliases, e.Emoji)
for _, alias := range e.Aliases {
if owner, ok := aliasOwners[alias]; ok {
errs = append(errs, fmt.Errorf("alias %q used by both %q and %q", alias, owner, e.Emoji))
}
aliasOwners[alias] = e.Emoji
}
}
for code, aliases := range existingAliases {
errs = append(errs, fmt.Errorf("emoji %q with aliases %v is missing from Unicode data", code, aliases))
}
if len(errs) > 0 {
return nil, errors.Join(errs...)
}
slices.SortFunc(emojis, func(a, b *emoji) int {
return strings.Compare(a.Aliases[0], b.Aliases[0])
})
return emojis, nil
}