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	position  Position
27}
28
29func newTree() *Tree {
30	return &Tree{
31		values:   make(map[string]interface{}),
32		position: Position{},
33	}
34}
35
36// TreeFromMap initializes a new Tree object using the given map.
37func TreeFromMap(m map[string]interface{}) (*Tree, error) {
38	result, err := toTree(m)
39	if err != nil {
40		return nil, err
41	}
42	return result.(*Tree), nil
43}
44
45// Position returns the position of the tree.
46func (t *Tree) Position() Position {
47	return t.position
48}
49
50// Has returns a boolean indicating if the given key exists.
51func (t *Tree) Has(key string) bool {
52	if key == "" {
53		return false
54	}
55	return t.HasPath(strings.Split(key, "."))
56}
57
58// HasPath returns true if the given path of keys exists, false otherwise.
59func (t *Tree) HasPath(keys []string) bool {
60	return t.GetPath(keys) != nil
61}
62
63// Keys returns the keys of the toplevel tree (does not recurse).
64func (t *Tree) Keys() []string {
65	keys := make([]string, len(t.values))
66	i := 0
67	for k := range t.values {
68		keys[i] = k
69		i++
70	}
71	return keys
72}
73
74// Get the value at key in the Tree.
75// Key is a dot-separated path (e.g. a.b.c) without single/double quoted strings.
76// If you need to retrieve non-bare keys, use GetPath.
77// Returns nil if the path does not exist in the tree.
78// If keys is of length zero, the current tree is returned.
79func (t *Tree) Get(key string) interface{} {
80	if key == "" {
81		return t
82	}
83	return t.GetPath(strings.Split(key, "."))
84}
85
86// GetPath returns the element in the tree indicated by 'keys'.
87// If keys is of length zero, the current tree is returned.
88func (t *Tree) GetPath(keys []string) interface{} {
89	if len(keys) == 0 {
90		return t
91	}
92	subtree := t
93	for _, intermediateKey := range keys[:len(keys)-1] {
94		value, exists := subtree.values[intermediateKey]
95		if !exists {
96			return nil
97		}
98		switch node := value.(type) {
99		case *Tree:
100			subtree = node
101		case []*Tree:
102			// go to most recent element
103			if len(node) == 0 {
104				return nil
105			}
106			subtree = node[len(node)-1]
107		default:
108			return nil // cannot navigate through other node types
109		}
110	}
111	// branch based on final node type
112	switch node := subtree.values[keys[len(keys)-1]].(type) {
113	case *tomlValue:
114		return node.value
115	default:
116		return node
117	}
118}
119
120// GetPosition returns the position of the given key.
121func (t *Tree) GetPosition(key string) Position {
122	if key == "" {
123		return t.position
124	}
125	return t.GetPositionPath(strings.Split(key, "."))
126}
127
128// GetPositionPath returns the element in the tree indicated by 'keys'.
129// If keys is of length zero, the current tree is returned.
130func (t *Tree) GetPositionPath(keys []string) Position {
131	if len(keys) == 0 {
132		return t.position
133	}
134	subtree := t
135	for _, intermediateKey := range keys[:len(keys)-1] {
136		value, exists := subtree.values[intermediateKey]
137		if !exists {
138			return Position{0, 0}
139		}
140		switch node := value.(type) {
141		case *Tree:
142			subtree = node
143		case []*Tree:
144			// go to most recent element
145			if len(node) == 0 {
146				return Position{0, 0}
147			}
148			subtree = node[len(node)-1]
149		default:
150			return Position{0, 0}
151		}
152	}
153	// branch based on final node type
154	switch node := subtree.values[keys[len(keys)-1]].(type) {
155	case *tomlValue:
156		return node.position
157	case *Tree:
158		return node.position
159	case []*Tree:
160		// go to most recent element
161		if len(node) == 0 {
162			return Position{0, 0}
163		}
164		return node[len(node)-1].position
165	default:
166		return Position{0, 0}
167	}
168}
169
170// GetDefault works like Get but with a default value
171func (t *Tree) GetDefault(key string, def interface{}) interface{} {
172	val := t.Get(key)
173	if val == nil {
174		return def
175	}
176	return val
177}
178
179// SetOptions arguments are supplied to the SetWithOptions and SetPathWithOptions functions to modify marshalling behaviour.
180// The default values within the struct are valid default options.
181type SetOptions struct {
182	Comment   string
183	Commented bool
184	Multiline bool
185}
186
187// SetWithOptions is the same as Set, but allows you to provide formatting
188// instructions to the key, that will be used by Marshal().
189func (t *Tree) SetWithOptions(key string, opts SetOptions, value interface{}) {
190	t.SetPathWithOptions(strings.Split(key, "."), opts, value)
191}
192
193// SetPathWithOptions is the same as SetPath, but allows you to provide
194// formatting instructions to the key, that will be reused by Marshal().
195func (t *Tree) SetPathWithOptions(keys []string, opts SetOptions, value interface{}) {
196	subtree := t
197	for _, intermediateKey := range keys[:len(keys)-1] {
198		nextTree, exists := subtree.values[intermediateKey]
199		if !exists {
200			nextTree = newTree()
201			subtree.values[intermediateKey] = nextTree // add new element here
202		}
203		switch node := nextTree.(type) {
204		case *Tree:
205			subtree = node
206		case []*Tree:
207			// go to most recent element
208			if len(node) == 0 {
209				// create element if it does not exist
210				subtree.values[intermediateKey] = append(node, newTree())
211			}
212			subtree = node[len(node)-1]
213		}
214	}
215
216	var toInsert interface{}
217
218	switch value.(type) {
219	case *Tree:
220		tt := value.(*Tree)
221		tt.comment = opts.Comment
222		toInsert = value
223	case []*Tree:
224		toInsert = value
225	case *tomlValue:
226		tt := value.(*tomlValue)
227		tt.comment = opts.Comment
228		toInsert = tt
229	default:
230		toInsert = &tomlValue{value: value, comment: opts.Comment, commented: opts.Commented, multiline: opts.Multiline}
231	}
232
233	subtree.values[keys[len(keys)-1]] = toInsert
234}
235
236// Set an element in the tree.
237// Key is a dot-separated path (e.g. a.b.c).
238// Creates all necessary intermediate trees, if needed.
239func (t *Tree) Set(key string, value interface{}) {
240	t.SetWithComment(key, "", false, value)
241}
242
243// SetWithComment is the same as Set, but allows you to provide comment
244// information to the key, that will be reused by Marshal().
245func (t *Tree) SetWithComment(key string, comment string, commented bool, value interface{}) {
246	t.SetPathWithComment(strings.Split(key, "."), comment, commented, value)
247}
248
249// SetPath sets an element in the tree.
250// Keys is an array of path elements (e.g. {"a","b","c"}).
251// Creates all necessary intermediate trees, if needed.
252func (t *Tree) SetPath(keys []string, value interface{}) {
253	t.SetPathWithComment(keys, "", false, value)
254}
255
256// SetPathWithComment is the same as SetPath, but allows you to provide comment
257// information to the key, that will be reused by Marshal().
258func (t *Tree) SetPathWithComment(keys []string, comment string, commented bool, value interface{}) {
259	subtree := t
260	for _, intermediateKey := range keys[:len(keys)-1] {
261		nextTree, exists := subtree.values[intermediateKey]
262		if !exists {
263			nextTree = newTree()
264			subtree.values[intermediateKey] = nextTree // add new element here
265		}
266		switch node := nextTree.(type) {
267		case *Tree:
268			subtree = node
269		case []*Tree:
270			// go to most recent element
271			if len(node) == 0 {
272				// create element if it does not exist
273				subtree.values[intermediateKey] = append(node, newTree())
274			}
275			subtree = node[len(node)-1]
276		}
277	}
278
279	var toInsert interface{}
280
281	switch value.(type) {
282	case *Tree:
283		tt := value.(*Tree)
284		tt.comment = comment
285		toInsert = value
286	case []*Tree:
287		toInsert = value
288	case *tomlValue:
289		tt := value.(*tomlValue)
290		tt.comment = comment
291		toInsert = tt
292	default:
293		toInsert = &tomlValue{value: value, comment: comment, commented: commented}
294	}
295
296	subtree.values[keys[len(keys)-1]] = toInsert
297}
298
299// createSubTree takes a tree and a key and create the necessary intermediate
300// subtrees to create a subtree at that point. In-place.
301//
302// e.g. passing a.b.c will create (assuming tree is empty) tree[a], tree[a][b]
303// and tree[a][b][c]
304//
305// Returns nil on success, error object on failure
306func (t *Tree) createSubTree(keys []string, pos Position) error {
307	subtree := t
308	for _, intermediateKey := range keys {
309		nextTree, exists := subtree.values[intermediateKey]
310		if !exists {
311			tree := newTree()
312			tree.position = pos
313			subtree.values[intermediateKey] = tree
314			nextTree = tree
315		}
316
317		switch node := nextTree.(type) {
318		case []*Tree:
319			subtree = node[len(node)-1]
320		case *Tree:
321			subtree = node
322		default:
323			return fmt.Errorf("unknown type for path %s (%s): %T (%#v)",
324				strings.Join(keys, "."), intermediateKey, nextTree, nextTree)
325		}
326	}
327	return nil
328}
329
330// LoadBytes creates a Tree from a []byte.
331func LoadBytes(b []byte) (tree *Tree, err error) {
332	defer func() {
333		if r := recover(); r != nil {
334			if _, ok := r.(runtime.Error); ok {
335				panic(r)
336			}
337			err = errors.New(r.(string))
338		}
339	}()
340	tree = parseToml(lexToml(b))
341	return
342}
343
344// LoadReader creates a Tree from any io.Reader.
345func LoadReader(reader io.Reader) (tree *Tree, err error) {
346	inputBytes, err := ioutil.ReadAll(reader)
347	if err != nil {
348		return
349	}
350	tree, err = LoadBytes(inputBytes)
351	return
352}
353
354// Load creates a Tree from a string.
355func Load(content string) (tree *Tree, err error) {
356	return LoadBytes([]byte(content))
357}
358
359// LoadFile creates a Tree from a file.
360func LoadFile(path string) (tree *Tree, err error) {
361	file, err := os.Open(path)
362	if err != nil {
363		return nil, err
364	}
365	defer file.Close()
366	return LoadReader(file)
367}
368