Added support for setting and clearing comments.
Return keys in the order they are in the original input.
Add support for writing properties files with comments.
diff --git a/parser.go b/parser.go
index a7028ca..297326c 100644
--- a/parser.go
+++ b/parser.go
@@ -32,6 +32,9 @@
continue
case itemKey:
key = token.val
+ if _, ok := properties.m[key]; !ok {
+ properties.k = append(properties.k, key)
+ }
}
token = p.expectOneOf(itemValue, itemEOF)
diff --git a/properties.go b/properties.go
index 898320e..ccf91fa 100644
--- a/properties.go
+++ b/properties.go
@@ -45,8 +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
@@ -55,8 +61,9 @@
return &Properties{
Prefix: "${",
Postfix: "}",
- m: make(map[string]string),
- c: make(map[string][]string),
+ m: map[string]string{},
+ c: map[string][]string{},
+ k: []string{},
}
}
@@ -92,8 +99,14 @@
// ----------------------------------------------------------------------------
-// GetComment returns the last comment before the given key or an
-// empty string.
+// 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 {
@@ -114,6 +127,25 @@
// ----------------------------------------------------------------------------
+// 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.
@@ -384,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
}
@@ -412,7 +444,7 @@
// 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)
}
@@ -420,17 +452,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 f0c6e49..a1b02bf 100644
--- a/properties_test.go
+++ b/properties_test.go
@@ -109,6 +109,8 @@
{"key=${USER}\nUSER=value", "key", "value", "USER", "value"},
}
+// ----------------------------------------------------------------------------
+
var commentTests = []struct {
input, key, value string
comments []string
@@ -128,6 +130,8 @@
{"! comment1\n\n! comment2\n\nkey=value", "key", "value", []string{"comment1", "comment2"}},
}
+// ----------------------------------------------------------------------------
+
var errorTests = []struct {
input, msg string
}{
@@ -147,6 +151,8 @@
{"key=valu${ke", "Malformed expression"},
}
+// ----------------------------------------------------------------------------
+
var writeTests = []struct {
input, output, encoding string
}{
@@ -165,6 +171,28 @@
// ----------------------------------------------------------------------------
+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
@@ -299,6 +327,7 @@
{"", []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"}},
}
@@ -530,6 +559,26 @@
} 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)
+ }
}
}
@@ -577,12 +626,10 @@
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) TestWrite(c *C) {
for _, test := range writeTests {
p, err := parse(test.input)
@@ -602,6 +649,25 @@
}
}
+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))
+ }
+}
+
func (l *TestSuite) TestCustomExpansionExpression(c *C) {
testKeyValuePrePostfix(c, "*[", "]*", "key=value\nkey2=*[key]*", "key", "value", "key2", "value")
}