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}