Add support for storing comments with keys
diff --git a/lex.go b/lex.go index f6c0c23..1d6b532 100644 --- a/lex.go +++ b/lex.go
@@ -44,8 +44,9 @@ const ( itemError itemType = iota // error occurred; value is text of error itemEOF - itemKey // a key - itemValue // a value + itemKey // a key + itemValue // a value + itemComment // a comment ) // defines a constant for EOF @@ -207,6 +208,8 @@ // lexComment scans a comment line. The comment character has already been scanned. func lexComment(l *lexer) stateFn { + l.acceptRun(whitespace) + l.ignore() for { switch r := l.next(); { case isEOF(r): @@ -214,8 +217,10 @@ l.emit(itemEOF) return nil case isEOL(r): - l.ignore() + l.emit(itemComment) return lexBeforeKey + default: + l.appendRune(r) } } }
diff --git a/parser.go b/parser.go index e6efccc..a7028ca 100644 --- a/parser.go +++ b/parser.go
@@ -19,22 +19,36 @@ p.lex = lex(input) properties = NewProperties() + key := "" + comments := []string{} for { - token := p.expectOneOf(itemKey, itemEOF) - if token.typ == itemEOF { - break + token := p.expectOneOf(itemComment, itemKey, itemEOF) + switch token.typ { + case itemEOF: + goto done + case itemComment: + comments = append(comments, token.val) + continue + case itemKey: + key = token.val } - key := token.val token = p.expectOneOf(itemValue, itemEOF) - if token.typ == itemEOF { - properties.m[key] = "" - break + if len(comments) > 0 { + properties.c[key] = comments + comments = []string{} } - properties.m[key] = token.val + switch token.typ { + case itemEOF: + properties.m[key] = "" + goto done + case itemValue: + properties.m[key] = token.val + } } +done: return properties, nil } @@ -51,12 +65,15 @@ return token } -func (p *parser) expectOneOf(expected1, expected2 itemType) (token item) { +func (p *parser) expectOneOf(expected ...itemType) (token item) { token = p.lex.nextItem() - if token.typ != expected1 && token.typ != expected2 { - p.unexpected(token) + for _, v := range expected { + if token.typ == v { + return token + } } - return token + p.unexpected(token) + panic("unexpected token") } func (p *parser) unexpected(token item) {
diff --git a/properties.go b/properties.go index cde68fe..898320e 100644 --- a/properties.go +++ b/properties.go
@@ -46,6 +46,7 @@ Postfix string m map[string]string + c map[string][]string } // NewProperties creates a new Properties struct with the default @@ -55,6 +56,7 @@ Prefix: "${", Postfix: "}", m: make(map[string]string), + c: make(map[string][]string), } } @@ -90,6 +92,28 @@ // ---------------------------------------------------------------------------- +// GetComment returns the last comment before the given key or an +// empty string. +func (p *Properties) GetComment(key string) string { + comments, ok := p.c[key] + if !ok || len(comments) == 0 { + return "" + } + return comments[len(comments)-1] +} + +// ---------------------------------------------------------------------------- + +// GetComments returns all comments that appeared before the given key or nil. +func (p *Properties) GetComments(key string) []string { + if comments, ok := p.c[key]; ok { + return comments + } + return nil +} + +// ---------------------------------------------------------------------------- + // GetBool checks if the expanded value is one of '1', 'yes', // 'true' or 'on' if the key exists. The comparison is case-insensitive. // If the key does not exist the default value is returned.
diff --git a/properties_test.go b/properties_test.go index fc99dad..f0c6e49 100644 --- a/properties_test.go +++ b/properties_test.go
@@ -109,8 +109,25 @@ {"key=${USER}\nUSER=value", "key", "value", "USER", "value"}, } -// define error test cases in the form of -// {"input", "expected error message"} +var commentTests = []struct { + input, key, value string + comments []string +}{ + {"key=value", "key", "value", nil}, + {"#comment\nkey=value", "key", "value", []string{"comment"}}, + {"# comment\nkey=value", "key", "value", []string{"comment"}}, + {"# comment\nkey=value", "key", "value", []string{"comment"}}, + {"# comment\n\nkey=value", "key", "value", []string{"comment"}}, + {"# comment1\n# comment2\nkey=value", "key", "value", []string{"comment1", "comment2"}}, + {"# comment1\n\n# comment2\n\nkey=value", "key", "value", []string{"comment1", "comment2"}}, + {"!comment\nkey=value", "key", "value", []string{"comment"}}, + {"! comment\nkey=value", "key", "value", []string{"comment"}}, + {"! comment\nkey=value", "key", "value", []string{"comment"}}, + {"! comment\n\nkey=value", "key", "value", []string{"comment"}}, + {"! comment1\n! comment2\nkey=value", "key", "value", []string{"comment1", "comment2"}}, + {"! comment1\n\n! comment2\n\nkey=value", "key", "value", []string{"comment1", "comment2"}}, +} + var errorTests = []struct { input, msg string }{ @@ -130,8 +147,6 @@ {"key=valu${ke", "Malformed expression"}, } -// define write encoding test cases in the form of -// {"input", "expected output after write", ["UTF-8", "ISO-8859-1"]} var writeTests = []struct { input, output, encoding string }{ @@ -504,6 +519,20 @@ c.Assert(func() { p.MustGetString("invalid") }, PanicMatches, "unknown property: invalid") } +func (l *TestSuite) TestComment(c *C) { + for _, test := range commentTests { + p, err := parse(test.input) + c.Assert(err, IsNil) + c.Assert(p.MustGetString(test.key), Equals, test.value) + c.Assert(p.GetComments(test.key), DeepEquals, test.comments) + if test.comments != nil { + c.Assert(p.GetComment(test.key), Equals, test.comments[len(test.comments)-1]) + } else { + c.Assert(p.GetComment(test.key), Equals, "") + } + } +} + func (l *TestSuite) TestFilter(c *C) { for _, test := range filterTests { p, err := parse(test.input)