github.com/keybase/client/go@v0.0.0-20241007131713-f10651d043c8/chat/emoji/main.go (about)

     1  package main
     2  
     3  import (
     4  	"bytes"
     5  	"encoding/json"
     6  	"flag"
     7  	"fmt"
     8  	"go/format"
     9  	"log"
    10  	"os"
    11  	"sort"
    12  	"strconv"
    13  	"strings"
    14  	"text/template"
    15  )
    16  
    17  // EmojiData json parse struct
    18  type EmojiData struct {
    19  	Unified     string `json:"unified"`
    20  	ShortName   string `json:"short_name"`
    21  	ObsoletedBy string `json:"obsoleted_by"`
    22  }
    23  
    24  // UnifiedToChar renders a character from its hexadecimal codepoint
    25  func UnifiedToChar(unified string) (string, error) {
    26  	codes := strings.Split(unified, "-")
    27  	var sb strings.Builder
    28  	for _, code := range codes {
    29  		s, err := strconv.ParseInt(code, 16, 32)
    30  		if err != nil {
    31  			return "", err
    32  		}
    33  		sb.WriteRune(rune(s))
    34  	}
    35  	return sb.String(), nil
    36  }
    37  
    38  func createEmojiDataCodeMap(path string) (map[string]string, map[string][]string, error) {
    39  	emojiFile, err := os.ReadFile(path)
    40  	if err != nil {
    41  		return nil, nil, err
    42  	}
    43  
    44  	var data []EmojiData
    45  	if err := json.Unmarshal(emojiFile, &data); err != nil {
    46  		return nil, nil, err
    47  	}
    48  
    49  	// emojiRevCodeMap maps unicode characters to lists of short codes.
    50  
    51  	emojiCodeMap := make(map[string]string)
    52  	emojiRevCodeMap := make(map[string][]string)
    53  	for _, emoji := range data {
    54  		if len(emoji.ShortName) == 0 || len(emoji.Unified) == 0 {
    55  			continue
    56  		}
    57  		unified := emoji.Unified
    58  		if len(emoji.ObsoletedBy) > 0 {
    59  			unified = emoji.ObsoletedBy
    60  		}
    61  		unicode, err := UnifiedToChar(unified)
    62  		if err != nil {
    63  			return nil, nil, err
    64  		}
    65  		unicode = fmt.Sprintf("%+q", strings.ToLower(unicode))
    66  		emojiCodeMap[emoji.ShortName] = unicode
    67  		emojiRevCodeMap[unicode] = append(emojiRevCodeMap[unicode], emoji.ShortName)
    68  	}
    69  
    70  	// ensure deterministic ordering for aliases
    71  	for _, value := range emojiRevCodeMap {
    72  		sort.Slice(value, func(i, j int) bool {
    73  			if len(value[i]) == len(value[j]) {
    74  				return value[i] < value[j]
    75  			}
    76  			return len(value[i]) < len(value[j])
    77  		})
    78  	}
    79  
    80  	return emojiCodeMap, emojiRevCodeMap, nil
    81  }
    82  
    83  // adapted from https://github.com/kyokomi/emoji/ to only use the emoji-data
    84  // emoji source
    85  var pkgName, inName, outName string
    86  
    87  // TemplateData emoji_codemap.go template
    88  type TemplateData struct {
    89  	PkgName    string
    90  	CodeMap    map[string]string
    91  	RevCodeMap map[string][]string
    92  }
    93  
    94  const templateMapCode = `
    95  package {{.PkgName}}
    96  
    97  // NOTE: THIS FILE WAS PRODUCED BY THE
    98  // EMOJICODEMAP CODE GENERATION TOOL (github.com/keybase/go/chat/emoji)
    99  // DO NOT EDIT
   100  
   101  // Mapping from character to concrete escape code.
   102  var emojiCodeMap = map[string]string{
   103  	{{range $key, $val := .CodeMap}}":{{$key}}:": {{$val}},
   104  {{end}}
   105  }
   106  
   107  var emojiRevCodeMap = map[string][]string{
   108  	{{range $key, $val := .RevCodeMap}} {{$key}}: { {{range $val}} ":{{.}}:", {{end}} },
   109  {{end}}
   110  }
   111  `
   112  
   113  func createCodeMapSource(pkgName string, emojiCodeMap map[string]string, emojiRevCodeMap map[string][]string) ([]byte, error) {
   114  	// Template GenerateSource
   115  
   116  	var buf bytes.Buffer
   117  	t := template.Must(template.New("template").Parse(templateMapCode))
   118  	if err := t.Execute(&buf, TemplateData{PkgName: pkgName, CodeMap: emojiCodeMap, RevCodeMap: emojiRevCodeMap}); err != nil {
   119  		return nil, err
   120  	}
   121  
   122  	// gofmt
   123  
   124  	bts, err := format.Source(buf.Bytes())
   125  	if err != nil {
   126  		fmt.Print(buf.String())
   127  		return nil, fmt.Errorf("gofmt: %s", err)
   128  	}
   129  
   130  	return bts, nil
   131  }
   132  
   133  func main() {
   134  	flag.StringVar(&pkgName, "pkg", "storage", "output package")
   135  	flag.StringVar(&outName, "o", "../storage/emoji_codemap.go", "output file")
   136  	flag.StringVar(&inName, "i", "../../../shared/node_modules/emoji-datasource-apple/emoji.json", "input file")
   137  	flag.Parse()
   138  	codeMap, revCodeMap, err := createEmojiDataCodeMap(inName)
   139  	if err != nil {
   140  		log.Fatalln(err)
   141  	}
   142  
   143  	codeMapSource, err := createCodeMapSource(pkgName, codeMap, revCodeMap)
   144  	if err != nil {
   145  		log.Fatalln(err)
   146  	}
   147  
   148  	os.Remove(outName)
   149  	file, err := os.Create(outName)
   150  	if err != nil {
   151  		log.Fatalln(err)
   152  	}
   153  	defer file.Close()
   154  
   155  	if _, err := file.Write(codeMapSource); err != nil {
   156  		log.Println(err)
   157  		return
   158  	}
   159  }