github.com/gitbundle/modules@v0.0.0-20231025071548-85b91c5c3b01/emoji/emoji.go (about) 1 // Copyright 2023 The GitBundle Inc. All rights reserved. 2 // Copyright 2017 The Gitea Authors. All rights reserved. 3 // Use of this source code is governed by a MIT-style 4 // license that can be found in the LICENSE file. 5 6 // Copyright 2020 The GitBundle Authors. All rights reserved. 7 // Copyright 2015 Kenneth Shaw 8 // Use of this source code is governed by a MIT-style 9 // license that can be found in the LICENSE file. 10 11 package emoji 12 13 import ( 14 "io" 15 "sort" 16 "strings" 17 "sync" 18 ) 19 20 // Gemoji is a set of emoji data. 21 type Gemoji []Emoji 22 23 // Emoji represents a single emoji and associated data. 24 type Emoji struct { 25 Emoji string 26 Description string 27 Aliases []string 28 UnicodeVersion string 29 SkinTones bool 30 } 31 32 var ( 33 // codeMap provides a map of the emoji unicode code to its emoji data. 34 codeMap map[string]int 35 36 // aliasMap provides a map of the alias to its emoji data. 37 aliasMap map[string]int 38 39 // emptyReplacer is the string replacer for emoji codes. 40 emptyReplacer *strings.Replacer 41 42 // codeReplacer is the string replacer for emoji codes. 43 codeReplacer *strings.Replacer 44 45 // aliasReplacer is the string replacer for emoji aliases. 46 aliasReplacer *strings.Replacer 47 48 once sync.Once 49 ) 50 51 func loadMap() { 52 once.Do(func() { 53 // initialize 54 codeMap = make(map[string]int, len(GemojiData)) 55 aliasMap = make(map[string]int, len(GemojiData)) 56 57 // process emoji codes and aliases 58 codePairs := make([]string, 0) 59 emptyPairs := make([]string, 0) 60 aliasPairs := make([]string, 0) 61 62 // sort from largest to small so we match combined emoji first 63 sort.Slice(GemojiData, func(i, j int) bool { 64 return len(GemojiData[i].Emoji) > len(GemojiData[j].Emoji) 65 }) 66 67 for i, e := range GemojiData { 68 if e.Emoji == "" || len(e.Aliases) == 0 { 69 continue 70 } 71 72 // setup codes 73 codeMap[e.Emoji] = i 74 codePairs = append(codePairs, e.Emoji, ":"+e.Aliases[0]+":") 75 emptyPairs = append(emptyPairs, e.Emoji, e.Emoji) 76 77 // setup aliases 78 for _, a := range e.Aliases { 79 if a == "" { 80 continue 81 } 82 83 aliasMap[a] = i 84 aliasPairs = append(aliasPairs, ":"+a+":", e.Emoji) 85 } 86 } 87 88 // create replacers 89 emptyReplacer = strings.NewReplacer(emptyPairs...) 90 codeReplacer = strings.NewReplacer(codePairs...) 91 aliasReplacer = strings.NewReplacer(aliasPairs...) 92 }) 93 } 94 95 // FromCode retrieves the emoji data based on the provided unicode code (ie, 96 // "\u2618" will return the Gemoji data for "shamrock"). 97 func FromCode(code string) *Emoji { 98 loadMap() 99 i, ok := codeMap[code] 100 if !ok { 101 return nil 102 } 103 104 return &GemojiData[i] 105 } 106 107 // FromAlias retrieves the emoji data based on the provided alias in the form 108 // "alias" or ":alias:" (ie, "shamrock" or ":shamrock:" will return the Gemoji 109 // data for "shamrock"). 110 func FromAlias(alias string) *Emoji { 111 loadMap() 112 if strings.HasPrefix(alias, ":") && strings.HasSuffix(alias, ":") { 113 alias = alias[1 : len(alias)-1] 114 } 115 116 i, ok := aliasMap[alias] 117 if !ok { 118 return nil 119 } 120 121 return &GemojiData[i] 122 } 123 124 // ReplaceCodes replaces all emoji codes with the first corresponding emoji 125 // alias (in the form of ":alias:") (ie, "\u2618" will be converted to 126 // ":shamrock:"). 127 func ReplaceCodes(s string) string { 128 loadMap() 129 return codeReplacer.Replace(s) 130 } 131 132 // ReplaceAliases replaces all aliases of the form ":alias:" with its 133 // corresponding unicode value. 134 func ReplaceAliases(s string) string { 135 loadMap() 136 return aliasReplacer.Replace(s) 137 } 138 139 type rememberSecondWriteWriter struct { 140 pos int 141 idx int 142 end int 143 writecount int 144 } 145 146 func (n *rememberSecondWriteWriter) Write(p []byte) (int, error) { 147 n.writecount++ 148 if n.writecount == 2 { 149 n.idx = n.pos 150 n.end = n.pos + len(p) 151 n.pos += len(p) 152 return len(p), io.EOF 153 } 154 n.pos += len(p) 155 return len(p), nil 156 } 157 158 func (n *rememberSecondWriteWriter) WriteString(s string) (int, error) { 159 n.writecount++ 160 if n.writecount == 2 { 161 n.idx = n.pos 162 n.end = n.pos + len(s) 163 n.pos += len(s) 164 return len(s), io.EOF 165 } 166 n.pos += len(s) 167 return len(s), nil 168 } 169 170 // FindEmojiSubmatchIndex returns index pair of longest emoji in a string 171 func FindEmojiSubmatchIndex(s string) []int { 172 loadMap() 173 secondWriteWriter := rememberSecondWriteWriter{} 174 175 // A faster and clean implementation would copy the trie tree formation in strings.NewReplacer but 176 // we can be lazy here. 177 // 178 // The implementation of strings.Replacer.WriteString is such that the first index of the emoji 179 // submatch is simply the second thing that is written to WriteString in the writer. 180 // 181 // Therefore we can simply take the index of the second write as our first emoji 182 // 183 // FIXME: just copy the trie implementation from strings.NewReplacer 184 _, _ = emptyReplacer.WriteString(&secondWriteWriter, s) 185 186 // if we wrote less than twice then we never "replaced" 187 if secondWriteWriter.writecount < 2 { 188 return nil 189 } 190 191 return []int{secondWriteWriter.idx, secondWriteWriter.end} 192 }