139 lines
2.1 KiB
Go
139 lines
2.1 KiB
Go
|
package main
|
||
|
|
||
|
import (
|
||
|
"bytes"
|
||
|
"fmt"
|
||
|
"io"
|
||
|
"log"
|
||
|
"os"
|
||
|
"strings"
|
||
|
|
||
|
"gopkg.in/yaml.v2"
|
||
|
)
|
||
|
|
||
|
func mergeIn(tgt, add map[any]any) {
|
||
|
mergeLoop:
|
||
|
for k, v := range add {
|
||
|
switch v := v.(type) {
|
||
|
case map[any]any:
|
||
|
if tgtV, ok := tgt[k]; ok {
|
||
|
switch tgtV := tgtV.(type) {
|
||
|
case map[any]any:
|
||
|
mergeIn(tgtV, v)
|
||
|
continue mergeLoop
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
tgt[k] = v
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func assemble(path string) (yamlBytes []byte, err error) {
|
||
|
obj := map[any]any{}
|
||
|
|
||
|
if Debug {
|
||
|
log.Printf("assemble %q", path)
|
||
|
}
|
||
|
|
||
|
err = eachFragment(path, searchList, func(r io.Reader) (err error) {
|
||
|
m := map[any]any{}
|
||
|
err = yaml.NewDecoder(r).Decode(&m)
|
||
|
if err != nil {
|
||
|
return
|
||
|
}
|
||
|
|
||
|
mergeIn(obj, m)
|
||
|
return
|
||
|
})
|
||
|
if err != nil {
|
||
|
err = fmt.Errorf("failed to assemble %q: %w", path, err)
|
||
|
return
|
||
|
}
|
||
|
|
||
|
yamlBytes, err = yaml.Marshal(obj)
|
||
|
if err != nil {
|
||
|
return
|
||
|
}
|
||
|
|
||
|
if Debug {
|
||
|
log.Printf("assemble %q result:\n%s", path, yamlBytes)
|
||
|
}
|
||
|
|
||
|
return
|
||
|
}
|
||
|
|
||
|
func eachFragment(path string, searchList []FS, walk func(io.Reader) error) (err error) {
|
||
|
var r io.ReadCloser
|
||
|
|
||
|
for len(searchList) != 0 {
|
||
|
fs := searchList[0]
|
||
|
|
||
|
r, err = fs.Open(path + ".yaml")
|
||
|
if os.IsNotExist(err) {
|
||
|
searchList = searchList[1:]
|
||
|
continue
|
||
|
}
|
||
|
|
||
|
if err != nil {
|
||
|
return
|
||
|
}
|
||
|
|
||
|
// found and open
|
||
|
break
|
||
|
}
|
||
|
|
||
|
if r == nil {
|
||
|
err = fmt.Errorf("%s: %w", path, os.ErrNotExist)
|
||
|
return
|
||
|
}
|
||
|
|
||
|
ba, err := io.ReadAll(r)
|
||
|
r.Close()
|
||
|
|
||
|
if err != nil {
|
||
|
return
|
||
|
}
|
||
|
|
||
|
if Debug {
|
||
|
log.Print("fragment:\n", string(ba))
|
||
|
}
|
||
|
|
||
|
in := bytes.NewBuffer(ba)
|
||
|
|
||
|
for {
|
||
|
var line string
|
||
|
line, err = in.ReadString('\n')
|
||
|
if err == io.EOF {
|
||
|
break
|
||
|
}
|
||
|
if err != nil {
|
||
|
return
|
||
|
}
|
||
|
|
||
|
line = strings.TrimSpace(line)
|
||
|
if len(line) == 0 {
|
||
|
continue
|
||
|
}
|
||
|
|
||
|
includePath, found := strings.CutPrefix(line, "#!include ")
|
||
|
if !found {
|
||
|
continue // or break?
|
||
|
}
|
||
|
|
||
|
includePath = strings.TrimSpace(includePath)
|
||
|
if Debug {
|
||
|
log.Print("#!include ", includePath)
|
||
|
}
|
||
|
err = eachFragment(includePath, searchList, walk)
|
||
|
if err != nil {
|
||
|
err = fmt.Errorf("include %q: %w", includePath, err)
|
||
|
return
|
||
|
}
|
||
|
}
|
||
|
|
||
|
in = bytes.NewBuffer(ba)
|
||
|
err = walk(in)
|
||
|
return
|
||
|
}
|