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