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 == ".rs" || filename == "Cargo.lock" || filename == "Cargo.toml" {
115 language = "rust"
116 } else if ext == ".ts" {
117 language = "typescript"
118 } else if ext == ".json" {
119 language = "json"
120 } else if ext == ".png" || ext == ".svg" {
121 cacheIconByExt[ext] = "🖼"
122 return "🖼"
123 } else {
124 typeMime := strings.Split(mime.TypeByExtension(ext), "/")
125 if len(typeMime) > 1 {
126 language = typeMime[1]
127 }
128 }
129 if file, err := fs.ReadFile(devicon, fmt.Sprintf("resources/icons/%s-original.svg", language)); err != nil {
130 if language != "" {
131 cacheIconByExt[ext] = "📄"
132 }
133 return "📄"
134 } else if ext != "" {
135 s := strings.TrimPrefix(string(file), "<svg ")
136 cacheIconByExt[ext] = fmt.Sprintf("<svg style=\"max-width:26px;min-width:26px;\" %s", s)
137 return cacheIconByExt[ext]
138 } else {
139 s := strings.TrimPrefix(string(file), "<svg ")
140 return fmt.Sprintf("<svg style=\"max-width:26px;min-width:26px;\" %s", s)
141 }
142}
143
144func loadEmojis() {
145 emojis = make(map[string]string)
146 all := gjson.Parse(emoji).Get("gitmojis").Array()
147 for _, e := range all {
148 c := e.Get("emoji").String()
149 n := e.Get("code").String()
150 emojis[n] = c
151 }
152}
153
154func replaceEmoji(message string) string {
155 r := regexp.MustCompile(`:[^:\s]+:`)
156 matches := r.FindAllString(message, -1)
157 newMessage := message
158 for _, v := range matches {
159 if c, ok := emojis[v]; ok {
160 newMessage = strings.Replace(newMessage, v, c, 1)
161 }
162 }
163 return newMessage
164}