summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDave Henderson <dhenderson@gmail.com>2021-01-02 11:08:39 -0500
committerGitHub <noreply@github.com>2021-01-02 11:08:39 -0500
commit09a33cdffa6d0b4e57e12827f7075fb2d6c6a543 (patch)
tree40e877686ffc7dc9a84a96bb4ee7a586f9aaf203
parent6c558e8a6531cddf37cf1e6f5ac88c512b271ad3 (diff)
parent9e952fc8b4158c5e64b3185acd5d2b3fc7b15445 (diff)
Merge pull request #1022 from hairyhenderson/internal-main
Move main logic to internal
-rw-r--r--cmd/gomplate/logger.go34
-rw-r--r--cmd/gomplate/main.go152
-rw-r--r--cmd/gomplate/main_test.go1
-rw-r--r--go.mod6
-rw-r--r--go.sum25
-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.go37
-rw-r--r--internal/cmd/main.go167
-rw-r--r--internal/cmd/main_test.go50
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
diff --git a/go.mod b/go.mod
index 904ca1df..a1a1af9c 100644
--- a/go.mod
+++ b/go.mod
@@ -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
diff --git a/go.sum b/go.sum
index c7714e56..9117e65f 100644
--- a/go.sum
+++ b/go.sum
@@ -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)
+}