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

[feat] ExecForEach: Get parameters by column index #153

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
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
4 changes: 3 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -172,7 +172,9 @@ In the `ping` example, we knew the exact arguments we wanted to send the command
We might like to be able to run the external command repeatedly, each time passing it the next line of data from the pipe as an argument. No worries:

```go
script.Args().ExecForEach("ping -c 1 {{.}}").Stdout()
// {{.Raw}} is original input
// {{index .Cols N}} N indicates which column;
script.Args().ExecForEach("ping -c 1 {{.Raw}}").Stdout()
```

That `{{.}}` is standard Go template syntax; it'll substitute each line of data from the pipe into the command line before it's executed. You can write as fancy a Go template expression as you want here (but this simple example probably covers most use cases).
Expand Down
13 changes: 12 additions & 1 deletion script.go
Original file line number Diff line number Diff line change
Expand Up @@ -399,6 +399,12 @@ func (p *Pipe) Exec(cmdLine string) *Pipe {
})
}

// inputFields for Go template
type inputFields struct {
Raw string // original input
Cols []string // columns of line
}

// ExecForEach renders cmdLine as a Go template for each line of input, running
// the resulting command, and produces the combined output of all these
// commands in sequence. See [Pipe.Exec] for error handling details.
Expand All @@ -416,7 +422,12 @@ func (p *Pipe) ExecForEach(cmdLine string) *Pipe {
scanner := bufio.NewScanner(r)
for scanner.Scan() {
cmdLine := strings.Builder{}
err := tpl.Execute(&cmdLine, scanner.Text())
line := scanner.Text()
fields := strings.Fields(line)
err := tpl.Execute(&cmdLine, inputFields{
Raw: line,
Cols: fields,
})
if err != nil {
return err
}
Expand Down
24 changes: 22 additions & 2 deletions script_unix_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ func TestExecErrorsRunningShellCommandWithUnterminatedStringArgument(t *testing.

func TestExecForEach_RunsEchoWithABCAndGetsOutputABC(t *testing.T) {
t.Parallel()
p := script.Echo("a\nb\nc\n").ExecForEach("echo {{.}}")
p := script.Echo("a\nb\nc\n").ExecForEach("echo {{.Raw}}")
if p.Error() != nil {
t.Fatal(p.Error())
}
Expand Down Expand Up @@ -82,6 +82,22 @@ func TestExecForEach_CorrectlyEvaluatesTemplateContainingIfStatement(t *testing.
}
}

func TestExecForEach_GetColunmsByIndex(t *testing.T) {
t.Parallel()
p := script.Echo("a b c d e\naa bb cc dd ee").ExecForEach("echo {{index .Cols 1}} {{index .Cols 4}}; ")
if p.Error() != nil {
t.Fatal(p.Error())
}
want := "b e;\nbb ee;\n"
got, err := p.String()
if err != nil {
t.Fatal(err)
}
if want != got {
t.Error(cmp.Diff(want, got))
}
}

func TestExecPipesDataToExternalCommandAndGetsExpectedOutput(t *testing.T) {
t.Parallel()
p := script.File("testdata/hello.txt").Exec("cat")
Expand Down Expand Up @@ -182,9 +198,13 @@ func ExamplePipe_Exec() {
}

func ExamplePipe_ExecForEach() {
script.Echo("a\nb\nc\n").ExecForEach("echo {{.}}").Stdout()
script.Echo("a\nb\nc\n").ExecForEach("echo {{.Raw}}").Stdout()
script.Echo("a aa\nb bb\nc cc\n").ExecForEach("echo {{index .Cols 1}} {{index .Cols 0}}").Stdout()
// Output:
// a
// b
// c
// aa a
// bb b
// cc c
}