package templates import ( "embed" "encoding/json" "path/filepath" "strings" "text/template" ) //go:embed *.tmpl var templateFS embed.FS // TemplateEngine provides template rendering functionality for Dockerfiles type TemplateEngine struct { templates map[string]*template.Template } // NewTemplateEngine creates a new template engine with all built-in templates func NewTemplateEngine() (*TemplateEngine, error) { te := &TemplateEngine{ templates: make(map[string]*template.Template), } // Create template functions funcMap := template.FuncMap{ "join": func(sep string, items []string) string { return strings.Join(items, sep) }, "toJSON": func(v interface{}) string { b, _ := json.Marshal(v) return string(b) }, "hasPrefix": func(prefix, str string) bool { return strings.HasPrefix(str, prefix) }, "hasSuffix": func(suffix, str string) bool { return strings.HasSuffix(str, suffix) }, "contains": func(substr, str string) bool { return strings.Contains(str, substr) }, "lower": strings.ToLower, "upper": strings.ToUpper, "trim": strings.TrimSpace, } // Load all template files entries, err := templateFS.ReadDir(".") if err != nil { return nil, err } for _, entry := range entries { if !entry.IsDir() && strings.HasSuffix(entry.Name(), ".tmpl") { name := strings.TrimSuffix(entry.Name(), ".tmpl") content, err := templateFS.ReadFile(entry.Name()) if err != nil { return nil, err } tmpl, err := template.New(name).Funcs(funcMap).Parse(string(content)) if err != nil { return nil, err } te.templates[name] = tmpl } } return te, nil } // Render renders a template with the given data func (te *TemplateEngine) Render(templateName string, data interface{}) (string, error) { tmpl, exists := te.templates[templateName] if !exists { return "", &template.ExecError{ Name: "template not found: " + templateName, } } var buf strings.Builder err := tmpl.Execute(&buf, data) if err != nil { return "", err } return buf.String(), nil } // GetTemplateNames returns all available template names func (te *TemplateEngine) GetTemplateNames() []string { names := make([]string, 0, len(te.templates)) for name := range te.templates { names = append(names, name) } return names } // RenderFromFile renders a template from an external file (for custom templates) func (te *TemplateEngine) RenderFromFile(templatePath string, data interface{}) (string, error) { funcMap := template.FuncMap{ "join": func(sep string, items []string) string { return strings.Join(items, sep) }, "toJSON": func(v interface{}) string { b, _ := json.Marshal(v) return string(b) }, "hasPrefix": func(prefix, str string) bool { return strings.HasPrefix(str, prefix) }, "hasSuffix": func(suffix, str string) bool { return strings.HasSuffix(str, suffix) }, "contains": func(substr, str string) bool { return strings.Contains(str, substr) }, "lower": strings.ToLower, "upper": strings.ToUpper, "trim": strings.TrimSpace, } name := filepath.Base(templatePath) tmpl, err := template.New(name).Funcs(funcMap).ParseFiles(templatePath) if err != nil { return "", err } var buf strings.Builder err = tmpl.Execute(&buf, data) if err != nil { return "", err } return buf.String(), nil }