mirror of
https://codeberg.org/forgejo/forgejo.git
synced 2024-12-26 18:12:17 +00:00
394 lines
10 KiB
Go
394 lines
10 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
|
||
|
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)
|
||
|
}
|