1// Package emoji terminal output.
2package emoji
3
4import (
5	"bytes"
6	"errors"
7	"fmt"
8	"io"
9	"regexp"
10	"unicode"
11)
12
13//go:generate generateEmojiCodeMap -pkg emoji -o emoji_codemap.go
14
15// Replace Padding character for emoji.
16var (
17	ReplacePadding = " "
18)
19
20// CodeMap gets the underlying map of emoji.
21func CodeMap() map[string]string {
22	return emojiCode()
23}
24
25// RevCodeMap gets the underlying map of emoji.
26func RevCodeMap() map[string][]string {
27	return emojiRevCode()
28}
29
30func AliasList(shortCode string) []string {
31	return emojiRevCode()[emojiCode()[shortCode]]
32}
33
34// HasAlias flags if the given `shortCode` has multiple aliases with other
35// codes.
36func HasAlias(shortCode string) bool {
37	return len(AliasList(shortCode)) > 1
38}
39
40// NormalizeShortCode normalizes a given `shortCode` to a deterministic alias.
41func NormalizeShortCode(shortCode string) string {
42	shortLists := AliasList(shortCode)
43	if len(shortLists) == 0 {
44		return shortCode
45	}
46	return shortLists[0]
47}
48
49// regular expression that matches :flag-[countrycode]:
50var flagRegexp = regexp.MustCompile(":flag-([a-z]{2}):")
51
52func emojize(x string) string {
53	str, ok := emojiCode()[x]
54	if ok {
55		return str + ReplacePadding
56	}
57	if match := flagRegexp.FindStringSubmatch(x); len(match) == 2 {
58		return regionalIndicator(match[1][0]) + regionalIndicator(match[1][1])
59	}
60	return x
61}
62
63// regionalIndicator maps a lowercase letter to a unicode regional indicator
64func regionalIndicator(i byte) string {
65	return string('\U0001F1E6' + rune(i) - 'a')
66}
67
68func replaseEmoji(input *bytes.Buffer) string {
69	emoji := bytes.NewBufferString(":")
70	for {
71		i, _, err := input.ReadRune()
72		if err != nil {
73			// not replase
74			return emoji.String()
75		}
76
77		if i == ':' && emoji.Len() == 1 {
78			return emoji.String() + replaseEmoji(input)
79		}
80
81		emoji.WriteRune(i)
82		switch {
83		case unicode.IsSpace(i):
84			return emoji.String()
85		case i == ':':
86			return emojize(emoji.String())
87		}
88	}
89}
90
91func compile(x string) string {
92	if x == "" {
93		return ""
94	}
95
96	input := bytes.NewBufferString(x)
97	output := bytes.NewBufferString("")
98
99	for {
100		i, _, err := input.ReadRune()
101		if err != nil {
102			break
103		}
104		switch i {
105		default:
106			output.WriteRune(i)
107		case ':':
108			output.WriteString(replaseEmoji(input))
109		}
110	}
111	return output.String()
112}
113
114// Print is fmt.Print which supports emoji
115func Print(a ...interface{}) (int, error) {
116	return fmt.Print(compile(fmt.Sprint(a...)))
117}
118
119// Println is fmt.Println which supports emoji
120func Println(a ...interface{}) (int, error) {
121	return fmt.Println(compile(fmt.Sprint(a...)))
122}
123
124// Printf is fmt.Printf which supports emoji
125func Printf(format string, a ...interface{}) (int, error) {
126	return fmt.Print(compile(fmt.Sprintf(format, a...)))
127}
128
129// Fprint is fmt.Fprint which supports emoji
130func Fprint(w io.Writer, a ...interface{}) (int, error) {
131	return fmt.Fprint(w, compile(fmt.Sprint(a...)))
132}
133
134// Fprintln is fmt.Fprintln which supports emoji
135func Fprintln(w io.Writer, a ...interface{}) (int, error) {
136	return fmt.Fprintln(w, compile(fmt.Sprint(a...)))
137}
138
139// Fprintf is fmt.Fprintf which supports emoji
140func Fprintf(w io.Writer, format string, a ...interface{}) (int, error) {
141	return fmt.Fprint(w, compile(fmt.Sprintf(format, a...)))
142}
143
144// Sprint is fmt.Sprint which supports emoji
145func Sprint(a ...interface{}) string {
146	return compile(fmt.Sprint(a...))
147}
148
149// Sprintf is fmt.Sprintf which supports emoji
150func Sprintf(format string, a ...interface{}) string {
151	return compile(fmt.Sprintf(format, a...))
152}
153
154// Errorf is fmt.Errorf which supports emoji
155func Errorf(format string, a ...interface{}) error {
156	return errors.New(compile(Sprintf(format, a...)))
157}
158