summaryrefslogtreecommitdiff
path: root/cmd
diff options
context:
space:
mode:
authorDave Henderson <dhenderson@gmail.com>2019-11-11 16:03:16 -0500
committerDave Henderson <dhenderson@gmail.com>2020-05-03 22:12:08 -0400
commit7ff174a86a935191a684f0c63f9e2a48058fabfb (patch)
tree00f59ab63d0e581d821307df57abd5cb6f2986c0 /cmd
parent8c8287777495dbb1e2b24e570db0bd504bf18372 (diff)
Support a config file to use instead of commandline arguments
Signed-off-by: Dave Henderson <dhenderson@gmail.com>
Diffstat (limited to 'cmd')
-rw-r--r--cmd/gomplate/config.go253
-rw-r--r--cmd/gomplate/config_test.go258
-rw-r--r--cmd/gomplate/logger.go3
-rw-r--r--cmd/gomplate/main.go115
-rw-r--r--cmd/gomplate/main_test.go22
-rw-r--r--cmd/gomplate/validate.go63
-rw-r--r--cmd/gomplate/validate_test.go95
7 files changed, 560 insertions, 249 deletions
diff --git a/cmd/gomplate/config.go b/cmd/gomplate/config.go
new file mode 100644
index 00000000..02278618
--- /dev/null
+++ b/cmd/gomplate/config.go
@@ -0,0 +1,253 @@
+package main
+
+import (
+ "context"
+ "fmt"
+ "time"
+
+ "github.com/hairyhenderson/gomplate/v3/conv"
+ "github.com/hairyhenderson/gomplate/v3/env"
+ "github.com/hairyhenderson/gomplate/v3/internal/config"
+
+ "github.com/rs/zerolog"
+
+ "github.com/spf13/afero"
+ "github.com/spf13/cobra"
+)
+
+const (
+ defaultConfigFile = ".gomplate.yaml"
+)
+
+var fs = afero.NewOsFs()
+
+// loadConfig is intended to be called before command execution. It:
+// - creates a config.Config from the cobra flags
+// - creates a config.Config from the config file (if present)
+// - merges the two (flags take precedence)
+// - validates the final config
+// - converts the config to a *gomplate.Config for further use (TODO: eliminate this part)
+func loadConfig(cmd *cobra.Command, args []string) (*config.Config, error) {
+ ctx := cmd.Context()
+ flagConfig, err := cobraConfig(cmd, args)
+ if err != nil {
+ return nil, err
+ }
+
+ cfg, err := readConfigFile(cmd)
+ if err != nil {
+ return nil, err
+ }
+ if cfg == nil {
+ cfg = flagConfig
+ } else {
+ cfg = cfg.MergeFrom(flagConfig)
+ }
+
+ cfg, err = applyEnvVars(ctx, cfg)
+ if err != nil {
+ return nil, err
+ }
+
+ // reset defaults before validation
+ cfg.ApplyDefaults()
+
+ err = cfg.Validate()
+ if err != nil {
+ return nil, fmt.Errorf("failed to validate merged config: %w\n%+v", err, cfg)
+ }
+ return cfg, nil
+}
+
+func pickConfigFile(cmd *cobra.Command) (cfgFile string, required bool) {
+ cfgFile = defaultConfigFile
+ if c := env.Getenv("GOMPLATE_CONFIG"); c != "" {
+ cfgFile = c
+ required = true
+ }
+ if cmd.Flags().Changed("config") && cmd.Flag("config").Value.String() != "" {
+ // Use config file from the flag if specified
+ cfgFile = cmd.Flag("config").Value.String()
+ required = true
+ }
+ return cfgFile, required
+}
+
+func readConfigFile(cmd *cobra.Command) (cfg *config.Config, err error) {
+ ctx := cmd.Context()
+ if ctx == nil {
+ ctx = context.Background()
+ }
+ log := zerolog.Ctx(ctx)
+
+ cfgFile, configRequired := pickConfigFile(cmd)
+
+ f, err := fs.Open(cfgFile)
+ if err != nil {
+ if configRequired {
+ return cfg, fmt.Errorf("config file requested, but couldn't be opened: %w", err)
+ }
+ return nil, nil
+ }
+
+ cfg, err = config.Parse(f)
+ if err != nil && configRequired {
+ return cfg, fmt.Errorf("config file requested, but couldn't be parsed: %w", err)
+ }
+
+ log.Debug().Str("cfgFile", cfgFile).Msg("using config file")
+
+ return cfg, err
+}
+
+// cobraConfig - initialize a config from the commandline options
+func cobraConfig(cmd *cobra.Command, args []string) (cfg *config.Config, err error) {
+ cfg = &config.Config{}
+ cfg.InputFiles, err = getStringSlice(cmd, "file")
+ if err != nil {
+ return nil, err
+ }
+ cfg.Input, err = getString(cmd, "in")
+ if err != nil {
+ return nil, err
+ }
+ cfg.InputDir, err = getString(cmd, "input-dir")
+ if err != nil {
+ return nil, err
+ }
+
+ cfg.ExcludeGlob, err = getStringSlice(cmd, "exclude")
+ if err != nil {
+ return nil, err
+ }
+ includesFlag, err := getStringSlice(cmd, "include")
+ if err != nil {
+ return nil, err
+ }
+ // support --include
+ cfg.ExcludeGlob = processIncludes(includesFlag, cfg.ExcludeGlob)
+
+ cfg.OutputFiles, err = getStringSlice(cmd, "out")
+ if err != nil {
+ return nil, err
+ }
+ cfg.Templates, err = getStringSlice(cmd, "template")
+ if err != nil {
+ return nil, err
+ }
+ cfg.OutputDir, err = getString(cmd, "output-dir")
+ if err != nil {
+ return nil, err
+ }
+ cfg.OutputMap, err = getString(cmd, "output-map")
+ if err != nil {
+ return nil, err
+ }
+ cfg.OutMode, err = getString(cmd, "chmod")
+ if err != nil {
+ return nil, err
+ }
+
+ if len(args) > 0 {
+ cfg.PostExec = args
+ }
+
+ cfg.ExecPipe, err = getBool(cmd, "exec-pipe")
+ if err != nil {
+ return nil, err
+ }
+
+ cfg.LDelim, err = getString(cmd, "left-delim")
+ if err != nil {
+ return nil, err
+ }
+ cfg.RDelim, err = getString(cmd, "right-delim")
+ if err != nil {
+ return nil, err
+ }
+
+ ds, err := getStringSlice(cmd, "datasource")
+ if err != nil {
+ return nil, err
+ }
+ cx, err := getStringSlice(cmd, "context")
+ if err != nil {
+ return nil, err
+ }
+ hdr, err := getStringSlice(cmd, "datasource-header")
+ if err != nil {
+ return nil, err
+ }
+ err = cfg.ParseDataSourceFlags(ds, cx, hdr)
+ if err != nil {
+ return nil, err
+ }
+
+ pl, err := getStringSlice(cmd, "plugin")
+ if err != nil {
+ return nil, err
+ }
+ err = cfg.ParsePluginFlags(pl)
+ if err != nil {
+ return nil, err
+ }
+ return cfg, nil
+}
+
+func getStringSlice(cmd *cobra.Command, flag string) (s []string, err error) {
+ if cmd.Flag(flag) != nil && cmd.Flag(flag).Changed {
+ s, err = cmd.Flags().GetStringSlice(flag)
+ }
+ return s, err
+}
+
+func getString(cmd *cobra.Command, flag string) (s string, err error) {
+ if cmd.Flag(flag) != nil && cmd.Flag(flag).Changed {
+ s, err = cmd.Flags().GetString(flag)
+ }
+ return s, err
+}
+
+func getBool(cmd *cobra.Command, flag string) (b bool, err error) {
+ if cmd.Flag(flag) != nil && cmd.Flag(flag).Changed {
+ b, err = cmd.Flags().GetBool(flag)
+ }
+ return b, err
+}
+
+// process --include flags - these are analogous to specifying --exclude '*',
+// then the inverse of the --include options.
+func processIncludes(includes, excludes []string) []string {
+ if len(includes) == 0 && len(excludes) == 0 {
+ return nil
+ }
+
+ out := []string{}
+ // if any --includes are set, we start by excluding everything
+ if len(includes) > 0 {
+ out = make([]string, 1+len(includes))
+ out[0] = "*"
+ }
+ for i, include := range includes {
+ // includes are just the opposite of an exclude
+ out[i+1] = "!" + include
+ }
+ out = append(out, excludes...)
+ return out
+}
+
+func applyEnvVars(ctx context.Context, cfg *config.Config) (*config.Config, error) {
+ if to := env.Getenv("GOMPLATE_PLUGIN_TIMEOUT"); cfg.PluginTimeout == 0 && to != "" {
+ t, err := time.ParseDuration(to)
+ if err != nil {
+ return nil, fmt.Errorf("GOMPLATE_PLUGIN_TIMEOUT set to invalid value %q: %w", to, err)
+ }
+ cfg.PluginTimeout = t
+ }
+
+ if !cfg.SuppressEmpty && conv.ToBool(env.Getenv("GOMPLATE_SUPPRESS_EMPTY", "false")) {
+ cfg.SuppressEmpty = true
+ }
+
+ return cfg, nil
+}
diff --git a/cmd/gomplate/config_test.go b/cmd/gomplate/config_test.go
new file mode 100644
index 00000000..dc05dfbb
--- /dev/null
+++ b/cmd/gomplate/config_test.go
@@ -0,0 +1,258 @@
+package main
+
+import (
+ "context"
+ "os"
+ "testing"
+ "time"
+
+ "github.com/hairyhenderson/gomplate/v3/internal/config"
+
+ "github.com/spf13/afero"
+ "github.com/spf13/cobra"
+ "github.com/stretchr/testify/assert"
+)
+
+func TestReadConfigFile(t *testing.T) {
+ fs = afero.NewMemMapFs()
+ defer func() { fs = afero.NewOsFs() }()
+ cmd := &cobra.Command{}
+
+ _, err := readConfigFile(cmd)
+ assert.NoError(t, err)
+
+ cmd.Flags().String("config", defaultConfigFile, "foo")
+
+ _, err = readConfigFile(cmd)
+ assert.NoError(t, err)
+
+ cmd.ParseFlags([]string{"--config", "config.file"})
+
+ _, err = readConfigFile(cmd)
+ assert.Error(t, err)
+
+ cmd = &cobra.Command{}
+ cmd.Flags().String("config", defaultConfigFile, "foo")
+
+ f, err := fs.Create(defaultConfigFile)
+ assert.NoError(t, err)
+ f.WriteString("")
+
+ cfg, err := readConfigFile(cmd)
+ assert.NoError(t, err)
+ assert.EqualValues(t, &config.Config{}, cfg)
+
+ cmd.ParseFlags([]string{"--config", "config.yaml"})
+
+ f, err = fs.Create("config.yaml")
+ assert.NoError(t, err)
+ f.WriteString("in: hello world\n")
+
+ cfg, err = readConfigFile(cmd)
+ assert.NoError(t, err)
+ assert.EqualValues(t, &config.Config{Input: "hello world"}, cfg)
+
+ f.WriteString("in: ")
+
+ _, err = readConfigFile(cmd)
+ assert.Error(t, err)
+}
+
+func TestLoadConfig(t *testing.T) {
+ fs = afero.NewMemMapFs()
+ defer func() { fs = afero.NewOsFs() }()
+
+ cmd := &cobra.Command{}
+ cmd.Args = optionalExecArgs
+ cmd.Flags().StringSlice("file", []string{"-"}, "...")
+ cmd.Flags().StringSlice("out", []string{"-"}, "...")
+ cmd.Flags().String("in", ".", "...")
+ cmd.Flags().String("output-dir", ".", "...")
+ cmd.Flags().String("left-delim", "{{", "...")
+ cmd.Flags().String("right-delim", "}}", "...")
+ cmd.Flags().Bool("exec-pipe", false, "...")
+ cmd.ParseFlags(nil)
+
+ out, err := loadConfig(cmd, cmd.Flags().Args())
+ expected := &config.Config{
+ InputFiles: []string{"-"},
+ OutputFiles: []string{"-"},
+ LDelim: "{{",
+ RDelim: "}}",
+ PostExecInput: os.Stdin,
+ OutWriter: os.Stdout,
+ PluginTimeout: 5 * time.Second,
+ }
+ assert.NoError(t, err)
+ assert.EqualValues(t, expected, out)
+
+ cmd.ParseFlags([]string{"--in", "foo"})
+ out, err = loadConfig(cmd, cmd.Flags().Args())
+ expected = &config.Config{
+ Input: "foo",
+ OutputFiles: []string{"-"},
+ LDelim: "{{",
+ RDelim: "}}",
+ PostExecInput: os.Stdin,
+ OutWriter: os.Stdout,
+ PluginTimeout: 5 * time.Second,
+ }
+ assert.NoError(t, err)
+ assert.EqualValues(t, expected, out)
+
+ cmd.ParseFlags([]string{"--in", "foo", "--exec-pipe", "--", "tr", "[a-z]", "[A-Z]"})
+ out, err = loadConfig(cmd, cmd.Flags().Args())
+ expected = &config.Config{
+ Input: "foo",
+ LDelim: "{{",
+ RDelim: "}}",
+ ExecPipe: true,
+ PostExec: []string{"tr", "[a-z]", "[A-Z]"},
+ PostExecInput: out.PostExecInput,
+ OutWriter: out.PostExecInput,
+ OutputFiles: []string{"-"},
+ PluginTimeout: 5 * time.Second,
+ }
+ assert.NoError(t, err)
+ assert.EqualValues(t, expected, out)
+}
+
+func TestCobraConfig(t *testing.T) {
+ t.Parallel()
+ cmd := &cobra.Command{}
+ cmd.Flags().StringSlice("file", []string{"-"}, "...")
+ cmd.Flags().StringSlice("out", []string{"-"}, "...")
+ cmd.Flags().String("output-dir", ".", "...")
+ cmd.Flags().String("left-delim", "{{", "...")
+ cmd.Flags().String("right-delim", "}}", "...")
+ cmd.ParseFlags(nil)
+
+ cfg, err := cobraConfig(cmd, cmd.Flags().Args())
+ assert.NoError(t, err)
+ assert.EqualValues(t, &config.Config{}, cfg)
+
+ cmd.ParseFlags([]string{"--file", "in", "--", "echo", "foo"})
+
+ cfg, err = cobraConfig(cmd, cmd.Flags().Args())
+ assert.NoError(t, err)
+ assert.EqualValues(t, &config.Config{
+ InputFiles: []string{"in"},
+ PostExec: []string{"echo", "foo"},
+ }, cfg)
+}
+
+func TestProcessIncludes(t *testing.T) {
+ t.Parallel()
+ data := []struct {
+ inc, exc, expected []string
+ }{
+ {nil, nil, nil},
+ {[]string{}, []string{}, nil},
+ {nil, []string{"*.foo"}, []string{"*.foo"}},
+ {[]string{"*.bar"}, []string{"a*.bar"}, []string{"*", "!*.bar", "a*.bar"}},
+ {[]string{"*.bar"}, nil, []string{"*", "!*.bar"}},
+ }
+
+ for _, d := range data {
+ assert.EqualValues(t, d.expected, processIncludes(d.inc, d.exc))
+ }
+}
+
+func TestPickConfigFile(t *testing.T) {
+ cmd := &cobra.Command{}
+ cmd.Flags().String("config", defaultConfigFile, "foo")
+
+ cf, req := pickConfigFile(cmd)
+ assert.False(t, req)
+ assert.Equal(t, defaultConfigFile, cf)
+
+ os.Setenv("GOMPLATE_CONFIG", "foo.yaml")
+ defer os.Unsetenv("GOMPLATE_CONFIG")
+ cf, req = pickConfigFile(cmd)
+ assert.True(t, req)
+ assert.Equal(t, "foo.yaml", cf)
+
+ cmd.ParseFlags([]string{"--config", "config.file"})
+ cf, req = pickConfigFile(cmd)
+ assert.True(t, req)
+ assert.Equal(t, "config.file", cf)
+
+ os.Setenv("GOMPLATE_CONFIG", "ignored.yaml")
+ cf, req = pickConfigFile(cmd)
+ assert.True(t, req)
+ assert.Equal(t, "config.file", cf)
+}
+
+func TestApplyEnvVars_PluginTimeout(t *testing.T) {
+ os.Setenv("GOMPLATE_PLUGIN_TIMEOUT", "bogus")
+
+ ctx := context.TODO()
+ cfg := &config.Config{}
+ _, err := applyEnvVars(ctx, cfg)
+ assert.Error(t, err)
+
+ cfg = &config.Config{
+ PluginTimeout: 2 * time.Second,
+ }
+ expected := &config.Config{
+ PluginTimeout: 2 * time.Second,
+ }
+ actual, err := applyEnvVars(ctx, cfg)
+ assert.NoError(t, err)
+ assert.EqualValues(t, expected, actual)
+
+ os.Setenv("GOMPLATE_PLUGIN_TIMEOUT", "2s")
+ defer os.Unsetenv("GOMPLATE_PLUGIN_TIMEOUT")
+
+ cfg = &config.Config{}
+ actual, err = applyEnvVars(ctx, cfg)
+ assert.NoError(t, err)
+ assert.EqualValues(t, expected, actual)
+
+ cfg = &config.Config{
+ PluginTimeout: 100 * time.Millisecond,
+ }
+ expected = &config.Config{
+ PluginTimeout: 100 * time.Millisecond,
+ }
+ actual, err = applyEnvVars(ctx, cfg)
+ assert.NoError(t, err)
+ assert.EqualValues(t, expected, actual)
+
+}
+
+func TestApplyEnvVars_SuppressEmpty(t *testing.T) {
+ os.Setenv("GOMPLATE_SUPPRESS_EMPTY", "bogus")
+ defer os.Unsetenv("GOMPLATE_SUPPRESS_EMPTY")
+
+ ctx := context.TODO()
+ cfg := &config.Config{}
+ expected := &config.Config{
+ SuppressEmpty: false,
+ }
+ actual, err := applyEnvVars(ctx, cfg)
+ assert.NoError(t, err)
+ assert.EqualValues(t, expected, actual)
+
+ os.Setenv("GOMPLATE_SUPPRESS_EMPTY", "true")
+
+ cfg = &config.Config{}
+ expected = &config.Config{
+ SuppressEmpty: true,
+ }
+ actual, err = applyEnvVars(ctx, cfg)
+ assert.NoError(t, err)
+ assert.EqualValues(t, expected, actual)
+
+ os.Setenv("GOMPLATE_SUPPRESS_EMPTY", "false")
+
+ cfg = &config.Config{
+ SuppressEmpty: true,
+ }
+ expected = &config.Config{
+ SuppressEmpty: true,
+ }
+ actual, err = applyEnvVars(ctx, cfg)
+ assert.NoError(t, err)
+ assert.EqualValues(t, expected, actual)
+}
diff --git a/cmd/gomplate/logger.go b/cmd/gomplate/logger.go
index d40281a5..c58dc31d 100644
--- a/cmd/gomplate/logger.go
+++ b/cmd/gomplate/logger.go
@@ -12,7 +12,8 @@ import (
)
func initLogger(ctx context.Context) context.Context {
- zerolog.SetGlobalLevel(zerolog.InfoLevel)
+ // default to warn level
+ zerolog.SetGlobalLevel(zerolog.WarnLevel)
zerolog.DurationFieldUnit = time.Second
stdlogger := log.With().Bool("stdlog", true).Logger()
diff --git a/cmd/gomplate/main.go b/cmd/gomplate/main.go
index 62c85fdb..110a0503 100644
--- a/cmd/gomplate/main.go
+++ b/cmd/gomplate/main.go
@@ -5,33 +5,26 @@ The gomplate command
package main
import (
- "bytes"
"context"
+ "fmt"
"os"
"os/exec"
"os/signal"
"github.com/hairyhenderson/gomplate/v3"
"github.com/hairyhenderson/gomplate/v3/env"
+ "github.com/hairyhenderson/gomplate/v3/internal/config"
"github.com/hairyhenderson/gomplate/v3/version"
"github.com/rs/zerolog"
- "github.com/spf13/cobra"
-)
-var (
- verbose bool
- execPipe bool
- opts gomplate.Config
- includes []string
-
- postRunInput *bytes.Buffer
+ "github.com/spf13/cobra"
)
// postRunExec - if templating succeeds, the command following a '--' will be executed
-func postRunExec(cmd *cobra.Command, args []string) error {
+func postRunExec(ctx context.Context, cfg *config.Config) error {
+ args := cfg.PostExec
if len(args) > 0 {
- ctx := cmd.Context()
log := zerolog.Ctx(ctx)
log.Debug().Strs("args", args).Msg("running post-exec command")
@@ -39,11 +32,7 @@ func postRunExec(cmd *cobra.Command, args []string) error {
args = args[1:]
// nolint: gosec
c := exec.CommandContext(ctx, name, args...)
- if execPipe {
- c.Stdin = postRunInput
- } else {
- c.Stdin = os.Stdin
- }
+ c.Stdin = cfg.PostExecInput
c.Stderr = os.Stderr
c.Stdout = os.Stdout
@@ -73,28 +62,10 @@ func optionalExecArgs(cmd *cobra.Command, args []string) error {
return cobra.NoArgs(cmd, args)
}
-// process --include flags - these are analogous to specifying --exclude '*',
-// then the inverse of the --include options.
-func processIncludes(includes, excludes []string) []string {
- out := []string{}
- // if any --includes are set, we start by excluding everything
- if len(includes) > 0 {
- out = make([]string, 1+len(includes))
- out[0] = "*"
- }
- for i, include := range includes {
- // includes are just the opposite of an exclude
- out[i+1] = "!" + include
- }
- out = append(out, excludes...)
- return out
-}
-
func newGomplateCmd() *cobra.Command {
rootCmd := &cobra.Command{
Use: "gomplate",
Short: "Process text files with Go templates",
- PreRunE: validateOpts,
Version: version.Version,
RunE: func(cmd *cobra.Command, args []string) error {
if v, _ := cmd.Flags().GetBool("verbose"); v {
@@ -103,27 +74,33 @@ func newGomplateCmd() *cobra.Command {
ctx := cmd.Context()
log := zerolog.Ctx(ctx)
- log.Debug().Msgf("%s version %s, build %s\nconfig is:\n%s",
- cmd.Name(), version.Version, version.GitCommit,
- &opts)
+ cfg, err := loadConfig(cmd, args)
+ if err != nil {
+ return err
+ }
- // support --include
- opts.ExcludeGlob = processIncludes(includes, opts.ExcludeGlob)
+ log.Debug().Msgf("starting %s", cmd.Name())
+ log.Debug().
+ Str("version", version.Version).
+ Str("build", version.GitCommit).
+ Msgf("config is:\n%v", cfg)
- if execPipe {
- postRunInput = &bytes.Buffer{}
- opts.Out = postRunInput
- }
- err := gomplate.RunTemplates(&opts)
+ err = gomplate.RunTemplatesWithContext(ctx, cfg)
cmd.SilenceErrors = true
cmd.SilenceUsage = true
- log.Debug().Msgf("rendered %d template(s) with %d error(s) in %v",
- gomplate.Metrics.TemplatesProcessed, gomplate.Metrics.Errors, gomplate.Metrics.TotalRenderDuration)
- return err
+ fmt.Fprintf(os.Stderr, "\n")
+ log.Debug().Int("templatesRendered", gomplate.Metrics.TemplatesProcessed).
+ Int("errors", gomplate.Metrics.Errors).
+ Dur("duration", gomplate.Metrics.TotalRenderDuration).
+ Msg("completed rendering")
+
+ if err != nil {
+ return err
+ }
+ return postRunExec(ctx, cfg)
},
- PostRunE: postRunExec,
- Args: optionalExecArgs,
+ Args: optionalExecArgs,
}
return rootCmd
}
@@ -131,34 +108,36 @@ func newGomplateCmd() *cobra.Command {
func initFlags(command *cobra.Command) {
command.Flags().SortFlags = false
- command.Flags().StringArrayVarP(&opts.DataSources, "datasource", "d", nil, "`datasource` in alias=URL form. Specify multiple times to add multiple sources.")
- command.Flags().StringArrayVarP(&opts.DataSourceHeaders, "datasource-header", "H", nil, "HTTP `header` field in 'alias=Name: value' form to be provided on HTTP-based data sources. Multiples can be set.")
+ command.Flags().StringSliceP("datasource", "d", nil, "`datasource` in alias=URL form. Specify multiple times to add multiple sources.")
+ command.Flags().StringSliceP("datasource-header", "H", nil, "HTTP `header` field in 'alias=Name: value' form to be provided on HTTP-based data sources. Multiples can be set.")
- command.Flags().StringArrayVarP(&opts.Contexts, "context", "c", nil, "pre-load a `datasource` into the context, in alias=URL form. Use the special alias `.` to set the root context.")
+ command.Flags().StringSliceP("context", "c", nil, "pre-load a `datasource` into the context, in alias=URL form. Use the special alias `.` to set the root context.")
- command.Flags().StringArrayVar(&opts.Plugins, "plugin", nil, "plug in an external command as a function in name=path form. Can be specified multiple times")
+ command.Flags().StringSlice("plugin", nil, "plug in an external command as a function in name=path form. Can be specified multiple times")
- command.Flags().StringArrayVarP(&opts.InputFiles, "file", "f", []string{"-"}, "Template `file` to process. Omit to use standard input, or use --in or --input-dir")
- command.Flags().StringVarP(&opts.Input, "in", "i", "", "Template `string` to process (alternative to --file and --input-dir)")
- command.Flags().StringVar(&opts.InputDir, "input-dir", "", "`directory` which is examined recursively for templates (alternative to --file and --in)")
+ command.Flags().StringSliceP("file", "f", []string{"-"}, "Template `file` to process. Omit to use standard input, or use --in or --input-dir")
+ command.Flags().StringP("in", "i", "", "Template `string` to process (alternative to --file and --input-dir)")
+ command.Flags().String("input-dir", "", "`directory` which is examined recursively for templates (alternative to --file and --in)")
- command.Flags().StringArrayVar(&opts.ExcludeGlob, "exclude", []string{}, "glob of files to not parse")
- command.Flags().StringArrayVar(&includes, "include", []string{}, "glob of files to parse")
+ command.Flags().StringSlice("exclude", []string{}, "glob of files to not parse")
+ command.Flags().StringSlice("include", []string{}, "glob of files to parse")
- command.Flags().StringArrayVarP(&opts.OutputFiles, "out", "o", []string{"-"}, "output `file` name. Omit to use standard output.")
- command.Flags().StringArrayVarP(&opts.Templates, "template", "t", []string{}, "Additional template file(s)")
- command.Flags().StringVar(&opts.OutputDir, "output-dir", ".", "`directory` to store the processed templates. Only used for --input-dir")
- command.Flags().StringVar(&opts.OutputMap, "output-map", "", "Template `string` to map the input file to an output path")
- command.Flags().StringVar(&opts.OutMode, "chmod", "", "set the mode for output file(s). Omit to inherit from input file(s)")
+ command.Flags().StringSliceP("out", "o", []string{"-"}, "output `file` name. Omit to use standard output.")
+ command.Flags().StringSliceP("template", "t", []string{}, "Additional template file(s)")
+ command.Flags().String("output-dir", ".", "`directory` to store the processed templates. Only used for --input-dir")
+ command.Flags().String("output-map", "", "Template `string` to map the input file to an output path")
+ command.Flags().String("chmod", "", "set the mode for output file(s). Omit to inherit from input file(s)")
- command.Flags().BoolVar(&execPipe, "exec-pipe", false, "pipe the output to the post-run exec command")
+ command.Flags().Bool("exec-pipe", false, "pipe the output to the post-run exec command")
ldDefault := env.Getenv("GOMPLATE_LEFT_DELIM", "{{")
rdDefault := env.Getenv("GOMPLATE_RIGHT_DELIM", "}}")
- command.Flags().StringVar(&opts.LDelim, "left-delim", ldDefault, "override the default left-`delimiter` [$GOMPLATE_LEFT_DELIM]")
- command.Flags().StringVar(&opts.RDelim, "right-delim", rdDefault, "override the default right-`delimiter` [$GOMPLATE_RIGHT_DELIM]")
+ command.Flags().String("left-delim", ldDefault, "override the default left-`delimiter` [$GOMPLATE_LEFT_DELIM]")
+ command.Flags().String("right-delim", rdDefault, "override the default right-`delimiter` [$GOMPLATE_RIGHT_DELIM]")
+
+ command.Flags().BoolP("verbose", "V", false, "output extra information about what gomplate is doing")
- command.Flags().BoolVarP(&verbose, "verbose", "V", false, "output extra information about what gomplate is doing")
+ command.Flags().String("config", defaultConfigFile, "config file (overridden by commandline flags)")
}
func main() {
diff --git a/cmd/gomplate/main_test.go b/cmd/gomplate/main_test.go
index e0d74e9d..06ab7d0f 100644
--- a/cmd/gomplate/main_test.go
+++ b/cmd/gomplate/main_test.go
@@ -1,23 +1 @@
package main
-
-import (
- "testing"
-
- "github.com/stretchr/testify/assert"
-)
-
-func TestProcessIncludes(t *testing.T) {
- data := []struct {
- inc, exc, expected []string
- }{
- {nil, nil, []string{}},
- {[]string{}, []string{}, []string{}},
- {nil, []string{"*.foo"}, []string{"*.foo"}},
- {[]string{"*.bar"}, []string{"a*.bar"}, []string{"*", "!*.bar", "a*.bar"}},
- {[]string{"*.bar"}, nil, []string{"*", "!*.bar"}},
- }
-
- for _, d := range data {
- assert.EqualValues(t, d.expected, processIncludes(d.inc, d.exc))
- }
-}
diff --git a/cmd/gomplate/validate.go b/cmd/gomplate/validate.go
deleted file mode 100644
index f4c73133..00000000
--- a/cmd/gomplate/validate.go
+++ /dev/null
@@ -1,63 +0,0 @@
-package main
-
-import (
- "fmt"
- "strings"
-
- "github.com/spf13/cobra"
-)
-
-func notTogether(cmd *cobra.Command, flags ...string) error {
- found := ""
- for _, flag := range flags {
- f := cmd.Flag(flag)
- if f != nil && f.Changed {
- if found != "" {
- a := make([]string, len(flags))
- for i := range a {
- a[i] = "--" + flags[i]
- }
- return fmt.Errorf("only one of these flags is supported at a time: %s", strings.Join(a, ", "))
- }
- found = flag
- }
- }
- return nil
-}
-
-func mustTogether(cmd *cobra.Command, left, right string) error {
- l := cmd.Flag(left)
- if l != nil && l.Changed {
- r := cmd.Flag(right)
- if r != nil && !r.Changed {
- return fmt.Errorf("--%s must be set when --%s is set", right, left)
- }
- }
-
- return nil
-}
-
-func validateOpts(cmd *cobra.Command, args []string) (err error) {
- err = notTogether(cmd, "in", "file", "input-dir")
- if err == nil {
- err = notTogether(cmd, "out", "output-dir", "output-map", "exec-pipe")
- }
-
- if err == nil && len(opts.InputFiles) != len(opts.OutputFiles) {
- err = fmt.Errorf("must provide same number of --out (%d) as --file (%d) options", len(opts.OutputFiles), len(opts.InputFiles))
- }
-
- if err == nil && cmd.Flag("exec-pipe").Changed && len(args) == 0 {
- err = fmt.Errorf("--exec-pipe may only be used with a post-exec command after --")
- }
-
- if err == nil {
- err = mustTogether(cmd, "output-dir", "input-dir")
- }
-
- if err == nil {
- err = mustTogether(cmd, "output-map", "input-dir")
- }
-
- return err
-}
diff --git a/cmd/gomplate/validate_test.go b/cmd/gomplate/validate_test.go
deleted file mode 100644
index 54f4ba01..00000000
--- a/cmd/gomplate/validate_test.go
+++ /dev/null
@@ -1,95 +0,0 @@
-package main
-
-import (
- "testing"
-
- "github.com/spf13/cobra"
- "github.com/stretchr/testify/assert"
-)
-
-func TestValidateOpts(t *testing.T) {
- err := validateOpts(parseFlags())
- assert.NoError(t, err)
-
- err = validateOpts(parseFlags("-i=foo", "-f", "bar"))
- assert.Error(t, err)
-
- err = validateOpts(parseFlags("-i=foo", "-o=bar", "-o=baz"))
- assert.Error(t, err)
-
- err = validateOpts(parseFlags("-i=foo", "--input-dir=baz"))
- assert.Error(t, err)
-
- err = validateOpts(parseFlags("--input-dir=foo", "-f=bar"))
- assert.Error(t, err)
-
- err = validateOpts(parseFlags("--output-dir=foo", "-o=bar"))
- assert.Error(t, err)
-
- err = validateOpts(parseFlags("--output-dir=foo"))
- assert.Error(t, err)
-
- err = validateOpts(parseFlags("--output-map", "bar"))
- assert.Error(t, err)
-
- err = validateOpts(parseFlags("-o", "foo", "--output-map", "bar"))
- assert.Error(t, err)
-
- err = validateOpts(parseFlags(
- "--input-dir", "in",
- "--output-dir", "foo",
- "--output-map", "bar",
- ))
- assert.Error(t, err)
-
- err = validateOpts(parseFlags("--exec-pipe"))
- assert.Error(t, err)
-
- err = validateOpts(parseFlags("--exec-pipe", "--"))
- assert.Error(t, err)
-
- err = validateOpts(parseFlags(
- "--exec-pipe",
- "--", "echo", "foo",
- ))
- assert.NoError(t, err)
-
- err = validateOpts(parseFlags(
- "--exec-pipe",
- "--out", "foo",
- "--", "echo",
- ))
- assert.Error(t, err)
-
- err = validateOpts(parseFlags(
- "--input-dir", "in",
- "--exec-pipe",
- "--output-dir", "foo",
- "--", "echo",
- ))
- assert.Error(t, err)
-
- err = validateOpts(parseFlags(
- "--input-dir", "in",
- "--exec-pipe",
- "--output-map", "foo",
- "--", "echo",
- ))
- assert.Error(t, err)
-
- err = validateOpts(parseFlags(
- "--input-dir", "in",
- "--output-map", "bar",
- ))
- assert.NoError(t, err)
-}
-
-func parseFlags(flags ...string) (cmd *cobra.Command, args []string) {
- cmd = &cobra.Command{}
- initFlags(cmd)
- err := cmd.ParseFlags(flags)
- if err != nil {
- panic(err)
- }
- return cmd, cmd.Flags().Args()
-}