Skip to content

Commit 109e9f3

Browse files
committed
properties.Map now keeps the insertion ordering
1 parent ad37f0c commit 109e9f3

File tree

3 files changed

+151
-108
lines changed

3 files changed

+151
-108
lines changed

objects.go

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -36,24 +36,24 @@ import (
3636
// GetBoolean returns true if the map contains the specified key and the value
3737
// equals to the string "true", in any other case returns false.
3838
func (m Map) GetBoolean(key string) bool {
39-
value, ok := m[key]
39+
value, ok := m.GetOk(key)
4040
return ok && value == "true"
4141
}
4242

4343
// SetBoolean sets the specified key to the string "true" or "false" if the value
4444
// is respectively true or false.
4545
func (m Map) SetBoolean(key string, value bool) {
4646
if value {
47-
m[key] = "true"
47+
m.Set(key, "true")
4848
} else {
49-
m[key] = "false"
49+
m.Set(key, "false")
5050
}
5151
}
5252

5353
// GetPath returns a paths.Path object using the map value as path. The function
5454
// returns nil if the key is not present.
5555
func (m Map) GetPath(key string) *paths.Path {
56-
value, ok := m[key]
56+
value, ok := m.GetOk(key)
5757
if !ok {
5858
return nil
5959
}
@@ -63,8 +63,8 @@ func (m Map) GetPath(key string) *paths.Path {
6363
// SetPath saves the paths.Path object in the map using the path as value of the map
6464
func (m Map) SetPath(key string, value *paths.Path) {
6565
if value == nil {
66-
m[key] = ""
66+
m.Set(key, "")
6767
} else {
68-
m[key] = value.String()
68+
m.Set(key, value.String())
6969
}
7070
}

properties.go

Lines changed: 99 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,9 @@ syntax, for example:
5656
5757
This library has methods to parse this kind of files into a Map object.
5858
59+
The Map internally keeps the insertion order so it can be retrieved later when
60+
cycling through the key sets.
61+
5962
The Map object has many helper methods to accomplish some common operation
6063
on this kind of data like cloning, merging, comparing and also extracting
6164
a submap or generating a map-of-submaps from the first "level" of the hierarchy.
@@ -72,14 +75,16 @@ import (
7275
"reflect"
7376
"regexp"
7477
"runtime"
75-
"sort"
7678
"strings"
7779

7880
"github.com/arduino/go-paths-helper"
7981
)
8082

8183
// Map is a container of properties
82-
type Map map[string]string
84+
type Map struct {
85+
kv map[string]string
86+
o []string
87+
}
8388

8489
var osSuffix string
8590

@@ -94,8 +99,16 @@ func init() {
9499
}
95100
}
96101

102+
// New returns a new Map
103+
func New() *Map {
104+
return &Map{
105+
kv: map[string]string{},
106+
o: []string{},
107+
}
108+
}
109+
97110
// Load reads a properties file and makes a Map out of it.
98-
func Load(filepath string) (Map, error) {
111+
func Load(filepath string) (*Map, error) {
99112
bytes, err := ioutil.ReadFile(filepath)
100113
if err != nil {
101114
return nil, fmt.Errorf("Error reading file: %s", err)
@@ -105,7 +118,7 @@ func Load(filepath string) (Map, error) {
105118
text = strings.Replace(text, "\r\n", "\n", -1)
106119
text = strings.Replace(text, "\r", "\n", -1)
107120

108-
properties := make(Map)
121+
properties := New()
109122

110123
for lineNum, line := range strings.Split(text, "\n") {
111124
if err := properties.parseLine(line); err != nil {
@@ -117,14 +130,14 @@ func Load(filepath string) (Map, error) {
117130
}
118131

119132
// LoadFromPath reads a properties file and makes a Map out of it.
120-
func LoadFromPath(path *paths.Path) (Map, error) {
133+
func LoadFromPath(path *paths.Path) (*Map, error) {
121134
return Load(path.String())
122135
}
123136

124137
// LoadFromSlice reads a properties file from an array of string
125138
// and makes a Map out of it
126-
func LoadFromSlice(lines []string) (Map, error) {
127-
properties := make(Map)
139+
func LoadFromSlice(lines []string) (*Map, error) {
140+
properties := New()
128141

129142
for lineNum, line := range lines {
130143
if err := properties.parseLine(line); err != nil {
@@ -135,7 +148,7 @@ func LoadFromSlice(lines []string) (Map, error) {
135148
return properties, nil
136149
}
137150

138-
func (m Map) parseLine(line string) error {
151+
func (m *Map) parseLine(line string) error {
139152
line = strings.TrimSpace(line)
140153

141154
// Skip empty lines or comments
@@ -151,23 +164,23 @@ func (m Map) parseLine(line string) error {
151164
value := strings.TrimSpace(lineParts[1])
152165

153166
key = strings.Replace(key, "."+osSuffix, "", 1)
154-
m[key] = value
167+
m.Set(key, value)
155168

156169
return nil
157170
}
158171

159172
// SafeLoadFromPath is like LoadFromPath, except that it returns an empty Map if
160173
// the specified file doesn't exists
161-
func SafeLoadFromPath(path *paths.Path) (Map, error) {
174+
func SafeLoadFromPath(path *paths.Path) (*Map, error) {
162175
return SafeLoad(path.String())
163176
}
164177

165178
// SafeLoad is like Load, except that it returns an empty Map if the specified
166179
// file doesn't exists
167-
func SafeLoad(filepath string) (Map, error) {
180+
func SafeLoad(filepath string) (*Map, error) {
168181
_, err := os.Stat(filepath)
169182
if os.IsNotExist(err) {
170-
return make(Map), nil
183+
return New(), nil
171184
}
172185

173186
properties, err := Load(filepath)
@@ -177,6 +190,49 @@ func SafeLoad(filepath string) (Map, error) {
177190
return properties, nil
178191
}
179192

193+
// Get retrieve the value corresponding to key
194+
func (m *Map) Get(key string) string {
195+
return m.kv[key]
196+
}
197+
198+
// GetOk retrieve the value corresponding to key and return a true/false indicator
199+
// to check if the key is present in the map (true if the key is present)
200+
func (m *Map) GetOk(key string) (string, bool) {
201+
v, ok := m.kv[key]
202+
return v, ok
203+
}
204+
205+
// ContainsKey returns true
206+
func (m *Map) ContainsKey(key string) bool {
207+
_, has := m.kv[key]
208+
return has
209+
}
210+
211+
// Set inserts or replaces an existing key-value pair in the map
212+
func (m *Map) Set(key, value string) {
213+
if _, has := m.kv[key]; has {
214+
m.Remove(key)
215+
}
216+
m.kv[key] = value
217+
m.o = append(m.o, key)
218+
}
219+
220+
// Size return the number of elements in the map
221+
func (m *Map) Size() int {
222+
return len(m.kv)
223+
}
224+
225+
// Remove removes the key from the map
226+
func (m *Map) Remove(key string) {
227+
delete(m.kv, key)
228+
for i, k := range m.o {
229+
if k == key {
230+
m.o = append(m.o[:i], m.o[i+1:]...)
231+
return
232+
}
233+
}
234+
}
235+
180236
// FirstLevelOf generates a map-of-Maps using the first level of the hierarchy
181237
// of the current Map. For example the following Map:
182238
//
@@ -209,17 +265,18 @@ func SafeLoad(filepath string) (Map, error) {
209265
// "bootloader.low_fuses": "0xFF",
210266
// }
211267
// }
212-
func (m Map) FirstLevelOf() map[string]Map {
213-
newMap := make(map[string]Map)
214-
for key, value := range m {
268+
func (m *Map) FirstLevelOf() map[string]*Map {
269+
newMap := make(map[string]*Map)
270+
for _, key := range m.o {
215271
if strings.Index(key, ".") == -1 {
216272
continue
217273
}
218274
keyParts := strings.SplitN(key, ".", 2)
219275
if newMap[keyParts[0]] == nil {
220-
newMap[keyParts[0]] = make(Map)
276+
newMap[keyParts[0]] = New()
221277
}
222-
newMap[keyParts[0]][keyParts[1]] = value
278+
value := m.kv[key]
279+
newMap[keyParts[0]].Set(keyParts[1], value)
223280
}
224281
return newMap
225282
}
@@ -248,14 +305,15 @@ func (m Map) FirstLevelOf() map[string]Map {
248305
// "upload.protocol": "arduino",
249306
// "upload.maximum_size": "32256",
250307
// },
251-
func (m Map) SubTree(rootKey string) Map {
308+
func (m *Map) SubTree(rootKey string) *Map {
252309
rootKey += "."
253-
newMap := Map{}
254-
for key, value := range m {
310+
newMap := New()
311+
for _, key := range m.o {
255312
if !strings.HasPrefix(key, rootKey) {
256313
continue
257314
}
258-
newMap[key[len(rootKey):]] = value
315+
value := m.kv[key]
316+
newMap.Set(key[len(rootKey):], value)
259317
}
260318
return newMap
261319
}
@@ -268,10 +326,10 @@ func (m Map) SubTree(rootKey string) Map {
268326
// Each marker is replaced by the corresponding value of the Map.
269327
// The values in the Map may contains other markers, they are evaluated
270328
// recursively up to 10 times.
271-
func (m Map) ExpandPropsInString(str string) string {
329+
func (m *Map) ExpandPropsInString(str string) string {
272330
for i := 0; i < 10; i++ {
273331
newStr := str
274-
for key, value := range m {
332+
for key, value := range m.kv {
275333
newStr = strings.Replace(newStr, "{"+key+"}", value, -1)
276334
}
277335
if str == newStr {
@@ -284,54 +342,49 @@ func (m Map) ExpandPropsInString(str string) string {
284342

285343
// Merge merges a Map into this one. Each key/value of the merged Maps replaces
286344
// the key/value present in the original Map.
287-
func (m Map) Merge(sources ...Map) Map {
345+
func (m *Map) Merge(sources ...*Map) *Map {
288346
for _, source := range sources {
289-
for key, value := range source {
290-
m[key] = value
347+
for _, key := range source.o {
348+
value := source.kv[key]
349+
m.Set(key, value)
291350
}
292351
}
293352
return m
294353
}
295354

296355
// Keys returns an array of the keys contained in the Map
297-
func (m Map) Keys() []string {
298-
keys := make([]string, len(m))
299-
i := 0
300-
for key := range m {
301-
keys[i] = key
302-
i++
303-
}
356+
func (m *Map) Keys() []string {
357+
keys := make([]string, len(m.o))
358+
copy(keys, m.o)
304359
return keys
305360
}
306361

307362
// Values returns an array of the values contained in the Map. Duplicated
308363
// values are repeated in the list accordingly.
309364
func (m Map) Values() []string {
310-
values := make([]string, len(m))
311-
i := 0
312-
for _, value := range m {
313-
values[i] = value
314-
i++
365+
values := make([]string, len(m.o))
366+
for i, key := range m.o {
367+
values[i] = m.kv[key]
315368
}
316369
return values
317370
}
318371

319372
// Clone makes a copy of the Map
320-
func (m Map) Clone() Map {
321-
clone := make(Map)
373+
func (m *Map) Clone() *Map {
374+
clone := New()
322375
clone.Merge(m)
323376
return clone
324377
}
325378

326379
// Equals returns true if the current Map contains the same key/value pairs of
327-
// the Map passed as argument.
328-
func (m Map) Equals(other Map) bool {
380+
// the Map passed as argument with the same order of insertion.
381+
func (m *Map) Equals(other *Map) bool {
329382
return reflect.DeepEqual(m, other)
330383
}
331384

332385
// MergeMapsOfProperties merge the map-of-Maps (obtained from the method FirstLevelOf()) into the
333386
// target map-of-Maps.
334-
func MergeMapsOfProperties(target map[string]Map, sources ...map[string]Map) map[string]Map {
387+
func MergeMapsOfProperties(target map[string]*Map, sources ...map[string]*Map) map[string]*Map {
335388
for _, source := range sources {
336389
for key, value := range source {
337390
target[key] = value
@@ -348,15 +401,10 @@ func DeleteUnexpandedPropsFromString(str string) string {
348401
}
349402

350403
// Dump returns a representation of the map in golang source format
351-
func (m Map) Dump() string {
404+
func (m *Map) Dump() string {
352405
res := "properties.Map{\n"
353-
keys := []string{}
354-
for k := range m {
355-
keys = append(keys, k)
356-
}
357-
sort.Strings(keys)
358-
for _, k := range keys {
359-
res += fmt.Sprintf(" \"%s\": \"%s\",\n", strings.Replace(k, `"`, `\"`, -1), strings.Replace(m[k], `"`, `\"`, -1))
406+
for _, k := range m.o {
407+
res += fmt.Sprintf(" \"%s\": \"%s\",\n", strings.Replace(k, `"`, `\"`, -1), strings.Replace(m.Get(k), `"`, `\"`, -1))
360408
}
361409
res += "}"
362410
return res

0 commit comments

Comments
 (0)