388 lines
9.1 KiB
Go
388 lines
9.1 KiB
Go
package toml
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"io/ioutil"
|
|
"runtime"
|
|
"strconv"
|
|
"strings"
|
|
"time"
|
|
)
|
|
|
|
type tomlValue struct {
|
|
value interface{}
|
|
position Position
|
|
}
|
|
|
|
// TomlTree is the result of the parsing of a TOML file.
|
|
type TomlTree struct {
|
|
values map[string]interface{}
|
|
position Position
|
|
}
|
|
|
|
func newTomlTree() *TomlTree {
|
|
return &TomlTree{
|
|
values: make(map[string]interface{}),
|
|
position: Position{},
|
|
}
|
|
}
|
|
|
|
func TreeFromMap(m map[string]interface{}) *TomlTree {
|
|
return &TomlTree{
|
|
values: m,
|
|
}
|
|
}
|
|
|
|
// Has returns a boolean indicating if the given key exists.
|
|
func (t *TomlTree) 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 *TomlTree) HasPath(keys []string) bool {
|
|
return t.GetPath(keys) != nil
|
|
}
|
|
|
|
// Keys returns the keys of the toplevel tree.
|
|
// Warning: this is a costly operation.
|
|
func (t *TomlTree) Keys() []string {
|
|
var keys []string
|
|
for k := range t.values {
|
|
keys = append(keys, k)
|
|
}
|
|
return keys
|
|
}
|
|
|
|
// Get the value at key in the TomlTree.
|
|
// Key is a dot-separated path (e.g. a.b.c).
|
|
// Returns nil if the path does not exist in the tree.
|
|
// If keys is of length zero, the current tree is returned.
|
|
func (t *TomlTree) Get(key string) interface{} {
|
|
if key == "" {
|
|
return t
|
|
}
|
|
comps, err := parseKey(key)
|
|
if err != nil {
|
|
return nil
|
|
}
|
|
return t.GetPath(comps)
|
|
}
|
|
|
|
// GetPath returns the element in the tree indicated by 'keys'.
|
|
// If keys is of length zero, the current tree is returned.
|
|
func (t *TomlTree) 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 *TomlTree:
|
|
subtree = node
|
|
case []*TomlTree:
|
|
// go to most recent element
|
|
if len(node) == 0 {
|
|
return nil
|
|
}
|
|
subtree = node[len(node)-1]
|
|
default:
|
|
return nil // cannot naigate 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 *TomlTree) 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 *TomlTree) 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 *TomlTree:
|
|
subtree = node
|
|
case []*TomlTree:
|
|
// 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 *TomlTree:
|
|
return node.position
|
|
case []*TomlTree:
|
|
// 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 *TomlTree) GetDefault(key string, def interface{}) interface{} {
|
|
val := t.Get(key)
|
|
if val == nil {
|
|
return def
|
|
}
|
|
return val
|
|
}
|
|
|
|
// Set an element in the tree.
|
|
// Key is a dot-separated path (e.g. a.b.c).
|
|
// Creates all necessary intermediates trees, if needed.
|
|
func (t *TomlTree) Set(key string, value interface{}) {
|
|
t.SetPath(strings.Split(key, "."), value)
|
|
}
|
|
|
|
// SetPath sets an element in the tree.
|
|
// Keys is an array of path elements (e.g. {"a","b","c"}).
|
|
// Creates all necessary intermediates trees, if needed.
|
|
func (t *TomlTree) SetPath(keys []string, value interface{}) {
|
|
subtree := t
|
|
for _, intermediateKey := range keys[:len(keys)-1] {
|
|
nextTree, exists := subtree.values[intermediateKey]
|
|
if !exists {
|
|
nextTree = newTomlTree()
|
|
subtree.values[intermediateKey] = nextTree // add new element here
|
|
}
|
|
switch node := nextTree.(type) {
|
|
case *TomlTree:
|
|
subtree = node
|
|
case []*TomlTree:
|
|
// go to most recent element
|
|
if len(node) == 0 {
|
|
// create element if it does not exist
|
|
subtree.values[intermediateKey] = append(node, newTomlTree())
|
|
}
|
|
subtree = node[len(node)-1]
|
|
}
|
|
}
|
|
|
|
var toInsert interface{}
|
|
|
|
switch value.(type) {
|
|
case *TomlTree:
|
|
toInsert = value
|
|
case []*TomlTree:
|
|
toInsert = value
|
|
case *tomlValue:
|
|
toInsert = value
|
|
default:
|
|
toInsert = &tomlValue{value: value}
|
|
}
|
|
|
|
subtree.values[keys[len(keys)-1]] = toInsert
|
|
}
|
|
|
|
// 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 *TomlTree) createSubTree(keys []string, pos Position) error {
|
|
subtree := t
|
|
for _, intermediateKey := range keys {
|
|
if intermediateKey == "" {
|
|
return fmt.Errorf("empty intermediate table")
|
|
}
|
|
nextTree, exists := subtree.values[intermediateKey]
|
|
if !exists {
|
|
tree := newTomlTree()
|
|
tree.position = pos
|
|
subtree.values[intermediateKey] = tree
|
|
nextTree = tree
|
|
}
|
|
|
|
switch node := nextTree.(type) {
|
|
case []*TomlTree:
|
|
subtree = node[len(node)-1]
|
|
case *TomlTree:
|
|
subtree = node
|
|
default:
|
|
return fmt.Errorf("unknown type for path %s (%s): %T (%#v)",
|
|
strings.Join(keys, "."), intermediateKey, nextTree, nextTree)
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// encodes a string to a TOML-compliant string value
|
|
func encodeTomlString(value string) string {
|
|
result := ""
|
|
for _, rr := range value {
|
|
intRr := uint16(rr)
|
|
switch rr {
|
|
case '\b':
|
|
result += "\\b"
|
|
case '\t':
|
|
result += "\\t"
|
|
case '\n':
|
|
result += "\\n"
|
|
case '\f':
|
|
result += "\\f"
|
|
case '\r':
|
|
result += "\\r"
|
|
case '"':
|
|
result += "\\\""
|
|
case '\\':
|
|
result += "\\\\"
|
|
default:
|
|
if intRr < 0x001F {
|
|
result += fmt.Sprintf("\\u%0.4X", intRr)
|
|
} else {
|
|
result += string(rr)
|
|
}
|
|
}
|
|
}
|
|
return result
|
|
}
|
|
|
|
// Value print support function for ToString()
|
|
// Outputs the TOML compliant string representation of a value
|
|
func toTomlValue(item interface{}, indent int) string {
|
|
tab := strings.Repeat(" ", indent)
|
|
switch value := item.(type) {
|
|
case int64:
|
|
return tab + strconv.FormatInt(value, 10)
|
|
case float64:
|
|
return tab + strconv.FormatFloat(value, 'f', -1, 64)
|
|
case string:
|
|
return tab + "\"" + encodeTomlString(value) + "\""
|
|
case bool:
|
|
if value {
|
|
return "true"
|
|
}
|
|
return "false"
|
|
case time.Time:
|
|
return tab + value.Format(time.RFC3339)
|
|
case []interface{}:
|
|
result := tab + "[\n"
|
|
for _, item := range value {
|
|
result += toTomlValue(item, indent+2) + ",\n"
|
|
}
|
|
return result + tab + "]"
|
|
default:
|
|
panic(fmt.Sprintf("unsupported value type: %v", value))
|
|
}
|
|
}
|
|
|
|
// Recursive support function for ToString()
|
|
// Outputs a tree, using the provided keyspace to prefix group names
|
|
func (t *TomlTree) toToml(indent, keyspace string) string {
|
|
result := ""
|
|
for k, v := range t.values {
|
|
// figure out the keyspace
|
|
combinedKey := k
|
|
if keyspace != "" {
|
|
combinedKey = keyspace + "." + combinedKey
|
|
}
|
|
// output based on type
|
|
switch node := v.(type) {
|
|
case []*TomlTree:
|
|
for _, item := range node {
|
|
if len(item.Keys()) > 0 {
|
|
result += fmt.Sprintf("\n%s[[%s]]\n", indent, combinedKey)
|
|
}
|
|
result += item.toToml(indent+" ", combinedKey)
|
|
}
|
|
case *TomlTree:
|
|
if len(node.Keys()) > 0 {
|
|
result += fmt.Sprintf("\n%s[%s]\n", indent, combinedKey)
|
|
}
|
|
result += node.toToml(indent+" ", combinedKey)
|
|
case map[string]interface{}:
|
|
sub := TreeFromMap(node)
|
|
|
|
if len(sub.Keys()) > 0 {
|
|
result += fmt.Sprintf("\n%s[%s]\n", indent, combinedKey)
|
|
}
|
|
result += sub.toToml(indent+" ", combinedKey)
|
|
case *tomlValue:
|
|
result += fmt.Sprintf("%s%s = %s\n", indent, k, toTomlValue(node.value, 0))
|
|
default:
|
|
result += fmt.Sprintf("%s%s = %s\n", indent, k, toTomlValue(v, 0))
|
|
}
|
|
}
|
|
return result
|
|
}
|
|
|
|
func (t *TomlTree) Query(query string) (*QueryResult, error) {
|
|
if q, err := CompileQuery(query); err != nil {
|
|
return nil, err
|
|
} else {
|
|
return q.Execute(t), nil
|
|
}
|
|
}
|
|
|
|
// ToString generates a human-readable representation of the current tree.
|
|
// Output spans multiple lines, and is suitable for ingest by a TOML parser
|
|
func (t *TomlTree) ToString() string {
|
|
return t.toToml("", "")
|
|
}
|
|
|
|
// Load creates a TomlTree from a string.
|
|
func Load(content string) (tree *TomlTree, err error) {
|
|
defer func() {
|
|
if r := recover(); r != nil {
|
|
if _, ok := r.(runtime.Error); ok {
|
|
panic(r)
|
|
}
|
|
err = errors.New(r.(string))
|
|
}
|
|
}()
|
|
tree = parseToml(lexToml(content))
|
|
return
|
|
}
|
|
|
|
// LoadFile creates a TomlTree from a file.
|
|
func LoadFile(path string) (tree *TomlTree, err error) {
|
|
buff, ferr := ioutil.ReadFile(path)
|
|
if ferr != nil {
|
|
err = ferr
|
|
} else {
|
|
s := string(buff)
|
|
tree, err = Load(s)
|
|
}
|
|
return
|
|
}
|