203 lines
4.7 KiB
Go
203 lines
4.7 KiB
Go
// Copyright 2024 Matthew Rich <matthewrich.conf@gmail.com>. All rights reserved.
|
|
|
|
package config
|
|
|
|
import (
|
|
"errors"
|
|
"encoding/json"
|
|
"fmt"
|
|
"gopkg.in/yaml.v3"
|
|
"io"
|
|
"log/slog"
|
|
_ "net/url"
|
|
"github.com/sters/yaml-diff/yamldiff"
|
|
"strings"
|
|
"decl/internal/codec"
|
|
_ "context"
|
|
)
|
|
|
|
var (
|
|
ErrConfigUndefinedName = errors.New("Config block is missing a defined name")
|
|
)
|
|
|
|
type ConfigNamesMap[Value any] map[string]Value
|
|
|
|
type Document struct {
|
|
names ConfigNamesMap[*Block]
|
|
ConfigBlocks []Block `json:"configurations" yaml:"configurations"`
|
|
}
|
|
|
|
func NewDocument() *Document {
|
|
return &Document{ names: make(ConfigNamesMap[*Block]) }
|
|
}
|
|
|
|
func (d *Document) Filter(filter ConfigurationSelector) []*Block {
|
|
configurations := make([]*Block, 0, len(d.ConfigBlocks))
|
|
for i := range d.ConfigBlocks {
|
|
filterConfig := &d.ConfigBlocks[i]
|
|
if filter == nil || filter(filterConfig) {
|
|
configurations = append(configurations, &d.ConfigBlocks[i])
|
|
}
|
|
}
|
|
return configurations
|
|
}
|
|
|
|
func (d *Document) Clone() *Document {
|
|
clone := NewDocument()
|
|
clone.ConfigBlocks = make([]Block, len(d.ConfigBlocks))
|
|
for i, res := range d.ConfigBlocks {
|
|
clone.ConfigBlocks[i] = *res.Clone()
|
|
}
|
|
return clone
|
|
}
|
|
|
|
func (d *Document) Load(r io.Reader) error {
|
|
c := codec.NewYAMLDecoder(r)
|
|
return c.Decode(d);
|
|
}
|
|
|
|
func (d *Document) Validate() error {
|
|
jsonDocument, jsonErr := d.JSON()
|
|
if jsonErr == nil {
|
|
s := NewSchema("document")
|
|
err := s.Validate(string(jsonDocument))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (d *Document) Configurations() []Block {
|
|
return d.ConfigBlocks
|
|
}
|
|
|
|
func (d *Document) Generate(w io.Writer) error {
|
|
e := codec.NewYAMLEncoder(w)
|
|
err := e.Encode(d);
|
|
if err == nil {
|
|
return e.Close()
|
|
}
|
|
e.Close()
|
|
return err
|
|
}
|
|
|
|
func (d *Document) Append(doc *Document) {
|
|
if doc != nil {
|
|
for i := range doc.ConfigBlocks {
|
|
slog.Info("Document.Append()", "doc", doc, "block", doc.ConfigBlocks[i], "targetdoc", d)
|
|
d.AddConfigurationBlock(doc.ConfigBlocks[i].Name, doc.ConfigBlocks[i].Type, doc.ConfigBlocks[i].Values)
|
|
}
|
|
}
|
|
}
|
|
|
|
func (d *Document) AddConfigurationBlock(configurationName string, configurationType TypeName, configuration Configuration) {
|
|
cfg := NewBlock()
|
|
cfg.Name = configurationName
|
|
cfg.Type = configurationType
|
|
cfg.Values = configuration
|
|
d.names[cfg.Name] = cfg
|
|
d.ConfigBlocks = append(d.ConfigBlocks, *cfg)
|
|
}
|
|
|
|
func (d *Document) AddConfiguration(uri string) error {
|
|
cfg := NewBlock()
|
|
if e := cfg.SetURI(uri); e != nil {
|
|
return e
|
|
}
|
|
if cfg.Name == "" {
|
|
return ErrConfigUndefinedName
|
|
}
|
|
d.names[cfg.Name] = cfg
|
|
d.ConfigBlocks = append(d.ConfigBlocks, *cfg)
|
|
return nil
|
|
}
|
|
|
|
func (d *Document) Has(name string) bool {
|
|
_, ok := d.names[name]
|
|
return ok
|
|
}
|
|
|
|
func (d *Document) Get(name string) *Block {
|
|
return d.names[name]
|
|
}
|
|
|
|
func (d *Document) JSON() ([]byte, error) {
|
|
return json.Marshal(d)
|
|
}
|
|
|
|
func (d *Document) YAML() ([]byte, error) {
|
|
return yaml.Marshal(d)
|
|
}
|
|
|
|
func (d *Document) IndexName() error {
|
|
for _, b := range d.ConfigBlocks {
|
|
d.names[b.Name] = &b
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (d *Document) UnmarshalYAML(value *yaml.Node) error {
|
|
documentBlocks := struct {
|
|
ConfigBlocks *[]Block `json:"configurations" yaml:"configurations"`
|
|
}{ ConfigBlocks: &d.ConfigBlocks }
|
|
if unmarshalDocumentErr := value.Decode(documentBlocks); unmarshalDocumentErr != nil {
|
|
return unmarshalDocumentErr
|
|
}
|
|
return d.IndexName()
|
|
}
|
|
|
|
func (d *Document) UnmarshalJSON(data []byte) error {
|
|
documentBlocks := struct {
|
|
ConfigBlocks *[]Block `json:"configurations" yaml:"configurations"`
|
|
}{ ConfigBlocks: &d.ConfigBlocks }
|
|
if unmarshalDocumentErr := json.Unmarshal(data, &documentBlocks); unmarshalDocumentErr != nil {
|
|
return unmarshalDocumentErr
|
|
}
|
|
return d.IndexName()
|
|
}
|
|
|
|
func (d *Document) Diff(with *Document, output io.Writer) (returnOutput string, diffErr error) {
|
|
defer func() {
|
|
if r := recover(); r != nil {
|
|
returnOutput = ""
|
|
diffErr = fmt.Errorf("%s", r)
|
|
}
|
|
}()
|
|
slog.Info("Document.Diff()")
|
|
opts := []yamldiff.DoOptionFunc{}
|
|
if output == nil {
|
|
output = &strings.Builder{}
|
|
}
|
|
ydata, yerr := d.YAML()
|
|
if yerr != nil {
|
|
return "", yerr
|
|
}
|
|
yamlDiff,yamlDiffErr := yamldiff.Load(string(ydata))
|
|
if yamlDiffErr != nil {
|
|
return "", yamlDiffErr
|
|
}
|
|
|
|
wdata,werr := with.YAML()
|
|
if werr != nil {
|
|
return "", werr
|
|
}
|
|
withDiff,withDiffErr := yamldiff.Load(string(wdata))
|
|
if withDiffErr != nil {
|
|
return "", withDiffErr
|
|
}
|
|
|
|
for _,docDiffResults := range yamldiff.Do(yamlDiff, withDiff, opts...) {
|
|
slog.Info("Diff()", "diff", docDiffResults, "dump", docDiffResults.Dump())
|
|
_,e := output.Write([]byte(docDiffResults.Dump()))
|
|
if e != nil {
|
|
return "", e
|
|
}
|
|
}
|
|
slog.Info("Document.Diff() ", "document.yaml", ydata, "with.yaml", wdata)
|
|
if stringOutput, ok := output.(*strings.Builder); ok {
|
|
return stringOutput.String(), nil
|
|
}
|
|
return "", nil
|
|
}
|