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