From 5b7034b22aafe890cbb216bc8b4b5f7daa8972df Mon Sep 17 00:00:00 2001 From: Steven Berlanga Date: Sun, 14 Feb 2016 23:37:40 -0500 Subject: [PATCH 1/2] Parsing string args correctly There was a bug in the code that cause commands that had quotes in them to not work as expected. Commands are no longer split just on a whitespace. If an opening quote is found the split function finds its ending quote and returns the entire chunk as one argument. --- builder.go | 43 +++++++++++++++++++++++++++++++++++++++++-- builder_test.go | 29 +++++++++++++++++++++++++++++ 2 files changed, 70 insertions(+), 2 deletions(-) diff --git a/builder.go b/builder.go index 43bddc0..c1ff548 100644 --- a/builder.go +++ b/builder.go @@ -1,6 +1,8 @@ package main import ( + "bufio" + "bytes" "fmt" "log" "os" @@ -41,8 +43,12 @@ func NewBuilder(c config) (*Bob, error) { return nil, err } - parseCmd := func(cmd string) []string { - c := strings.Split(cmd, " ") + parseCmd := func(cmd string) (c []string) { + s := bufio.NewScanner(strings.NewReader(cmd)) + s.Split(splitFunc) + for s.Scan() { + c = append(c, s.Text()) + } // check for environment variables inside script if strings.Contains(cmd, "$$") { @@ -73,6 +79,39 @@ func NewBuilder(c config) (*Bob, error) { }, nil } +func splitFunc(data []byte, atEOF bool) (advance int, token []byte, err error) { + advance, token, err = bufio.ScanWords(data, atEOF) + if err != nil { + return + } + + if len(token) == 0 { + return + } + + b := token[0] + if b != '"' && b != '\'' { + return + } + + if token[len(token)-1] == b { + return + } + + chunk := data[advance-1:] + i := bytes.IndexByte(chunk, b) + if i == -1 { + advance = len(data) + token = append(token, chunk...) + return + } + + advance += i + token = append(token, chunk[:i+1]...) + + return +} + func replaceEnv(cmds []string) { for i, c := range cmds { if !strings.HasPrefix(c, "$$") { diff --git a/builder_test.go b/builder_test.go index cf27260..77cf044 100644 --- a/builder_test.go +++ b/builder_test.go @@ -33,6 +33,35 @@ func TestNewBuilder(t *testing.T) { assert.Equal(t, c.IgnoredItems, b.ignoredItems) } +func TestNewBuilder_CmdWithQuotes(t *testing.T) { + c := config{ + Build: []string{ + `echo "hello world" foo`, + `echo "ga ga oh la la`, + `echo "ga" "foo"`, + `echo -c 'foo "bar"'`, + }, + } + + b, err := NewBuilder(c) + assert.NoError(t, err) + + assert.Equal(t, "echo", b.buildCmds[0][0]) + assert.Equal(t, `"hello world"`, b.buildCmds[0][1]) + assert.Equal(t, "foo", b.buildCmds[0][2]) + + assert.Equal(t, "echo", b.buildCmds[1][0]) + assert.Equal(t, `"ga ga oh la la`, b.buildCmds[1][1]) + + assert.Equal(t, "echo", b.buildCmds[2][0]) + assert.Equal(t, `"ga"`, b.buildCmds[2][1]) + assert.Equal(t, `"foo"`, b.buildCmds[2][2]) + + assert.Equal(t, "echo", b.buildCmds[3][0]) + assert.Equal(t, `-c`, b.buildCmds[3][1]) + assert.Equal(t, `'foo "bar"'`, b.buildCmds[3][2]) +} + func TestClose(t *testing.T) { b, err := NewBuilder(config{}) require.NoError(t, err) From 81645cc134fef95107b09873d2bccc7a2bcea1a9 Mon Sep 17 00:00:00 2001 From: Steven Berlanga Date: Thu, 18 Feb 2016 15:31:01 -0500 Subject: [PATCH 2/2] coverting cmdWithQuoteTest to table test --- builder_test.go | 62 ++++++++++++++++++++++++++++++++----------------- 1 file changed, 41 insertions(+), 21 deletions(-) diff --git a/builder_test.go b/builder_test.go index 77cf044..d2820d0 100644 --- a/builder_test.go +++ b/builder_test.go @@ -34,32 +34,52 @@ func TestNewBuilder(t *testing.T) { } func TestNewBuilder_CmdWithQuotes(t *testing.T) { - c := config{ - Build: []string{ - `echo "hello world" foo`, - `echo "ga ga oh la la`, - `echo "ga" "foo"`, - `echo -c 'foo "bar"'`, + tests := []struct { + Command string + Chunks []string + }{ + { // one single quote pair + Command: `echo 'hello world' foo`, + Chunks: []string{`echo`, `'hello world'`, `foo`}, + }, + { // one double quote pair + Command: `echo "hello world" foo`, + Chunks: []string{`echo`, `"hello world"`, `foo`}, + }, + { // no ending double quote + Command: `echo "ga ga oh la la`, + Chunks: []string{`echo`, `"ga ga oh la la`}, + }, + { // no ending single quote + Command: `echo 'ga ga oh la la`, + Chunks: []string{`echo`, `'ga ga oh la la`}, + }, + { // multiple double quotes + Command: `echo "ga" "foo"`, + Chunks: []string{`echo`, `"ga"`, `"foo"`}, + }, + { // double quotes inside single quotes + Command: `echo -c 'foo "bar"'`, + Chunks: []string{`echo`, `-c`, `'foo "bar"'`}, + }, + { // single quotes inside double quotes + Command: `echo -c "foo 'bar'"`, + Chunks: []string{`echo`, `-c`, `"foo 'bar'"`}, }, } - b, err := NewBuilder(c) - assert.NoError(t, err) - - assert.Equal(t, "echo", b.buildCmds[0][0]) - assert.Equal(t, `"hello world"`, b.buildCmds[0][1]) - assert.Equal(t, "foo", b.buildCmds[0][2]) - - assert.Equal(t, "echo", b.buildCmds[1][0]) - assert.Equal(t, `"ga ga oh la la`, b.buildCmds[1][1]) + for _, test := range tests { + c := config{ + Build: []string{test.Command}, + Run: []string{test.Command}, + } - assert.Equal(t, "echo", b.buildCmds[2][0]) - assert.Equal(t, `"ga"`, b.buildCmds[2][1]) - assert.Equal(t, `"foo"`, b.buildCmds[2][2]) + b, err := NewBuilder(c) + require.NoError(t, err) - assert.Equal(t, "echo", b.buildCmds[3][0]) - assert.Equal(t, `-c`, b.buildCmds[3][1]) - assert.Equal(t, `'foo "bar"'`, b.buildCmds[3][2]) + assert.Equal(t, test.Chunks, b.buildCmds[0]) + assert.Equal(t, test.Chunks, b.runCmds[0]) + } } func TestClose(t *testing.T) {