jx/internal/config/document.go

203 lines
4.7 KiB
Go
Raw Normal View History

// 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
}