@@ -56,6 +56,9 @@ syntax, for example:
56
56
57
57
This library has methods to parse this kind of files into a Map object.
58
58
59
+ The Map internally keeps the insertion order so it can be retrieved later when
60
+ cycling through the key sets.
61
+
59
62
The Map object has many helper methods to accomplish some common operation
60
63
on this kind of data like cloning, merging, comparing and also extracting
61
64
a submap or generating a map-of-submaps from the first "level" of the hierarchy.
@@ -72,14 +75,16 @@ import (
72
75
"reflect"
73
76
"regexp"
74
77
"runtime"
75
- "sort"
76
78
"strings"
77
79
78
80
"github.com/arduino/go-paths-helper"
79
81
)
80
82
81
83
// 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
+ }
83
88
84
89
var osSuffix string
85
90
@@ -94,8 +99,16 @@ func init() {
94
99
}
95
100
}
96
101
102
+ // New returns a new Map
103
+ func New () * Map {
104
+ return & Map {
105
+ kv : map [string ]string {},
106
+ o : []string {},
107
+ }
108
+ }
109
+
97
110
// 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 ) {
99
112
bytes , err := ioutil .ReadFile (filepath )
100
113
if err != nil {
101
114
return nil , fmt .Errorf ("Error reading file: %s" , err )
@@ -105,7 +118,7 @@ func Load(filepath string) (Map, error) {
105
118
text = strings .Replace (text , "\r \n " , "\n " , - 1 )
106
119
text = strings .Replace (text , "\r " , "\n " , - 1 )
107
120
108
- properties := make ( Map )
121
+ properties := New ( )
109
122
110
123
for lineNum , line := range strings .Split (text , "\n " ) {
111
124
if err := properties .parseLine (line ); err != nil {
@@ -117,14 +130,14 @@ func Load(filepath string) (Map, error) {
117
130
}
118
131
119
132
// 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 ) {
121
134
return Load (path .String ())
122
135
}
123
136
124
137
// LoadFromSlice reads a properties file from an array of string
125
138
// 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 ( )
128
141
129
142
for lineNum , line := range lines {
130
143
if err := properties .parseLine (line ); err != nil {
@@ -135,7 +148,7 @@ func LoadFromSlice(lines []string) (Map, error) {
135
148
return properties , nil
136
149
}
137
150
138
- func (m Map ) parseLine (line string ) error {
151
+ func (m * Map ) parseLine (line string ) error {
139
152
line = strings .TrimSpace (line )
140
153
141
154
// Skip empty lines or comments
@@ -151,23 +164,23 @@ func (m Map) parseLine(line string) error {
151
164
value := strings .TrimSpace (lineParts [1 ])
152
165
153
166
key = strings .Replace (key , "." + osSuffix , "" , 1 )
154
- m [ key ] = value
167
+ m . Set ( key , value )
155
168
156
169
return nil
157
170
}
158
171
159
172
// SafeLoadFromPath is like LoadFromPath, except that it returns an empty Map if
160
173
// the specified file doesn't exists
161
- func SafeLoadFromPath (path * paths.Path ) (Map , error ) {
174
+ func SafeLoadFromPath (path * paths.Path ) (* Map , error ) {
162
175
return SafeLoad (path .String ())
163
176
}
164
177
165
178
// SafeLoad is like Load, except that it returns an empty Map if the specified
166
179
// file doesn't exists
167
- func SafeLoad (filepath string ) (Map , error ) {
180
+ func SafeLoad (filepath string ) (* Map , error ) {
168
181
_ , err := os .Stat (filepath )
169
182
if os .IsNotExist (err ) {
170
- return make ( Map ), nil
183
+ return New ( ), nil
171
184
}
172
185
173
186
properties , err := Load (filepath )
@@ -177,6 +190,49 @@ func SafeLoad(filepath string) (Map, error) {
177
190
return properties , nil
178
191
}
179
192
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
+
180
236
// FirstLevelOf generates a map-of-Maps using the first level of the hierarchy
181
237
// of the current Map. For example the following Map:
182
238
//
@@ -209,17 +265,18 @@ func SafeLoad(filepath string) (Map, error) {
209
265
// "bootloader.low_fuses": "0xFF",
210
266
// }
211
267
// }
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 {
215
271
if strings .Index (key , "." ) == - 1 {
216
272
continue
217
273
}
218
274
keyParts := strings .SplitN (key , "." , 2 )
219
275
if newMap [keyParts [0 ]] == nil {
220
- newMap [keyParts [0 ]] = make ( Map )
276
+ newMap [keyParts [0 ]] = New ( )
221
277
}
222
- newMap [keyParts [0 ]][keyParts [1 ]] = value
278
+ value := m .kv [key ]
279
+ newMap [keyParts [0 ]].Set (keyParts [1 ], value )
223
280
}
224
281
return newMap
225
282
}
@@ -248,14 +305,15 @@ func (m Map) FirstLevelOf() map[string]Map {
248
305
// "upload.protocol": "arduino",
249
306
// "upload.maximum_size": "32256",
250
307
// },
251
- func (m Map ) SubTree (rootKey string ) Map {
308
+ func (m * Map ) SubTree (rootKey string ) * Map {
252
309
rootKey += "."
253
- newMap := Map {}
254
- for key , value := range m {
310
+ newMap := New ()
311
+ for _ , key := range m . o {
255
312
if ! strings .HasPrefix (key , rootKey ) {
256
313
continue
257
314
}
258
- newMap [key [len (rootKey ):]] = value
315
+ value := m .kv [key ]
316
+ newMap .Set (key [len (rootKey ):], value )
259
317
}
260
318
return newMap
261
319
}
@@ -268,10 +326,10 @@ func (m Map) SubTree(rootKey string) Map {
268
326
// Each marker is replaced by the corresponding value of the Map.
269
327
// The values in the Map may contains other markers, they are evaluated
270
328
// recursively up to 10 times.
271
- func (m Map ) ExpandPropsInString (str string ) string {
329
+ func (m * Map ) ExpandPropsInString (str string ) string {
272
330
for i := 0 ; i < 10 ; i ++ {
273
331
newStr := str
274
- for key , value := range m {
332
+ for key , value := range m . kv {
275
333
newStr = strings .Replace (newStr , "{" + key + "}" , value , - 1 )
276
334
}
277
335
if str == newStr {
@@ -284,54 +342,49 @@ func (m Map) ExpandPropsInString(str string) string {
284
342
285
343
// Merge merges a Map into this one. Each key/value of the merged Maps replaces
286
344
// the key/value present in the original Map.
287
- func (m Map ) Merge (sources ... Map ) Map {
345
+ func (m * Map ) Merge (sources ... * Map ) * Map {
288
346
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 )
291
350
}
292
351
}
293
352
return m
294
353
}
295
354
296
355
// 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 )
304
359
return keys
305
360
}
306
361
307
362
// Values returns an array of the values contained in the Map. Duplicated
308
363
// values are repeated in the list accordingly.
309
364
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 ]
315
368
}
316
369
return values
317
370
}
318
371
319
372
// 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 ( )
322
375
clone .Merge (m )
323
376
return clone
324
377
}
325
378
326
379
// 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 {
329
382
return reflect .DeepEqual (m , other )
330
383
}
331
384
332
385
// MergeMapsOfProperties merge the map-of-Maps (obtained from the method FirstLevelOf()) into the
333
386
// 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 {
335
388
for _ , source := range sources {
336
389
for key , value := range source {
337
390
target [key ] = value
@@ -348,15 +401,10 @@ func DeleteUnexpandedPropsFromString(str string) string {
348
401
}
349
402
350
403
// Dump returns a representation of the map in golang source format
351
- func (m Map ) Dump () string {
404
+ func (m * Map ) Dump () string {
352
405
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 ))
360
408
}
361
409
res += "}"
362
410
return res
0 commit comments