Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Emoji duplicate detection #23439

Merged
merged 5 commits into from
Apr 2, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
86 changes: 86 additions & 0 deletions go/chat/emoji/emoji_data.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
package main

import (
"encoding/json"
"fmt"
"io/ioutil"
"net/http"
"sort"
"strconv"
"strings"
)

// NOTE when update shared/package.json when updating this version
const emojiDataJSONURL = "https://github.com/iamcal/emoji-data/raw/v4.1.0/emoji.json"

// EmojiData json parse struct
type EmojiData struct {
Unified string `json:"unified"`
ShortName string `json:"short_name"`
ObsoletedBy string `json:"obsoleted_by"`
}

// UnifiedToChar renders a character from its hexadecimal codepoint
func UnifiedToChar(unified string) (string, error) {
codes := strings.Split(unified, "-")
var sb strings.Builder
for _, code := range codes {
s, err := strconv.ParseInt(code, 16, 32)
if err != nil {
return "", err
}
sb.WriteRune(rune(s))
}
return sb.String(), nil
}

func createEmojiDataCodeMap() (map[string]string, map[string][]string, error) {
res, err := http.Get(emojiDataJSONURL)
if err != nil {
return nil, nil, err
}
defer res.Body.Close()

emojiFile, err := ioutil.ReadAll(res.Body)
if err != nil {
return nil, nil, err
}

var data []EmojiData
if err := json.Unmarshal(emojiFile, &data); err != nil {
return nil, nil, err
}

// emojiRevCodeMap maps unicode characters to lists of short codes.

emojiCodeMap := make(map[string]string)
emojiRevCodeMap := make(map[string][]string)
for _, emoji := range data {
if len(emoji.ShortName) == 0 || len(emoji.Unified) == 0 {
continue
}
unified := emoji.Unified
if len(emoji.ObsoletedBy) > 0 {
unified = emoji.ObsoletedBy
}
unicode, err := UnifiedToChar(unified)
if err != nil {
return nil, nil, err
}
unicode = fmt.Sprintf("%+q", strings.ToLower(unicode))
emojiCodeMap[emoji.ShortName] = unicode
emojiRevCodeMap[unicode] = append(emojiRevCodeMap[unicode], emoji.ShortName)
}

// ensure deterministic ordering for aliases
for _, value := range emojiRevCodeMap {
sort.Slice(value, func(i, j int) bool {
if len(value[i]) == len(value[j]) {
return value[i] < value[j]
}
return len(value[i]) < len(value[j])
})
}

return emojiCodeMap, emojiRevCodeMap, nil
}
88 changes: 88 additions & 0 deletions go/chat/emoji/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
package main

import (
"bytes"
"flag"
"fmt"
"go/format"
"log"
"os"
"text/template"
)

// adapted from https://github.com/kyokomi/emoji/ to only use the emoji-data
// emoji source
var pkgName string
var fileName string

// TemplateData emoji_codemap.go template
type TemplateData struct {
PkgName string
CodeMap map[string]string
RevCodeMap map[string][]string
}

const templateMapCode = `
package {{.PkgName}}

// NOTE: THIS FILE WAS PRODUCED BY THE
// EMOJICODEMAP CODE GENERATION TOOL (github.com/keybase/go/chat/emoji)
// DO NOT EDIT

// Mapping from character to concrete escape code.
var emojiCodeMap = map[string]string{
{{range $key, $val := .CodeMap}}":{{$key}}:": {{$val}},
{{end}}
}

var emojiRevCodeMap = map[string][]string{
{{range $key, $val := .RevCodeMap}} {{$key}}: { {{range $val}} ":{{.}}:", {{end}} },
{{end}}
}
`

func createCodeMapSource(pkgName string, emojiCodeMap map[string]string, emojiRevCodeMap map[string][]string) ([]byte, error) {
// Template GenerateSource

var buf bytes.Buffer
t := template.Must(template.New("template").Parse(templateMapCode))
if err := t.Execute(&buf, TemplateData{PkgName: pkgName, CodeMap: emojiCodeMap, RevCodeMap: emojiRevCodeMap}); err != nil {
return nil, err
}

// gofmt

bts, err := format.Source(buf.Bytes())
if err != nil {
fmt.Print(buf.String())
return nil, fmt.Errorf("gofmt: %s", err)
}

return bts, nil
}

func main() {
flag.StringVar(&pkgName, "pkg", "storage", "output package")
flag.StringVar(&fileName, "o", "../storage/emoji_codemap.go", "output file")
flag.Parse()
codeMap, revCodeMap, err := createEmojiDataCodeMap()
if err != nil {
log.Fatalln(err)
}

codeMapSource, err := createCodeMapSource(pkgName, codeMap, revCodeMap)
if err != nil {
log.Fatalln(err)
}

os.Remove(fileName)
file, err := os.Create(fileName)
if err != nil {
log.Fatalln(err)
}
defer file.Close()

if _, err := file.Write(codeMapSource); err != nil {
log.Fatalln(err)
}
}
2 changes: 1 addition & 1 deletion go/chat/server_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -7298,7 +7298,7 @@ func TestReacjiStore(t *testing.T) {
tc.ChatG.Syncer.(*Syncer).isConnected = true
reacjiStore := storage.NewReacjiStore(ctc.as(t, user).h.G())
assertReacjiStore := func(actual, expected keybase1.UserReacjis, expectedData storage.ReacjiInternalStorage) {
require.Equal(t, expected, actual)
require.Equal(t, actual, expected)
data := reacjiStore.GetInternalStore(ctx, uid)
require.Equal(t, len(data.FrequencyMap), len(data.MtimeMap))
for name := range data.MtimeMap {
Expand Down
Loading