Added support for comments. Added benchmark
diff --git a/lex.go b/lex.go index aad9c00..67205dd 100644 --- a/lex.go +++ b/lex.go
@@ -127,6 +127,13 @@ l.backup() } +// acceptRunUntil consumes a run of runes up to a terminator. +func (l *lexer) acceptRunUntil(term rune) { + for term != l.next() { + } + l.backup() +} + // hasText returns true if the current parsed text is not empty. func (l *lexer) isNotEmpty() bool { return l.pos > l.start @@ -166,22 +173,39 @@ // run runs the state machine for the lexer. func (l *lexer) run() { - for l.state = lexKey(l); l.state != nil; { + for l.state = lexBeforeKey(l); l.state != nil; { l.state = l.state(l) } } // state functions // TODO: handle comments -// TODO: handle multi-line values + +// lexBeforeKey scans until a key begins. +func lexBeforeKey(l *lexer) stateFn { + switch r := l.next(); { + case isEOF(r): + l.emit(itemEOF) + return nil + + case isEOL(r): + l.ignore() + return lexBeforeKey + + case isComment(r): + l.acceptRunUntil('\n') + l.ignore() + return lexBeforeKey + + default: + l.backup() + return lexKey + } + +} // lexKey scans the key up to a delimiter func lexKey(l *lexer) stateFn { - if l.peek() == eof { - l.emit(itemEOF) - return nil - } - Loop: for { switch r := l.next(); { @@ -250,7 +274,7 @@ // ignore the new line l.ignore() - return lexKey + return lexBeforeKey case isEOF(r): l.emit(itemValue) @@ -321,6 +345,11 @@ } } +// isComment reports whether we are at the start of a comment. +func isComment(r rune) bool { + return r == '#' || r == '!' +} + // isEOF reports whether we are at EOF. func isEOF(r rune) bool { return r == eof
diff --git a/properties_test.go b/properties_test.go index b8e034a..2420da5 100644 --- a/properties_test.go +++ b/properties_test.go
@@ -24,6 +24,27 @@ testAllDelimiterCombinations(c, "key", "value") } +func (l *LoadSuite) TestTwoKeysAndValues(c *C) { + testKeyValue(c, "key1=value1\nkey2=value2", "key1", "value1", "key2", "value2") +} + +func (l *LoadSuite) TestWithBlankLines(c *C) { + testKeyValue(c, "\n\nkey=value\n\n", "key", "value") +} + +func (l *LoadSuite) TestWithComments(c *C) { + input := ` +# this is a comment +! and so is this +key1=value1 +key#2=value#2 +key!3=value!3 +# and another one +! and the final one +` + testKeyValue(c, input, "key1", "value1", "key#2", "value#2", "key!3", "value!3") +} + func (l *LoadSuite) TestValueWithTrailingSpaces(c *C) { testAllDelimiterCombinations(c, "key", "value ") } @@ -42,26 +63,30 @@ } func (l *LoadSuite) TestMultilineValue(c *C) { - input := "key = valueA,\\\n valueB" - testKeyValue(c, input, "key", "valueA,valueB") + testKeyValue(c, "key = valueA,\\\n valueB", "key", "valueA,valueB") } func (l *LoadSuite) TestFailWithPrematureEOF(c *C) { - _, err := NewPropertiesFromString("key") - c.Assert(err, NotNil) - c.Assert(strings.Contains(err.Error(), "premature EOF"), Equals, true) + testError(c, "key", "premature EOF") } func (l *LoadSuite) TestFailWithNonISO8859_1Input(c *C) { - _, err := NewPropertiesFromString("key₡") - c.Assert(err, NotNil) - c.Assert(strings.Contains(err.Error(), "invalid ISO-8859-1 input"), Equals, true) + testError(c, "key₡", "invalid ISO-8859-1 input") } func (l *LoadSuite) TestFailWithInvalidUnicodeLiteralInKey(c *C) { - _, err := NewPropertiesFromString("key\\ugh32 = value") - c.Assert(err, NotNil) - c.Assert(strings.Contains(err.Error(), "invalid unicode literal"), Equals, true) + testError(c, "key\\ugh32 = value", "invalid unicode literal") +} + +func BenchmarkNewPropertiesFromString(b *testing.B) { + input := "" + for i := 0; i < 1000; i++ { + input += fmt.Sprintf("key%d=value%d\n", i, i) + } + b.ResetTimer() + for i := 0; i < b.N; i++ { + NewPropertiesFromString(input) + } } // tests all combinations of delimiters plus leading and/or trailing spaces. @@ -73,14 +98,22 @@ } } -// tests a single key/value combination for a given input. -func testKeyValue(c *C, input, key, value string) { - // fmt.Printf("Testing '%s'\n", input) +// tests key/value pairs for a given input. +func testKeyValue(c *C, input string, keyvalues ...string) { p, err := NewPropertiesFromString(input) c.Assert(err, IsNil) c.Assert(p, NotNil) - c.Assert(p.Len(), Equals, 1) - assertKeyValue(c, p, key, value) + c.Assert(p.Len(), Equals, len(keyvalues)/2) + for i := 0; i < len(keyvalues)/2; i += 2 { + assertKeyValue(c, p, keyvalues[i], keyvalues[i+1]) + } +} + +// tests whether a given input produces a given error message. +func testError(c *C, input, msg string) { + _, err := NewPropertiesFromString(input) + c.Assert(err, NotNil) + c.Assert(strings.Contains(err.Error(), msg), Equals, true) } func assertKeyValue(c *C, p *Properties, key, value string) {