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}