blob: db0f1fbfb2606f1d9b275165838d9f318f88bc7b [file] [log] [blame]
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package ycfg
import (
"fmt"
"strings"
log "github.com/Sirupsen/logrus"
"github.com/spf13/cast"
"mynewt.apache.org/newt/newt/parse"
)
// YAML configuration object. This is a substitute for a viper configuration
// object, with the following newt-specific advantages:
// 1. Case sensitive.
// 2. Efficient conditionals based on syscfg values.
//
// A single YCfg setting is implemented as a tree of nodes. Each word in the
// setting name represents a node; each "." in the name is a link in the tree.
// For example, the following syscfg lines:
//
// OS_MAIN_STACK_SIZE: 100
// OS_MAIN_STACK_SIZE.BLE_DEVICE: 200
// OS_MAIN_STACK_SIZE.SHELL_TASK: 300
//
// Is represented as the following tree:
//
// [OS_MAIN_STACK_SIZE (100)]
// / \
// [BLE_DEVICE (200)] [SHELL_TASK (300)]
//
// This allows us to quickly determine the value of OS_MAIN_STACK_SIZE. After
// finding the OS_MAIN_STACK_SIZE node, the logic is something like this:
//
// Is BLE_DEVICE true? --> 200
// Is SHELL_TASK true? --> 300
// Else: --> 100
//
// The tree structure also allows for arbitrary expressions as conditionals, as
// opposed to simple setting names. For example:
//
// OS_MAIN_STACK_SIZE: 100
// OS_MAIN_STACK_SIZE.'(BLE_DEVICE && !SHELL_TASK): 200
// OS_MAIN_STACK_SIZE.'(SHELL_TASK && !BLE_DEVICE): 300
// OS_MAIN_STACK_SIZE.'(SHELL_TASK && BLE_DEVICE): 400
//
// Since each expression is a child node of the setting in question, they are
// all known at the time of the lookup. To determine the value of the setting,
// each expression is parsed, and only the one evaluating to true is selected.
type YCfg map[string]*YCfgNode
type YCfgEntry struct {
Value interface{}
Expr string
}
type YCfgNode struct {
Overwrite bool
Name string
Value interface{}
Children YCfg
Parent *YCfgNode
}
func (node *YCfgNode) addChild(name string) (*YCfgNode, error) {
if node.Children == nil {
node.Children = YCfg{}
}
if node.Children[name] != nil {
return nil, fmt.Errorf("Duplicate YCfgNode: %s", name)
}
child := &YCfgNode{
Name: name,
Parent: node,
}
node.Children[name] = child
return child, nil
}
func (yc YCfg) Replace(key string, val interface{}) error {
elems := strings.Split(key, ".")
if len(elems) == 0 {
return fmt.Errorf("Invalid ycfg key: \"\"")
}
var overwrite bool
if elems[len(elems)-1] == "OVERWRITE" {
overwrite = true
elems = elems[:len(elems)-1]
}
var parent *YCfgNode
for i, e := range elems {
var parentChildren YCfg
if parent == nil {
parentChildren = yc
} else {
if parent.Children == nil {
parent.Children = YCfg{}
}
parentChildren = parent.Children
}
child := parentChildren[e]
if child == nil {
var err error
if parent != nil {
child, err = parent.addChild(e)
if err != nil {
return err
}
} else {
child = &YCfgNode{Name: e}
parentChildren[e] = child
}
}
if i == len(elems)-1 {
child.Overwrite = overwrite
child.Value = val
}
parent = child
}
return nil
}
func NewYCfg(kv map[string]interface{}) (YCfg, error) {
yc := YCfg{}
for k, v := range kv {
if err := yc.Replace(k, v); err != nil {
return nil, err
}
}
return yc, nil
}
func (yc YCfg) find(key string) *YCfgNode {
elems := strings.Split(key, ".")
if len(elems) == 0 {
return nil
}
cur := &YCfgNode{
Children: yc,
}
for _, e := range elems {
if cur.Children == nil {
return nil
}
cur = cur.Children[e]
if cur == nil {
return nil
}
}
return cur
}
func (yc YCfg) Get(key string, settings map[string]string) []YCfgEntry {
node := yc.find(key)
if node == nil {
return nil
}
entries := []YCfgEntry{}
if node.Value != nil {
entry := YCfgEntry{Value: node.Value}
entries = append(entries, entry)
}
for _, child := range node.Children {
val, err := parse.ParseAndEval(child.Name, settings)
if err != nil {
log.Error(err.Error())
} else if val {
entry := YCfgEntry{
Value: child.Value,
Expr: child.Name,
}
if child.Overwrite {
return []YCfgEntry{entry}
}
entries = append(entries, entry)
}
}
return entries
}
func (yc YCfg) GetSlice(key string, settings map[string]string) []YCfgEntry {
sliceEntries := yc.Get(key, settings)
if len(sliceEntries) == 0 {
return nil
}
result := []YCfgEntry{}
for _, sliceEntry := range sliceEntries {
if sliceEntry.Value != nil {
slice, err := cast.ToSliceE(sliceEntry.Value)
if err != nil {
// Not a slice. Put the single value in a new slice.
slice = []interface{}{sliceEntry.Value}
}
for _, v := range slice {
entry := YCfgEntry{
Value: v,
Expr: sliceEntry.Expr,
}
result = append(result, entry)
}
}
}
return result
}
func (yc YCfg) GetValSlice(
key string, settings map[string]string) []interface{} {
entries := yc.GetSlice(key, settings)
if len(entries) == 0 {
return nil
}
vals := make([]interface{}, len(entries))
for i, e := range entries {
vals[i] = e.Value
}
return vals
}
func (yc YCfg) GetStringSlice(key string,
settings map[string]string) []YCfgEntry {
sliceEntries := yc.Get(key, settings)
if len(sliceEntries) == 0 {
return nil
}
result := []YCfgEntry{}
for _, sliceEntry := range sliceEntries {
if sliceEntry.Value != nil {
slice, err := cast.ToStringSliceE(sliceEntry.Value)
if err != nil {
// Not a slice. Put the single value in a new slice.
slice = []string{cast.ToString(sliceEntry.Value)}
}
for _, v := range slice {
entry := YCfgEntry{
Value: v,
Expr: sliceEntry.Expr,
}
result = append(result, entry)
}
}
}
return result
}
func (yc YCfg) GetValStringSlice(
key string, settings map[string]string) []string {
entries := yc.GetStringSlice(key, settings)
if len(entries) == 0 {
return nil
}
vals := make([]string, len(entries))
for i, e := range entries {
if e.Value != nil {
vals[i] = cast.ToString(e.Value)
}
}
return vals
}
func (yc YCfg) GetValStringSliceNonempty(
key string, settings map[string]string) []string {
strs := yc.GetValStringSlice(key, settings)
filtered := make([]string, 0, len(strs))
for _, s := range strs {
if s != "" {
filtered = append(filtered, s)
}
}
return filtered
}
func (yc YCfg) GetStringMap(
key string, settings map[string]string) map[string]YCfgEntry {
mapEntries := yc.Get(key, settings)
if len(mapEntries) == 0 {
return nil
}
result := map[string]YCfgEntry{}
for _, mapEntry := range mapEntries {
for k, v := range cast.ToStringMap(mapEntry.Value) {
entry := YCfgEntry{
Value: v,
Expr: mapEntry.Expr,
}
// XXX: Report collisions?
result[k] = entry
}
}
return result
}
func (yc YCfg) GetValStringMap(
key string, settings map[string]string) map[string]interface{} {
entryMap := yc.GetStringMap(key, settings)
smap := make(map[string]interface{}, len(entryMap))
for k, v := range entryMap {
if v.Value != nil {
smap[k] = v.Value
}
}
return smap
}
func (yc YCfg) GetFirst(key string, settings map[string]string) (YCfgEntry, bool) {
entries := yc.Get(key, settings)
if len(entries) == 0 {
return YCfgEntry{}, false
}
return entries[0], true
}
func (yc YCfg) GetFirstVal(key string, settings map[string]string) interface{} {
entry, ok := yc.GetFirst(key, settings)
if !ok {
return nil
}
return entry.Value
}
func (yc YCfg) GetValString(key string, settings map[string]string) string {
entry, ok := yc.GetFirst(key, settings)
if !ok {
return ""
} else {
return cast.ToString(entry.Value)
}
}
func (yc YCfg) GetValInt(key string, settings map[string]string) int {
entry, ok := yc.GetFirst(key, settings)
if !ok {
return 0
} else {
return cast.ToInt(entry.Value)
}
}
func (yc YCfg) GetValBoolDflt(key string, settings map[string]string,
dflt bool) bool {
entry, ok := yc.GetFirst(key, settings)
if !ok {
return dflt
} else {
return cast.ToBool(entry.Value)
}
}
func (yc YCfg) GetValBool(key string, settings map[string]string) bool {
return yc.GetValBoolDflt(key, settings, false)
}
func (yc YCfg) GetStringMapString(key string,
settings map[string]string) map[string]YCfgEntry {
mapEntries := yc.Get(key, settings)
if len(mapEntries) == 0 {
return nil
}
result := map[string]YCfgEntry{}
for _, mapEntry := range mapEntries {
for k, v := range cast.ToStringMapString(mapEntry.Value) {
entry := YCfgEntry{
Value: v,
Expr: mapEntry.Expr,
}
// XXX: Report collisions?
result[k] = entry
}
}
return result
}
func (yc YCfg) GetValStringMapString(key string,
settings map[string]string) map[string]string {
entryMap := yc.GetStringMapString(key, settings)
valMap := make(map[string]string, len(entryMap))
for k, v := range entryMap {
if v.Value != nil {
valMap[k] = cast.ToString(v.Value)
}
}
return valMap
}
func (node *YCfgNode) FullName() string {
tokens := []string{}
for n := node; n != nil; n = n.Parent {
tokens = append(tokens, n.Name)
}
last := len(tokens) - 1
for i := 0; i < len(tokens)/2; i++ {
tokens[i], tokens[last-i] = tokens[last-i], tokens[i]
}
return strings.Join(tokens, ".")
}
func (yc YCfg) Delete(name string) {
delete(yc, name)
}
func (yc YCfg) Traverse(cb func(node *YCfgNode, depth int)) {
var traverseLevel func(
node *YCfgNode,
cb func(node *YCfgNode, depth int),
depth int)
traverseLevel = func(
node *YCfgNode,
cb func(node *YCfgNode, depth int),
depth int) {
cb(node, depth)
for _, child := range node.Children {
traverseLevel(child, cb, depth+1)
}
}
for _, n := range yc {
traverseLevel(n, cb, 0)
}
}
func (yc YCfg) AllSettings() map[string]interface{} {
settings := map[string]interface{}{}
yc.Traverse(func(node *YCfgNode, depth int) {
if node.Value != nil {
settings[node.FullName()] = node.Value
}
})
return settings
}
func (yc YCfg) AllSettingsAsStrings() map[string]string {
settings := yc.AllSettings()
smap := make(map[string]string, len(settings))
for k, v := range settings {
smap[k] = fmt.Sprintf("%v", v)
}
return smap
}
func (yc YCfg) String() string {
lines := make([]string, 0, len(yc))
yc.Traverse(func(node *YCfgNode, depth int) {
line := strings.Repeat(" ", depth*4) + node.Name
if node.Value != nil {
line += fmt.Sprintf(": %+v", node.Value)
}
lines = append(lines, line)
})
return strings.Join(lines, "\n")
}