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