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}