Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

New --exec-pipe output option #643

Merged
merged 1 commit into from
Oct 24, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 15 additions & 1 deletion cmd/gomplate/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ The gomplate command
package main

import (
"bytes"
"fmt"
"os"
"os/exec"
Expand All @@ -19,8 +20,11 @@ import (
var (
printVer bool
verbose bool
execPipe bool
opts gomplate.Config
includes []string

postRunInput *bytes.Buffer
)

func printVersion(name string) {
Expand All @@ -34,7 +38,11 @@ func postRunExec(cmd *cobra.Command, args []string) error {
args = args[1:]
// nolint: gosec
c := exec.Command(name, args...)
c.Stdin = os.Stdin
if execPipe {
c.Stdin = postRunInput
} else {
c.Stdin = os.Stdin
}
c.Stderr = os.Stderr
c.Stdout = os.Stdout

Expand Down Expand Up @@ -101,6 +109,10 @@ func newGomplateCmd() *cobra.Command {
// support --include
opts.ExcludeGlob = processIncludes(includes, opts.ExcludeGlob)

if execPipe {
postRunInput = &bytes.Buffer{}
opts.Out = postRunInput
}
err := gomplate.RunTemplates(&opts)
cmd.SilenceErrors = true
cmd.SilenceUsage = true
Expand Down Expand Up @@ -140,6 +152,8 @@ func initFlags(command *cobra.Command) {
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().BoolVar(&execPipe, "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]")
Expand Down
4 changes: 4 additions & 0 deletions cmd/gomplate/validate.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,10 @@ func validateOpts(cmd *cobra.Command, args []string) (err error) {
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")
}
Expand Down
35 changes: 35 additions & 0 deletions cmd/gomplate/validate_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,41 @@ func TestValidateOpts(t *testing.T) {
))
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",
Expand Down
2 changes: 2 additions & 0 deletions config.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package gomplate

import (
"io"
"os"
"strconv"
"strings"
Expand All @@ -17,6 +18,7 @@ type Config struct {
OutputDir string
OutputMap string
OutMode string
Out io.Writer

DataSources []string
DataSourceHeaders []string
Expand Down
18 changes: 18 additions & 0 deletions docs/content/usage.md
Original file line number Diff line number Diff line change
Expand Up @@ -232,6 +232,21 @@ By default, plugins will time out after 5 seconds. To adjust this, set the
`GOMPLATE_PLUGIN_TIMEOUT` environment variable to a valid [duration](../functions/time/#time-parseduration)
such as `10s` or `3m`.

### `--exec-pipe`

When using [post-template command execution](#post-template-command-execution),
it may be useful to pipe gomplate's rendered output directly into the command's
standard input.

To do this, simply use `--exec-pipe` instead of `--out` or any other output flag:

```console
$ gomplate -i 'hello world' --exec-pipe -- tr a-z A-Z
HELLO WORLD
```

Note that multiple inputs are not yet supported when using this option.

## Post-template command execution

Gomplate can launch other commands when template execution is successful. Simply
Expand All @@ -242,6 +257,9 @@ $ gomplate -i 'hello world' -o out.txt -- cat out.txt
hello world
```

See also [`--exec-pipe`](#exec-pipe) for piping output directly into the
post-exec command.

## Suppressing empty output

Sometimes it can be desirable to suppress empty output (i.e. output consisting of only whitespace). To do so, set `GOMPLATE_SUPPRESS_EMPTY=true` in your environment:
Expand Down
10 changes: 0 additions & 10 deletions gomplate_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ package gomplate

import (
"bytes"
"io"
"net/http/httptest"
"os"
"path/filepath"
Expand All @@ -19,15 +18,6 @@ import (
"github.com/stretchr/testify/assert"
)

// like ioutil.NopCloser(), except for io.WriteClosers...
type nopWCloser struct {
io.Writer
}

func (n *nopWCloser) Close() error {
return nil
}

func testTemplate(g *gomplate, tmpl string) string {
var out bytes.Buffer
err := g.runTemplate(&tplate{name: "testtemplate", contents: tmpl, target: &out})
Expand Down
14 changes: 14 additions & 0 deletions template.go
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,11 @@ func gatherTemplates(o *Config, outFileNamer func(string) (string, error)) (temp
return nil, err
}

// --exec-pipe redirects standard out to the out pipe
if o.Out != nil {
Stdout = &nopWCloser{o.Out}
}

switch {
// the arg-provided input string gets a special name
case o.Input != "":
Expand Down Expand Up @@ -336,3 +341,12 @@ func allWhitespace(p []byte) bool {
}
return true
}

// like ioutil.NopCloser(), except for io.WriteClosers...
type nopWCloser struct {
io.Writer
}

func (n *nopWCloser) Close() error {
return nil
}
11 changes: 11 additions & 0 deletions tests/integration/basic_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -184,6 +184,17 @@ func (s *BasicSuite) TestExecCommand(c *C) {
})
}

func (s *BasicSuite) TestPostRunExecPipe(c *C) {
result := icmd.RunCmd(icmd.Command(GomplateBin,
"-i", `{{print "hello world"}}`,
"--exec-pipe",
"--", "tr", "a-z", "A-Z"))
result.Assert(c, icmd.Expected{
ExitCode: 0,
Out: "HELLO WORLD",
})
}

func (s *BasicSuite) TestEmptyOutputSuppression(c *C) {
out := s.tmpDir.Join("out")
result := icmd.RunCmd(icmd.Command(GomplateBin,
Expand Down