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
  7// compile cmd
  8// tinygo build -o silo-0.0.1.wasm -scheduler=none --no-debug -target=wasi ./
  9
 10import (
 11	_ "embed"
 12	"fmt"
 13	"math"
 14	"path/filepath"
 15	"slices"
 16	"strconv"
 17	"strings"
 18
 19	gitroot "gitroot.dev/libs/golang/plugin"
 20	"gitroot.dev/libs/golang/plugin/model"
 21)
 22
 23const cachePath string = "boards"
 24
 25type Plugin struct {
 26	server      gitroot.Server
 27	config      []conf
 28	commitsHash []string
 29	cache       []CacheLine
 30}
 31
 32func (p *Plugin) Init(repoName string, confHasChanged bool, serializedConf string) error {
 33	p.cache = p.fromCache()
 34	p.commitsHash = []string{}
 35	p.config = p.unmarshalConf(serializedConf)
 36	return nil
 37}
 38
 39func (p *Plugin) StartCommit(commit model.Commit) error {
 40	p.commitsHash = append(p.commitsHash, commit.Hash)
 41	return nil
 42}
 43
 44func (p *Plugin) AddFile(fp string) error {
 45	toAdd, toDel := p.extractCacheLines(fp)
 46	for _, line := range toAdd {
 47		p.appendInCache(line)
 48	}
 49	for _, line := range toDel {
 50		p.deleteInCacheConf(line)
 51	}
 52	return nil
 53}
 54
 55func (p *Plugin) DelFile(fp string) error {
 56	p.deleteInCache(fp)
 57	return nil
 58}
 59
 60func (p *Plugin) ModFile(fromPath string, toPath string) error {
 61	if fromPath != toPath {
 62		p.DelFile(fromPath)
 63		p.AddFile(toPath)
 64		return nil
 65	}
 66
 67	toMod, toDel := p.extractCacheLines(toPath)
 68	for _, line := range toMod {
 69		p.modifyInCache(fromPath, line)
 70	}
 71	for _, line := range toDel {
 72		p.deleteInCacheConf(line)
 73	}
 74	return nil
 75}
 76
 77func (p *Plugin) EndCommit(commit model.Commit) error {
 78	return nil
 79}
 80
 81func (p *Plugin) Finish() error {
 82	confs := make(map[string]conf)
 83	for _, conf := range p.config {
 84		confs[conf.To] = conf
 85	}
 86	cachedFiles := make(map[string][]CacheLine)
 87	for _, cachedLine := range p.cache {
 88		if _, ok := confs[cachedLine.toPath]; ok {
 89			if oldContent, ok := cachedFiles[cachedLine.toPath]; !ok {
 90				cachedFiles[cachedLine.toPath] = []CacheLine{cachedLine}
 91			} else {
 92				cachedFiles[cachedLine.toPath] = append(oldContent, cachedLine)
 93			}
 94		}
 95	}
 96
 97	contents := make(map[string]string)
 98	if len(confs) > len(cachedFiles) {
 99		for _, c := range confs {
100			found := false
101			for cache := range cachedFiles {
102				if c.To == cache {
103					found = true
104					break
105				}
106			}
107			if !found {
108				contents[c.To] = fmt.Sprintf("%s\n%s", c.initFile(), "Nothing here.")
109			}
110		}
111	}
112
113	for confTo, cachedFile := range cachedFiles {
114		if conf, ok := confs[confTo]; ok {
115			slices.SortFunc(cachedFile, func(a CacheLine, b CacheLine) int {
116				res := 0
117				if conf.Sort == "select[0]" && len(a.selects) > 0 && len(b.selects) > 0 {
118					i1, err := strconv.Atoi(a.selects[0])
119					i2, err2 := strconv.Atoi(b.selects[0])
120					if err != nil || err2 != nil {
121						res = strings.Compare(a.selects[0], b.selects[0])
122					} else {
123						res = i1 - i2
124					}
125				} else if conf.Sort == "select[1]" && len(a.selects) > 1 && len(b.selects) > 1 {
126					i1, err := strconv.Atoi(a.selects[1])
127					i2, err2 := strconv.Atoi(b.selects[1])
128					if err != nil || err2 != nil {
129						res = strings.Compare(a.selects[1], b.selects[1])
130					} else {
131						res = i1 - i2
132					}
133				}
134				// if nothing has been picked to sort or if sort == "title"
135				if res == 0 {
136					res = strings.Compare(a.title, b.title)
137				}
138				if conf.SortOrder == "desc" {
139					return res * -1
140				}
141				return res
142			})
143			currentFile := confTo
144			dir, file := filepath.Split(confTo)
145			confToNoExt, _ := strings.CutSuffix(file, ".md")
146			nbPages := int(math.Ceil(float64(len(cachedFile)) / float64(conf.Paginator)))
147			for nbLine, cachedLine := range cachedFile {
148				newLine := conf.makeLink(p, cachedLine)
149				curPage := int(math.Ceil(float64(nbLine+1) / float64(conf.Paginator)))
150				if conf.Paginator != -1 {
151					if curPage > 1 {
152						currentFile = fmt.Sprintf("%s%s_%d.md", dir, confToNoExt, curPage)
153					}
154				}
155				if oldContent, ok := contents[currentFile]; !ok {
156					contents[currentFile] = fmt.Sprintf("%s\n%s", conf.initFile(), newLine)
157				} else {
158					contents[currentFile] = fmt.Sprintf("%s\n%s", oldContent, newLine)
159				}
160				if conf.Paginator != -1 && nbPages > 1 {
161					nextPage := int(math.Ceil(float64(nbLine+2) / float64(conf.Paginator)))
162					if nextPage != curPage || nbLine+1 >= len(cachedFile) {
163						buttonPages := ""
164						for i := range nbPages {
165							if i == 0 && curPage > 1 {
166								buttonPages = fmt.Sprintf("[%d](./%s.md)\n", i+1, confToNoExt)
167							} else if i == 0 {
168								buttonPages = fmt.Sprintf("%d\n", i+1)
169							} else if curPage != i+1 {
170								buttonPages = fmt.Sprintf("%s[%d](./%s_%d.md)\n", buttonPages, i+1, confToNoExt, i+1)
171							} else {
172								buttonPages = fmt.Sprintf("%s%d\n", buttonPages, i+1)
173							}
174						}
175						contents[currentFile] = fmt.Sprintf("%s\n\n%s", contents[currentFile], buttonPages)
176					}
177				}
178			}
179		}
180	}
181	newCache := p.toCache()
182	p.server.ModifyCacheContent(cachePath, newCache)
183	for pathTo, content := range contents {
184		p.server.ModifyContent(pathTo, content)
185	}
186	p.server.CommitAllIfNeeded(fmt.Sprintf("silo plugin\n\nfor %s", strings.Join(p.commitsHash, ", ")))
187	p.cache = nil
188	p.config = nil
189	return nil
190}
191
192func Build(server gitroot.Server) gitroot.Plugin {
193	return &Plugin{
194		server: server,
195	}
196}
197
198//go:wasmexport install
199func main() {
200	gitroot.Register(defaultRun, Build)
201}