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 }