package main import ( "bytes" "fmt" "log" "os" "path/filepath" "strings" "text/template" "text/template/parse" ) func extractIncludes(tmpl *template.Template) []string { var includes []string for _, node := range tmpl.Root.Nodes { if n, ok := node.(*parse.TemplateNode); ok { includes = append(includes, n.Name) } } return includes } func absolutePath(root, fn string) (string, error) { pwd, err := filepath.Abs(fn) if err != nil { return "", fmt.Errorf("cannot determine absolute location of %q: %w", fn, err) } pwd, err = filepath.Rel(root, filepath.Dir(pwd)) if err != nil { return "", fmt.Errorf("cannot determine location of %q relative to %q: %w", pwd, root, err) } return string(filepath.Separator) + pwd, nil } func main() { // Estimate Telegraf root to be able to handle absolute paths cwd, err := os.Getwd() if err != nil { log.Fatalf("Cannot get working directory: %v", err) } cwd, err = filepath.Abs(cwd) if err != nil { log.Fatalf("Cannot resolve working directory: %v", err) } var root string idx := strings.LastIndex(cwd, filepath.FromSlash("/plugins/")) if idx <= 0 { log.Fatalln("Cannot determine include root!") } root = cwd[:idx] var parent, inputFilename, outputFilename string switch len(os.Args) { case 1: parent = strings.TrimPrefix(filepath.ToSlash(cwd[idx:]), "/plugins/") parent = strings.ReplaceAll(parent, "/", ".") inputFilename = "sample.conf.in" outputFilename = "sample.conf" case 2: parent = os.Args[1] inputFilename = "sample.conf.in" outputFilename = "sample.conf" case 3: parent = os.Args[1] inputFilename = os.Args[2] if !strings.HasSuffix(inputFilename, ".in") { log.Fatalf("Template filename %q does not have '.in' suffix!", inputFilename) } outputFilename = strings.TrimSuffix(inputFilename, ".in") case 4: parent = os.Args[1] inputFilename = os.Args[2] outputFilename = os.Args[3] default: log.Fatalln("Invalid number of arguments") } roottmpl := template.New(inputFilename) known := make(map[string]bool) inroot, err := absolutePath(root, inputFilename) if err != nil { log.Fatal(err) } unresolved := map[string]string{inputFilename: filepath.Join(inroot, inputFilename)} for { if len(unresolved) == 0 { break } newUnresolved := make(map[string]string) for name, fn := range unresolved { if strings.HasPrefix(filepath.ToSlash(fn), "/") { fn = filepath.Join(root, fn) } if known[name] { // Include already resolved, skipping continue } tmpl, err := template.ParseFiles(fn) if err != nil { log.Fatalf("Reading template %q failed: %v", fn, err) } known[name] = true if _, err := roottmpl.AddParseTree(name, tmpl.Tree); err != nil { log.Fatalf("Adding include %q failed: %v", fn, err) } // For relative paths we need to make it relative to the include pwd, err := filepath.Abs(fn) if err != nil { log.Fatalf("Cannot determine absolute location of %q: %v", fn, err) } pwd, err = filepath.Rel(root, filepath.Dir(pwd)) if err != nil { log.Fatalf("Cannot determine location of %q relative to %q: %v", pwd, root, err) } pwd = string(filepath.Separator) + pwd for _, iname := range extractIncludes(tmpl) { if !strings.HasPrefix(iname, "/") { newUnresolved[iname] = filepath.Join(pwd, iname) } else { newUnresolved[iname] = iname } } } unresolved = newUnresolved } defines := map[string]string{"parent": parent} var buf bytes.Buffer if err := roottmpl.Execute(&buf, defines); err != nil { log.Fatalf("Executing template failed: %v", err) } if err := os.WriteFile(outputFilename, buf.Bytes(), 0640); err != nil { log.Fatalf("Writing output %q failed: %v", outputFilename, err) } }