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