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}