diff options
| author | Dave Henderson <dhenderson@gmail.com> | 2021-01-02 11:08:39 -0500 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2021-01-02 11:08:39 -0500 |
| commit | 09a33cdffa6d0b4e57e12827f7075fb2d6c6a543 (patch) | |
| tree | 40e877686ffc7dc9a84a96bb4ee7a586f9aaf203 | |
| parent | 6c558e8a6531cddf37cf1e6f5ac88c512b271ad3 (diff) | |
| parent | 9e952fc8b4158c5e64b3185acd5d2b3fc7b15445 (diff) | |
Merge pull request #1022 from hairyhenderson/internal-main
Move main logic to internal
| -rw-r--r-- | cmd/gomplate/logger.go | 34 | ||||
| -rw-r--r-- | cmd/gomplate/main.go | 152 | ||||
| -rw-r--r-- | cmd/gomplate/main_test.go | 1 | ||||
| -rw-r--r-- | go.mod | 6 | ||||
| -rw-r--r-- | go.sum | 25 | ||||
| -rw-r--r-- | internal/cmd/config.go (renamed from cmd/gomplate/config.go) | 2 | ||||
| -rw-r--r-- | internal/cmd/config_test.go (renamed from cmd/gomplate/config_test.go) | 2 | ||||
| -rw-r--r-- | internal/cmd/logger.go | 37 | ||||
| -rw-r--r-- | internal/cmd/main.go | 167 | ||||
| -rw-r--r-- | internal/cmd/main_test.go | 50 |
10 files changed, 275 insertions, 201 deletions
diff --git a/cmd/gomplate/logger.go b/cmd/gomplate/logger.go deleted file mode 100644 index 2694c89a..00000000 --- a/cmd/gomplate/logger.go +++ /dev/null @@ -1,34 +0,0 @@ -package main - -import ( - "context" - stdlog "log" - "os" - "time" - - "github.com/rs/zerolog" - "github.com/rs/zerolog/log" - "golang.org/x/term" -) - -func initLogger(ctx context.Context) context.Context { - // default to warn level - zerolog.SetGlobalLevel(zerolog.WarnLevel) - zerolog.DurationFieldUnit = time.Second - - stdlogger := log.With().Bool("stdlog", true).Logger() - stdlog.SetFlags(0) - stdlog.SetOutput(stdlogger) - - if term.IsTerminal(int(os.Stderr.Fd())) { - log.Logger = log.Output(zerolog.ConsoleWriter{Out: os.Stderr, TimeFormat: "15:04:05"}) - noLevelWriter := zerolog.ConsoleWriter{ - Out: os.Stderr, - FormatLevel: func(i interface{}) string { return "" }, - } - stdlogger = stdlogger.Output(noLevelWriter) - stdlog.SetOutput(stdlogger) - } - - return log.Logger.WithContext(ctx) -} diff --git a/cmd/gomplate/main.go b/cmd/gomplate/main.go index 730ac8eb..ac5fa07b 100644 --- a/cmd/gomplate/main.go +++ b/cmd/gomplate/main.go @@ -6,164 +6,22 @@ package main import ( "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" + "github.com/hairyhenderson/gomplate/v3/internal/cmd" ) -// postRunExec - if templating succeeds, the command following a '--' will be executed -func postRunExec(ctx context.Context, cfg *config.Config) error { - args := cfg.PostExec - if len(args) > 0 { - log := zerolog.Ctx(ctx) - log.Debug().Strs("args", args).Msg("running post-exec command") - - name := args[0] - args = args[1:] - // nolint: gosec - c := exec.CommandContext(ctx, name, args...) - c.Stdin = cfg.PostExecInput - c.Stderr = os.Stderr - c.Stdout = os.Stdout - - // make sure all signals are propagated - sigs := make(chan os.Signal, 1) - signal.Notify(sigs) - go func() { - // Pass signals to the sub-process - sig := <-sigs - if c.Process != nil { - // nolint: gosec - _ = c.Process.Signal(sig) - } - }() - - return c.Run() - } - return nil -} - -// optionalExecArgs - implements cobra.PositionalArgs. Allows extra args following -// a '--', but not otherwise. -func optionalExecArgs(cmd *cobra.Command, args []string) error { - if cmd.ArgsLenAtDash() == 0 { - return nil - } - return cobra.NoArgs(cmd, args) -} - -func newGomplateCmd() *cobra.Command { - rootCmd := &cobra.Command{ - Use: "gomplate", - Short: "Process text files with Go templates", - Version: version.Version, - RunE: func(cmd *cobra.Command, args []string) error { - if v, _ := cmd.Flags().GetBool("verbose"); v { - zerolog.SetGlobalLevel(zerolog.DebugLevel) - } - ctx := cmd.Context() - log := zerolog.Ctx(ctx) - - cfg, err := loadConfig(cmd, args) - if err != nil { - return err - } - ctx = config.ContextWithConfig(ctx, cfg) - if cfg.Experimental { - log.UpdateContext(func(c zerolog.Context) zerolog.Context { - return c.Bool("experimental", true) - }) - log.Info().Msg("experimental functions and features enabled!") - } - - log.Debug().Msgf("starting %s", cmd.Name()) - log.Debug(). - Str("version", version.Version). - Str("build", version.GitCommit). - Msgf("config is:\n%v", cfg) - - err = gomplate.Run(ctx, cfg) - cmd.SilenceErrors = true - cmd.SilenceUsage = true - - 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) - }, - Args: optionalExecArgs, - } - return rootCmd -} - -func initFlags(command *cobra.Command) { - command.Flags().SortFlags = false - - 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().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().StringSlice("plugin", nil, "plug in an external command as a function in name=path form. Can be specified multiple times") - - 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().StringSlice("exclude", []string{}, "glob of files to not parse") - command.Flags().StringSlice("include", []string{}, "glob of files to parse") - - 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().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().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().Bool("experimental", false, "enable experimental features [$GOMPLATE_EXPERIMENTAL]") - - 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)") -} - func main() { exitCode := 0 - // defer the exit first to make sure that other deferred functions have a - // chance to run + // defer the exit first, so it executes last, to let the deferred cancel run defer func() { os.Exit(exitCode) }() ctx, cancel := context.WithCancel(context.Background()) defer cancel() - ctx = initLogger(ctx) - command := newGomplateCmd() - initFlags(command) - if err := command.ExecuteContext(ctx); err != nil { - log := zerolog.Ctx(ctx) - log.Error().Err(err).Send() + // need to strip os.Args[0] so we only pass the actual flags + err := cmd.Main(ctx, os.Args[1:], os.Stdin, os.Stdout, os.Stderr) + if err != nil { exitCode = 1 } } diff --git a/cmd/gomplate/main_test.go b/cmd/gomplate/main_test.go deleted file mode 100644 index 06ab7d0f..00000000 --- a/cmd/gomplate/main_test.go +++ /dev/null @@ -1 +0,0 @@ -package main @@ -25,9 +25,9 @@ require ( github.com/ugorji/go/codec v1.2.2 github.com/zealic/xignore v0.3.3 gocloud.dev v0.21.0 - golang.org/x/crypto v0.0.0-20201208171446-5f87f3452ae9 - golang.org/x/sys v0.0.0-20201214210602-f9fddec55a1e - golang.org/x/term v0.0.0-20201117132131-f5c789dd3221 + golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad + golang.org/x/sys v0.0.0-20201223074533-0d417f636930 + golang.org/x/term v0.0.0-20201210144234-2321bbc49cbf gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c gopkg.in/src-d/go-billy.v4 v4.3.2 gopkg.in/src-d/go-git.v4 v4.13.1 @@ -105,10 +105,7 @@ github.com/aws/aws-sdk-go v1.15.27/go.mod h1:mFuSZ37Z9YOHbQEwBWztmVzqXrEkub65tZo github.com/aws/aws-sdk-go v1.17.4/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= github.com/aws/aws-sdk-go v1.23.20/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= github.com/aws/aws-sdk-go v1.36.1/go.mod h1:hcU610XS61/+aQV88ixoOzUoG7v3b31pl2zKMmprdro= -github.com/aws/aws-sdk-go v1.36.8 h1:3nvY3Ax2RC6PN1i0OKppxjq3doHWqiYtvenLQ/oZ5jI= -github.com/aws/aws-sdk-go v1.36.8/go.mod h1:hcU610XS61/+aQV88ixoOzUoG7v3b31pl2zKMmprdro= -github.com/aws/aws-sdk-go v1.36.12/go.mod h1:hcU610XS61/+aQV88ixoOzUoG7v3b31pl2zKMmprdro= -github.com/aws/aws-sdk-go v1.36.15/go.mod h1:hcU610XS61/+aQV88ixoOzUoG7v3b31pl2zKMmprdro= +github.com/aws/aws-sdk-go v1.36.19 h1:zbJZKkxeDiYxUYFjymjWxPye+qa1G2gRVyhIzZrB9zA= github.com/aws/aws-sdk-go v1.36.19/go.mod h1:hcU610XS61/+aQV88ixoOzUoG7v3b31pl2zKMmprdro= github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= @@ -258,8 +255,8 @@ github.com/google/pprof v0.0.0-20201023163331-3e6fc7fc9c4c/go.mod h1:kpwsk12EmLe github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= github.com/google/subcommands v1.0.1/go.mod h1:ZjhPrFU+Olkh9WazFPsl27BQ4UPiG37m3yTrtFlrHVk= github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/google/uuid v1.1.2 h1:EVhdT+1Kseyi1/pUmXKaFxYsDNy9RQYkMWRH68J/W7Y= github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/uuid v1.1.3 h1:twObb+9XcuH5B9V1TBCvvvZoO6iEdILi2a76PYn5rJI= github.com/google/uuid v1.1.3/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/wire v0.4.0 h1:kXcsA/rIGzJImVqPdhfnr6q0xsS9gU0515q1EPpJ9fE= github.com/google/wire v0.4.0/go.mod h1:ngWDr9Qvq3yZA10YrxfyGELY/AFWGVpy9c1LTRi1EoU= @@ -508,12 +505,10 @@ github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/ github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw= github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw= -github.com/ugorji/go v1.2.1 h1:dz+JxTe7GZQdErTo7SREc1jQj/hFP1k7jyIAwODoW+k= -github.com/ugorji/go v1.2.1/go.mod h1:cSVypSfTLm2o9fKxXvQgn3rMmkPXovcWor6Qn5tbFmI= +github.com/ugorji/go v1.2.2 h1:60ZHIOcsJlo3bJm9CbTVu7OSqT2mxaEmyQbK2NwCkn0= github.com/ugorji/go v1.2.2/go.mod h1:bitgyERdV7L7Db/Z5gfd5v2NQMNhhiFiZwpgMw2SP7k= github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY= -github.com/ugorji/go/codec v1.2.1 h1:/TRfW3XKkvWvmAYyCUaQlhoCDGjcvNR8xVVA/l5p/jQ= -github.com/ugorji/go/codec v1.2.1/go.mod h1:s/WxCRi46t8rA+fowL40EnmD7ec0XhR7ZypxeBNdzsM= +github.com/ugorji/go/codec v1.2.2 h1:08Gah8d+dXj4cZNUHhtuD/S4PXD5WpVbj5B8/ClELAQ= github.com/ugorji/go/codec v1.2.2/go.mod h1:OM8g7OAy52uYl3Yk+RE/3AS1nXFn1Wh4PPLtupCxbuU= github.com/urfave/cli v1.22.4/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= github.com/xanzy/ssh-agent v0.2.1 h1:TCbipTQL2JiiCprBWx9frJ2eJlCYT00NmctrHxVAr70= @@ -555,8 +550,8 @@ golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPh golang.org/x/crypto v0.0.0-20200728195943-123391ffb6de/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20201002170205-7f63de1d35b0/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20201203163018-be400aefbc4c/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= -golang.org/x/crypto v0.0.0-20201208171446-5f87f3452ae9 h1:sYNJzB4J8toYPQTM6pAkcmBRgw9SnQKP9oXCHfgy604= -golang.org/x/crypto v0.0.0-20201208171446-5f87f3452ae9/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= +golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad h1:DN0cp81fZ3njFcrLCytUHRSUkqBjfTo4Tx9RJTWs0EY= +golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= @@ -695,12 +690,14 @@ golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20200828194041-157a740278f4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201202213521-69691e467435/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20201214210602-f9fddec55a1e h1:AyodaIpKjppX+cBfTASF2E1US3H2JFBj920Ot3rtDjs= -golang.org/x/sys v0.0.0-20201214210602-f9fddec55a1e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/term v0.0.0-20201117132131-f5c789dd3221 h1:/ZHdbVpdR/jk3g30/d4yUL0JU9kksj8+F/bnQUVLGDM= +golang.org/x/sys v0.0.0-20201223074533-0d417f636930 h1:vRgIt+nup/B/BwIS0g2oC0haq0iqbV3ZA+u6+0TlNCo= +golang.org/x/sys v0.0.0-20201223074533-0d417f636930/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= +golang.org/x/term v0.0.0-20201210144234-2321bbc49cbf h1:MZ2shdL+ZM/XzY3ZGOnh4Nlpnxz5GSOhOmtHo3iPU6M= +golang.org/x/term v0.0.0-20201210144234-2321bbc49cbf/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= diff --git a/cmd/gomplate/config.go b/internal/cmd/config.go index caaf4963..c3b49582 100644 --- a/cmd/gomplate/config.go +++ b/internal/cmd/config.go @@ -1,4 +1,4 @@ -package main +package cmd import ( "context" diff --git a/cmd/gomplate/config_test.go b/internal/cmd/config_test.go index 97b9a89f..0b59662c 100644 --- a/cmd/gomplate/config_test.go +++ b/internal/cmd/config_test.go @@ -1,4 +1,4 @@ -package main +package cmd import ( "context" diff --git a/internal/cmd/logger.go b/internal/cmd/logger.go new file mode 100644 index 00000000..a60dc96f --- /dev/null +++ b/internal/cmd/logger.go @@ -0,0 +1,37 @@ +package cmd + +import ( + "context" + "io" + stdlog "log" + "os" + "time" + + "github.com/rs/zerolog" + "github.com/rs/zerolog/log" + "golang.org/x/term" +) + +func initLogger(ctx context.Context, out io.Writer) context.Context { + // default to warn level + zerolog.SetGlobalLevel(zerolog.WarnLevel) + zerolog.DurationFieldUnit = time.Second + + stdlogger := log.With().Bool("stdlog", true).Logger() + stdlog.SetFlags(0) + stdlog.SetOutput(stdlogger) + + if f, ok := out.(*os.File); ok { + if term.IsTerminal(int(f.Fd())) { + log.Logger = log.Output(zerolog.ConsoleWriter{Out: f, TimeFormat: "15:04:05"}) + noLevelWriter := zerolog.ConsoleWriter{ + Out: f, + FormatLevel: func(i interface{}) string { return "" }, + } + stdlogger = stdlogger.Output(noLevelWriter) + stdlog.SetOutput(stdlogger) + } + } + + return log.Logger.WithContext(ctx) +} diff --git a/internal/cmd/main.go b/internal/cmd/main.go new file mode 100644 index 00000000..e4d6008d --- /dev/null +++ b/internal/cmd/main.go @@ -0,0 +1,167 @@ +package cmd + +import ( + "context" + "fmt" + "io" + "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" +) + +// postRunExec - if templating succeeds, the command following a '--' will be executed +func postRunExec(ctx context.Context, cfg *config.Config) error { + args := cfg.PostExec + if len(args) > 0 { + log := zerolog.Ctx(ctx) + log.Debug().Strs("args", args).Msg("running post-exec command") + + name := args[0] + args = args[1:] + // nolint: gosec + c := exec.CommandContext(ctx, name, args...) + c.Stdin = cfg.PostExecInput + c.Stderr = os.Stderr + c.Stdout = os.Stdout + + // make sure all signals are propagated + sigs := make(chan os.Signal, 1) + signal.Notify(sigs) + go func() { + // Pass signals to the sub-process + sig := <-sigs + if c.Process != nil { + // nolint: gosec + _ = c.Process.Signal(sig) + } + }() + + return c.Run() + } + return nil +} + +// optionalExecArgs - implements cobra.PositionalArgs. Allows extra args following +// a '--', but not otherwise. +func optionalExecArgs(cmd *cobra.Command, args []string) error { + if cmd.ArgsLenAtDash() == 0 { + return nil + } + return cobra.NoArgs(cmd, args) +} + +// NewGomplateCmd - +func NewGomplateCmd() *cobra.Command { + rootCmd := &cobra.Command{ + Use: "gomplate", + Short: "Process text files with Go templates", + Version: version.Version, + RunE: func(cmd *cobra.Command, args []string) error { + if v, _ := cmd.Flags().GetBool("verbose"); v { + zerolog.SetGlobalLevel(zerolog.DebugLevel) + } + ctx := cmd.Context() + log := zerolog.Ctx(ctx) + + cfg, err := loadConfig(cmd, args) + if err != nil { + return err + } + ctx = config.ContextWithConfig(ctx, cfg) + if cfg.Experimental { + log.UpdateContext(func(c zerolog.Context) zerolog.Context { + return c.Bool("experimental", true) + }) + log.Info().Msg("experimental functions and features enabled!") + } + + log.Debug().Msgf("starting %s", cmd.Name()) + log.Debug(). + Str("version", version.Version). + Str("build", version.GitCommit). + Msgf("config is:\n%v", cfg) + + err = gomplate.Run(ctx, cfg) + cmd.SilenceErrors = true + cmd.SilenceUsage = true + + 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) + }, + Args: optionalExecArgs, + } + return rootCmd +} + +// InitFlags - +func InitFlags(command *cobra.Command) { + command.Flags().SortFlags = false + + 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().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().StringSlice("plugin", nil, "plug in an external command as a function in name=path form. Can be specified multiple times") + + 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().StringSlice("exclude", []string{}, "glob of files to not parse") + command.Flags().StringSlice("include", []string{}, "glob of files to parse") + + 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().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().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().Bool("experimental", false, "enable experimental features [$GOMPLATE_EXPERIMENTAL]") + + 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)") +} + +// Main - +func Main(ctx context.Context, args []string, stdin io.Reader, stdout, stderr io.Writer) error { + ctx = initLogger(ctx, stderr) + + command := NewGomplateCmd() + InitFlags(command) + command.SetArgs(args) + command.SetIn(stdin) + command.SetOut(stdout) + command.SetErr(stderr) + + err := command.ExecuteContext(ctx) + if err != nil { + log := zerolog.Ctx(ctx) + log.Error().Err(err).Send() + } + return err +} diff --git a/internal/cmd/main_test.go b/internal/cmd/main_test.go new file mode 100644 index 00000000..5c688443 --- /dev/null +++ b/internal/cmd/main_test.go @@ -0,0 +1,50 @@ +package cmd + +import ( + "context" + "testing" + + "github.com/spf13/cobra" + "github.com/stretchr/testify/assert" +) + +func TestOptionalExecArgs(t *testing.T) { + cmd := &cobra.Command{} + cmd.SetArgs(nil) + cmd.ParseFlags(nil) + + err := optionalExecArgs(cmd, nil) + assert.NoError(t, err) + + cmd = &cobra.Command{} + cmd.SetArgs(nil) + cmd.ParseFlags(nil) + + err = optionalExecArgs(cmd, []string{"bogus"}) + assert.Error(t, err) + + cmd = &cobra.Command{} + cmd.SetArgs(nil) + cmd.ParseFlags([]string{"--", "foo"}) + + err = optionalExecArgs(cmd, []string{}) + assert.NoError(t, err) + + cmd = &cobra.Command{} + cmd.SetArgs(nil) + cmd.ParseFlags([]string{"--"}) + + err = optionalExecArgs(cmd, []string{"foo"}) + assert.NoError(t, err) +} + +func TestRunMain(t *testing.T) { + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + err := Main(ctx, []string{"-h"}, nil, nil, nil) + assert.NoError(t, err) + + err = Main(ctx, []string{"--bogus"}, nil, nil, nil) + assert.Error(t, err) +} |
