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 "errors"
10 "fmt"
11 "io/fs"
12 "path/filepath"
13 "strings"
14
15 gitroot "gitroot.dev/libs/golang/plugin"
16 "gitroot.dev/libs/golang/plugin/model"
17)
18
19//go:embed resources/styles/add.css
20var addStyle string
21
22//go:embed resources/styles/pico.min.css
23var picoStyle string
24
25//go:embed resources/styles/simple.min.css
26var simpleStyle string
27
28//go:embed resources/index.md
29var index string
30
31type Plugin struct {
32 server model.Server
33 config *conf
34 renderer *renderer
35 gitWorktree *worktree
36 currentCommit model.Commit
37 branchCommits []*branchCommits
38}
39
40func (p *Plugin) Init(repoName string, confHasChanged bool, serializedConf string) error {
41 p.config = p.newConf(serializedConf)
42
43 forgeConf, err := p.server.ForgeConf()
44 if err != nil {
45 p.server.LogError("can't get forge conf", err)
46 }
47 p.renderer = p.newRender(repoName, forgeConf)
48 p.branchCommits = make([]*branchCommits, 0)
49
50 if p.config.generateGitWorktree {
51 p.LoadWorktree()
52 }
53
54 // css style
55 if _, err := fs.Stat(p.server.Webcontent(), "styles/style.css"); errors.Is(err, fs.ErrNotExist) || confHasChanged {
56 style := ""
57 switch p.config.style {
58 case "pico.min.css":
59 style = picoStyle
60 case "simple.min.css":
61 style = simpleStyle
62 default:
63 // TODO download if distant? Copy if local?
64 }
65 if style == "" {
66 style = simpleStyle
67 }
68 p.server.ModifyWebContent(p.config.style, strings.Join([]string{style, addStyle}, "\n"))
69 } else if err != nil {
70 p.server.LogError("can't stats styles", err)
71 }
72
73 // index.html
74 if _, err := fs.Stat(p.server.Webcontent(), "index.html"); errors.Is(err, fs.ErrNotExist) || confHasChanged {
75 if _, err := fs.Stat(p.server.Worktree(), "index.html"); errors.Is(err, fs.ErrNotExist) {
76 if _, err := fs.Stat(p.server.Worktree(), "index.md"); errors.Is(err, fs.ErrNotExist) {
77 p.server.ModifyContent("index.md", index)
78 p.server.CommitAllIfNeeded("init web page")
79 p.AddFile("index.md")
80 } else if err != nil {
81 p.server.LogError("can't stats index.md in wortree", err)
82 }
83 } else if err != nil {
84 p.server.LogError("can't stats index.html in wortree", err)
85 }
86 } else if err != nil {
87 p.server.LogError("can't stats index in webContent", err)
88 }
89
90 // 404.html
91 if _, err := fs.Stat(p.server.Webcontent(), "404.html"); errors.Is(err, fs.ErrNotExist) || confHasChanged {
92 newContent := p.renderer.render("404.html", "<p>Not found</p>", map[string]string{"title": "not found"})
93 p.server.ModifyWebContent("404.html", newContent)
94 }
95 return nil
96}
97
98func (p *Plugin) StartCommit(commit model.Commit) error {
99 p.currentCommit = commit
100 if p.config.branchesDir != "" {
101 p.AddIfNotExist(commit)
102 }
103 return nil
104}
105
106func (p *Plugin) AddFile(fp string) error {
107 if strings.HasSuffix(fp, ".md") {
108 mdContent, err := fs.ReadFile(p.server.Worktree(), fp)
109 if err != nil {
110 p.server.LogError("AddFile ReadFile "+fp, err)
111 return nil
112 }
113 md, meta := mdToHTML(fp, mdContent, p.readInclude)
114 newContent := p.renderer.render(fp, md, meta)
115 p.server.ModifyWebContent(fmt.Sprintf("%s.html", strings.TrimSuffix(fp, ".md")), newContent)
116 } else {
117 content, err := fs.ReadFile(p.server.Worktree(), fp)
118 if err != nil {
119 p.server.LogError("AddFile ReadFile "+fp, err)
120 return nil
121 }
122 p.server.ModifyWebContent(fp, string(content))
123 }
124 if p.config.generateGitWorktree {
125 p.gitWorktree.addOrModFile(fp, p.currentCommit)
126 }
127 return nil
128}
129
130func relativePath(fromPath string, toPath string) string {
131 absFromPath, _ := filepath.Abs(fromPath)
132 absToPath, _ := filepath.Abs(toPath)
133
134 dirFromPath, _ := filepath.Split(absFromPath)
135 dirToPath, fileToPath := filepath.Split(absToPath)
136
137 path, err := filepath.Rel(dirFromPath, dirToPath)
138 if err != nil {
139 return toPath
140 }
141 return fmt.Sprintf("%s/%s", path, fileToPath)
142}
143
144func (p *Plugin) readInclude(from, path string, address []byte) []byte {
145 content, _ := fs.ReadFile(p.server.Worktree(), path)
146 return content
147}
148
149func (p *Plugin) DelFile(fp string) error {
150 if p.config.generateGitWorktree {
151 p.gitWorktree.delFile(fp)
152 }
153 return nil
154}
155
156func (p *Plugin) ModFile(fromPath string, toPath string) error {
157 if fromPath != toPath {
158 p.DelFile(fromPath)
159 }
160 return p.AddFile(toPath)
161}
162
163func (p *Plugin) EndCommit(commit model.Commit) error {
164 return nil
165}
166
167func (p *Plugin) Finish() error {
168 if p.config.generateGitWorktree {
169 p.StoreWorktree()
170 p.gitWorktree.renderHtml("", "worktree", func(fp string, htmlContent string) {
171 p.server.ModifyWebContent(fp, p.renderer.render(fp, htmlContent, map[string]string{"title": "worktree"}))
172 })
173 }
174 if p.config.branchesDir != "" {
175 p.RenderBranches()
176 }
177 p.config = nil
178 p.gitWorktree = nil
179 return nil
180}
181
182func Build(server model.Server) model.Plugin {
183 return &Plugin{
184 server: server,
185 }
186}
187
188//go:wasmexport install
189func main() {
190 loadMimeType()
191 loadEmojis()
192 gitroot.Register(defaultRun, Build)
193}