Merge pull request #51 from pelletier/pelletier/fix-crlf-support
Fix support for CRLF line ending
diff --git a/example-crlf.toml b/example-crlf.toml
new file mode 100644
index 0000000..12950a1
--- /dev/null
+++ b/example-crlf.toml
@@ -0,0 +1,29 @@
+# This is a TOML document. Boom.
+
+title = "TOML Example"
+
+[owner]
+name = "Tom Preston-Werner"
+organization = "GitHub"
+bio = "GitHub Cofounder & CEO\nLikes tater tots and beer."
+dob = 1979-05-27T07:32:00Z # First class dates? Why not?
+
+[database]
+server = "192.168.1.1"
+ports = [ 8001, 8001, 8002 ]
+connection_max = 5000
+enabled = true
+
+[servers]
+
+ # You can indent as you please. Tabs or spaces. TOML don't care.
+ [servers.alpha]
+ ip = "10.0.0.1"
+ dc = "eqdc10"
+
+ [servers.beta]
+ ip = "10.0.0.2"
+ dc = "eqdc10"
+
+[clients]
+data = [ ["gamma", "delta"], [1, 2] ] # just an update to make sure parsers support it
diff --git a/lexer.go b/lexer.go
index df0596b..0bffb0d 100644
--- a/lexer.go
+++ b/lexer.go
@@ -132,6 +132,8 @@
return l.lexComment
case '=':
return l.lexEqual
+ case '\r':
+ fallthrough
case '\n':
l.skip()
continue
@@ -185,6 +187,8 @@
return l.lexLiteralString
case ',':
return l.lexComma
+ case '\r':
+ fallthrough
case '\n':
l.skip()
if l.depth == 0 {
@@ -277,7 +281,7 @@
func (l *tomlLexer) lexKey() tomlLexStateFn {
inQuotes := false
- for r := l.peek(); isKeyChar(r) || r == '\n'; r = l.peek() {
+ for r := l.peek(); isKeyChar(r) || r == '\n' || r == '\r'; r = l.peek() {
if r == '"' {
inQuotes = !inQuotes
} else if r == '\n' {
@@ -295,6 +299,9 @@
func (l *tomlLexer) lexComment() tomlLexStateFn {
for next := l.peek(); next != '\n' && next != eof; next = l.peek() {
+ if (next == '\r' && l.follow("\r\n")) {
+ break
+ }
l.next()
}
l.ignore()
@@ -319,7 +326,10 @@
terminator = "'''"
// special case: discard leading newline
- if l.peek() == '\n' {
+ if l.follow("\r\n") {
+ l.skip()
+ l.skip()
+ } else if l.peek() == '\n' {
l.skip()
}
}
@@ -355,7 +365,10 @@
terminator = "\"\"\""
// special case: discard leading newline
- if l.peek() == '\n' {
+ if l.follow("\r\n") {
+ l.skip()
+ l.skip()
+ } else if l.peek() == '\n' {
l.skip()
}
}
diff --git a/lexer_test.go b/lexer_test.go
index 9fa8be8..6183061 100644
--- a/lexer_test.go
+++ b/lexer_test.go
@@ -87,6 +87,19 @@
})
}
+
+func TestSimpleWindowsCRLF(t *testing.T) {
+ testFlow(t, "a=4\r\nb=2", []token{
+ token{Position{1, 1}, tokenKey, "a"},
+ token{Position{1, 2}, tokenEqual, "="},
+ token{Position{1, 3}, tokenInteger, "4"},
+ token{Position{2, 1}, tokenKey, "b"},
+ token{Position{2, 2}, tokenEqual, "="},
+ token{Position{2, 3}, tokenInteger, "2"},
+ token{Position{2, 4}, tokenEOF, ""},
+ })
+}
+
func TestBasicKey(t *testing.T) {
testFlow(t, "hello", []token{
token{Position{1, 1}, tokenKey, "hello"},
diff --git a/parser_test.go b/parser_test.go
index f9191b6..d21604a 100644
--- a/parser_test.go
+++ b/parser_test.go
@@ -494,6 +494,42 @@
})
}
+func TestParseFileCRLF(t *testing.T) {
+ tree, err := LoadFile("example-crlf.toml")
+
+ assertTree(t, tree, err, map[string]interface{}{
+ "title": "TOML Example",
+ "owner": map[string]interface{}{
+ "name": "Tom Preston-Werner",
+ "organization": "GitHub",
+ "bio": "GitHub Cofounder & CEO\nLikes tater tots and beer.",
+ "dob": time.Date(1979, time.May, 27, 7, 32, 0, 0, time.UTC),
+ },
+ "database": map[string]interface{}{
+ "server": "192.168.1.1",
+ "ports": []int64{8001, 8001, 8002},
+ "connection_max": 5000,
+ "enabled": true,
+ },
+ "servers": map[string]interface{}{
+ "alpha": map[string]interface{}{
+ "ip": "10.0.0.1",
+ "dc": "eqdc10",
+ },
+ "beta": map[string]interface{}{
+ "ip": "10.0.0.2",
+ "dc": "eqdc10",
+ },
+ },
+ "clients": map[string]interface{}{
+ "data": []interface{}{
+ []string{"gamma", "delta"},
+ []int64{1, 2},
+ },
+ },
+ })
+}
+
func TestParseKeyGroupArray(t *testing.T) {
tree, err := Load("[[foo.bar]] a = 42\n[[foo.bar]] a = 69")
assertTree(t, tree, err, map[string]interface{}{