internal/generator/main.go (view raw)
1package main
2
3import (
4 "bufio"
5 "bytes"
6 "fmt"
7 "go/format"
8 "io"
9 "io/ioutil"
10 "net/http"
11 "os"
12 "strings"
13 "text/template"
14 "time"
15)
16
17const (
18 emojiListUrl = "https://unicode.org/Public/emoji/13.0/emoji-test.txt"
19)
20
21func main() {
22 emojis, err := fetch()
23 if err != nil {
24 panic(err)
25 }
26
27 constants := generate(emojis)
28
29 if err = save(constants); err != nil {
30 panic(err)
31 }
32}
33
34func fetch() (*groups, error) {
35 var emojis groups
36 b, err := fetchData(emojiListUrl)
37 if err != nil {
38 return nil, err
39 }
40
41 var grp *group
42 var subgrp *subgroup
43
44 parseLine := func(line string) {
45 switch {
46 case strings.HasPrefix(line, "# group:"):
47 name := strings.TrimSpace(strings.ReplaceAll(line, "# group:", ""))
48 grp = emojis.Append(name)
49 case strings.HasPrefix(line, "# subgroup:"):
50 name := strings.TrimSpace(strings.ReplaceAll(line, "# subgroup:", ""))
51 subgrp = grp.Append(name)
52 case !strings.HasPrefix(line, "#"):
53 if e := newEmoji(line); e != nil {
54 subgrp.Append(*e)
55 }
56 }
57 }
58
59 if err = readLines(b, parseLine); err != nil {
60 return nil, err
61 }
62
63 return &emojis, nil
64}
65
66func generate(emojis *groups) string {
67 var res string
68 for _, grp := range emojis.Groups {
69 res += fmt.Sprintf("\n// GROUP: %v\n", grp.Name)
70 for _, subgrp := range grp.Subgroups {
71 res += fmt.Sprintf("// SUBGROUP: %v\n", subgrp.Name)
72 for _, c := range subgrp.Constants {
73 res += emojiConstant(subgrp.Emojis[c])
74 }
75 }
76 }
77
78 return res
79}
80
81func emojiConstant(emojis []emoji) string {
82 basic := emojis[0]
83 switch len(emojis) {
84 case 1:
85 return fmt.Sprintf("%s Emoji = %+q // %s\n", basic.Constant, basic.Code, basic.Name)
86 case 6:
87 oneTonedCode := replaceTones(emojis[1].Code)
88 defaultTone := defaultTone(basic.Code, oneTonedCode)
89
90 if defaultTone != "" {
91 return fmt.Sprintf("%s EmojiWithTone = newEmojiWithTone(%+q).withDefaultTone(%+q) // %s\n", basic.Constant, oneTonedCode, defaultTone, basic.Name)
92 }
93
94 return fmt.Sprintf("%s EmojiWithTone = newEmojiWithTone(%+q) // %s\n", basic.Constant, oneTonedCode, basic.Name)
95 case 26:
96 oneTonedCode := replaceTones(emojis[1].Code)
97 twoTonedCode := replaceTones(emojis[2].Code)
98
99 return fmt.Sprintf("%s EmojiWithTone = newEmojiWithTone(%+q, %+q) // %s\n", basic.Constant, oneTonedCode, twoTonedCode, basic.Name)
100 default:
101 panic(fmt.Errorf("not expected emoji count for a constant: %v", len(emojis)))
102 }
103}
104
105func save(constants string) error {
106 tmpl, err := template.ParseFiles("internal/generator/constants.go.tmpl")
107 if err != nil {
108 return err
109 }
110
111 data := struct {
112 Link string
113 Date string
114 Constants string
115 }{
116 Link: emojiListUrl,
117 Date: time.Now().Format(time.RFC3339),
118 Constants: constants,
119 }
120 var w bytes.Buffer
121 if err = tmpl.Execute(&w, data); err != nil {
122 return err
123 }
124
125 content, err := format.Source(w.Bytes())
126
127 file, err := os.Create("constants.go")
128 if err != nil {
129 return fmt.Errorf("could not create file: %v", err)
130 }
131 defer file.Close()
132
133 if _, err := file.Write(content); err != nil {
134 return fmt.Errorf("could not write to file: %v", err)
135 }
136 return nil
137}
138
139func fetchData(url string) ([]byte, error) {
140 resp, err := http.Get(url)
141 if err != nil {
142 return nil, err
143 }
144 defer resp.Body.Close()
145
146 // Check server response
147 if resp.StatusCode != http.StatusOK {
148 return nil, fmt.Errorf("bad status: %s", resp.Status)
149 }
150
151 b, err := ioutil.ReadAll(resp.Body)
152 if err != nil {
153 return nil, err
154 }
155
156 return b, nil
157}
158
159func readLines(b []byte, fn func(string)) error {
160 reader := bufio.NewReader(bytes.NewReader(b))
161
162 var line string
163 var err error
164 for {
165 line, err = reader.ReadString('\n')
166 if err != nil {
167 break
168 }
169
170 fn(line)
171 }
172
173 if err != io.EOF {
174 return err
175 }
176
177 return nil
178}