all repos — emoji @ convert

A minimalistic emoji package for Go (golang)

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}