Платформа ЦРНП "Мирокод" для разработки проектов
https://git.mirocod.ru
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
393 lines
10 KiB
393 lines
10 KiB
package toml |
|
|
|
import ( |
|
"errors" |
|
"fmt" |
|
"io" |
|
"io/ioutil" |
|
"os" |
|
"runtime" |
|
"strings" |
|
) |
|
|
|
type tomlValue struct { |
|
value interface{} // string, int64, uint64, float64, bool, time.Time, [] of any of this list |
|
comment string |
|
commented bool |
|
multiline bool |
|
position Position |
|
} |
|
|
|
// Tree is the result of the parsing of a TOML file. |
|
type Tree struct { |
|
values map[string]interface{} // string -> *tomlValue, *Tree, []*Tree |
|
comment string |
|
commented bool |
|
position Position |
|
} |
|
|
|
func newTree() *Tree { |
|
return newTreeWithPosition(Position{}) |
|
} |
|
|
|
func newTreeWithPosition(pos Position) *Tree { |
|
return &Tree{ |
|
values: make(map[string]interface{}), |
|
position: pos, |
|
} |
|
} |
|
|
|
// TreeFromMap initializes a new Tree object using the given map. |
|
func TreeFromMap(m map[string]interface{}) (*Tree, error) { |
|
result, err := toTree(m) |
|
if err != nil { |
|
return nil, err |
|
} |
|
return result.(*Tree), nil |
|
} |
|
|
|
// Position returns the position of the tree. |
|
func (t *Tree) Position() Position { |
|
return t.position |
|
} |
|
|
|
// Has returns a boolean indicating if the given key exists. |
|
func (t *Tree) Has(key string) bool { |
|
if key == "" { |
|
return false |
|
} |
|
return t.HasPath(strings.Split(key, ".")) |
|
} |
|
|
|
// HasPath returns true if the given path of keys exists, false otherwise. |
|
func (t *Tree) HasPath(keys []string) bool { |
|
return t.GetPath(keys) != nil |
|
} |
|
|
|
// Keys returns the keys of the toplevel tree (does not recurse). |
|
func (t *Tree) Keys() []string { |
|
keys := make([]string, len(t.values)) |
|
i := 0 |
|
for k := range t.values { |
|
keys[i] = k |
|
i++ |
|
} |
|
return keys |
|
} |
|
|
|
// Get the value at key in the Tree. |
|
// Key is a dot-separated path (e.g. a.b.c) without single/double quoted strings. |
|
// If you need to retrieve non-bare keys, use GetPath. |
|
// Returns nil if the path does not exist in the tree. |
|
// If keys is of length zero, the current tree is returned. |
|
func (t *Tree) Get(key string) interface{} { |
|
if key == "" { |
|
return t |
|
} |
|
return t.GetPath(strings.Split(key, ".")) |
|
} |
|
|
|
// GetPath returns the element in the tree indicated by 'keys'. |
|
// If keys is of length zero, the current tree is returned. |
|
func (t *Tree) GetPath(keys []string) interface{} { |
|
if len(keys) == 0 { |
|
return t |
|
} |
|
subtree := t |
|
for _, intermediateKey := range keys[:len(keys)-1] { |
|
value, exists := subtree.values[intermediateKey] |
|
if !exists { |
|
return nil |
|
} |
|
switch node := value.(type) { |
|
case *Tree: |
|
subtree = node |
|
case []*Tree: |
|
// go to most recent element |
|
if len(node) == 0 { |
|
return nil |
|
} |
|
subtree = node[len(node)-1] |
|
default: |
|
return nil // cannot navigate through other node types |
|
} |
|
} |
|
// branch based on final node type |
|
switch node := subtree.values[keys[len(keys)-1]].(type) { |
|
case *tomlValue: |
|
return node.value |
|
default: |
|
return node |
|
} |
|
} |
|
|
|
// GetPosition returns the position of the given key. |
|
func (t *Tree) GetPosition(key string) Position { |
|
if key == "" { |
|
return t.position |
|
} |
|
return t.GetPositionPath(strings.Split(key, ".")) |
|
} |
|
|
|
// GetPositionPath returns the element in the tree indicated by 'keys'. |
|
// If keys is of length zero, the current tree is returned. |
|
func (t *Tree) GetPositionPath(keys []string) Position { |
|
if len(keys) == 0 { |
|
return t.position |
|
} |
|
subtree := t |
|
for _, intermediateKey := range keys[:len(keys)-1] { |
|
value, exists := subtree.values[intermediateKey] |
|
if !exists { |
|
return Position{0, 0} |
|
} |
|
switch node := value.(type) { |
|
case *Tree: |
|
subtree = node |
|
case []*Tree: |
|
// go to most recent element |
|
if len(node) == 0 { |
|
return Position{0, 0} |
|
} |
|
subtree = node[len(node)-1] |
|
default: |
|
return Position{0, 0} |
|
} |
|
} |
|
// branch based on final node type |
|
switch node := subtree.values[keys[len(keys)-1]].(type) { |
|
case *tomlValue: |
|
return node.position |
|
case *Tree: |
|
return node.position |
|
case []*Tree: |
|
// go to most recent element |
|
if len(node) == 0 { |
|
return Position{0, 0} |
|
} |
|
return node[len(node)-1].position |
|
default: |
|
return Position{0, 0} |
|
} |
|
} |
|
|
|
// GetDefault works like Get but with a default value |
|
func (t *Tree) GetDefault(key string, def interface{}) interface{} { |
|
val := t.Get(key) |
|
if val == nil { |
|
return def |
|
} |
|
return val |
|
} |
|
|
|
// SetOptions arguments are supplied to the SetWithOptions and SetPathWithOptions functions to modify marshalling behaviour. |
|
// The default values within the struct are valid default options. |
|
type SetOptions struct { |
|
Comment string |
|
Commented bool |
|
Multiline bool |
|
} |
|
|
|
// SetWithOptions is the same as Set, but allows you to provide formatting |
|
// instructions to the key, that will be used by Marshal(). |
|
func (t *Tree) SetWithOptions(key string, opts SetOptions, value interface{}) { |
|
t.SetPathWithOptions(strings.Split(key, "."), opts, value) |
|
} |
|
|
|
// SetPathWithOptions is the same as SetPath, but allows you to provide |
|
// formatting instructions to the key, that will be reused by Marshal(). |
|
func (t *Tree) SetPathWithOptions(keys []string, opts SetOptions, value interface{}) { |
|
subtree := t |
|
for i, intermediateKey := range keys[:len(keys)-1] { |
|
nextTree, exists := subtree.values[intermediateKey] |
|
if !exists { |
|
nextTree = newTreeWithPosition(Position{Line: t.position.Line + i, Col: t.position.Col}) |
|
subtree.values[intermediateKey] = nextTree // add new element here |
|
} |
|
switch node := nextTree.(type) { |
|
case *Tree: |
|
subtree = node |
|
case []*Tree: |
|
// go to most recent element |
|
if len(node) == 0 { |
|
// create element if it does not exist |
|
subtree.values[intermediateKey] = append(node, newTreeWithPosition(Position{Line: t.position.Line + i, Col: t.position.Col})) |
|
} |
|
subtree = node[len(node)-1] |
|
} |
|
} |
|
|
|
var toInsert interface{} |
|
|
|
switch v := value.(type) { |
|
case *Tree: |
|
v.comment = opts.Comment |
|
toInsert = value |
|
case []*Tree: |
|
toInsert = value |
|
case *tomlValue: |
|
v.comment = opts.Comment |
|
toInsert = v |
|
default: |
|
toInsert = &tomlValue{value: value, |
|
comment: opts.Comment, |
|
commented: opts.Commented, |
|
multiline: opts.Multiline, |
|
position: Position{Line: subtree.position.Line + len(subtree.values) + 1, Col: subtree.position.Col}} |
|
} |
|
|
|
subtree.values[keys[len(keys)-1]] = toInsert |
|
} |
|
|
|
// Set an element in the tree. |
|
// Key is a dot-separated path (e.g. a.b.c). |
|
// Creates all necessary intermediate trees, if needed. |
|
func (t *Tree) Set(key string, value interface{}) { |
|
t.SetWithComment(key, "", false, value) |
|
} |
|
|
|
// SetWithComment is the same as Set, but allows you to provide comment |
|
// information to the key, that will be reused by Marshal(). |
|
func (t *Tree) SetWithComment(key string, comment string, commented bool, value interface{}) { |
|
t.SetPathWithComment(strings.Split(key, "."), comment, commented, value) |
|
} |
|
|
|
// SetPath sets an element in the tree. |
|
// Keys is an array of path elements (e.g. {"a","b","c"}). |
|
// Creates all necessary intermediate trees, if needed. |
|
func (t *Tree) SetPath(keys []string, value interface{}) { |
|
t.SetPathWithComment(keys, "", false, value) |
|
} |
|
|
|
// SetPathWithComment is the same as SetPath, but allows you to provide comment |
|
// information to the key, that will be reused by Marshal(). |
|
func (t *Tree) SetPathWithComment(keys []string, comment string, commented bool, value interface{}) { |
|
t.SetPathWithOptions(keys, SetOptions{Comment: comment, Commented: commented}, value) |
|
} |
|
|
|
// Delete removes a key from the tree. |
|
// Key is a dot-separated path (e.g. a.b.c). |
|
func (t *Tree) Delete(key string) error { |
|
keys, err := parseKey(key) |
|
if err != nil { |
|
return err |
|
} |
|
return t.DeletePath(keys) |
|
} |
|
|
|
// DeletePath removes a key from the tree. |
|
// Keys is an array of path elements (e.g. {"a","b","c"}). |
|
func (t *Tree) DeletePath(keys []string) error { |
|
keyLen := len(keys) |
|
if keyLen == 1 { |
|
delete(t.values, keys[0]) |
|
return nil |
|
} |
|
tree := t.GetPath(keys[:keyLen-1]) |
|
item := keys[keyLen-1] |
|
switch node := tree.(type) { |
|
case *Tree: |
|
delete(node.values, item) |
|
return nil |
|
} |
|
return errors.New("no such key to delete") |
|
} |
|
|
|
// createSubTree takes a tree and a key and create the necessary intermediate |
|
// subtrees to create a subtree at that point. In-place. |
|
// |
|
// e.g. passing a.b.c will create (assuming tree is empty) tree[a], tree[a][b] |
|
// and tree[a][b][c] |
|
// |
|
// Returns nil on success, error object on failure |
|
func (t *Tree) createSubTree(keys []string, pos Position) error { |
|
subtree := t |
|
for i, intermediateKey := range keys { |
|
nextTree, exists := subtree.values[intermediateKey] |
|
if !exists { |
|
tree := newTreeWithPosition(Position{Line: t.position.Line + i, Col: t.position.Col}) |
|
tree.position = pos |
|
subtree.values[intermediateKey] = tree |
|
nextTree = tree |
|
} |
|
|
|
switch node := nextTree.(type) { |
|
case []*Tree: |
|
subtree = node[len(node)-1] |
|
case *Tree: |
|
subtree = node |
|
default: |
|
return fmt.Errorf("unknown type for path %s (%s): %T (%#v)", |
|
strings.Join(keys, "."), intermediateKey, nextTree, nextTree) |
|
} |
|
} |
|
return nil |
|
} |
|
|
|
// LoadBytes creates a Tree from a []byte. |
|
func LoadBytes(b []byte) (tree *Tree, err error) { |
|
defer func() { |
|
if r := recover(); r != nil { |
|
if _, ok := r.(runtime.Error); ok { |
|
panic(r) |
|
} |
|
err = errors.New(r.(string)) |
|
} |
|
}() |
|
|
|
if len(b) >= 4 && (hasUTF32BigEndianBOM4(b) || hasUTF32LittleEndianBOM4(b)) { |
|
b = b[4:] |
|
} else if len(b) >= 3 && hasUTF8BOM3(b) { |
|
b = b[3:] |
|
} else if len(b) >= 2 && (hasUTF16BigEndianBOM2(b) || hasUTF16LittleEndianBOM2(b)) { |
|
b = b[2:] |
|
} |
|
|
|
tree = parseToml(lexToml(b)) |
|
return |
|
} |
|
|
|
func hasUTF16BigEndianBOM2(b []byte) bool { |
|
return b[0] == 0xFE && b[1] == 0xFF |
|
} |
|
|
|
func hasUTF16LittleEndianBOM2(b []byte) bool { |
|
return b[0] == 0xFF && b[1] == 0xFE |
|
} |
|
|
|
func hasUTF8BOM3(b []byte) bool { |
|
return b[0] == 0xEF && b[1] == 0xBB && b[2] == 0xBF |
|
} |
|
|
|
func hasUTF32BigEndianBOM4(b []byte) bool { |
|
return b[0] == 0x00 && b[1] == 0x00 && b[2] == 0xFE && b[3] == 0xFF |
|
} |
|
|
|
func hasUTF32LittleEndianBOM4(b []byte) bool { |
|
return b[0] == 0xFF && b[1] == 0xFE && b[2] == 0x00 && b[3] == 0x00 |
|
} |
|
|
|
// LoadReader creates a Tree from any io.Reader. |
|
func LoadReader(reader io.Reader) (tree *Tree, err error) { |
|
inputBytes, err := ioutil.ReadAll(reader) |
|
if err != nil { |
|
return |
|
} |
|
tree, err = LoadBytes(inputBytes) |
|
return |
|
} |
|
|
|
// Load creates a Tree from a string. |
|
func Load(content string) (tree *Tree, err error) { |
|
return LoadBytes([]byte(content)) |
|
} |
|
|
|
// LoadFile creates a Tree from a file. |
|
func LoadFile(path string) (tree *Tree, err error) { |
|
file, err := os.Open(path) |
|
if err != nil { |
|
return nil, err |
|
} |
|
defer file.Close() |
|
return LoadReader(file) |
|
}
|
|
|