// TOML markup language parser. // // This version supports the specification as described in // https://github.com/toml-lang/toml/blob/master/versions/toml-v0.2.0.md package toml import ( "errors" "fmt" "io/ioutil" "runtime" "strconv" "strings" "time" ) // Definition of a TomlTree. // This is the result of the parsing of a TOML file. type TomlTree map[string]interface{} // Has returns a boolean indicating if the toplevel tree contains the given // key. func (t *TomlTree) Has(key string) bool { mp := (map[string]interface{})(*t) for k, _ := range mp { if k == key { return true } } return false } // Keys returns the keys of the toplevel tree. // Warning: this is a costly operation. func (t *TomlTree) Keys() []string { keys := make([]string, 0) mp := (map[string]interface{})(*t) for k, _ := range mp { 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, ".")) } // 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 _, intermediate_key := range keys[:len(keys)-1] { _, exists := (*subtree)[intermediate_key] if !exists { return nil } subtree = (*subtree)[intermediate_key].(*TomlTree) } return (*subtree)[keys[len(keys)-1]] } // Same as 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) } func (t *TomlTree) SetPath(keys []string, value interface{}) { subtree := t for _, intermediate_key := range keys[:len(keys)-1] { _, exists := (*subtree)[intermediate_key] if !exists { var new_tree TomlTree = make(TomlTree) (*subtree)[intermediate_key] = &new_tree } subtree = (*subtree)[intermediate_key].(*TomlTree) } (*subtree)[keys[len(keys)-1]] = value } // 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] func (t *TomlTree) createSubTree(key string) { subtree := t for _, intermediate_key := range strings.Split(key, ".") { if intermediate_key == "" { panic("empty intermediate table") } _, exists := (*subtree)[intermediate_key] if !exists { var new_tree TomlTree = make(TomlTree) (*subtree)[intermediate_key] = &new_tree } subtree = ((*subtree)[intermediate_key]).(*TomlTree) } } // encodes a string to a TOML-compliant string value func encodeTomlString(value string) string { result := "" for _, rr := range value { int_rr := 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 int_rr < 0x001F { result += fmt.Sprintf("\\u%0.4X", int_rr) } 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" } else { 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(keyspace string) string { result := "" for k, v := range (map[string]interface{})(*t) { // figure out the keyspace combined_key := k if keyspace != "" { combined_key = keyspace + "." + combined_key } // output based on type switch node := v.(type) { case []*TomlTree: for _, item := range node { if len(item.Keys()) > 0 { result += fmt.Sprintf("\n[[%s]]\n", combined_key) } result += item.toToml(combined_key) } case *TomlTree: if len(node.Keys()) > 0 { result += fmt.Sprintf("\n[%s]\n", combined_key) } result += node.toToml(combined_key) default: result += fmt.Sprintf("%s = %s\n", k, toTomlValue(node, 0)) } } return result } // 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("") } // Create 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)) } }() _, flow := lex(content) tree = parse(flow) return } // Create 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 }