Add support for tab in basic string value and quoted key (#364)
This commit is contained in:
@@ -313,7 +313,7 @@ func (l *tomlLexer) lexKey() tomlLexStateFn {
|
|||||||
for r := l.peek(); isKeyChar(r) || r == '\n' || r == '\r'; r = l.peek() {
|
for r := l.peek(); isKeyChar(r) || r == '\n' || r == '\r'; r = l.peek() {
|
||||||
if r == '"' {
|
if r == '"' {
|
||||||
l.next()
|
l.next()
|
||||||
str, err := l.lexStringAsString(`"`, false, true, false)
|
str, err := l.lexStringAsString(`"`, false, true)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return l.errorf(err.Error())
|
return l.errorf(err.Error())
|
||||||
}
|
}
|
||||||
@@ -419,7 +419,7 @@ func (l *tomlLexer) lexLiteralString() tomlLexStateFn {
|
|||||||
// Lex a string and return the results as a string.
|
// Lex a string and return the results as a string.
|
||||||
// Terminator is the substring indicating the end of the token.
|
// Terminator is the substring indicating the end of the token.
|
||||||
// The resulting string does not include the terminator.
|
// The resulting string does not include the terminator.
|
||||||
func (l *tomlLexer) lexStringAsString(terminator string, discardLeadingNewLine, acceptNewLines bool, acceptTab bool) (string, error) {
|
func (l *tomlLexer) lexStringAsString(terminator string, discardLeadingNewLine, acceptNewLines bool) (string, error) {
|
||||||
growingString := ""
|
growingString := ""
|
||||||
|
|
||||||
if discardLeadingNewLine {
|
if discardLeadingNewLine {
|
||||||
@@ -512,8 +512,7 @@ func (l *tomlLexer) lexStringAsString(terminator string, discardLeadingNewLine,
|
|||||||
} else {
|
} else {
|
||||||
r := l.peek()
|
r := l.peek()
|
||||||
|
|
||||||
if 0x00 <= r && r <= 0x1F && !(acceptNewLines && (r == '\n' || r == '\r')) &&
|
if 0x00 <= r && r <= 0x1F && r != '\t' && !(acceptNewLines && (r == '\n' || r == '\r')) {
|
||||||
!(acceptTab && r == '\t') {
|
|
||||||
return "", fmt.Errorf("unescaped control character %U", r)
|
return "", fmt.Errorf("unescaped control character %U", r)
|
||||||
}
|
}
|
||||||
l.next()
|
l.next()
|
||||||
@@ -535,17 +534,15 @@ func (l *tomlLexer) lexString() tomlLexStateFn {
|
|||||||
terminator := `"`
|
terminator := `"`
|
||||||
discardLeadingNewLine := false
|
discardLeadingNewLine := false
|
||||||
acceptNewLines := false
|
acceptNewLines := false
|
||||||
acceptTab := false
|
|
||||||
if l.follow(`""`) {
|
if l.follow(`""`) {
|
||||||
l.skip()
|
l.skip()
|
||||||
l.skip()
|
l.skip()
|
||||||
terminator = `"""`
|
terminator = `"""`
|
||||||
discardLeadingNewLine = true
|
discardLeadingNewLine = true
|
||||||
acceptNewLines = true
|
acceptNewLines = true
|
||||||
acceptTab = true
|
|
||||||
}
|
}
|
||||||
|
|
||||||
str, err := l.lexStringAsString(terminator, discardLeadingNewLine, acceptNewLines, acceptTab)
|
str, err := l.lexStringAsString(terminator, discardLeadingNewLine, acceptNewLines)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return l.errorf(err.Error())
|
return l.errorf(err.Error())
|
||||||
|
|||||||
@@ -707,6 +707,15 @@ func TestEscapeInString(t *testing.T) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestTabInString(t *testing.T) {
|
||||||
|
testFlow(t, `foo = "hello world"`, []token{
|
||||||
|
{Position{1, 1}, tokenKey, "foo"},
|
||||||
|
{Position{1, 5}, tokenEqual, "="},
|
||||||
|
{Position{1, 8}, tokenString, "hello\tworld"},
|
||||||
|
{Position{1, 20}, tokenEOF, ""},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
func TestKeyGroupArray(t *testing.T) {
|
func TestKeyGroupArray(t *testing.T) {
|
||||||
testFlow(t, "[[foo]]", []token{
|
testFlow(t, "[[foo]]", []token{
|
||||||
{Position{1, 1}, tokenDoubleLeftBracket, "[["},
|
{Position{1, 1}, tokenDoubleLeftBracket, "[["},
|
||||||
@@ -725,6 +734,15 @@ func TestQuotedKey(t *testing.T) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestQuotedKeyTab(t *testing.T) {
|
||||||
|
testFlow(t, "\"num\tber\" = 123", []token{
|
||||||
|
{Position{1, 1}, tokenKey, "\"num\tber\""},
|
||||||
|
{Position{1, 11}, tokenEqual, "="},
|
||||||
|
{Position{1, 13}, tokenInteger, "123"},
|
||||||
|
{Position{1, 16}, tokenEOF, ""},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
func TestKeyNewline(t *testing.T) {
|
func TestKeyNewline(t *testing.T) {
|
||||||
testFlow(t, "a\n= 4", []token{
|
testFlow(t, "a\n= 4", []token{
|
||||||
{Position{1, 1}, tokenError, "keys cannot contain new lines"},
|
{Position{1, 1}, tokenError, "keys cannot contain new lines"},
|
||||||
|
|||||||
+43
-15
@@ -1419,26 +1419,54 @@ func TestMarshalDirectMultilineString(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//issue 354
|
func TestUnmarshalTabInStringAndQuotedKey(t *testing.T) {
|
||||||
func TestUnmarshalMultilineStringWithTab(t *testing.T) {
|
|
||||||
input := []byte(`
|
|
||||||
Field = """
|
|
||||||
hello world"""
|
|
||||||
`)
|
|
||||||
|
|
||||||
type Test struct {
|
type Test struct {
|
||||||
Field string
|
Field1 string `toml:"Fie ld1"`
|
||||||
|
Field2 string
|
||||||
}
|
}
|
||||||
|
|
||||||
expected := Test{"hello\tworld"}
|
type TestCase struct {
|
||||||
result := Test{}
|
desc string
|
||||||
err := Unmarshal(input, &result)
|
input []byte
|
||||||
if err != nil {
|
expected Test
|
||||||
t.Fatal("unmarshal should not error:", err)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if !reflect.DeepEqual(result, expected) {
|
testCases := []TestCase{
|
||||||
t.Errorf("Bad unmarshal: expected\n-----\n%+v\n-----\ngot\n-----\n%+v\n-----\n", expected, result)
|
{
|
||||||
|
desc: "multiline string with tab",
|
||||||
|
input: []byte("Field2 = \"\"\"\nhello\tworld\"\"\""),
|
||||||
|
expected: Test{
|
||||||
|
Field2: "hello\tworld",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "quoted key with tab",
|
||||||
|
input: []byte("\"Fie\tld1\" = \"key with tab\""),
|
||||||
|
expected: Test{
|
||||||
|
Field1: "key with tab",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "basic string tab",
|
||||||
|
input: []byte("Field2 = \"hello\tworld\""),
|
||||||
|
expected: Test{
|
||||||
|
Field2: "hello\tworld",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := range testCases {
|
||||||
|
result := Test{}
|
||||||
|
err := Unmarshal(testCases[i].input, &result)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("%s test error:%v", testCases[i].desc, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if !reflect.DeepEqual(result, testCases[i].expected) {
|
||||||
|
t.Errorf("%s test error: expected\n-----\n%+v\n-----\ngot\n-----\n%+v\n-----\n",
|
||||||
|
testCases[i].desc, testCases[i].expected, result)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user