code.gitea.io/gitea@v1.21.7/build/generate-emoji.go (about)

     1  // Copyright 2020 The Gitea Authors. All rights reserved.
     2  // Copyright 2015 Kenneth Shaw
     3  // SPDX-License-Identifier: MIT
     4  
     5  //go:build ignore
     6  
     7  package main
     8  
     9  import (
    10  	"flag"
    11  	"fmt"
    12  	"go/format"
    13  	"io"
    14  	"log"
    15  	"net/http"
    16  	"os"
    17  	"regexp"
    18  	"sort"
    19  	"strconv"
    20  	"strings"
    21  	"unicode/utf8"
    22  
    23  	"code.gitea.io/gitea/modules/json"
    24  )
    25  
    26  const (
    27  	gemojiURL         = "https://raw.githubusercontent.com/github/gemoji/master/db/emoji.json"
    28  	maxUnicodeVersion = 15
    29  )
    30  
    31  var flagOut = flag.String("o", "modules/emoji/emoji_data.go", "out")
    32  
    33  // Gemoji is a set of emoji data.
    34  type Gemoji []Emoji
    35  
    36  // Emoji represents a single emoji and associated data.
    37  type Emoji struct {
    38  	Emoji          string   `json:"emoji"`
    39  	Description    string   `json:"description,omitempty"`
    40  	Aliases        []string `json:"aliases"`
    41  	UnicodeVersion string   `json:"unicode_version,omitempty"`
    42  	SkinTones      bool     `json:"skin_tones,omitempty"`
    43  }
    44  
    45  // Don't include some fields in JSON
    46  func (e Emoji) MarshalJSON() ([]byte, error) {
    47  	type emoji Emoji
    48  	x := emoji(e)
    49  	x.UnicodeVersion = ""
    50  	x.Description = ""
    51  	x.SkinTones = false
    52  	return json.Marshal(x)
    53  }
    54  
    55  func main() {
    56  	var err error
    57  
    58  	flag.Parse()
    59  
    60  	// generate data
    61  	buf, err := generate()
    62  	if err != nil {
    63  		log.Fatalf("generate err: %v", err)
    64  	}
    65  
    66  	// write
    67  	err = os.WriteFile(*flagOut, buf, 0o644)
    68  	if err != nil {
    69  		log.Fatalf("WriteFile err: %v", err)
    70  	}
    71  }
    72  
    73  var replacer = strings.NewReplacer(
    74  	"main.Gemoji", "Gemoji",
    75  	"main.Emoji", "\n",
    76  	"}}", "},\n}",
    77  	", Description:", ", ",
    78  	", Aliases:", ", ",
    79  	", UnicodeVersion:", ", ",
    80  	", SkinTones:", ", ",
    81  )
    82  
    83  var emojiRE = regexp.MustCompile(`\{Emoji:"([^"]*)"`)
    84  
    85  func generate() ([]byte, error) {
    86  	var err error
    87  
    88  	// load gemoji data
    89  	res, err := http.Get(gemojiURL)
    90  	if err != nil {
    91  		return nil, err
    92  	}
    93  	defer res.Body.Close()
    94  
    95  	// read all
    96  	body, err := io.ReadAll(res.Body)
    97  	if err != nil {
    98  		return nil, err
    99  	}
   100  
   101  	// unmarshal
   102  	var data Gemoji
   103  	err = json.Unmarshal(body, &data)
   104  	if err != nil {
   105  		return nil, err
   106  	}
   107  
   108  	skinTones := make(map[string]string)
   109  
   110  	skinTones["\U0001f3fb"] = "Light Skin Tone"
   111  	skinTones["\U0001f3fc"] = "Medium-Light Skin Tone"
   112  	skinTones["\U0001f3fd"] = "Medium Skin Tone"
   113  	skinTones["\U0001f3fe"] = "Medium-Dark Skin Tone"
   114  	skinTones["\U0001f3ff"] = "Dark Skin Tone"
   115  
   116  	var tmp Gemoji
   117  
   118  	// filter out emoji that require greater than max unicode version
   119  	for i := range data {
   120  		val, _ := strconv.ParseFloat(data[i].UnicodeVersion, 64)
   121  		if int(val) <= maxUnicodeVersion {
   122  			tmp = append(tmp, data[i])
   123  		}
   124  	}
   125  	data = tmp
   126  
   127  	sort.Slice(data, func(i, j int) bool {
   128  		return data[i].Aliases[0] < data[j].Aliases[0]
   129  	})
   130  
   131  	aliasMap := make(map[string]int, len(data))
   132  
   133  	for i, e := range data {
   134  		if e.Emoji == "" || len(e.Aliases) == 0 {
   135  			continue
   136  		}
   137  		for _, a := range e.Aliases {
   138  			if a == "" {
   139  				continue
   140  			}
   141  			aliasMap[a] = i
   142  		}
   143  	}
   144  
   145  	// gitea customizations
   146  	i, ok := aliasMap["tada"]
   147  	if ok {
   148  		data[i].Aliases = append(data[i].Aliases, "hooray")
   149  	}
   150  	i, ok = aliasMap["laughing"]
   151  	if ok {
   152  		data[i].Aliases = append(data[i].Aliases, "laugh")
   153  	}
   154  
   155  	// write a JSON file to use with tribute (write before adding skin tones since we can't support them there yet)
   156  	file, _ := json.Marshal(data)
   157  	_ = os.WriteFile("assets/emoji.json", file, 0o644)
   158  
   159  	// Add skin tones to emoji that support it
   160  	var (
   161  		s              []string
   162  		newEmoji       string
   163  		newDescription string
   164  		newData        Emoji
   165  	)
   166  
   167  	for i := range data {
   168  		if data[i].SkinTones {
   169  			for k, v := range skinTones {
   170  				s = strings.Split(data[i].Emoji, "")
   171  
   172  				if utf8.RuneCountInString(data[i].Emoji) == 1 {
   173  					s = append(s, k)
   174  				} else {
   175  					// insert into slice after first element because all emoji that support skin tones
   176  					// have that modifier placed at this spot
   177  					s = append(s, "")
   178  					copy(s[2:], s[1:])
   179  					s[1] = k
   180  				}
   181  
   182  				newEmoji = strings.Join(s, "")
   183  				newDescription = data[i].Description + ": " + v
   184  				newAlias := data[i].Aliases[0] + "_" + strings.ReplaceAll(v, " ", "_")
   185  
   186  				newData = Emoji{newEmoji, newDescription, []string{newAlias}, "12.0", false}
   187  				data = append(data, newData)
   188  			}
   189  		}
   190  	}
   191  
   192  	sort.Slice(data, func(i, j int) bool {
   193  		return data[i].Aliases[0] < data[j].Aliases[0]
   194  	})
   195  
   196  	// add header
   197  	str := replacer.Replace(fmt.Sprintf(hdr, gemojiURL, data))
   198  
   199  	// change the format of the unicode string
   200  	str = emojiRE.ReplaceAllStringFunc(str, func(s string) string {
   201  		var err error
   202  		s, err = strconv.Unquote(s[len("{Emoji:"):])
   203  		if err != nil {
   204  			panic(err)
   205  		}
   206  		return "{" + strconv.QuoteToASCII(s)
   207  	})
   208  
   209  	// format
   210  	return format.Source([]byte(str))
   211  }
   212  
   213  const hdr = `
   214  // Copyright 2020 The Gitea Authors. All rights reserved.
   215  // SPDX-License-Identifier: MIT
   216  
   217  
   218  package emoji
   219  
   220  // Code generated by build/generate-emoji.go. DO NOT EDIT.
   221  // Sourced from %s
   222  var GemojiData = %#v
   223  `