github.com/keybase/client/go@v0.0.0-20241007131713-f10651d043c8/chat/emoji/main.go (about) 1 package main 2 3 import ( 4 "bytes" 5 "encoding/json" 6 "flag" 7 "fmt" 8 "go/format" 9 "log" 10 "os" 11 "sort" 12 "strconv" 13 "strings" 14 "text/template" 15 ) 16 17 // EmojiData json parse struct 18 type EmojiData struct { 19 Unified string `json:"unified"` 20 ShortName string `json:"short_name"` 21 ObsoletedBy string `json:"obsoleted_by"` 22 } 23 24 // UnifiedToChar renders a character from its hexadecimal codepoint 25 func UnifiedToChar(unified string) (string, error) { 26 codes := strings.Split(unified, "-") 27 var sb strings.Builder 28 for _, code := range codes { 29 s, err := strconv.ParseInt(code, 16, 32) 30 if err != nil { 31 return "", err 32 } 33 sb.WriteRune(rune(s)) 34 } 35 return sb.String(), nil 36 } 37 38 func createEmojiDataCodeMap(path string) (map[string]string, map[string][]string, error) { 39 emojiFile, err := os.ReadFile(path) 40 if err != nil { 41 return nil, nil, err 42 } 43 44 var data []EmojiData 45 if err := json.Unmarshal(emojiFile, &data); err != nil { 46 return nil, nil, err 47 } 48 49 // emojiRevCodeMap maps unicode characters to lists of short codes. 50 51 emojiCodeMap := make(map[string]string) 52 emojiRevCodeMap := make(map[string][]string) 53 for _, emoji := range data { 54 if len(emoji.ShortName) == 0 || len(emoji.Unified) == 0 { 55 continue 56 } 57 unified := emoji.Unified 58 if len(emoji.ObsoletedBy) > 0 { 59 unified = emoji.ObsoletedBy 60 } 61 unicode, err := UnifiedToChar(unified) 62 if err != nil { 63 return nil, nil, err 64 } 65 unicode = fmt.Sprintf("%+q", strings.ToLower(unicode)) 66 emojiCodeMap[emoji.ShortName] = unicode 67 emojiRevCodeMap[unicode] = append(emojiRevCodeMap[unicode], emoji.ShortName) 68 } 69 70 // ensure deterministic ordering for aliases 71 for _, value := range emojiRevCodeMap { 72 sort.Slice(value, func(i, j int) bool { 73 if len(value[i]) == len(value[j]) { 74 return value[i] < value[j] 75 } 76 return len(value[i]) < len(value[j]) 77 }) 78 } 79 80 return emojiCodeMap, emojiRevCodeMap, nil 81 } 82 83 // adapted from https://github.com/kyokomi/emoji/ to only use the emoji-data 84 // emoji source 85 var pkgName, inName, outName string 86 87 // TemplateData emoji_codemap.go template 88 type TemplateData struct { 89 PkgName string 90 CodeMap map[string]string 91 RevCodeMap map[string][]string 92 } 93 94 const templateMapCode = ` 95 package {{.PkgName}} 96 97 // NOTE: THIS FILE WAS PRODUCED BY THE 98 // EMOJICODEMAP CODE GENERATION TOOL (github.com/keybase/go/chat/emoji) 99 // DO NOT EDIT 100 101 // Mapping from character to concrete escape code. 102 var emojiCodeMap = map[string]string{ 103 {{range $key, $val := .CodeMap}}":{{$key}}:": {{$val}}, 104 {{end}} 105 } 106 107 var emojiRevCodeMap = map[string][]string{ 108 {{range $key, $val := .RevCodeMap}} {{$key}}: { {{range $val}} ":{{.}}:", {{end}} }, 109 {{end}} 110 } 111 ` 112 113 func createCodeMapSource(pkgName string, emojiCodeMap map[string]string, emojiRevCodeMap map[string][]string) ([]byte, error) { 114 // Template GenerateSource 115 116 var buf bytes.Buffer 117 t := template.Must(template.New("template").Parse(templateMapCode)) 118 if err := t.Execute(&buf, TemplateData{PkgName: pkgName, CodeMap: emojiCodeMap, RevCodeMap: emojiRevCodeMap}); err != nil { 119 return nil, err 120 } 121 122 // gofmt 123 124 bts, err := format.Source(buf.Bytes()) 125 if err != nil { 126 fmt.Print(buf.String()) 127 return nil, fmt.Errorf("gofmt: %s", err) 128 } 129 130 return bts, nil 131 } 132 133 func main() { 134 flag.StringVar(&pkgName, "pkg", "storage", "output package") 135 flag.StringVar(&outName, "o", "../storage/emoji_codemap.go", "output file") 136 flag.StringVar(&inName, "i", "../../../shared/node_modules/emoji-datasource-apple/emoji.json", "input file") 137 flag.Parse() 138 codeMap, revCodeMap, err := createEmojiDataCodeMap(inName) 139 if err != nil { 140 log.Fatalln(err) 141 } 142 143 codeMapSource, err := createCodeMapSource(pkgName, codeMap, revCodeMap) 144 if err != nil { 145 log.Fatalln(err) 146 } 147 148 os.Remove(outName) 149 file, err := os.Create(outName) 150 if err != nil { 151 log.Fatalln(err) 152 } 153 defer file.Close() 154 155 if _, err := file.Write(codeMapSource); err != nil { 156 log.Println(err) 157 return 158 } 159 }