diff --git a/config.go b/config.go index 21567c40e..64f7b1094 100644 --- a/config.go +++ b/config.go @@ -76,6 +76,7 @@ type Config struct { ExecPipe bool `yaml:"execPipe,omitempty"` Experimental bool `yaml:"experimental,omitempty"` + DryRun bool `yaml:"dryRun,omitempty"` } // TODO: remove when we remove the deprecated array format for templates @@ -107,6 +108,7 @@ type rawConfig struct { ExecPipe bool `yaml:"execPipe,omitempty"` Experimental bool `yaml:"experimental,omitempty"` + DryRun bool `yaml:"dryRun,omitempty"` } // TODO: remove when we remove the deprecated array format for templates @@ -140,6 +142,7 @@ func (c *Config) UnmarshalYAML(value *yaml.Node) error { PluginTimeout: r.PluginTimeout, ExecPipe: r.ExecPipe, Experimental: r.Experimental, + DryRun: r.DryRun, } return nil @@ -170,6 +173,7 @@ func (c Config) MarshalYAML() (interface{}, error) { PluginTimeout: c.PluginTimeout, ExecPipe: c.ExecPipe, Experimental: c.Experimental, + DryRun: c.DryRun, } return aux, nil @@ -274,6 +278,10 @@ func (c *Config) MergeFrom(o *Config) *Config { } } + if !isZero(o.DryRun) { + c.DryRun = true + } + if !isZero(o.OutputMap) { c.OutputDir = "" c.OutputFiles = nil diff --git a/gomplate.go b/gomplate.go index c849e66e7..57fa05bef 100644 --- a/gomplate.go +++ b/gomplate.go @@ -71,6 +71,17 @@ func Run(ctx context.Context, cfg *Config) error { } Metrics.TemplatesGathered = len(tmpl) + // Check for dry-run flag + slog.InfoContext(ctx, "dry-run", "value", cfg.DryRun) + if cfg.DryRun { + err = tr.RenderTemplatesToBuffer(ctx, cfg, namer, tmpl) + if err != nil { + return fmt.Errorf("failed to render templates: %w", err) + } + + return nil + } + err = tr.RenderTemplates(ctx, tmpl) if err != nil { return err @@ -133,6 +144,7 @@ func mappingNamer(outMap string, tr *renderer) outputNamer { return "", fmt.Errorf("failed to render outputMap with ctx %+v and inPath %s: %w", tctx, inPath, err) } + slog.Info("outputMap rendered", "in", inPath, "out", out.String()) return filepath.Clean(strings.TrimSpace(out.String())), nil }) } diff --git a/internal/cmd/config.go b/internal/cmd/config.go index 11886bbaa..cc23836bc 100644 --- a/internal/cmd/config.go +++ b/internal/cmd/config.go @@ -215,6 +215,12 @@ func cobraConfig(cmd *cobra.Command, args []string) (cfg *gomplate.Config, err e if err != nil { return nil, err } + + cfg.DryRun, err = getBool(cmd, "dry-run") + if err != nil { + return nil, err + } + return cfg, nil } diff --git a/internal/cmd/main.go b/internal/cmd/main.go index fa7c97c3a..edf3f585f 100644 --- a/internal/cmd/main.go +++ b/internal/cmd/main.go @@ -161,6 +161,8 @@ func InitFlags(command *cobra.Command) { command.Flags().BoolP("verbose", "V", false, "output extra information about what gomplate is doing") command.Flags().String("config", defaultConfigFile, "config file (overridden by commandline flags)") + + command.Flags().Bool("dry-run", false, "render the templates without writing the output files") } // Main - diff --git a/render.go b/render.go index d5e74be7a..6bd447ca8 100644 --- a/render.go +++ b/render.go @@ -1,6 +1,7 @@ package gomplate import ( + "bytes" "context" "fmt" "io" @@ -173,13 +174,7 @@ type Template struct { } func (r *renderer) RenderTemplates(ctx context.Context, templates []Template) error { - if datafs.FSProviderFromContext(ctx) == nil { - ctx = datafs.ContextWithFSProvider(ctx, DefaultFSProvider) - } - - // configure the template context with the refreshed Data value - // only done here because the data context may have changed - tmplctx, err := createTmplContext(ctx, r.tctxAliases, r.sr) + ctx, tmplctx, err := r.initializeTemplateContext(ctx) if err != nil { return err } @@ -188,21 +183,13 @@ func (r *renderer) RenderTemplates(ctx context.Context, templates []Template) er } func (r *renderer) renderTemplatesWithData(ctx context.Context, templates []Template, tmplctx interface{}) error { - // update funcs with the current context - // only done here to ensure the context is properly set in func namespaces - f := CreateFuncs(ctx) - - // add datasource funcs here because they need to share the source reader - addToMap(f, funcs.CreateDataSourceFuncs(ctx, r.sr)) - - // add user-defined funcs last so they override the built-in funcs - addToMap(f, r.funcs) + f := r.initializeTemplateFuncs(ctx) // track some metrics for debug output start := time.Now() defer func() { Metrics.TotalRenderDuration = time.Since(start) }() - for _, template := range templates { - err := r.renderTemplate(ctx, template, f, tmplctx) + for _, t := range templates { + err := r.renderTemplate(ctx, t, f, tmplctx) if err != nil { return fmt.Errorf("renderTemplate: %w", err) } @@ -210,6 +197,19 @@ func (r *renderer) renderTemplatesWithData(ctx context.Context, templates []Temp return nil } +func (r *renderer) initializeTemplateFuncs(ctx context.Context) template.FuncMap { + // update funcs with the current context + // only done here to ensure the context is properly set in func namespaces + f := CreateFuncs(ctx) + + // add datasource funcs here because they need to share the source reader + addToMap(f, funcs.CreateDataSourceFuncs(ctx, r.sr)) + + // add user-defined funcs last so they override the built-in funcs + addToMap(f, r.funcs) + return f +} + func (r *renderer) renderTemplate(ctx context.Context, template Template, f template.FuncMap, tmplctx interface{}) error { if template.Writer != nil { if wr, ok := template.Writer.(io.Closer); ok { @@ -335,6 +335,58 @@ func (r *renderer) parseNestedTemplates(ctx context.Context, tmpl *template.Temp return nil } +func (r *renderer) RenderTemplatesToBuffer(ctx context.Context, cfg *Config, namer outputNamer, tmpl []Template) error { + ctx, tmplctx, err := r.initializeTemplateContext(ctx) + if err != nil { + return err + } + + f := r.initializeTemplateFuncs(ctx) + + // track some metrics for debug output + start := time.Now() + defer func() { Metrics.TotalRenderDuration = time.Since(start) }() + for _, t := range tmpl { + inFile := t.Name + if cfg.InputDir != "" { + inFile = strings.Replace(inFile, cfg.InputDir, "", 1) + } + + outFile, err := namer.Name(ctx, inFile) + fmt.Println("---") + fmt.Println("##::gomplate::output::file", outFile) + fmt.Println("##::gomplate::template::file", t.Name) + fmt.Println("---") + var buf bytes.Buffer + // Set the template's writer to the buffer + t.Writer = &buf + err = r.renderTemplate(ctx, t, f, tmplctx) + if err != nil { + return fmt.Errorf("renderTemplate: %w", err) + } + + // Print the rendered content to the console + fmt.Println(buf.String() + "\n") + } + + return nil +} + +func (r *renderer) initializeTemplateContext(ctx context.Context) (context.Context, interface{}, error) { + if datafs.FSProviderFromContext(ctx) == nil { + ctx = datafs.ContextWithFSProvider(ctx, DefaultFSProvider) + } + + // configure the template context with the refreshed Data value + // only done here because the data context may have changed + tmplctx, err := createTmplContext(ctx, r.tctxAliases, r.sr) + if err != nil { + return nil, nil, err + } + + return ctx, tmplctx, nil +} + func parseNestedTemplateDir(ctx context.Context, fsys fs.FS, alias, fname string, tmpl *template.Template) error { files, err := fs.ReadDir(fsys, fname) if err != nil {