internal/generator/unicode.go (view raw)
1package main
2
3import (
4 "fmt"
5 "regexp"
6 "strconv"
7 "strings"
8
9 emojipkg "github.com/enescakir/emoji"
10)
11
12const emojiListURL = "https://unicode.org/Public/emoji/15.1/emoji-test.txt"
13
14var (
15 emojiRegex = regexp.MustCompile(`^(?m)(?P<code>[A-Z\d ]+[A-Z\d])\s+;\s+(fully-qualified|component)\s+#\s+.+\s+E\d+\.\d+ (?P<name>.+)$`)
16 toneRegex = regexp.MustCompile(`:\s.*tone,?`)
17)
18
19func fetchEmojis() (*groups, error) {
20 var emojis groups
21 b, err := fetchData(emojiListURL)
22 if err != nil {
23 return nil, err
24 }
25
26 var grp *group
27 var subgrp *subgroup
28
29 parseLine := func(line string) {
30 switch {
31 case strings.HasPrefix(line, "# group:"):
32 name := strings.TrimSpace(strings.ReplaceAll(line, "# group:", ""))
33 grp = emojis.Append(name)
34 case strings.HasPrefix(line, "# subgroup:"):
35 name := strings.TrimSpace(strings.ReplaceAll(line, "# subgroup:", ""))
36 subgrp = grp.Append(name)
37 case !strings.HasPrefix(line, "#"):
38 if e := newEmoji(line); e != nil {
39 subgrp.Append(*e)
40 }
41 }
42 }
43
44 if err = readLines(b, parseLine); err != nil {
45 return nil, err
46 }
47
48 return &emojis, nil
49}
50
51type groups struct {
52 Groups []*group
53}
54
55func (g *groups) Append(grpName string) *group {
56 // fmt.Printf("group: %v\n", grpName)
57 grp := group{Name: grpName}
58 g.Groups = append(g.Groups, &grp)
59
60 return &grp
61}
62
63type group struct {
64 Name string
65 Subgroups []*subgroup
66}
67
68func (g *group) Append(subgrpName string) *subgroup {
69 // fmt.Printf("subgroup: %v\n", subgrpName)
70 subgrp := subgroup{Name: subgrpName, Emojis: make(map[string][]emoji)}
71 g.Subgroups = append(g.Subgroups, &subgrp)
72
73 return &subgrp
74}
75
76type subgroup struct {
77 Name string
78 Emojis map[string][]emoji
79 Constants []string
80}
81
82func (s *subgroup) Append(e emoji) {
83 // fmt.Printf("emoji: %v\n", e)
84 if _, ok := s.Emojis[e.Constant]; ok {
85 s.Emojis[e.Constant] = append(s.Emojis[e.Constant], e)
86 } else {
87 s.Emojis[e.Constant] = []emoji{e}
88 s.Constants = append(s.Constants, e.Constant)
89 }
90}
91
92type emoji struct {
93 Name string
94 Constant string
95 Code string
96 Tones []string
97}
98
99func (e *emoji) String() string {
100 return fmt.Sprintf("name:%v, constant:%v, code:%v, tones: %v\n", e.Name, e.Constant, e.Code, e.Tones)
101}
102
103func newEmoji(line string) *emoji {
104 matches := emojiRegex.FindStringSubmatch(line)
105 if len(matches) < 4 {
106 return nil
107 }
108 code := matches[1]
109 name := matches[3]
110
111 e := emoji{
112 Name: name,
113 Constant: name,
114 Code: code,
115 Tones: []string{},
116 }
117 e.extractAttr()
118 e.generateConstant()
119 e.generateUnicode()
120
121 return &e
122}
123
124func (e *emoji) extractAttr() {
125 parts := strings.Split(e.Constant, ":")
126 if len(parts) < 2 {
127 // no attributes
128 return
129 }
130 c := parts[0]
131 attrs := strings.Split(parts[1], ",")
132 for _, attr := range attrs {
133 switch {
134 case strings.Contains(attr, "tone"):
135 e.Tones = append(e.Tones, attr)
136 case strings.Contains(attr, "beard"):
137 fallthrough
138 case strings.Contains(attr, "hair"):
139 c += " with " + attr
140 case strings.HasPrefix(c, "flag"):
141 c += " for " + attr
142 default:
143 c += " " + attr
144 }
145 }
146 e.Constant = c
147}
148
149func (e *emoji) generateConstant() {
150 c := clean(e.Constant)
151 c = strings.Title(strings.ToLower(c))
152 c = removeSpaces(c)
153
154 e.Constant = c
155}
156
157func (e *emoji) generateUnicode() {
158 unicodes := []string{}
159 for _, v := range strings.Split(e.Code, " ") {
160 u, err := strconv.ParseInt(v, 16, 32)
161 if err != nil {
162 panic(fmt.Errorf("unknown unicode: %v", v))
163 }
164 unicodes = append(unicodes, string(u))
165 }
166
167 e.Code = strings.Join(unicodes, "")
168}
169
170func defaultTone(basic, toned string) string {
171 toneInd := strings.IndexRune(toned, []rune(emojipkg.TonePlaceholder)[0])
172 for i, ch := range basic {
173 if i != toneInd {
174 continue
175 }
176 if ch == '\ufe0f' {
177 return "\ufe0f"
178 }
179 break
180 }
181
182 return ""
183}
184
185func replaceTones(code string) string {
186 for _, tone := range []emojipkg.Tone{
187 emojipkg.Light,
188 emojipkg.MediumLight,
189 emojipkg.Medium,
190 emojipkg.MediumDark,
191 emojipkg.Dark,
192 } {
193 code = strings.ReplaceAll(code, tone.String(), emojipkg.TonePlaceholder)
194 }
195
196 return code
197}