1package toml 2 3import ( 4 "errors" 5 "fmt" 6 "io" 7 "io/ioutil" 8 "os" 9 "runtime" 10 "strings" 11) 12 13type tomlValue struct { 14 value interface{} // string, int64, uint64, float64, bool, time.Time, [] of any of this list 15 comment string 16 commented bool 17 multiline bool 18 position Position 19} 20 21// Tree is the result of the parsing of a TOML file. 22type Tree struct { 23 values map[string]interface{} // string -> *tomlValue, *Tree, []*Tree 24 comment string 25 commented bool 26 inline bool 27 position Position 28} 29 30func newTree() *Tree { 31 return newTreeWithPosition(Position{}) 32} 33 34func newTreeWithPosition(pos Position) *Tree { 35 return &Tree{ 36 values: make(map[string]interface{}), 37 position: pos, 38 } 39} 40 41// TreeFromMap initializes a new Tree object using the given map. 42func TreeFromMap(m map[string]interface{}) (*Tree, error) { 43 result, err := toTree(m) 44 if err != nil { 45 return nil, err 46 } 47 return result.(*Tree), nil 48} 49 50// Position returns the position of the tree. 51func (t *Tree) Position() Position { 52 return t.position 53} 54 55// Has returns a boolean indicating if the given key exists. 56func (t *Tree) Has(key string) bool { 57 if key == "" { 58 return false 59 } 60 return t.HasPath(strings.Split(key, ".")) 61} 62 63// HasPath returns true if the given path of keys exists, false otherwise. 64func (t *Tree) HasPath(keys []string) bool { 65 return t.GetPath(keys) != nil 66} 67 68// Keys returns the keys of the toplevel tree (does not recurse). 69func (t *Tree) Keys() []string { 70 keys := make([]string, len(t.values)) 71 i := 0 72 for k := range t.values { 73 keys[i] = k 74 i++ 75 } 76 return keys 77} 78 79// Get the value at key in the Tree. 80// Key is a dot-separated path (e.g. a.b.c) without single/double quoted strings. 81// If you need to retrieve non-bare keys, use GetPath. 82// Returns nil if the path does not exist in the tree. 83// If keys is of length zero, the current tree is returned. 84func (t *Tree) Get(key string) interface{} { 85 if key == "" { 86 return t 87 } 88 return t.GetPath(strings.Split(key, ".")) 89} 90 91// GetPath returns the element in the tree indicated by 'keys'. 92// If keys is of length zero, the current tree is returned. 93func (t *Tree) GetPath(keys []string) interface{} { 94 if len(keys) == 0 { 95 return t 96 } 97 subtree := t 98 for _, intermediateKey := range keys[:len(keys)-1] { 99 value, exists := subtree.values[intermediateKey] 100 if !exists { 101 return nil 102 } 103 switch node := value.(type) { 104 case *Tree: 105 subtree = node 106 case []*Tree: 107 // go to most recent element 108 if len(node) == 0 { 109 return nil 110 } 111 subtree = node[len(node)-1] 112 default: 113 return nil // cannot navigate through other node types 114 } 115 } 116 // branch based on final node type 117 switch node := subtree.values[keys[len(keys)-1]].(type) { 118 case *tomlValue: 119 return node.value 120 default: 121 return node 122 } 123} 124 125// GetArray returns the value at key in the Tree. 126// It returns []string, []int64, etc type if key has homogeneous lists 127// Key is a dot-separated path (e.g. a.b.c) without single/double quoted strings. 128// Returns nil if the path does not exist in the tree. 129// If keys is of length zero, the current tree is returned. 130func (t *Tree) GetArray(key string) interface{} { 131 if key == "" { 132 return t 133 } 134 return t.GetArrayPath(strings.Split(key, ".")) 135} 136 137// GetArrayPath returns the element in the tree indicated by 'keys'. 138// If keys is of length zero, the current tree is returned. 139func (t *Tree) GetArrayPath(keys []string) interface{} { 140 if len(keys) == 0 { 141 return t 142 } 143 subtree := t 144 for _, intermediateKey := range keys[:len(keys)-1] { 145 value, exists := subtree.values[intermediateKey] 146 if !exists { 147 return nil 148 } 149 switch node := value.(type) { 150 case *Tree: 151 subtree = node 152 case []*Tree: 153 // go to most recent element 154 if len(node) == 0 { 155 return nil 156 } 157 subtree = node[len(node)-1] 158 default: 159 return nil // cannot navigate through other node types 160 } 161 } 162 // branch based on final node type 163 switch node := subtree.values[keys[len(keys)-1]].(type) { 164 case *tomlValue: 165 switch n := node.value.(type) { 166 case []interface{}: 167 return getArray(n) 168 default: 169 return node.value 170 } 171 default: 172 return node 173 } 174} 175 176// if homogeneous array, then return slice type object over []interface{} 177func getArray(n []interface{}) interface{} { 178 var s []string 179 var i64 []int64 180 var f64 []float64 181 var bl []bool 182 for _, value := range n { 183 switch v := value.(type) { 184 case string: 185 s = append(s, v) 186 case int64: 187 i64 = append(i64, v) 188 case float64: 189 f64 = append(f64, v) 190 case bool: 191 bl = append(bl, v) 192 default: 193 return n 194 } 195 } 196 if len(s) == len(n) { 197 return s 198 } else if len(i64) == len(n) { 199 return i64 200 } else if len(f64) == len(n) { 201 return f64 202 } else if len(bl) == len(n) { 203 return bl 204 } 205 return n 206} 207 208// GetPosition returns the position of the given key. 209func (t *Tree) GetPosition(key string) Position { 210 if key == "" { 211 return t.position 212 } 213 return t.GetPositionPath(strings.Split(key, ".")) 214} 215 216// SetPositionPath sets the position of element in the tree indicated by 'keys'. 217// If keys is of length zero, the current tree position is set. 218func (t *Tree) SetPositionPath(keys []string, pos Position) { 219 if len(keys) == 0 { 220 t.position = pos 221 return 222 } 223 subtree := t 224 for _, intermediateKey := range keys[:len(keys)-1] { 225 value, exists := subtree.values[intermediateKey] 226 if !exists { 227 return 228 } 229 switch node := value.(type) { 230 case *Tree: 231 subtree = node 232 case []*Tree: 233 // go to most recent element 234 if len(node) == 0 { 235 return 236 } 237 subtree = node[len(node)-1] 238 default: 239 return 240 } 241 } 242 // branch based on final node type 243 switch node := subtree.values[keys[len(keys)-1]].(type) { 244 case *tomlValue: 245 node.position = pos 246 return 247 case *Tree: 248 node.position = pos 249 return 250 case []*Tree: 251 // go to most recent element 252 if len(node) == 0 { 253 return 254 } 255 node[len(node)-1].position = pos 256 return 257 } 258} 259 260// GetPositionPath returns the element in the tree indicated by 'keys'. 261// If keys is of length zero, the current tree is returned. 262func (t *Tree) GetPositionPath(keys []string) Position { 263 if len(keys) == 0 { 264 return t.position 265 } 266 subtree := t 267 for _, intermediateKey := range keys[:len(keys)-1] { 268 value, exists := subtree.values[intermediateKey] 269 if !exists { 270 return Position{0, 0} 271 } 272 switch node := value.(type) { 273 case *Tree: 274 subtree = node 275 case []*Tree: 276 // go to most recent element 277 if len(node) == 0 { 278 return Position{0, 0} 279 } 280 subtree = node[len(node)-1] 281 default: 282 return Position{0, 0} 283 } 284 } 285 // branch based on final node type 286 switch node := subtree.values[keys[len(keys)-1]].(type) { 287 case *tomlValue: 288 return node.position 289 case *Tree: 290 return node.position 291 case []*Tree: 292 // go to most recent element 293 if len(node) == 0 { 294 return Position{0, 0} 295 } 296 return node[len(node)-1].position 297 default: 298 return Position{0, 0} 299 } 300} 301 302// GetDefault works like Get but with a default value 303func (t *Tree) GetDefault(key string, def interface{}) interface{} { 304 val := t.Get(key) 305 if val == nil { 306 return def 307 } 308 return val 309} 310 311// SetOptions arguments are supplied to the SetWithOptions and SetPathWithOptions functions to modify marshalling behaviour. 312// The default values within the struct are valid default options. 313type SetOptions struct { 314 Comment string 315 Commented bool 316 Multiline bool 317} 318 319// SetWithOptions is the same as Set, but allows you to provide formatting 320// instructions to the key, that will be used by Marshal(). 321func (t *Tree) SetWithOptions(key string, opts SetOptions, value interface{}) { 322 t.SetPathWithOptions(strings.Split(key, "."), opts, value) 323} 324 325// SetPathWithOptions is the same as SetPath, but allows you to provide 326// formatting instructions to the key, that will be reused by Marshal(). 327func (t *Tree) SetPathWithOptions(keys []string, opts SetOptions, value interface{}) { 328 subtree := t 329 for i, intermediateKey := range keys[:len(keys)-1] { 330 nextTree, exists := subtree.values[intermediateKey] 331 if !exists { 332 nextTree = newTreeWithPosition(Position{Line: t.position.Line + i, Col: t.position.Col}) 333 subtree.values[intermediateKey] = nextTree // add new element here 334 } 335 switch node := nextTree.(type) { 336 case *Tree: 337 subtree = node 338 case []*Tree: 339 // go to most recent element 340 if len(node) == 0 { 341 // create element if it does not exist 342 node = append(node, newTreeWithPosition(Position{Line: t.position.Line + i, Col: t.position.Col})) 343 subtree.values[intermediateKey] = node 344 } 345 subtree = node[len(node)-1] 346 } 347 } 348 349 var toInsert interface{} 350 351 switch v := value.(type) { 352 case *Tree: 353 v.comment = opts.Comment 354 v.commented = opts.Commented 355 toInsert = value 356 case []*Tree: 357 for i := range v { 358 v[i].commented = opts.Commented 359 } 360 toInsert = value 361 case *tomlValue: 362 v.comment = opts.Comment 363 v.commented = opts.Commented 364 v.multiline = opts.Multiline 365 toInsert = v 366 default: 367 toInsert = &tomlValue{value: value, 368 comment: opts.Comment, 369 commented: opts.Commented, 370 multiline: opts.Multiline, 371 position: Position{Line: subtree.position.Line + len(subtree.values) + 1, Col: subtree.position.Col}} 372 } 373 374 subtree.values[keys[len(keys)-1]] = toInsert 375} 376 377// Set an element in the tree. 378// Key is a dot-separated path (e.g. a.b.c). 379// Creates all necessary intermediate trees, if needed. 380func (t *Tree) Set(key string, value interface{}) { 381 t.SetWithComment(key, "", false, value) 382} 383 384// SetWithComment is the same as Set, but allows you to provide comment 385// information to the key, that will be reused by Marshal(). 386func (t *Tree) SetWithComment(key string, comment string, commented bool, value interface{}) { 387 t.SetPathWithComment(strings.Split(key, "."), comment, commented, value) 388} 389 390// SetPath sets an element in the tree. 391// Keys is an array of path elements (e.g. {"a","b","c"}). 392// Creates all necessary intermediate trees, if needed. 393func (t *Tree) SetPath(keys []string, value interface{}) { 394 t.SetPathWithComment(keys, "", false, value) 395} 396 397// SetPathWithComment is the same as SetPath, but allows you to provide comment 398// information to the key, that will be reused by Marshal(). 399func (t *Tree) SetPathWithComment(keys []string, comment string, commented bool, value interface{}) { 400 t.SetPathWithOptions(keys, SetOptions{Comment: comment, Commented: commented}, value) 401} 402 403// Delete removes a key from the tree. 404// Key is a dot-separated path (e.g. a.b.c). 405func (t *Tree) Delete(key string) error { 406 keys, err := parseKey(key) 407 if err != nil { 408 return err 409 } 410 return t.DeletePath(keys) 411} 412 413// DeletePath removes a key from the tree. 414// Keys is an array of path elements (e.g. {"a","b","c"}). 415func (t *Tree) DeletePath(keys []string) error { 416 keyLen := len(keys) 417 if keyLen == 1 { 418 delete(t.values, keys[0]) 419 return nil 420 } 421 tree := t.GetPath(keys[:keyLen-1]) 422 item := keys[keyLen-1] 423 switch node := tree.(type) { 424 case *Tree: 425 delete(node.values, item) 426 return nil 427 } 428 return errors.New("no such key to delete") 429} 430 431// createSubTree takes a tree and a key and create the necessary intermediate 432// subtrees to create a subtree at that point. In-place. 433// 434// e.g. passing a.b.c will create (assuming tree is empty) tree[a], tree[a][b] 435// and tree[a][b][c] 436// 437// Returns nil on success, error object on failure 438func (t *Tree) createSubTree(keys []string, pos Position) error { 439 subtree := t 440 for i, intermediateKey := range keys { 441 nextTree, exists := subtree.values[intermediateKey] 442 if !exists { 443 tree := newTreeWithPosition(Position{Line: t.position.Line + i, Col: t.position.Col}) 444 tree.position = pos 445 tree.inline = subtree.inline 446 subtree.values[intermediateKey] = tree 447 nextTree = tree 448 } 449 450 switch node := nextTree.(type) { 451 case []*Tree: 452 subtree = node[len(node)-1] 453 case *Tree: 454 subtree = node 455 default: 456 return fmt.Errorf("unknown type for path %s (%s): %T (%#v)", 457 strings.Join(keys, "."), intermediateKey, nextTree, nextTree) 458 } 459 } 460 return nil 461} 462 463// LoadBytes creates a Tree from a []byte. 464func LoadBytes(b []byte) (tree *Tree, err error) { 465 defer func() { 466 if r := recover(); r != nil { 467 if _, ok := r.(runtime.Error); ok { 468 panic(r) 469 } 470 err = errors.New(r.(string)) 471 } 472 }() 473 474 if len(b) >= 4 && (hasUTF32BigEndianBOM4(b) || hasUTF32LittleEndianBOM4(b)) { 475 b = b[4:] 476 } else if len(b) >= 3 && hasUTF8BOM3(b) { 477 b = b[3:] 478 } else if len(b) >= 2 && (hasUTF16BigEndianBOM2(b) || hasUTF16LittleEndianBOM2(b)) { 479 b = b[2:] 480 } 481 482 tree = parseToml(lexToml(b)) 483 return 484} 485 486func hasUTF16BigEndianBOM2(b []byte) bool { 487 return b[0] == 0xFE && b[1] == 0xFF 488} 489 490func hasUTF16LittleEndianBOM2(b []byte) bool { 491 return b[0] == 0xFF && b[1] == 0xFE 492} 493 494func hasUTF8BOM3(b []byte) bool { 495 return b[0] == 0xEF && b[1] == 0xBB && b[2] == 0xBF 496} 497 498func hasUTF32BigEndianBOM4(b []byte) bool { 499 return b[0] == 0x00 && b[1] == 0x00 && b[2] == 0xFE && b[3] == 0xFF 500} 501 502func hasUTF32LittleEndianBOM4(b []byte) bool { 503 return b[0] == 0xFF && b[1] == 0xFE && b[2] == 0x00 && b[3] == 0x00 504} 505 506// LoadReader creates a Tree from any io.Reader. 507func LoadReader(reader io.Reader) (tree *Tree, err error) { 508 inputBytes, err := ioutil.ReadAll(reader) 509 if err != nil { 510 return 511 } 512 tree, err = LoadBytes(inputBytes) 513 return 514} 515 516// Load creates a Tree from a string. 517func Load(content string) (tree *Tree, err error) { 518 return LoadBytes([]byte(content)) 519} 520 521// LoadFile creates a Tree from a file. 522func LoadFile(path string) (tree *Tree, err error) { 523 file, err := os.Open(path) 524 if err != nil { 525 return nil, err 526 } 527 defer file.Close() 528 return LoadReader(file) 529} 530