Merge branch 'comment_branch'
diff --git a/README.md b/README.md index f7ebfe5..462004f 100644 --- a/README.md +++ b/README.md
@@ -10,6 +10,9 @@ Filenames can also contain environment variables like in `/home/${USER}/myapp.properties`. +Comments and the order of keys are preserved. Comments can be modified +and can be written to the output. + The properties library supports both ISO-8859-1 and UTF-8 encoded data. Starting from version 1.3.0 the behavior of the MustXXX() functions is @@ -41,9 +44,23 @@ $ go get -u github.com/magiconair/properties ``` +For testing and debugging you need the [go-check](https://github.com/go-check/check) library + +``` +$ go get -u gopkg.in/check.v1 +``` + History ------- +v1.5.0, 18 Nov 2014 +------------------- + * Added support for single and multi-line comments (reading, writing and updating) + * The order of keys is now preserved + * Calling Set() with an empty key now silently ignores the call and does not create a new entry + * Added a MustSet() method + * Migrated test library from launchpad.net/gocheck to gopkg.in/check.v1 + v1.4.2, 15 Nov 2014 ------------------- * Issue #2: Fixed goroutine leak in parser which created two lexers but cleaned up only one
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/load_test.go b/load_test.go index 66f9b7c..9d1e3b6 100644 --- a/load_test.go +++ b/load_test.go
@@ -10,7 +10,7 @@ "os" "strings" - . "launchpad.net/gocheck" + . "gopkg.in/check.v1" ) type LoadSuite struct {
diff --git a/parser.go b/parser.go index d996d00..bb71fb9 100644 --- a/parser.go +++ b/parser.go
@@ -18,22 +18,39 @@ defer p.recover(&err) 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 + if _, ok := properties.m[key]; !ok { + properties.k = append(properties.k, key) + } } - 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 } @@ -50,12 +67,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..e9aca7a 100644 --- a/properties.go +++ b/properties.go
@@ -45,7 +45,14 @@ Prefix string Postfix string + // Stores the key/value pairs m map[string]string + + // Stores the comments per key. + c map[string][]string + + // Stores the keys in order of appearance. + k []string } // NewProperties creates a new Properties struct with the default @@ -54,7 +61,9 @@ return &Properties{ Prefix: "${", Postfix: "}", - m: make(map[string]string), + m: map[string]string{}, + c: map[string][]string{}, + k: []string{}, } } @@ -90,6 +99,53 @@ // ---------------------------------------------------------------------------- +// ClearComments removes the comments for all keys. +func (p *Properties) ClearComments() { + p.c = map[string][]string{} +} + +// ---------------------------------------------------------------------------- + +// 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 +} + +// ---------------------------------------------------------------------------- + +// SetComment sets the comment for the key. +func (p *Properties) SetComment(key, comment string) { + p.c[key] = []string{comment} +} + +// ---------------------------------------------------------------------------- + +// SetComments sets the comments for the key. If the comments are nil then +// all comments for this key are deleted. +func (p *Properties) SetComments(key string, comments []string) { + if comments == nil { + delete(p.c, key) + return + } + p.c[key] = comments +} + +// ---------------------------------------------------------------------------- + // 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. @@ -360,11 +416,11 @@ return len(p.m) } -// Keys returns all keys. +// Keys returns all keys in the same order as in the input. func (p *Properties) Keys() []string { - keys := make([]string, 0, len(p.m)) - for k, _ := range p.m { - keys = append(keys, k) + keys := make([]string, len(p.k)) + for i, k := range p.k { + keys[i] = k } return keys } @@ -374,21 +430,55 @@ // contains the previous value. If the value contains a // circular reference or a malformed expression then // an error is returned. +// An empty key is silently ignored. func (p *Properties) Set(key, value string) (prev string, ok bool, err error) { + if key == "" { + return "", false, nil + } + + // to check for a circular reference we temporarily need + // to set the new value. If there is an error then revert + // to the previous state. Only if all tests are successful + // then we add the key to the p.k list. + prev, ok = p.Get(key) + p.m[key] = value + + // now check for a circular reference _, err = p.expand(value) if err != nil { + + // revert to the previous state + if ok { + p.m[key] = prev + } else { + delete(p.m, key) + } + return "", false, err } - v, ok := p.Get(key) - p.m[key] = value - return v, ok, nil + if !ok { + p.k = append(p.k, key) + } + + return prev, ok, nil +} + +// MustSet sets the property key to the corresponding value. +// If a value for key existed before then ok is true and prev +// contains the previous value. An empty key is silently ignored. +func (p *Properties) MustSet(key, value string) (prev string, ok bool) { + prev, ok, err := p.Set(key, value) + if err != nil { + ErrorHandler(err) + } + return prev, ok } // String returns a string of all expanded 'key = value' pairs. func (p *Properties) String() string { var s string - for key, _ := range p.m { + for _, key := range p.k { value, _ := p.Get(key) s = fmt.Sprintf("%s%s = %s\n", s, key, value) } @@ -396,17 +486,51 @@ } // Write writes all unexpanded 'key = value' pairs to the given writer. -func (p *Properties) Write(w io.Writer, enc Encoding) (int, error) { - total := 0 - for key, value := range p.m { - s := fmt.Sprintf("%s = %s\n", encode(key, " :", enc), encode(value, "", enc)) - n, err := w.Write([]byte(s)) - if err != nil { - return total, err +// Write returns the number of bytes written and any write error encountered. +func (p *Properties) Write(w io.Writer, enc Encoding) (n int, err error) { + return p.WriteComment(w, "", enc) +} + +// WriteComment writes all unexpanced 'key = value' pairs to the given writer. +// If prefix is not empty then comments are written with a blank line and the +// given prefix. The prefix should be either "# " or "! " to be compatible with +// the properties file format. Otherwise, the properties parser will not be +// able to read the file back in. It returns the number of bytes written and +// any write error encountered. +func (p *Properties) WriteComment(w io.Writer, prefix string, enc Encoding) (n int, err error) { + var x int + + for _, key := range p.k { + value := p.m[key] + + if prefix != "" { + if comments, ok := p.c[key]; ok { + // add a blank line between entries but not at the top + if len(comments) > 0 && n > 0 { + x, err = fmt.Fprintln(w) + if err != nil { + return + } + n += x + } + + for _, c := range comments { + x, err = fmt.Fprintf(w, "%s%s\n", prefix, encode(c, "", enc)) + if err != nil { + return + } + n += x + } + } } - total += n + + x, err = fmt.Fprintf(w, "%s = %s\n", encode(key, " :", enc), encode(value, "", enc)) + if err != nil { + return + } + n += x } - return total, nil + return } // ----------------------------------------------------------------------------
diff --git a/properties_test.go b/properties_test.go index 4abe20b..fda5b15 100644 --- a/properties_test.go +++ b/properties_test.go
@@ -14,7 +14,7 @@ "testing" "time" - . "launchpad.net/gocheck" + . "gopkg.in/check.v1" ) func Test(t *testing.T) { TestingT(t) } @@ -109,9 +109,32 @@ {"key=${USER}\nUSER=value", "key", "value", "USER", "value"}, } -// define error test cases in the form of -// {"input", "expected error message"} -var errorTests = [][]string{ +// ---------------------------------------------------------------------------- + +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 +}{ // unicode literals {"key\\u1 = value", "Invalid unicode literal"}, {"key\\u12 = value", "Invalid unicode literal"}, @@ -128,9 +151,11 @@ {"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 = [][]string{ +// ---------------------------------------------------------------------------- + +var writeTests = []struct { + input, output, encoding string +}{ // ISO-8859-1 tests {"key = value", "key = value\n", "ISO-8859-1"}, {"key = value \\\n continued", "key = value continued\n", "ISO-8859-1"}, @@ -146,202 +171,224 @@ // ---------------------------------------------------------------------------- -type boolTest struct { +var writeCommentTests = []struct { + input, output, encoding string +}{ + // ISO-8859-1 tests + {"key = value", "key = value\n", "ISO-8859-1"}, + {"# comment\nkey = value", "# comment\nkey = value\n", "ISO-8859-1"}, + {"\n# comment\nkey = value", "# comment\nkey = value\n", "ISO-8859-1"}, + {"# comment\n\nkey = value", "# comment\nkey = value\n", "ISO-8859-1"}, + {"# comment1\n# comment2\nkey = value", "# comment1\n# comment2\nkey = value\n", "ISO-8859-1"}, + {"#comment1\nkey1 = value1\n#comment2\nkey2 = value2", "# comment1\nkey1 = value1\n\n# comment2\nkey2 = value2\n", "ISO-8859-1"}, + + // UTF-8 tests + {"key = value", "key = value\n", "UTF-8"}, + {"# comment⌘\nkey = value⌘", "# comment⌘\nkey = value⌘\n", "UTF-8"}, + {"\n# comment⌘\nkey = value⌘", "# comment⌘\nkey = value⌘\n", "UTF-8"}, + {"# comment⌘\n\nkey = value⌘", "# comment⌘\nkey = value⌘\n", "UTF-8"}, + {"# comment1⌘\n# comment2⌘\nkey = value⌘", "# comment1⌘\n# comment2⌘\nkey = value⌘\n", "UTF-8"}, + {"#comment1⌘\nkey1 = value1⌘\n#comment2⌘\nkey2 = value2⌘", "# comment1⌘\nkey1 = value1⌘\n\n# comment2⌘\nkey2 = value2⌘\n", "UTF-8"}, +} + +// ---------------------------------------------------------------------------- + +var boolTests = []struct { input, key string def, value bool -} - -var boolTests = []*boolTest{ +}{ // valid values for TRUE - &boolTest{"key = 1", "key", false, true}, - &boolTest{"key = on", "key", false, true}, - &boolTest{"key = On", "key", false, true}, - &boolTest{"key = ON", "key", false, true}, - &boolTest{"key = true", "key", false, true}, - &boolTest{"key = True", "key", false, true}, - &boolTest{"key = TRUE", "key", false, true}, - &boolTest{"key = yes", "key", false, true}, - &boolTest{"key = Yes", "key", false, true}, - &boolTest{"key = YES", "key", false, true}, + {"key = 1", "key", false, true}, + {"key = on", "key", false, true}, + {"key = On", "key", false, true}, + {"key = ON", "key", false, true}, + {"key = true", "key", false, true}, + {"key = True", "key", false, true}, + {"key = TRUE", "key", false, true}, + {"key = yes", "key", false, true}, + {"key = Yes", "key", false, true}, + {"key = YES", "key", false, true}, // valid values for FALSE (all other) - &boolTest{"key = 0", "key", true, false}, - &boolTest{"key = off", "key", true, false}, - &boolTest{"key = false", "key", true, false}, - &boolTest{"key = no", "key", true, false}, + {"key = 0", "key", true, false}, + {"key = off", "key", true, false}, + {"key = false", "key", true, false}, + {"key = no", "key", true, false}, // non existent key - &boolTest{"key = true", "key2", false, false}, + {"key = true", "key2", false, false}, } // ---------------------------------------------------------------------------- -type durationTest struct { +var durationTests = []struct { input, key string def, value time.Duration -} - -var durationTests = []*durationTest{ +}{ // valid values - &durationTest{"key = 1", "key", 999, 1}, - &durationTest{"key = 0", "key", 999, 0}, - &durationTest{"key = -1", "key", 999, -1}, - &durationTest{"key = 0123", "key", 999, 123}, + {"key = 1", "key", 999, 1}, + {"key = 0", "key", 999, 0}, + {"key = -1", "key", 999, -1}, + {"key = 0123", "key", 999, 123}, // invalid values - &durationTest{"key = 0xff", "key", 999, 999}, - &durationTest{"key = 1.0", "key", 999, 999}, - &durationTest{"key = a", "key", 999, 999}, + {"key = 0xff", "key", 999, 999}, + {"key = 1.0", "key", 999, 999}, + {"key = a", "key", 999, 999}, // non existent key - &durationTest{"key = 1", "key2", 999, 999}, + {"key = 1", "key2", 999, 999}, } // ---------------------------------------------------------------------------- -type floatTest struct { +var floatTests = []struct { input, key string def, value float64 -} - -var floatTests = []*floatTest{ +}{ // valid values - &floatTest{"key = 1.0", "key", 999, 1.0}, - &floatTest{"key = 0.0", "key", 999, 0.0}, - &floatTest{"key = -1.0", "key", 999, -1.0}, - &floatTest{"key = 1", "key", 999, 1}, - &floatTest{"key = 0", "key", 999, 0}, - &floatTest{"key = -1", "key", 999, -1}, - &floatTest{"key = 0123", "key", 999, 123}, + {"key = 1.0", "key", 999, 1.0}, + {"key = 0.0", "key", 999, 0.0}, + {"key = -1.0", "key", 999, -1.0}, + {"key = 1", "key", 999, 1}, + {"key = 0", "key", 999, 0}, + {"key = -1", "key", 999, -1}, + {"key = 0123", "key", 999, 123}, // invalid values - &floatTest{"key = 0xff", "key", 999, 999}, - &floatTest{"key = a", "key", 999, 999}, + {"key = 0xff", "key", 999, 999}, + {"key = a", "key", 999, 999}, // non existent key - &floatTest{"key = 1", "key2", 999, 999}, + {"key = 1", "key2", 999, 999}, } // ---------------------------------------------------------------------------- -type int64Test struct { +var int64Tests = []struct { input, key string def, value int64 -} - -var int64Tests = []*int64Test{ +}{ // valid values - &int64Test{"key = 1", "key", 999, 1}, - &int64Test{"key = 0", "key", 999, 0}, - &int64Test{"key = -1", "key", 999, -1}, - &int64Test{"key = 0123", "key", 999, 123}, + {"key = 1", "key", 999, 1}, + {"key = 0", "key", 999, 0}, + {"key = -1", "key", 999, -1}, + {"key = 0123", "key", 999, 123}, // invalid values - &int64Test{"key = 0xff", "key", 999, 999}, - &int64Test{"key = 1.0", "key", 999, 999}, - &int64Test{"key = a", "key", 999, 999}, + {"key = 0xff", "key", 999, 999}, + {"key = 1.0", "key", 999, 999}, + {"key = a", "key", 999, 999}, // non existent key - &int64Test{"key = 1", "key2", 999, 999}, + {"key = 1", "key2", 999, 999}, } // ---------------------------------------------------------------------------- -type uint64Test struct { +var uint64Tests = []struct { input, key string def, value uint64 -} - -var uint64Tests = []*uint64Test{ +}{ // valid values - &uint64Test{"key = 1", "key", 999, 1}, - &uint64Test{"key = 0", "key", 999, 0}, - &uint64Test{"key = 0123", "key", 999, 123}, + {"key = 1", "key", 999, 1}, + {"key = 0", "key", 999, 0}, + {"key = 0123", "key", 999, 123}, // invalid values - &uint64Test{"key = -1", "key", 999, 999}, - &uint64Test{"key = 0xff", "key", 999, 999}, - &uint64Test{"key = 1.0", "key", 999, 999}, - &uint64Test{"key = a", "key", 999, 999}, + {"key = -1", "key", 999, 999}, + {"key = 0xff", "key", 999, 999}, + {"key = 1.0", "key", 999, 999}, + {"key = a", "key", 999, 999}, // non existent key - &uint64Test{"key = 1", "key2", 999, 999}, + {"key = 1", "key2", 999, 999}, } // ---------------------------------------------------------------------------- -type stringTest struct { +var stringTests = []struct { input, key string def, value string -} - -var stringTests = []*stringTest{ +}{ // valid values - &stringTest{"key = abc", "key", "def", "abc"}, + {"key = abc", "key", "def", "abc"}, // non existent key - &stringTest{"key = abc", "key2", "def", "def"}, + {"key = abc", "key2", "def", "def"}, } // ---------------------------------------------------------------------------- -type keysTest struct { +var keysTests = []struct { input string keys []string -} - -var keysTests = []*keysTest{ - &keysTest{"", []string{}}, - &keysTest{"key = abc", []string{"key"}}, - &keysTest{"key = abc\nkey2=def", []string{"key", "key2"}}, - &keysTest{"key = abc\nkey=def", []string{"key"}}, +}{ + {"", []string{}}, + {"key = abc", []string{"key"}}, + {"key = abc\nkey2=def", []string{"key", "key2"}}, + {"key2 = abc\nkey=def", []string{"key2", "key"}}, + {"key = abc\nkey=def", []string{"key"}}, } // ---------------------------------------------------------------------------- -type filterTest struct { +var filterTests = []struct { input string pattern string keys []string err string -} - -var filterTests = []*filterTest{ - &filterTest{"", "", []string{}, ""}, - &filterTest{"", "abc", []string{}, ""}, - &filterTest{"key=value", "", []string{"key"}, ""}, - &filterTest{"key=value", "key=", []string{}, ""}, - &filterTest{"key=value\nfoo=bar", "", []string{"foo", "key"}, ""}, - &filterTest{"key=value\nfoo=bar", "f", []string{"foo"}, ""}, - &filterTest{"key=value\nfoo=bar", "fo", []string{"foo"}, ""}, - &filterTest{"key=value\nfoo=bar", "foo", []string{"foo"}, ""}, - &filterTest{"key=value\nfoo=bar", "fooo", []string{}, ""}, - &filterTest{"key=value\nkey2=value2\nfoo=bar", "ey", []string{"key", "key2"}, ""}, - &filterTest{"key=value\nkey2=value2\nfoo=bar", "key", []string{"key", "key2"}, ""}, - &filterTest{"key=value\nkey2=value2\nfoo=bar", "^key", []string{"key", "key2"}, ""}, - &filterTest{"key=value\nkey2=value2\nfoo=bar", "^(key|foo)", []string{"foo", "key", "key2"}, ""}, - &filterTest{"key=value\nkey2=value2\nfoo=bar", "[ abc", nil, "error parsing regexp.*"}, +}{ + {"", "", []string{}, ""}, + {"", "abc", []string{}, ""}, + {"key=value", "", []string{"key"}, ""}, + {"key=value", "key=", []string{}, ""}, + {"key=value\nfoo=bar", "", []string{"foo", "key"}, ""}, + {"key=value\nfoo=bar", "f", []string{"foo"}, ""}, + {"key=value\nfoo=bar", "fo", []string{"foo"}, ""}, + {"key=value\nfoo=bar", "foo", []string{"foo"}, ""}, + {"key=value\nfoo=bar", "fooo", []string{}, ""}, + {"key=value\nkey2=value2\nfoo=bar", "ey", []string{"key", "key2"}, ""}, + {"key=value\nkey2=value2\nfoo=bar", "key", []string{"key", "key2"}, ""}, + {"key=value\nkey2=value2\nfoo=bar", "^key", []string{"key", "key2"}, ""}, + {"key=value\nkey2=value2\nfoo=bar", "^(key|foo)", []string{"foo", "key", "key2"}, ""}, + {"key=value\nkey2=value2\nfoo=bar", "[ abc", nil, "error parsing regexp.*"}, } // ---------------------------------------------------------------------------- -type filterPrefixTest struct { +var filterPrefixTests = []struct { input string prefix string keys []string +}{ + {"", "", []string{}}, + {"", "abc", []string{}}, + {"key=value", "", []string{"key"}}, + {"key=value", "key=", []string{}}, + {"key=value\nfoo=bar", "", []string{"foo", "key"}}, + {"key=value\nfoo=bar", "f", []string{"foo"}}, + {"key=value\nfoo=bar", "fo", []string{"foo"}}, + {"key=value\nfoo=bar", "foo", []string{"foo"}}, + {"key=value\nfoo=bar", "fooo", []string{}}, + {"key=value\nkey2=value2\nfoo=bar", "key", []string{"key", "key2"}}, } -var filterPrefixTests = []*filterPrefixTest{ - &filterPrefixTest{"", "", []string{}}, - &filterPrefixTest{"", "abc", []string{}}, - &filterPrefixTest{"key=value", "", []string{"key"}}, - &filterPrefixTest{"key=value", "key=", []string{}}, - &filterPrefixTest{"key=value\nfoo=bar", "", []string{"foo", "key"}}, - &filterPrefixTest{"key=value\nfoo=bar", "f", []string{"foo"}}, - &filterPrefixTest{"key=value\nfoo=bar", "fo", []string{"foo"}}, - &filterPrefixTest{"key=value\nfoo=bar", "foo", []string{"foo"}}, - &filterPrefixTest{"key=value\nfoo=bar", "fooo", []string{}}, - &filterPrefixTest{"key=value\nkey2=value2\nfoo=bar", "key", []string{"key", "key2"}}, +// ---------------------------------------------------------------------------- + +var setTests = []struct { + input string + key, value string + prev string + ok bool + err string + keys []string +}{ + {"", "", "", "", false, "", []string{}}, + {"", "key", "value", "", false, "", []string{"key"}}, + {"key=value", "key2", "value2", "", false, "", []string{"key", "key2"}}, + {"key=value", "abc", "value3", "", false, "", []string{"key", "abc"}}, + {"key=value", "key", "value3", "value", true, "", []string{"key"}}, } // ---------------------------------------------------------------------------- @@ -362,8 +409,9 @@ func (l *TestSuite) TestErrors(c *C) { for _, test := range errorTests { - input, msg := test[0], test[1] - testError(c, input, msg) + _, err := Load([]byte(test.input), ISO_8859_1) + c.Assert(err, NotNil) + c.Assert(strings.Contains(err.Error(), test.msg), Equals, true, Commentf("Expected %q got %q", test.msg, err.Error())) } } @@ -517,6 +565,40 @@ 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, "") + } + + // test setting comments + if len(test.comments) > 0 { + // set single comment + p.ClearComments() + c.Assert(len(p.c), Equals, 0) + p.SetComment(test.key, test.comments[0]) + c.Assert(p.GetComment(test.key), Equals, test.comments[0]) + + // set multiple comments + p.ClearComments() + c.Assert(len(p.c), Equals, 0) + p.SetComments(test.key, test.comments) + c.Assert(p.GetComments(test.key), DeepEquals, test.comments) + + // clear comments for a key + p.SetComments(test.key, nil) + c.Assert(p.GetComment(test.key), Equals, "") + c.Assert(p.GetComments(test.key), IsNil) + } + } +} + func (l *TestSuite) TestFilter(c *C) { for _, test := range filterTests { p, err := parse(test.input) @@ -561,20 +643,43 @@ c.Assert(err, IsNil) c.Assert(p.Len(), Equals, len(test.keys)) c.Assert(len(p.Keys()), Equals, len(test.keys)) - for _, key := range test.keys { - _, ok := p.Get(key) - c.Assert(ok, Equals, true) - } + c.Assert(p.Keys(), DeepEquals, test.keys) } } + +func (l *TestSuite) TestSet(c *C) { + for _, test := range setTests { + p, err := parse(test.input) + c.Assert(err, IsNil) + prev, ok, err := p.Set(test.key, test.value) + if test.err != "" { + c.Assert(err, ErrorMatches, test.err) + continue + } + + c.Assert(err, IsNil) + c.Assert(ok, Equals, test.ok) + if ok { + c.Assert(prev, Equals, test.prev) + } + c.Assert(p.Keys(), DeepEquals, test.keys) + } +} + +func (l *TestSuite) TestMustSet(c *C) { + input := "key=${key}" + p, err := parse(input) + c.Assert(err, IsNil) + c.Assert(func() { p.MustSet("key", "${key}") }, PanicMatches, "Circular reference .*") +} + func (l *TestSuite) TestWrite(c *C) { for _, test := range writeTests { - input, output, enc := test[0], test[1], test[2] - p, err := parse(input) + p, err := parse(test.input) buf := new(bytes.Buffer) var n int - switch enc { + switch test.encoding { case "UTF-8": n, err = p.Write(buf, UTF8) case "ISO-8859-1": @@ -582,8 +687,27 @@ } c.Assert(err, IsNil) s := string(buf.Bytes()) - c.Assert(n, Equals, len(output), Commentf("input=%q expected=%q obtained=%q", input, output, s)) - c.Assert(s, Equals, output, Commentf("input=%q expected=%q obtained=%q", input, output, s)) + c.Assert(n, Equals, len(test.output), Commentf("input=%q expected=%q obtained=%q", test.input, test.output, s)) + c.Assert(s, Equals, test.output, Commentf("input=%q expected=%q obtained=%q", test.input, test.output, s)) + } +} + +func (l *TestSuite) TestWriteComment(c *C) { + for _, test := range writeCommentTests { + p, err := parse(test.input) + + buf := new(bytes.Buffer) + var n int + switch test.encoding { + case "UTF-8": + n, err = p.WriteComment(buf, "# ", UTF8) + case "ISO-8859-1": + n, err = p.WriteComment(buf, "# ", ISO_8859_1) + } + c.Assert(err, IsNil) + s := string(buf.Bytes()) + c.Assert(n, Equals, len(test.output), Commentf("input=%q expected=%q obtained=%q", test.input, test.output, s)) + c.Assert(s, Equals, test.output, Commentf("input=%q expected=%q obtained=%q", test.input, test.output, s)) } } @@ -655,15 +779,6 @@ assertKeyValues(c, input, p, keyvalues...) } -// tests whether some input produces a given error message. -func testError(c *C, input, msg string) { - printf("%q\n", input) - - _, err := Load([]byte(input), ISO_8859_1) - c.Assert(err, NotNil) - c.Assert(strings.Contains(err.Error(), msg), Equals, true, Commentf("Expected %q got %q", msg, err.Error())) -} - // tests whether key/value pairs exist for a given input. // keyvalues is expected to be an even number of strings of "key", "value", ... func assertKeyValues(c *C, input string, p *Properties, keyvalues ...string) {