| 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 |
| } |
| 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 *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 |
| } |