attempt to improve string escaping

This commit is contained in:
Brad Rydzewski 2019-06-06 19:09:32 -07:00
parent 596b719f96
commit 82ab5cffdd
7 changed files with 137 additions and 29 deletions

View File

@ -5,7 +5,7 @@ kind: cron
spec: spec:
branch: master branch: master
schedule: "1 * * * *" schedule: 1 * * * *
deployment: deployment:

View File

@ -3,7 +3,7 @@ version: 1
kind: cron kind: cron
name: nightly name: nightly
spec: spec:
schedule: "1 * * * *" schedule: 1 * * * *
branch: master branch: master
deployment: deployment:
target: production target: production

View File

@ -45,7 +45,7 @@ data: >
kind: cron kind: cron
name: nightly name: nightly
spec: spec:
schedule: "1 * * * *" schedule: 1 * * * *
branch: master branch: master
deployment: deployment:
target: production target: production

View File

@ -14,7 +14,11 @@
package pretty package pretty
import "github.com/drone/drone-yaml/yaml" import (
"strings"
"github.com/drone/drone-yaml/yaml"
)
func isPrimative(v interface{}) bool { func isPrimative(v interface{}) bool {
switch v.(type) { switch v.(type) {
@ -63,10 +67,8 @@ func isZero(v interface{}) bool {
} }
} }
func isQuoted(b rune) bool { func isEscapeCode(b rune) bool {
switch b { switch b {
case '#', ',', '[', ']', '{', '}', '&', '*', '!', '\'', '"', '%', '@', '`':
return true
case '\a', '\b', '\f', '\n', '\r', '\t', '\v': case '\a', '\b', '\f', '\n', '\r', '\t', '\v':
return true return true
default: default:
@ -74,6 +76,61 @@ func isQuoted(b rune) bool {
} }
} }
func isQuoted(s string) bool {
// if the string is empty it should be quoted.
if len(s) == 0 {
return true
}
var r0, r1 byte
t := strings.TrimSpace(s)
// if the trimmed string does not match the string, it
// has starting or tailing white space and therefore
// needs to be quoted to preserve the whitespace
if t != s {
return true
}
if len(t) > 0 {
r0 = t[0]
}
if len(t) > 1 {
r1 = t[1]
}
switch r0 {
// if the yaml starts with any of these characters
// the string should be quoted.
case ',', '[', ']', '{', '}', '*', '"', '\'', '%', '@', '`', '|', '>', '#':
return true
case '&', '!', '-', ':', '?':
// if the yaml starts with any of these characters,
// followed by whitespace, the string should be quoted.
if r1 == ' ' {
return true
}
}
var prev rune
for _, b := range s {
switch {
case isEscapeCode(b):
return true
case b == ' ' && prev == ':':
return true
case b == '#' && prev == ' ':
return true
}
prev = b
}
// if the string ends in : it should be quoted otherwise
// it is interpreted as an object.
return strings.HasSuffix(t, ":")
}
func chunk(s string, chunkSize int) []string { func chunk(s string, chunkSize int) []string {
if len(s) == 0 { if len(s) == 0 {
return []string{s} return []string{s}

View File

@ -21,6 +21,73 @@ import (
"github.com/google/go-cmp/cmp" "github.com/google/go-cmp/cmp"
) )
func TestQuoted(t *testing.T) {
tests := []struct{
before, after string
}{
{"", `""`},
{"foo", "foo"},
// special characters only quoted when followed
// by whitespace.
{"&foo", "&foo"},
{"!foo", "!foo"},
{"-foo", "-foo"},
{":foo", ":foo"},
{"& foo", `"& foo"`},
{"! foo", `"! foo"`},
{"- foo", `"- foo"`},
{": foo", `": foo"`},
{" & foo", `" & foo"`},
{" ! foo", `" ! foo"`},
{" - foo", `" - foo"`},
{" : foo", `" : foo"`},
// special characters only quoted when it is the
// first character in the string.
{",foo", `",foo"`},
{"[foo", `"[foo"`},
{"]foo", `"]foo"`},
{"{foo", `"{foo"`},
{"}foo", `"}foo"`},
{"*foo", `"*foo"`},
{`"foo`, `"\"foo"`},
{`'foo`, `"'foo"`},
{`%foo`, `"%foo"`},
{`@foo`, `"@foo"`},
{`|foo`, `"|foo"`},
{`>foo`, `">foo"`},
{`#foo`, `"#foo"`},
{`foo:bar`, `foo:bar`},
{`foo :bar`, `foo :bar`},
{`foo: bar`, `"foo: bar"`},
{`foo:`, `"foo:"`},
{`alpine:3.8`, `alpine:3.8`}, // verify docker image names are ok
// comments should be escaped. A comment is a pound
// sybol preceded by a space.
{`foo#bar`, `foo#bar`},
{`foo #bar`, `"foo #bar"`},
// strings with newlines and control characters
// should be escaped
{"foo\nbar", "\"foo\\nbar\""},
}
for _, test := range tests {
buf := new(baseWriter)
writeEncode(buf, test.before)
a := test.after
b := buf.String()
if b != a {
t.Errorf("Want %q, got %q", a, b)
}
}
}
func TestChunk(t *testing.T) { func TestChunk(t *testing.T) {
s := strings.Join(testChunk, "") s := strings.Join(testChunk, "")
got, want := chunk(s, 64), testChunk got, want := chunk(s, 64), testChunk

View File

@ -19,7 +19,6 @@ import (
"fmt" "fmt"
"sort" "sort"
"strconv" "strconv"
"strings"
"github.com/drone/drone-yaml/yaml" "github.com/drone/drone-yaml/yaml"
) )
@ -196,28 +195,11 @@ func writeEncode(w writer, v string) {
w.WriteByte('"') w.WriteByte('"')
return return
} }
trimmed := strings.TrimSpace(v) if isQuoted(v) {
if strings.HasPrefix(trimmed, "| ") {
fmt.Fprintf(w, "%q", v) fmt.Fprintf(w, "%q", v)
return } else {
w.WriteString(v)
} }
if strings.HasPrefix(trimmed, "> ") {
fmt.Fprintf(w, "%q", v)
return
}
var prev rune
for _, b := range v {
if isQuoted(b) {
fmt.Fprintf(w, "%q", v)
return
}
if b == ' ' && prev == ':' {
fmt.Fprintf(w, "%q", v)
return
}
prev = b
}
w.WriteString(v)
} }
func writeValue(w writer, v interface{}) { func writeValue(w writer, v interface{}) {

View File

@ -36,7 +36,9 @@ func TestWriteComplexValue(t *testing.T) {
got, want := b.String(), strings.TrimSpace(testComplexValue) got, want := b.String(), strings.TrimSpace(testComplexValue)
if got != want { if got != want {
t.Errorf("Unexpected block format") t.Errorf("Unexpected block format")
print(got) println(got)
println("---")
println(want)
} }
} }