blob: 2559a49650d7fae3074a152920a90409f118a321 [file] [log] [blame]
package shell
import (
"fmt"
"io/ioutil"
"log"
"reflect"
"strings"
"testing"
)
func TestQuote(t *testing.T) {
tests := []struct {
in, want string
}{
{"", "''"}, // empty is special
{"abc", "abc"}, // nothing to quote
{"--flag", "--flag"}, // "
{"'abc", `\'abc`}, // single quote only
{"abc'", `abc\'`}, // "
{`shan't`, `shan\'t`}, // "
{"--flag=value", `'--flag=value'`},
{"a b\tc", "'a b\tc'"},
{`a"b"c`, `'a"b"c'`},
{`'''`, `\'\'\'`},
{`\`, `'\'`},
{`'a=b`, `\''a=b'`}, // quotes and other stuff
{`a='b`, `'a='\''b'`}, // "
{`a=b'`, `'a=b'\'`}, // "
}
for _, test := range tests {
got := Quote(test.in)
if got != test.want {
t.Errorf("Quote %q: got %q, want %q", test.in, got, test.want)
}
}
}
func TestSplit(t *testing.T) {
tests := []struct {
in string
want []string
ok bool
}{
// Variations of empty input yield an empty split.
{"", nil, true},
{" ", nil, true},
{"\t", nil, true},
{"\n ", nil, true},
// Various escape sequences work properly.
{`\ `, []string{" "}, true},
{`a\ `, []string{"a "}, true},
{`\\a`, []string{`\a`}, true},
{`"a\"b"`, []string{`a"b`}, true},
{`'\'`, []string{"\\"}, true},
// Leading and trailing whitespace are discarded correctly.
{"a", []string{"a"}, true},
{" a", []string{"a"}, true},
{"a\n", []string{"a"}, true},
// Escaped newlines are magic in the correct ways.
{"a\\\nb", []string{"ab"}, true},
{"a \\\n b\tc", []string{"a", "b", "c"}, true},
// Various splits with and without quotes. Quoted whitespace is
// preserved.
{"a b c", []string{"a", "b", "c"}, true},
{`a 'b c'`, []string{"a", "b c"}, true},
{"\"a\nb\"cd e'f'", []string{"a\nbcd", "ef"}, true},
{"'\n \t '", []string{"\n \t "}, true},
// Quoted empty strings are preserved in various places.
{"''", []string{""}, true},
{"a ''", []string{"a", ""}, true},
{" a \"\" b ", []string{"a", "", "b"}, true},
{"'' a", []string{"", "a"}, true},
// Unbalanced quotation marks and escapes are detected.
{"\\", []string{""}, false}, // escape without a target
{"'", []string{""}, false}, // unclosed single
{`"`, []string{""}, false}, // unclosed double
{`'\''`, []string{`\`}, false}, // unclosed connected double
{`"\\" '`, []string{`\`, ``}, false}, // unclosed separate single
{"a 'b c", []string{"a", "b c"}, false},
{`a "b c`, []string{"a", "b c"}, false},
{`a "b \"`, []string{"a", `b "`}, false},
}
for _, test := range tests {
got, ok := Split(test.in)
if ok != test.ok {
t.Errorf("Split %#q: got valid=%v, want %v", test.in, ok, test.ok)
}
if !reflect.DeepEqual(got, test.want) {
t.Errorf("Split %#q: got %+q, want %+q", test.in, got, test.want)
}
}
}
func TestRoundTrip(t *testing.T) {
tests := [][]string{
nil,
{"a"},
{"a "},
{"a", "b", "c"},
{"a", "b c"},
{"--flag=value"},
{"m='$USER'", "nop+", "$$"},
{`"a" b `, "c"},
{"a=b", "--foo", "${bar}", `\$`},
{"cat", "a${b}.txt", "|", "tee", "capture", "2>", "/dev/null"},
}
for _, test := range tests {
s := Join(test)
t.Logf("Join %#q = %v", test, s)
got, ok := Split(s)
if !ok {
t.Errorf("Split %+q: should be valid, but is not", s)
}
if !reflect.DeepEqual(got, test) {
t.Errorf("Split %+q: got %q, want %q", s, got, test)
}
}
}
func ExampleScanner() {
const input = `a "free range" exploration of soi\ disant novelties`
s := NewScanner(strings.NewReader(input))
sum, count := 0, 0
for s.Next() {
count++
sum += len(s.Text())
}
fmt.Println(len(input), count, sum, s.Complete(), s.Err())
// Output: 51 6 43 true EOF
}
func ExampleScanner_Rest() {
const input = `things 'and stuff' %end% all the remaining stuff`
s := NewScanner(strings.NewReader(input))
for s.Next() {
if s.Text() == "%end%" {
fmt.Print("found marker; ")
break
}
}
rest, err := ioutil.ReadAll(s.Rest())
if err != nil {
log.Fatal(err)
}
fmt.Println(string(rest))
// Output: found marker; all the remaining stuff
}
func ExampleScanner_Each() {
const input = `a\ b 'c d' "e f's g" stop "go directly to jail"`
if err := NewScanner(strings.NewReader(input)).Each(func(tok string) bool {
fmt.Println(tok)
return tok != "stop"
}); err != nil {
log.Fatal(err)
}
// Output:
// a b
// c d
// e f's g
// stop
}