Skip to content

Commit

Permalink
fixup! fixup! Support a config file to use instead of commandline arg…
Browse files Browse the repository at this point in the history
…uments
  • Loading branch information
hairyhenderson committed Feb 28, 2020
1 parent 90a519f commit 71de05e
Show file tree
Hide file tree
Showing 13 changed files with 1,429 additions and 498 deletions.
231 changes: 231 additions & 0 deletions cmd/gomplate/config.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,231 @@
package main

import (
"fmt"

"github.com/hairyhenderson/gomplate/v3/internal/config"
"github.com/rs/zerolog"

"github.com/spf13/cobra"
)

const (
defaultConfigFile = ".gomplate.yaml"
)

// processConfig 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 processConfig(cmd *cobra.Command, args []string) (*config.Config, error) {
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)
}

// reset defaults before validation
cfg = applyDefaults(cfg)

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 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) {
log := zerolog.Ctx(cmd.Context())

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
}

func applyDefaults(cfg *config.Config) *config.Config {
if cfg.InputDir != "" && cfg.OutputDir == "" {
cfg.OutputDir = "."
}
if cfg.Input == "" && cfg.InputDir == "" && len(cfg.InputFiles) == 0 {
cfg.InputFiles = []string{"-"}
}
if cfg.OutputDir == "" && cfg.OutputMap == "" && len(cfg.OutputFiles) == 0 {
cfg.OutputFiles = []string{"-"}
}
if cfg.LDelim == "" {
cfg.LDelim = "{{"
}
if cfg.RDelim == "" {
cfg.RDelim = "}}"
}
return cfg
}

// 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
}

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
}
Loading

0 comments on commit 71de05e

Please sign in to comment.