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 `