all repos — emoji @ 84a0a67371e0847129343a1165bdd41e95f5b49d

A minimalistic emoji package for Go (golang)

internal/generator/main.go (view raw)

  1package main
  2
  3import (
  4	"bufio"
  5	"bytes"
  6	"encoding/json"
  7	"fmt"
  8	"go/format"
  9	"io"
 10	"io/ioutil"
 11	"net/http"
 12	"os"
 13	"sort"
 14	"strings"
 15	"text/template"
 16	"time"
 17	"unicode"
 18)
 19
 20const (
 21	emojiListUrl  = "https://unicode.org/Public/emoji/13.0/emoji-test.txt"
 22	gemojiURL     = "https://raw.githubusercontent.com/github/gemoji/master/db/emoji.json"
 23	constantsFile = "constants.go"
 24	aliasesFile   = "map.go"
 25)
 26
 27// unicode and gemoji databases don't have alias like that
 28var customEmojis = map[string]string{
 29	":robot_face:": "\U0001f916", // slack
 30}
 31
 32func main() {
 33	emojis, err := fetch()
 34	if err != nil {
 35		panic(err)
 36	}
 37
 38	constants := generateConstants(emojis)
 39	aliases := generateAliases(emojis)
 40
 41	if err = save(constantsFile, constants); err != nil {
 42		panic(err)
 43	}
 44
 45	if err = save(aliasesFile, aliases); err != nil {
 46		panic(err)
 47	}
 48}
 49
 50func fetch() (*groups, error) {
 51	var emojis groups
 52	b, err := fetchData(emojiListUrl)
 53	if err != nil {
 54		return nil, err
 55	}
 56
 57	var grp *group
 58	var subgrp *subgroup
 59
 60	parseLine := func(line string) {
 61		switch {
 62		case strings.HasPrefix(line, "# group:"):
 63			name := strings.TrimSpace(strings.ReplaceAll(line, "# group:", ""))
 64			grp = emojis.Append(name)
 65		case strings.HasPrefix(line, "# subgroup:"):
 66			name := strings.TrimSpace(strings.ReplaceAll(line, "# subgroup:", ""))
 67			subgrp = grp.Append(name)
 68		case !strings.HasPrefix(line, "#"):
 69			if e := newEmoji(line); e != nil {
 70				subgrp.Append(*e)
 71			}
 72		}
 73	}
 74
 75	if err = readLines(b, parseLine); err != nil {
 76		return nil, err
 77	}
 78
 79	return &emojis, nil
 80}
 81
 82func generateAliases(emojis *groups) string {
 83	var aliases []string
 84	var emojiMap = make(map[string]string)
 85
 86	for _, grp := range emojis.Groups {
 87		for _, subgrp := range grp.Subgroups {
 88			for _, c := range subgrp.Constants {
 89				emoji := subgrp.Emojis[c][0]
 90				alias := ":" + snakeCase(emoji.Constant) + ":"
 91				aliases = append(aliases, alias)
 92				emojiMap[alias] = emoji.Code
 93			}
 94		}
 95	}
 96
 97	// add gemoji aliases
 98	{
 99		gemojis, err := fetchGemoji()
100		if err != nil {
101			panic(err)
102		}
103
104		for alias, code := range gemojis {
105			_, ok := emojiMap[alias]
106			if !ok {
107				aliases = append(aliases, alias)
108			}
109			emojiMap[alias] = code
110		}
111	}
112
113	// add custom emoji aliases
114	{
115		for alias, code := range customEmojis {
116			_, ok := emojiMap[alias]
117			if !ok {
118				aliases = append(aliases, alias)
119			}
120			emojiMap[alias] = code
121		}
122	}
123
124	var res string
125	sort.Strings(aliases)
126	for _, alias := range aliases {
127		res += fmt.Sprintf("%q: %+q,\n", alias, emojiMap[alias])
128	}
129
130	return res
131}
132
133func snakeCase(str string) string {
134	var output strings.Builder
135	for i, r := range str {
136		switch {
137		case unicode.IsUpper(r):
138			if i != 0 {
139				output.WriteRune('_')
140			}
141			output.WriteRune(unicode.ToLower(r))
142		case unicode.IsDigit(r):
143			if i != 0 && !unicode.IsDigit(rune(str[i-1])) {
144				output.WriteRune('_')
145			}
146			output.WriteRune(r)
147		default:
148			output.WriteRune(r)
149		}
150	}
151
152	return output.String()
153}
154
155type gemoji struct {
156	Emoji   string   `json:"emoji"`
157	Aliases []string `json:"aliases"`
158}
159
160func fetchGemoji() (map[string]string, error) {
161	b, err := fetchData(gemojiURL)
162	if err != nil {
163		return nil, err
164	}
165
166	var gemojis []gemoji
167	r := make(map[string]string)
168
169	if err = json.Unmarshal(b, &gemojis); err != nil {
170		return nil, err
171	}
172
173	for _, gemoji := range gemojis {
174		for _, alias := range gemoji.Aliases {
175			if len(alias) == 0 || len(gemoji.Emoji) == 0 {
176				continue
177			}
178
179			r[":"+alias+":"] = gemoji.Emoji
180		}
181	}
182
183	return r, nil
184}
185
186func generateConstants(emojis *groups) string {
187	var res string
188	for _, grp := range emojis.Groups {
189		res += fmt.Sprintf("\n// GROUP: %v\n", grp.Name)
190		for _, subgrp := range grp.Subgroups {
191			res += fmt.Sprintf("// SUBGROUP: %v\n", subgrp.Name)
192			for _, c := range subgrp.Constants {
193				res += emojiConstant(subgrp.Emojis[c])
194			}
195		}
196	}
197
198	return res
199}
200
201func emojiConstant(emojis []emoji) string {
202	basic := emojis[0]
203	switch len(emojis) {
204	case 1:
205		return fmt.Sprintf("%s Emoji = %+q // %s\n", basic.Constant, basic.Code, basic.Name)
206	case 6:
207		oneTonedCode := replaceTones(emojis[1].Code)
208		defaultTone := defaultTone(basic.Code, oneTonedCode)
209
210		if defaultTone != "" {
211			return fmt.Sprintf("%s EmojiWithTone = newEmojiWithTone(%+q).withDefaultTone(%+q) // %s\n",
212				basic.Constant, oneTonedCode, defaultTone, basic.Name)
213		}
214
215		return fmt.Sprintf("%s EmojiWithTone = newEmojiWithTone(%+q) // %s\n",
216			basic.Constant, oneTonedCode, basic.Name)
217	case 26:
218		oneTonedCode := replaceTones(emojis[1].Code)
219		twoTonedCode := replaceTones(emojis[2].Code)
220
221		return fmt.Sprintf("%s EmojiWithTone = newEmojiWithTone(%+q, %+q) // %s\n",
222			basic.Constant, oneTonedCode, twoTonedCode, basic.Name)
223	default:
224		panic(fmt.Errorf("not expected emoji count for a constant: %v", len(emojis)))
225	}
226}
227
228func save(filename, data string) error {
229	tmpl, err := template.ParseFiles(fmt.Sprintf("internal/generator/%v.tmpl", filename))
230	if err != nil {
231		return err
232	}
233
234	d := struct {
235		Link string
236		Date string
237		Data string
238	}{
239		Link: emojiListUrl,
240		Date: time.Now().Format(time.RFC3339),
241		Data: data,
242	}
243
244	var w bytes.Buffer
245	if err = tmpl.Execute(&w, d); err != nil {
246		return err
247	}
248
249	content, err := format.Source(w.Bytes())
250
251	file, err := os.Create(filename)
252	if err != nil {
253		return fmt.Errorf("could not create file: %v", err)
254	}
255	defer file.Close()
256
257	if _, err := file.Write(content); err != nil {
258		return fmt.Errorf("could not write to file: %v", err)
259	}
260	return nil
261}
262
263func fetchData(url string) ([]byte, error) {
264	resp, err := http.Get(url)
265	if err != nil {
266		return nil, err
267	}
268	defer resp.Body.Close()
269
270	// Check server response
271	if resp.StatusCode != http.StatusOK {
272		return nil, fmt.Errorf("bad status: %s", resp.Status)
273	}
274
275	b, err := ioutil.ReadAll(resp.Body)
276	if err != nil {
277		return nil, err
278	}
279
280	return b, nil
281}
282
283func readLines(b []byte, fn func(string)) error {
284	reader := bufio.NewReader(bytes.NewReader(b))
285
286	var line string
287	var err error
288	for {
289		line, err = reader.ReadString('\n')
290		if err != nil {
291			break
292		}
293
294		fn(line)
295	}
296
297	if err != io.EOF {
298		return err
299	}
300
301	return nil
302}