Skip to content

Commit

Permalink
chore(api)!: Overhauling config and rendering types
Browse files Browse the repository at this point in the history
Signed-off-by: Dave Henderson <[email protected]>
  • Loading branch information
hairyhenderson committed Jun 6, 2024
1 parent 29bd9ed commit d261bb9
Show file tree
Hide file tree
Showing 12 changed files with 312 additions and 168 deletions.
46 changes: 39 additions & 7 deletions gomplate.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"bytes"
"context"
"fmt"
"log/slog"
"path/filepath"
"strings"
"text/template"
Expand Down Expand Up @@ -38,6 +39,14 @@ func Run(ctx context.Context, cfg *config.Config) error {
return fmt.Errorf("failed to validate config: %w\n%+v", err, cfg)
}

if cfg.Experimental {
slog.SetDefault(slog.With("experimental", true))
slog.InfoContext(ctx, "experimental functions and features enabled!")

ctx = SetExperimental(ctx)
}

// bind plugins from the configuration to the funcMap
funcMap := template.FuncMap{}
err = bindPlugins(ctx, cfg, funcMap)
if err != nil {
Expand All @@ -47,14 +56,26 @@ func Run(ctx context.Context, cfg *config.Config) error {
// if a custom Stdin is set in the config, inject it into the context now
ctx = datafs.ContextWithStdin(ctx, cfg.Stdin)

// if a custom FSProvider is set in the context, use it, otherwise inject
// the default now - one is needed for the calls below to gatherTemplates
// as well as the rendering itself
if datafs.FSProviderFromContext(ctx) == nil {
ctx = datafs.ContextWithFSProvider(ctx, DefaultFSProvider)
}

// extract the rendering options from the config
opts := optionsFromConfig(cfg)
opts.Funcs = funcMap
tr := newRenderer(opts)

start := time.Now()

// figure out how to name output files (only relevant if we're dealing with an InputDir)
namer := chooseNamer(cfg, tr)

// prepare to render templates (read them in, open output writers, etc)
tmpl, err := gatherTemplates(ctx, cfg, namer)

Metrics.GatherDuration = time.Since(start)
if err != nil {
Metrics.Errors++
Expand All @@ -70,22 +91,33 @@ func Run(ctx context.Context, cfg *config.Config) error {
return nil
}

func chooseNamer(cfg *config.Config, tr *renderer) func(context.Context, string) (string, error) {
type outputNamer interface {
// Name the output file for the given input path
Name(ctx context.Context, inPath string) (string, error)
}

type outputNamerFunc func(context.Context, string) (string, error)

func (f outputNamerFunc) Name(ctx context.Context, inPath string) (string, error) {
return f(ctx, inPath)
}

func chooseNamer(cfg *config.Config, tr *renderer) outputNamer {
if cfg.OutputMap == "" {
return simpleNamer(cfg.OutputDir)
}
return mappingNamer(cfg.OutputMap, tr)
}

func simpleNamer(outDir string) func(ctx context.Context, inPath string) (string, error) {
return func(_ context.Context, inPath string) (string, error) {
func simpleNamer(outDir string) outputNamer {
return outputNamerFunc(func(_ context.Context, inPath string) (string, error) {
outPath := filepath.Join(outDir, inPath)
return filepath.Clean(outPath), nil
}
})
}

func mappingNamer(outMap string, tr *renderer) func(context.Context, string) (string, error) {
return func(ctx context.Context, inPath string) (string, error) {
func mappingNamer(outMap string, tr *renderer) outputNamer {
return outputNamerFunc(func(ctx context.Context, inPath string) (string, error) {
tcontext, err := createTmplContext(ctx, tr.tctxAliases, tr.sr)
if err != nil {
return "", err
Expand Down Expand Up @@ -114,5 +146,5 @@ func mappingNamer(outMap string, tr *renderer) func(context.Context, string) (st
}

return filepath.Clean(strings.TrimSpace(out.String())), nil
}
})
}
24 changes: 12 additions & 12 deletions gomplate_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ func testTemplate(t *testing.T, tr *renderer, tmpl string) string {
}

func TestGetenvTemplates(t *testing.T) {
tr := newRenderer(Options{
tr := newRenderer(RenderOptions{
Funcs: template.FuncMap{
"getenv": env.Getenv,
"bool": conv.ToBool,
Expand All @@ -41,7 +41,7 @@ func TestGetenvTemplates(t *testing.T) {
}

func TestBoolTemplates(t *testing.T) {
g := newRenderer(Options{
g := newRenderer(RenderOptions{
Funcs: template.FuncMap{
"bool": conv.ToBool,
},
Expand All @@ -55,7 +55,7 @@ func TestBoolTemplates(t *testing.T) {
func TestEc2MetaTemplates(t *testing.T) {
createGomplate := func(data map[string]string, region string) *renderer {
ec2meta := aws.MockEC2Meta(data, nil, region)
return newRenderer(Options{Funcs: template.FuncMap{"ec2meta": ec2meta.Meta}})
return newRenderer(RenderOptions{Funcs: template.FuncMap{"ec2meta": ec2meta.Meta}})
}

g := createGomplate(nil, "")
Expand All @@ -70,7 +70,7 @@ func TestEc2MetaTemplates(t *testing.T) {
func TestEc2MetaTemplates_WithJSON(t *testing.T) {
ec2meta := aws.MockEC2Meta(map[string]string{"obj": `"foo": "bar"`}, map[string]string{"obj": `"foo": "baz"`}, "")

g := newRenderer(Options{
g := newRenderer(RenderOptions{
Funcs: template.FuncMap{
"ec2meta": ec2meta.Meta,
"ec2dynamic": ec2meta.Dynamic,
Expand All @@ -83,7 +83,7 @@ func TestEc2MetaTemplates_WithJSON(t *testing.T) {
}

func TestJSONArrayTemplates(t *testing.T) {
g := newRenderer(Options{
g := newRenderer(RenderOptions{
Funcs: template.FuncMap{
"jsonArray": parsers.JSONArray,
},
Expand All @@ -94,7 +94,7 @@ func TestJSONArrayTemplates(t *testing.T) {
}

func TestYAMLTemplates(t *testing.T) {
g := newRenderer(Options{
g := newRenderer(RenderOptions{
Funcs: template.FuncMap{
"yaml": parsers.YAML,
"yamlArray": parsers.YAMLArray,
Expand All @@ -107,7 +107,7 @@ func TestYAMLTemplates(t *testing.T) {
}

func TestHasTemplate(t *testing.T) {
g := newRenderer(Options{
g := newRenderer(RenderOptions{
Funcs: template.FuncMap{
"yaml": parsers.YAML,
"has": conv.Has,
Expand Down Expand Up @@ -141,7 +141,7 @@ func TestMissingKey(t *testing.T) {
}
for name, tt := range tests {
t.Run(name, func(t *testing.T) {
g := newRenderer(Options{
g := newRenderer(RenderOptions{
MissingKey: tt.MissingKey,
})
tmpl := `{{ .name }}`
Expand All @@ -151,7 +151,7 @@ func TestMissingKey(t *testing.T) {
}

func TestCustomDelim(t *testing.T) {
g := newRenderer(Options{
g := newRenderer(RenderOptions{
LDelim: "[",
RDelim: "]",
})
Expand All @@ -171,7 +171,7 @@ func TestRunTemplates(t *testing.T) {

func TestSimpleNamer(t *testing.T) {
n := simpleNamer("out/")
out, err := n(context.Background(), "file")
out, err := n.Name(context.Background(), "file")
require.NoError(t, err)
expected := filepath.FromSlash("out/file")
assert.Equal(t, expected, out)
Expand All @@ -187,13 +187,13 @@ func TestMappingNamer(t *testing.T) {
},
}
n := mappingNamer("out/{{ .in }}", tr)
out, err := n(ctx, "file")
out, err := n.Name(ctx, "file")
require.NoError(t, err)
expected := filepath.FromSlash("out/file")
assert.Equal(t, expected, out)

n = mappingNamer("out/{{ foo }}{{ .in }}", tr)
out, err = n(ctx, "file")
out, err = n.Name(ctx, "file")
require.NoError(t, err)
expected = filepath.FromSlash("out/foofile")
assert.Equal(t, expected, out)
Expand Down
24 changes: 24 additions & 0 deletions internal/cmd/config.go
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
package cmd

import (
"bytes"
"context"
"fmt"
"io"
"log/slog"
"os"
"time"

"github.com/hairyhenderson/gomplate/v4/conv"
Expand Down Expand Up @@ -263,3 +266,24 @@ func applyEnvVars(_ context.Context, cfg *config.Config) (*config.Config, error)

return cfg, nil
}

// postExecInput - return the input to be used after the post-exec command. The
// input config may be modified if ExecPipe is set (OutputFiles is set to "-"),
// and Stdout is redirected to a pipe.
func postExecInput(cfg *config.Config) io.Reader {
if cfg.ExecPipe {
pipe := &bytes.Buffer{}
cfg.OutputFiles = []string{"-"}

// --exec-pipe redirects standard out to the out pipe
cfg.Stdout = pipe

return pipe
}

if cfg.Stdin != nil {
return cfg.Stdin
}

return os.Stdin
}
33 changes: 26 additions & 7 deletions internal/cmd/config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"fmt"
"io/fs"
"net/url"
"os"
"testing"
"testing/fstest"
"time"
Expand Down Expand Up @@ -109,18 +110,36 @@ func TestLoadConfig(t *testing.T) {
cmd.ParseFlags([]string{"--in", "foo", "--exec-pipe", "--", "tr", "[a-z]", "[A-Z]"})
out, err = loadConfig(ctx, cmd, cmd.Flags().Args())
expected = &config.Config{
Input: "foo",
ExecPipe: true,
PostExec: []string{"tr", "[a-z]", "[A-Z]"},
PostExecInput: out.PostExecInput,
Stdin: stdin,
Stdout: out.Stdout,
Stderr: stderr,
Input: "foo",
ExecPipe: true,
PostExec: []string{"tr", "[a-z]", "[A-Z]"},
Stdin: stdin,
Stdout: out.Stdout,
Stderr: stderr,
}
require.NoError(t, err)
assert.EqualValues(t, expected, out)
}

func TestPostExecInput(t *testing.T) {
t.Parallel()

cfg := &config.Config{ExecPipe: false}
assert.Equal(t, os.Stdin, postExecInput(cfg))

cfg = &config.Config{ExecPipe: true}

pipe := postExecInput(cfg)
assert.IsType(t, &bytes.Buffer{}, pipe)
assert.Equal(t, []string{"-"}, cfg.OutputFiles)
assert.Equal(t, pipe, cfg.Stdout)

stdin := &bytes.Buffer{}
cfg = &config.Config{ExecPipe: false, Stdin: stdin}
pipe = postExecInput(cfg)
assert.Equal(t, stdin, pipe)
}

func TestCobraConfig(t *testing.T) {
t.Parallel()
cmd := &cobra.Command{}
Expand Down
12 changes: 5 additions & 7 deletions internal/cmd/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -88,19 +88,16 @@ func NewGomplateCmd(stderr io.Writer) *cobra.Command {
return err
}

if cfg.Experimental {
slog.SetDefault(slog.With("experimental", true))
slog.InfoContext(ctx, "experimental functions and features enabled!")

ctx = gomplate.SetExperimental(ctx)
}
// get the post-exec reader now as this may modify cfg
postExecReader := postExecInput(cfg)

slog.DebugContext(ctx, fmt.Sprintf("starting %s", cmd.Name()))
slog.DebugContext(ctx, fmt.Sprintf("config is:\n%v", cfg),
slog.String("version", version.Version),
slog.String("build", version.GitCommit),
)

// run the main command
err = gomplate.Run(ctx, cfg)
cmd.SilenceErrors = true
cmd.SilenceUsage = true
Expand All @@ -113,7 +110,8 @@ func NewGomplateCmd(stderr io.Writer) *cobra.Command {
if err != nil {
return err
}
return postRunExec(ctx, cfg.PostExec, cfg.PostExecInput, cmd.OutOrStdout(), cmd.ErrOrStderr())

return postRunExec(ctx, cfg.PostExec, postExecReader, cmd.OutOrStdout(), cmd.ErrOrStderr())
},
Args: optionalExecArgs,
}
Expand Down
Loading

0 comments on commit d261bb9

Please sign in to comment.