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 plugin
  6
  7import (
  8	"encoding/json"
  9	"fmt"
 10	"io"
 11	"os"
 12	"reflect"
 13	"slices"
 14	"strings"
 15
 16	"github.com/goccy/go-yaml"
 17	"github.com/samber/oops"
 18	"gitroot.dev/libs/golang/glob"
 19	pluginLib "gitroot.dev/libs/golang/plugin"
 20	"gitroot.dev/server/user"
 21)
 22
 23type Plugin struct {
 24	Url      string `yaml:",omitempty"`
 25	Crc      string `yaml:",omitempty"`
 26	Name     string
 27	Active   bool
 28	content  []byte
 29	commiter *user.Commiter
 30	Run      []PluginRun
 31}
 32
 33type PluginRun struct {
 34	pluginLib.PluginRun `yaml:",inline"`
 35	glob                *glob.Glob
 36	write               PluginWrite
 37}
 38
 39type PluginWrite struct { //TODO not implemented
 40	git []PluginWriteRight
 41	web []PluginWriteRight
 42}
 43
 44type PluginWriteRight struct {
 45	pluginLib.PluginWriteRight
 46	glob *glob.Glob
 47}
 48
 49func pluginLibToPlugin(run []pluginLib.PluginRun) ([]PluginRun, error) {
 50	p := make([]PluginRun, len(run))
 51	for i, pl := range run {
 52		g, err := glob.NewGlob(pl.Path)
 53		if err != nil {
 54			return nil, oops.Wrapf(err, "can't glob %s", pl.Path)
 55		}
 56		pluginWrite := PluginWrite{
 57			git: make([]PluginWriteRight, len(pl.Write.Git)),
 58			web: make([]PluginWriteRight, len(pl.Write.Web)),
 59		}
 60		for j, pw := range pl.Write.Git {
 61			g, err := glob.NewGlob(pl.Path)
 62			if err != nil {
 63				return nil, oops.Wrapf(err, "can't glob %s", pl.Path)
 64			}
 65			pluginWrite.git[j] = PluginWriteRight{PluginWriteRight: pw, glob: g}
 66		}
 67		for j, pw := range pl.Write.Web {
 68			g, err := glob.NewGlob(pl.Path)
 69			if err != nil {
 70				return nil, oops.Wrapf(err, "can't glob %s", pl.Path)
 71			}
 72			pluginWrite.web[j] = PluginWriteRight{PluginWriteRight: pw, glob: g}
 73		}
 74		p[i] = PluginRun{PluginRun: pl, glob: g, write: pluginWrite}
 75	}
 76	return p, nil
 77}
 78
 79func ParsePlugins(fileContent []byte, withRun bool) ([]Plugin, error) {
 80	plugins := make([]Plugin, 0)
 81	if err := yaml.Unmarshal(fileContent, &plugins); err != nil {
 82		return nil, oops.Wrapf(err, "can't parse yaml config")
 83	}
 84	if withRun {
 85		for i, plugin := range plugins {
 86			for j, pluginRun := range plugin.Run {
 87				g, err := glob.NewGlob(pluginRun.Path)
 88				if err != nil {
 89					return nil, oops.Wrapf(err, "can't glob %s", pluginRun.Path)
 90				}
 91				plugins[i].Run[j].glob = g
 92				for h, pluginWrite := range pluginRun.Write.Git {
 93					g, err = glob.NewGlob(pluginWrite.Path)
 94					if err != nil {
 95						return nil, oops.Wrapf(err, "can't glob git %s", pluginRun.Path)
 96					}
 97					if plugins[i].Run[j].write.git == nil {
 98						plugins[i].Run[j].write.git = make([]PluginWriteRight, len(pluginRun.Write.Git))
 99					}
100					plugins[i].Run[j].write.git[h] = PluginWriteRight{PluginWriteRight: pluginWrite, glob: g}
101				}
102				for h, pluginWrite := range pluginRun.Write.Web {
103					g, err = glob.NewGlob(pluginWrite.Path)
104					if err != nil {
105						return nil, oops.Wrapf(err, "can't glob git %s", pluginRun.Path)
106					}
107					if plugins[i].Run[j].write.web == nil {
108						plugins[i].Run[j].write.web = make([]PluginWriteRight, len(pluginRun.Write.Web))
109					}
110					plugins[i].Run[j].write.web[h] = PluginWriteRight{PluginWriteRight: pluginWrite, glob: g}
111				}
112			}
113		}
114	}
115	return plugins, nil
116}
117
118func (p Plugin) SetActive(active bool) Plugin {
119	return Plugin{
120		Url:      p.Url,
121		Crc:      p.Crc,
122		Name:     p.Name,
123		Active:   active,
124		content:  p.content,
125		commiter: p.commiter,
126		Run:      p.Run,
127	}
128}
129
130func (p Plugin) OverrideWith(other Plugin) Plugin {
131	copy := Plugin{
132		Url:      p.Url,
133		Crc:      p.Crc,
134		Name:     p.Name,
135		Active:   p.Active,
136		content:  p.content,
137		commiter: p.commiter,
138		Run:      p.Run,
139	}
140	if copy.Url == "" {
141		copy.Url = other.Url
142	}
143	if copy.Crc == "" {
144		copy.Crc = other.Crc
145	}
146	if copy.Name == "" {
147		copy.Name = other.Name
148	}
149	if !copy.Active {
150		copy.Active = other.Active
151	}
152	if len(copy.Run) == 0 {
153		copy.Run = other.Run
154	}
155	if len(copy.content) == 0 {
156		copy.content = other.content
157	}
158	if copy.commiter == nil {
159		copy.commiter = other.commiter
160	}
161	return copy
162}
163
164func (p Plugin) Commiter() *user.Commiter {
165	return p.commiter
166}
167
168func (p Plugin) DetermineNameFromUrl() Plugin {
169	versions := strings.Split(p.Url, "/") // todo find a better way to have version
170	name := versions[len(versions)-1]
171	name = strings.Split(name, "-")[0]
172	return p.OverrideWith(Plugin{Name: name})
173}
174
175func (pr PluginRun) Marshal() ([]byte, error) {
176	return json.Marshal(pr.Configuration)
177}
178
179func (m *Manager) PathWasm(p Plugin) string {
180	pathPlugin := m.conf.GetDirPathDataPlugin(p.Name)
181	versions := strings.Split(p.Url, "/") // TODO find a better way to have version
182	version := versions[len(versions)-1]
183	version = strings.Split(version, "-")[1]
184	return fmt.Sprintf("%s/%s", pathPlugin, fmt.Sprintf("%s-%s", p.Name, version))
185}
186
187func (m *Manager) Install(p Plugin) error {
188	r, err := os.Open(p.Url)
189	if err != nil {
190		return err
191	}
192	defer r.Close()
193	w, err := os.Create(m.PathWasm(p))
194	if err != nil {
195		return err
196	}
197	defer func() {
198		if c := w.Close(); err == nil {
199			err = c
200		}
201	}()
202	_, err = io.Copy(w, r)
203	return err
204}
205
206func (m *Manager) Equal(aPlugin, bPlugin Plugin) bool {
207	return aPlugin.Name == bPlugin.Name &&
208		aPlugin.Active == bPlugin.Active &&
209		m.equalPluginRun(aPlugin.Run, bPlugin.Run)
210}
211
212func (m *Manager) equalPluginRun(aPlugin, bPlugin []PluginRun) bool {
213	if len(aPlugin) == len(bPlugin) {
214		for i, w := range aPlugin {
215			isEqual := w.Path == bPlugin[i].Path &&
216				slices.Equal(w.Branch, bPlugin[i].Branch) &&
217				slices.Equal(w.When, bPlugin[i].When) &&
218				m.equalPluginWrite(w.Write, bPlugin[i].Write) &&
219				reflect.DeepEqual(w.Configuration, bPlugin[i].Configuration)
220			if !isEqual {
221				return false
222			}
223		}
224		return true
225	}
226	return false
227}
228
229func (m *Manager) equalPluginWrite(aPlugin, bPlugin pluginLib.PluginWrite) bool {
230	if len(aPlugin.Git) == len(bPlugin.Git) {
231		for i, w := range aPlugin.Git {
232			if w.Path != bPlugin.Git[i].Path || slices.Equal(w.Can, bPlugin.Git[i].Can) {
233				return false
234			}
235		}
236		return true
237	}
238	return false
239}