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 ladybug-0.0.1.wasm -scheduler=none --no-debug -target=wasi ./
9
10import (
11 "bytes"
12 _ "embed"
13 "fmt"
14 "io/fs"
15
16 "github.com/sigurn/crc16"
17 "github.com/tidwall/gjson"
18 gitroot "gitroot.dev/libs/golang/plugin"
19 "gitroot.dev/libs/golang/plugin/model"
20)
21
22type MyConf struct {
23 Name string `json:"name"`
24 Type string `json:"type"`
25 Mandatory bool `json:"mandatory"`
26 Default any `json:"default"`
27}
28
29var defaultConf = map[string]any{"metadata": []MyConf{
30 {Name: "id", Type: "crc16", Mandatory: true, Default: "autogenerated"},
31 {Name: "priority", Type: "int", Mandatory: true, Default: 50},
32 {Name: "assignee", Type: "user", Mandatory: false, Default: nil},
33 {Name: "status", Type: "string", Mandatory: false, Default: "triage"},
34 {Name: "kind", Type: "string", Mandatory: false, Default: "issue"},
35}}
36
37var defaultRun = []model.PluginRun{{
38 Path: "issues/**/*.md",
39 Branch: []string{"*"},
40 When: []model.PluginRunWhen{model.PluginRunWhenAdd, model.PluginRunWhenMod},
41 Func: []model.PluginFunc{},
42 Write: model.PluginWrite{
43 Git: []model.PluginWriteRight{{
44 Path: "issues/**/*.md",
45 Can: []model.PluginWriteRightCan{model.PluginWriteRightCanMod},
46 }},
47 Web: []model.PluginWriteRight{},
48 Exec: []model.PluginExecRight{},
49 CallFunc: []model.PluginCallFuncRight{},
50 },
51 Configuration: defaultConf,
52}}
53
54var table = crc16.MakeTable(crc16.CRC16_MAXIM)
55
56type Plugin struct {
57 server model.Server
58 conf []MyConf
59}
60
61func (p *Plugin) Init(repoName string, confHasChanged bool, serializedConf string) error {
62 p.conf = []MyConf{}
63 for _, meta := range gjson.Parse(serializedConf).Get("metadata").Array() {
64 myConf := MyConf{
65 Name: meta.Get("name").String(),
66 Type: meta.Get("type").String(),
67 Mandatory: meta.Get("mandatory").Bool(),
68 Default: meta.Get("default").Value(),
69 }
70 p.conf = append(p.conf, myConf)
71 }
72 return nil
73}
74
75func (p *Plugin) StartCommit(commit model.Commit) error {
76 return nil
77}
78
79func (p *Plugin) AddFile(file model.File) error {
80 content, err := fs.ReadFile(p.server.Worktree(), file.Path)
81 if err != nil {
82 p.server.LogError("AddFile ReadFile "+file.Path, err)
83 return nil
84 }
85 if !bytes.HasPrefix(content, []byte("---")) {
86 header := ""
87 for _, props := range p.conf {
88 if props.Name != "" {
89 if props.Mandatory {
90 if props.Default != nil {
91 def := props.Default
92 if props.Default == "autogenerated" && props.Type == "crc16" {
93 def = fmt.Sprintf("%X", crc16.Checksum([]byte(file.Path), table))
94 }
95 header += fmt.Sprintf("%s: %v\n", props.Name, def)
96 }
97 } else {
98 if props.Default != nil {
99 header += fmt.Sprintf("%s: %v\n", props.Name, props.Default)
100 } else {
101 header += fmt.Sprintf("%s: null\n", props.Name)
102 }
103 }
104 }
105 }
106 newContent := fmt.Sprintf("---\n%s---\n\n%s", header, string(content))
107 p.server.ModifyContent(file.Path, newContent)
108 } else {
109 metas := bytes.Split(content, []byte("---"))
110 meta := bytes.Trim(metas[1], "\n")
111 header := ""
112 for _, props := range p.conf {
113 if props.Name != "" {
114 if !bytes.Contains(meta, []byte(props.Name)) {
115 if props.Mandatory {
116 if props.Default != "" {
117 def := props.Default
118 if props.Default == "autogenerated" && props.Type == "crc16" {
119 def = fmt.Sprintf("%X", crc16.Checksum([]byte(file.Path), table))
120 }
121 header += fmt.Sprintf("%s: %s\n", props.Name, def)
122 }
123 } else {
124 if props.Default != nil && props.Default != "" {
125 header += fmt.Sprintf("%s: %s\n", props.Name, props.Default)
126 } else {
127 header += fmt.Sprintf("%s: null\n", props.Name)
128 }
129 }
130 }
131 }
132 }
133 //no change so don't override content
134 if header != "" {
135 newContent := fmt.Sprintf("---\n%s\n%s---%s", meta, header, bytes.Join(metas[2:], []byte("---")))
136 p.server.ModifyContent(file.Path, newContent)
137 }
138 }
139 return nil
140}
141
142func (p *Plugin) ModFile(file model.File) error {
143 return p.AddFile(file)
144}
145
146func (p *Plugin) DelFile(file model.File) error {
147 return nil
148}
149
150func (p *Plugin) EndCommit(commit model.Commit) error {
151 return nil
152}
153
154func (p *Plugin) Finish() error {
155 p.server.CommitAllIfNeeded("issue metadata added")
156 return nil
157}
158
159func Build(server model.Server) model.Plugin {
160 return &Plugin{
161 server: server,
162 }
163}
164
165//go:wasmexport install
166func main() {
167 gitroot.Register(defaultRun, Build)
168}