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}