GitRoot
craft your forge, build your project, grow your community freely
1// SPDX-FileCopyrightText: 2025 Romain Maneschi <romain@gitroot.dev>
2//
3// SPDX-License-Identifier: EUPL-1.2
4
5package main
6
7import (
8 "embed"
9 "fmt"
10 "io/fs"
11 "mime"
12 "path/filepath"
13 "regexp"
14 "slices"
15 "strings"
16
17 "github.com/tidwall/gjson"
18)
19
20//go:embed resources/good.mime.types
21var mediaTypes string
22
23//go:embed resources/good.languages.yml
24var languages string
25
26//go:embed resources/gitmojis.json
27var emoji string
28
29//go:embed resources/icons/*
30var devicon embed.FS
31
32var hilightByExt = make(map[string]string)
33var cacheIconByExt = make(map[string]string)
34var emojis = make(map[string]string)
35
36var mimeTypeToRetain = []string{"image/", "text/"}
37
38func loadMimeType() {
39 for _, txt := range strings.Split(mediaTypes, "\n") {
40 fields := strings.Fields(txt)
41 if len(fields) <= 1 || fields[0][0] == '#' {
42 continue
43 }
44 mimeType := fields[0]
45 if slices.ContainsFunc(mimeTypeToRetain, func(mt string) bool { return strings.HasPrefix(mimeType, mt) }) {
46 for _, ext := range fields[1:] {
47 if ext[0] == '#' {
48 break
49 }
50 mime.AddExtensionType("."+ext, mimeType)
51 }
52 }
53 }
54 exts := []string{}
55 mimeType := ""
56 aceMode := ""
57 for _, line := range strings.Split(languages, "\n") {
58 if strings.HasPrefix(line, "#") || strings.HasPrefix(line, "-") {
59 continue
60 }
61 if !strings.HasPrefix(line, " ") {
62 if mimeType != "" && len(exts) > 0 && slices.ContainsFunc(mimeTypeToRetain, func(mt string) bool { return strings.HasPrefix(mimeType, mt) }) {
63 for _, ext := range exts {
64 mime.AddExtensionType(ext, mimeType)
65 }
66 }
67 if aceMode != "" {
68 for _, ext := range exts {
69 hilightByExt[ext] = aceMode
70 }
71 }
72 clear(exts)
73 mimeType = ""
74 aceMode = ""
75 continue
76 }
77 if strings.HasPrefix(line, " - \".") {
78 exts = append(exts, strings.TrimSuffix(strings.TrimPrefix(line, " - \""), "\""))
79 }
80 if strings.HasPrefix(line, " codemirror_mime_type: ") {
81 mimeType = strings.TrimPrefix(line, " codemirror_mime_type: ")
82 }
83 if strings.HasPrefix(line, " ace_mode: ") {
84 aceMode = strings.TrimPrefix(line, " ace_mode: ")
85 }
86 }
87}
88
89func getIcon(fullPath string) string {
90 _, filename := filepath.Split(fullPath)
91 ext := filepath.Ext(filename)
92 if icon, ok := cacheIconByExt[ext]; ok {
93 return icon
94 }
95 language := ""
96 if ext == ".md" {
97 language = "markdown"
98 } else if ext == ".wasm" {
99 language = "wasm"
100 } else if ext == ".sh" {
101 language = "bash"
102 } else if ext == ".go" || filename == "go.mod" || filename == "go.sum" {
103 language = "go"
104 } else if ext == ".html" {
105 language = "html5"
106 } else if ext == ".gitignore" || filename == "allowed_signers" {
107 language = "git"
108 } else if ext == ".yml" || ext == ".yaml" {
109 language = "yaml"
110 } else if ext == ".css" {
111 language = "css3"
112 } else if filename == "makefile" {
113 language = "cmake"
114 } else if ext == ".png" || ext == ".svg" {
115 cacheIconByExt[ext] = "🖼"
116 return "🖼"
117 } else {
118 typeMime := strings.Split(mime.TypeByExtension(ext), "/")
119 if len(typeMime) > 1 {
120 language = typeMime[1]
121 }
122 }
123 if file, err := fs.ReadFile(devicon, fmt.Sprintf("resources/icons/%s-original.svg", language)); err != nil {
124 if language != "" {
125 cacheIconByExt[ext] = "📄"
126 }
127 return "📄"
128 } else if ext != "" {
129 s := strings.TrimPrefix(string(file), "<svg ")
130 cacheIconByExt[ext] = fmt.Sprintf("<svg style=\"max-width:26px;min-width:26px;\" %s", s)
131 return cacheIconByExt[ext]
132 } else {
133 s := strings.TrimPrefix(string(file), "<svg ")
134 return fmt.Sprintf("<svg style=\"max-width:26px;min-width:26px;\" %s", s)
135 }
136}
137
138func loadEmojis() {
139 emojis = make(map[string]string)
140 all := gjson.Parse(emoji).Get("gitmojis").Array()
141 for _, e := range all {
142 c := e.Get("emoji").String()
143 n := e.Get("code").String()
144 emojis[n] = c
145 }
146}
147
148func replaceEmoji(message string) string {
149 r := regexp.MustCompile(`:[^:\s]+:`)
150 matches := r.FindAllString(message, -1)
151 newMessage := message
152 for _, v := range matches {
153 if c, ok := emojis[v]; ok {
154 newMessage = strings.Replace(newMessage, v, c, 1)
155 }
156 }
157 return newMessage
158}