Skip to content

Commit

Permalink
New --exec-pipe output option
Browse files Browse the repository at this point in the history
Signed-off-by: Dave Henderson <dhenderson@gmail.com>
  • Loading branch information
hairyhenderson committed Oct 23, 2019
1 parent 08d705e commit 36c9ba3
Show file tree
Hide file tree
Showing 7 changed files with 120 additions and 37 deletions.
25 changes: 24 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"
"errors"
"fmt"
"os"
Expand All @@ -20,8 +21,11 @@ import (
var (
printVer bool
verbose bool
execPipe bool
opts gomplate.Config
includes []string

postRunInput *bytes.Buffer
)

// nolint: gocyclo
Expand All @@ -38,6 +42,15 @@ func validateOpts(cmd *cobra.Command, args []string) error {
return errors.New("--input-dir can not be used together with --in or --file")
}

if cmd.Flag("exec-pipe").Changed {
if len(args) == 0 {
return errors.New("--exec-pipe may only be used with a post-exec command after --")
}
if cmd.Flag("out").Changed || cmd.Flag("output-dir").Changed || cmd.Flag("output-map").Changed {
return errors.New("--exec-pipe can not be used together with --out or --output-dir or --output-map")
}
}

if cmd.Flag("output-dir").Changed {
if cmd.Flag("out").Changed {
return errors.New("--output-dir can not be used together with --out")
Expand Down Expand Up @@ -69,7 +82,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 @@ -136,6 +153,10 @@ func newGomplateCmd() *cobra.Command {
// support --include
opts.ExcludeGlob = processIncludes(includes, opts.ExcludeGlob)

if execPipe {
postRunInput = &bytes.Buffer{}
opts.OutPipe = postRunInput
}
err := gomplate.RunTemplates(&opts)
cmd.SilenceErrors = true
cmd.SilenceUsage = true
Expand Down Expand Up @@ -175,6 +196,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
77 changes: 51 additions & 26 deletions cmd/gomplate/main_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,65 +8,90 @@ import (
)

func TestValidateOpts(t *testing.T) {
err := validateOpts(parseFlags(), nil)
err := validateOpts(parseFlags())
assert.NoError(t, err)

cmd := parseFlags("-i=foo", "-f", "bar")
err = validateOpts(cmd, nil)
err = validateOpts(parseFlags("-i=foo", "-f", "bar"))
assert.Error(t, err)

cmd = parseFlags("-i=foo", "-o=bar", "-o=baz")
err = validateOpts(cmd, nil)
err = validateOpts(parseFlags("-i=foo", "-o=bar", "-o=baz"))
assert.Error(t, err)

cmd = parseFlags("-i=foo", "--input-dir=baz")
err = validateOpts(cmd, nil)
err = validateOpts(parseFlags("-i=foo", "--input-dir=baz"))
assert.Error(t, err)

cmd = parseFlags("--input-dir=foo", "-f=bar")
err = validateOpts(cmd, nil)
err = validateOpts(parseFlags("--input-dir=foo", "-f=bar"))
assert.Error(t, err)

cmd = parseFlags("--output-dir=foo", "-o=bar")
err = validateOpts(cmd, nil)
err = validateOpts(parseFlags("--output-dir=foo", "-o=bar"))
assert.Error(t, err)

cmd = parseFlags("--output-dir=foo")
err = validateOpts(cmd, nil)
err = validateOpts(parseFlags("--output-dir=foo"))
assert.Error(t, err)

cmd = parseFlags("--output-map", "bar")
err = validateOpts(cmd, nil)
err = validateOpts(parseFlags("--output-map", "bar"))
assert.Error(t, err)

cmd = parseFlags("-o", "foo", "--output-map", "bar")
err = validateOpts(cmd, nil)
err = validateOpts(parseFlags("-o", "foo", "--output-map", "bar"))
assert.Error(t, err)

cmd = parseFlags(
err = validateOpts(parseFlags(
"--input-dir", "in",
"--output-dir", "foo",
"--output-map", "bar",
)
err = validateOpts(cmd, nil)
))
assert.Error(t, err)

cmd = parseFlags(
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",
)
err = validateOpts(cmd, nil)
))
assert.NoError(t, err)
}

func parseFlags(flags ...string) *cobra.Command {
cmd := &cobra.Command{}
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
return cmd, cmd.Flags().Args()
}

func TestProcessIncludes(t *testing.T) {
Expand Down
4 changes: 4 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 @@ -18,6 +19,9 @@ type Config struct {
OutputMap string
OutMode string

OutPipe io.Writer
InPipe io.Reader

DataSources []string
DataSourceHeaders []string
Contexts []string
Expand Down
16 changes: 16 additions & 0 deletions docs/content/usage.md
Original file line number Diff line number Diff line change
Expand Up @@ -232,6 +232,19 @@ 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
```

## Post-template command execution

Gomplate can launch other commands when template execution is successful. Simply
Expand All @@ -242,6 +255,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.OutPipe != nil {
Stdout = &nopWCloser{o.OutPipe}
}

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

0 comments on commit 36c9ba3

Please sign in to comment.