// SPDX-FileCopyrightText: 2025 Romain Maneschi // // SPDX-License-Identifier: EUPL-1.2 package main import ( _ "embed" "encoding/json" "errors" "fmt" "io/fs" "path/filepath" "strings" gitroot "gitroot.dev/libs/golang/plugin" "gitroot.dev/libs/golang/plugin/model" ) const MARKDOWN_PLUGIN = "apex_markdown" const CODE_PLUGIN = "apex_code" //go:embed resources/styles/add.css var addStyle string //go:embed resources/styles/pico.min.css var picoStyle string //go:embed resources/styles/simple.min.css var simpleStyle string //go:embed resources/index.md var index string type Plugin struct { server model.Server config *conf renderer *renderer gitWorktree *worktree currentCommit model.Commit branchCommits []*branchCommits reports []string } func (p *Plugin) Init(repoName string, confHasChanged bool, serializedConf string) error { p.config = p.newConf(serializedConf) p.reports = []string{} forgeConf, err := p.server.ForgeConf() if err != nil { p.server.LogError("can't get forge conf", err) } p.renderer = p.newRender(repoName, forgeConf) p.branchCommits = make([]*branchCommits, 0) if p.config.generateGitWorktree { p.LoadWorktree() } // css style if _, err := fs.Stat(p.server.Webcontent(), p.config.style); errors.Is(err, fs.ErrNotExist) || confHasChanged { style := "" switch p.config.style { case "pico.min.css": style = picoStyle case "simple.min.css": style = simpleStyle default: // TODO download if distant? Copy if local? } if style == "" { style = simpleStyle } p.server.ModifyWebContent(p.config.style, strings.Join([]string{style, addStyle}, "\n")) } else if err != nil { p.server.LogError("can't stats styles", err) } // index.html if _, err := fs.Stat(p.server.Webcontent(), "index.html"); errors.Is(err, fs.ErrNotExist) || confHasChanged { if _, err := fs.Stat(p.server.Worktree(), "index.html"); errors.Is(err, fs.ErrNotExist) { if _, err := fs.Stat(p.server.Worktree(), "index.md"); errors.Is(err, fs.ErrNotExist) { p.server.ModifyContent("index.md", index) p.server.CommitAllIfNeeded("init web page") p.AddFile(model.File{Path: "index.md"}) } else if err != nil { p.server.LogError("can't stats index.md in wortree", err) } } else if err != nil { p.server.LogError("can't stats index.html in wortree", err) } } else if err != nil { p.server.LogError("can't stats index in webContent", err) } // 404.html if _, err := fs.Stat(p.server.Webcontent(), "404.html"); errors.Is(err, fs.ErrNotExist) || confHasChanged { newContent := p.renderer.render("404.html", "

Not found

", map[string]string{"title": "not found"}) p.server.ModifyWebContent("404.html", newContent) } return nil } func (p *Plugin) StartCommit(commit model.Commit) error { p.currentCommit = commit if p.config.branchesDir != "" { p.AddIfNotExist(commit) } return nil } func (p *Plugin) AddFile(fp model.File) error { newContent := "" path := "" if strings.HasSuffix(fp.Path, ".md") { mdContent, err := fs.ReadFile(p.server.Worktree(), fp.Path) if err != nil { p.server.LogError("AddFile ReadFile "+fp.Path, err) return nil } res, err := p.server.CallFunc(MARKDOWN_PLUGIN, "renderMd", map[string]string{"fp": fp.Path, "md": string(mdContent)}) if err != nil { p.server.LogError("AddFile call renderMd fail", err) return nil } html := res["html"] metasJson := res["metas"] metas := map[string]string{} if len(metasJson) > 0 { if err := json.Unmarshal([]byte(metasJson), &metas); err != nil { p.server.LogError("AddFile md metasdata unmarshal fail "+metasJson, err) return nil } } newContent = p.renderer.render(fp.Path, html, metas) path = fmt.Sprintf("%s.html", strings.TrimSuffix(fp.Path, ".md")) } else { content, err := fs.ReadFile(p.server.Worktree(), fp.Path) if err != nil { p.server.LogError("AddFile ReadFile "+fp.Path, err) return nil } newContent = string(content) path = fp.Path } // branch can be "" in init scenario if p.currentCommit.Branch != "" && p.currentCommit.Branch != p.config.defaultBranch { path = filepath.Join(p.config.branchesDir, p.currentCommit.Branch, path) p.reports = append(p.reports, fmt.Sprintf(`- Render [%s](%s%s)`, fp.Path, p.renderer.vars["repo.url"], path)) } else { p.server.Log(fmt.Sprintf("no report because branch is %s", p.currentCommit.Branch)) } p.server.ModifyWebContent(path, newContent) if p.config.generateGitWorktree && p.currentCommit.Branch == p.config.defaultBranch { p.gitWorktree.addOrModFile(fp.Path, p.currentCommit) } return nil } func (p *Plugin) DelFile(fp model.File) error { if p.config.generateGitWorktree && p.currentCommit.Branch == p.config.defaultBranch { p.gitWorktree.delFile(fp.Path) } return nil } func (p *Plugin) ModFile(fp model.File) error { if fp.OldPath != "" && fp.OldPath != fp.Path { p.DelFile(model.File{Path: fp.OldPath, FileHash: fp.OldFileHash}) } return p.AddFile(fp) } func (p *Plugin) EndCommit(commit model.Commit) error { return nil } func (p *Plugin) Finish() error { if p.config.generateGitWorktree && p.currentCommit.Branch == p.config.defaultBranch { p.StoreWorktree() p.gitWorktree.renderHtml("", "worktree", func(fp string, htmlContent string) { p.server.ModifyWebContent(fp, p.renderer.render(fp, htmlContent, map[string]string{"title": fp})) }) } if p.config.branchesDir != "" { p.RenderBranches() } if len(p.reports) > 0 { p.server.Report(model.ReportLevelInfo, p.reports) p.reports = []string{} } p.config = nil p.gitWorktree = nil return nil } func Build(server model.Server) model.Plugin { return &Plugin{ server: server, } } //go:wasmexport install func main() { loadMimeType() loadEmojis() gitroot.Register(defaultRun, Build, []string{MARKDOWN_PLUGIN, CODE_PLUGIN}) }