package main import ( "bytes" "fmt" "io" "log" "os" "os/exec" "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 } genCmd, found := strings.CutPrefix(line, "#!gen ") if found { cmdArgs := strings.Fields(genCmd) if Debug { log.Print("#!gen ", cmdArgs) } cmd := "gen/" + cmdArgs[0] args := cmdArgs[1:] genOutput, err := exec.Command(cmd, args...).Output() if err != nil { return fmt.Errorf("gen %v: %w", cmdArgs, err) } walk(bytes.NewBuffer(genOutput)) continue } includePath, found := strings.CutPrefix(line, "#!include ") if !found { continue } includePath = strings.TrimSpace(includePath) if Debug { log.Print("#!include ", includePath) } err = eachFragment(includePath, searchList, walk) if err != nil { return fmt.Errorf("include %q: %w", includePath, err) } } in = bytes.NewBuffer(ba) err = walk(in) return }