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
7// compile cmd
8// tinygo build -o apex-0.0.1.wasm -scheduler=none --no-debug -target=wasi ./
9
10import (
11 "bytes"
12 _ "embed"
13 "errors"
14 "fmt"
15 "io/fs"
16 "path/filepath"
17 "strings"
18
19 gitroot "gitroot.dev/libs/golang/plugin"
20)
21
22const simple = `<!doctype html>
23<html>
24<head>
25<meta charset="UTF-8">
26<meta name="viewport" content="width=device-width, initial-scale=1.0">
27<title>%s</title>
28<link rel="stylesheet" href="%s">
29</head>
30<body>
31<header>
32 %s
33 <nav>
34 %s
35 </nav>
36</header>
37%s
38<footer>
39 %s
40</footer>
41</body>
42</html>
43`
44
45//go:embed resources/styles/add.css
46var addStyle string
47
48//go:embed resources/styles/pico.min.css
49var picoStyle string
50
51//go:embed resources/styles/simple.min.css
52var simpleStyle string
53
54//go:embed resources/index.md
55var index string
56
57type Plugin struct {
58 server gitroot.Server
59 repoName string
60 config *conf
61 gitWorktree *worktree
62 currentCommit gitroot.Commit
63}
64
65func (p *Plugin) Init(repoName string, confHasChanged bool, serializedConf string) error {
66 p.config = p.NewConf(serializedConf)
67 p.repoName = repoName
68
69 if p.config.generateGitWorktree {
70 p.LoadWorktree()
71 }
72
73 // css style
74 if _, err := fs.Stat(p.server.Webcontent(), "styles/style.css"); errors.Is(err, fs.ErrNotExist) || confHasChanged {
75 style := ""
76 if p.config.style == "pico.min.css" {
77 style = picoStyle
78 } else if p.config.style == "simple.min.css" {
79 style = simpleStyle
80 } else {
81 // TODO download if distant? Copy if local?
82 }
83 p.server.ModifyWebContent(p.config.style, strings.Join([]string{style, addStyle}, "\n"))
84 } else if err != nil {
85 p.server.LogError("can't stats styles", err)
86 }
87
88 // index.html
89 if _, err := fs.Stat(p.server.Webcontent(), "index.html"); errors.Is(err, fs.ErrNotExist) || confHasChanged {
90 if _, err := fs.Stat(p.server.Worktree(), "index.html"); errors.Is(err, fs.ErrNotExist) {
91 if _, err := fs.Stat(p.server.Worktree(), "index.md"); errors.Is(err, fs.ErrNotExist) {
92 p.server.ModifyContent("index.md", index)
93 p.server.CommitAllIfNeeded("init web page")
94 p.AddFile("index.md")
95 } else if err != nil {
96 p.server.LogError("can't stats index.md in wortree", err)
97 }
98 } else if err != nil {
99 p.server.LogError("can't stats index.html in wortree", err)
100 }
101 } else if err != nil {
102 p.server.LogError("can't stats index in webContent", err)
103 }
104
105 // 404.html
106 if _, err := fs.Stat(p.server.Webcontent(), "404.html"); errors.Is(err, fs.ErrNotExist) || confHasChanged {
107 p.buildPage("404.md", []byte("Not found"))
108 }
109 return nil
110}
111
112func (p *Plugin) StartCommit(commit gitroot.Commit) error {
113 p.currentCommit = commit
114 return nil
115}
116
117func (p *Plugin) AddFile(fp string) error {
118 if strings.HasSuffix(fp, ".md") {
119 content, err := fs.ReadFile(p.server.Worktree(), fp)
120 if err != nil {
121 p.server.LogError("AddFile ReadFile "+fp, err)
122 return nil
123 }
124 p.buildPage(fp, content)
125 } else {
126 content, err := fs.ReadFile(p.server.Worktree(), fp)
127 if err != nil {
128 p.server.LogError("AddFile ReadFile "+fp, err)
129 return nil
130 }
131 p.server.ModifyWebContent(fp, string(content))
132 }
133 if p.config.generateGitWorktree {
134 p.gitWorktree.addOrModFile(fp, p.currentCommit)
135 }
136 return nil
137}
138
139func (p *Plugin) buildPage(fp string, mdContent []byte) {
140 menu := p.buildMenu(fp)
141 stylePath := relativePath(fp, p.config.style)
142 newContent := fmt.Sprintf(simple, p.repoName, stylePath, p.config.header, menu, string(mdToHTML(mdContent, p.readInclude)), p.config.footer)
143 p.server.ModifyWebContent(fmt.Sprintf("%s.html", strings.TrimSuffix(fp, ".md")), newContent)
144}
145
146func relativePath(fromPath string, toPath string) string {
147 absFromPath, _ := filepath.Abs(fromPath)
148 absToPath, _ := filepath.Abs(toPath)
149
150 dirFromPath, _ := filepath.Split(absFromPath)
151 dirToPath, fileToPath := filepath.Split(absToPath)
152
153 path, err := filepath.Rel(dirFromPath, dirToPath)
154 if err != nil {
155 return toPath
156 }
157 return fmt.Sprintf("%s/%s", path, fileToPath)
158}
159
160func (p *Plugin) buildMenu(fp string) string {
161 menu := bytes.NewBufferString("<ul>")
162 mdOrHtml := filepath.Ext(fp)
163 for _, link := range p.config.menu {
164 targetLink := link.Link
165 if strings.HasSuffix(targetLink, "/") {
166 targetLink = fmt.Sprintf("%sindex%s", targetLink, mdOrHtml)
167 }
168 l := relativePath(fp, link.Link)
169 if fp == targetLink {
170 menu.WriteString(fmt.Sprintf("<li><a aria-current=\"page\">%s</a></li>", link.Display))
171 } else {
172 menu.WriteString(fmt.Sprintf("<li><a href=\"%s\">%s</a></li>", l, link.Display))
173 }
174 }
175 menu.WriteString("</ul>")
176 return menu.String()
177}
178
179func (p *Plugin) readInclude(from, path string, address []byte) []byte {
180 content, _ := fs.ReadFile(p.server.Worktree(), path)
181 return content
182}
183
184func (p *Plugin) DelFile(fp string) error {
185 if p.config.generateGitWorktree {
186 p.gitWorktree.delFile(fp)
187 }
188 return nil
189}
190
191func (p *Plugin) ModFile(fromPath string, toPath string) error {
192 if fromPath != toPath {
193 p.DelFile(fromPath)
194 }
195 return p.AddFile(toPath)
196}
197
198func (p *Plugin) EndCommit(commit gitroot.Commit) error {
199 return nil
200}
201
202func (p *Plugin) Finish() error {
203 if p.config.generateGitWorktree {
204 p.StoreWorktree()
205 p.gitWorktree.renderHtml("", "worktree", func(fp string, htmlContent string) {
206 menu := p.buildMenu(fp)
207 stylePath := relativePath(fp, p.config.style)
208 newContent := fmt.Sprintf(simple, p.repoName, stylePath, p.config.header, menu, htmlContent, p.config.footer)
209 p.server.ModifyWebContent(fp, newContent)
210 })
211 }
212 p.config = nil
213 p.gitWorktree = nil
214 return nil
215}
216
217func Build(server gitroot.Server) gitroot.Plugin {
218 return &Plugin{
219 server: server,
220 }
221}
222
223//go:wasmexport install
224func main() {
225 loadMimeType()
226 loadEmojis()
227 gitroot.Register(defaultRun, Build)
228}