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}