
full diff: https://github.com/pelletier/go-toml/compare/v1.8.1...v1.9.3 - v1.9.3: Clarify license and comply with Apache 2.0 - v1.9.2: Add Encoder.CompactComments to omit extra new line - v1.9.1: Fix empty trees line counting v1.9.0 ------------------- The highlight of this version is that the whole toml.Tree structure has been made public in a backward compatible way. This allows everyone using v1.x to fully access the data and metadata in the tree to extend the library. This is hopefully the last release in the v1.x track, as go-toml v2 is the main focus of development. What's new - TOML 1.0.0-rc.3 - Improved default tag for durations - Provide Tree and treeValue public aliases - Expose MarshalOrder - Value string representation public function Fixed bugs - Do not allow T-prefix on local dates - toml.Unmarshaler supports leaf nodes - Fix date lexer to only support 4-digit year - Fix ToMap for tables in mixed-type arrays - Fix ToMap for tables in nested mixed-type arrays - Support literal multiline marshal Performance - Remove date regexp - Remove underscore regexps Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
534 lines
13 KiB
Go
534 lines
13 KiB
Go
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
|
|
literal 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
|
|
inline 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
|
|
}
|
|
}
|
|
|
|
// GetArray returns the value at key in the Tree.
|
|
// It returns []string, []int64, etc type if key has homogeneous lists
|
|
// Key is a dot-separated path (e.g. a.b.c) without single/double quoted strings.
|
|
// 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) GetArray(key string) interface{} {
|
|
if key == "" {
|
|
return t
|
|
}
|
|
return t.GetArrayPath(strings.Split(key, "."))
|
|
}
|
|
|
|
// GetArrayPath returns the element in the tree indicated by 'keys'.
|
|
// If keys is of length zero, the current tree is returned.
|
|
func (t *Tree) GetArrayPath(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:
|
|
switch n := node.value.(type) {
|
|
case []interface{}:
|
|
return getArray(n)
|
|
default:
|
|
return node.value
|
|
}
|
|
default:
|
|
return node
|
|
}
|
|
}
|
|
|
|
// if homogeneous array, then return slice type object over []interface{}
|
|
func getArray(n []interface{}) interface{} {
|
|
var s []string
|
|
var i64 []int64
|
|
var f64 []float64
|
|
var bl []bool
|
|
for _, value := range n {
|
|
switch v := value.(type) {
|
|
case string:
|
|
s = append(s, v)
|
|
case int64:
|
|
i64 = append(i64, v)
|
|
case float64:
|
|
f64 = append(f64, v)
|
|
case bool:
|
|
bl = append(bl, v)
|
|
default:
|
|
return n
|
|
}
|
|
}
|
|
if len(s) == len(n) {
|
|
return s
|
|
} else if len(i64) == len(n) {
|
|
return i64
|
|
} else if len(f64) == len(n) {
|
|
return f64
|
|
} else if len(bl) == len(n) {
|
|
return bl
|
|
}
|
|
return n
|
|
}
|
|
|
|
// 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, "."))
|
|
}
|
|
|
|
// SetPositionPath sets the position of element in the tree indicated by 'keys'.
|
|
// If keys is of length zero, the current tree position is set.
|
|
func (t *Tree) SetPositionPath(keys []string, pos Position) {
|
|
if len(keys) == 0 {
|
|
t.position = pos
|
|
return
|
|
}
|
|
subtree := t
|
|
for _, intermediateKey := range keys[:len(keys)-1] {
|
|
value, exists := subtree.values[intermediateKey]
|
|
if !exists {
|
|
return
|
|
}
|
|
switch node := value.(type) {
|
|
case *Tree:
|
|
subtree = node
|
|
case []*Tree:
|
|
// go to most recent element
|
|
if len(node) == 0 {
|
|
return
|
|
}
|
|
subtree = node[len(node)-1]
|
|
default:
|
|
return
|
|
}
|
|
}
|
|
// branch based on final node type
|
|
switch node := subtree.values[keys[len(keys)-1]].(type) {
|
|
case *tomlValue:
|
|
node.position = pos
|
|
return
|
|
case *Tree:
|
|
node.position = pos
|
|
return
|
|
case []*Tree:
|
|
// go to most recent element
|
|
if len(node) == 0 {
|
|
return
|
|
}
|
|
node[len(node)-1].position = pos
|
|
return
|
|
}
|
|
}
|
|
|
|
// 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
|
|
Literal 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
|
|
node = append(node, newTreeWithPosition(Position{Line: t.position.Line + i, Col: t.position.Col}))
|
|
subtree.values[intermediateKey] = node
|
|
}
|
|
subtree = node[len(node)-1]
|
|
}
|
|
}
|
|
|
|
var toInsert interface{}
|
|
|
|
switch v := value.(type) {
|
|
case *Tree:
|
|
v.comment = opts.Comment
|
|
v.commented = opts.Commented
|
|
toInsert = value
|
|
case []*Tree:
|
|
for i := range v {
|
|
v[i].commented = opts.Commented
|
|
}
|
|
toInsert = value
|
|
case *tomlValue:
|
|
v.comment = opts.Comment
|
|
v.commented = opts.Commented
|
|
v.multiline = opts.Multiline
|
|
v.literal = opts.Literal
|
|
toInsert = v
|
|
default:
|
|
toInsert = &tomlValue{value: value,
|
|
comment: opts.Comment,
|
|
commented: opts.Commented,
|
|
multiline: opts.Multiline,
|
|
literal: opts.Literal,
|
|
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
|
|
tree.inline = subtree.inline
|
|
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)
|
|
}
|