func makeFullPath(path string) []string {
	parts := stringex.SplitMap(path, "/", func(p string) (string, bool) {
		if p == "" {
			return "", false
		}
		return strings.ToLower(p), true
	})
	return append(etcRoot, parts...)
}
--------------------
HELPERS
--------------------
makeFullPath creates the full path out of a string.
package etc
import (
	"context"
	"fmt"
	"io"
	"io/ioutil"
	"os"
	"regexp"
	"strings"
	"time"
	"github.com/tideland/golib/collections"
	"github.com/tideland/golib/errors"
	"github.com/tideland/golib/sml"
	"github.com/tideland/golib/stringex"
)
type key int
var (
	etcKey    key = 1
	etcRoot       = []string{"etc"}
	defaulter     = stringex.NewDefaulter("etc", false)
)
type value struct {
	path    []string
	changer collections.KeyStringValueChanger
}
func (v *value) Value() (string, error) {
	sv, err := v.changer.Value()
	if err != nil {
		return "", errors.New(ErrInvalidPath, errorMessages, fullPathToString(v.path))
	}
	return sv, nil
}
type Application map[string]string
type Etc interface {
	fmt.Stringer
	HasPath(path string) bool
	Do(path string, f func(p string) error) error
	ValueAsString(path, dv string) string
	ValueAsBool(path string, dv bool) bool
	ValueAsInt(path string, dv int) int
	ValueAsFloat64(path string, dv float64) float64
	ValueAsTime(path, layout string, dv time.Time) time.Time
	ValueAsDuration(path string, dv time.Duration) time.Duration
	Split(path string) (Etc, error)
	Dump() (Application, error)
	Apply(appl Application) (Etc, error)
	Write(target io.Writer, prettyPrint bool) error
}
type etc struct {
	values collections.KeyStringValueTree
}
func Read(source io.Reader) (Etc, error) {
	builder := sml.NewKeyStringValueTreeBuilder()
	err := sml.ReadSML(source, builder)
	if err != nil {
		return nil, errors.Annotate(err, ErrIllegalSourceFormat, errorMessages)
	}
	values, err := builder.Tree()
	if err != nil {
		return nil, errors.Annotate(err, ErrIllegalSourceFormat, errorMessages)
	}
	if err = values.At("etc").Error(); err != nil {
		return nil, errors.Annotate(err, ErrIllegalSourceFormat, errorMessages)
	}
	cfg := &etc{
		values: values,
	}
	if err = cfg.postProcess(); err != nil {
		return nil, errors.Annotate(err, ErrCannotPostProcess, errorMessages)
	}
	return cfg, nil
}
func ReadString(source string) (Etc, error) {
	return Read(strings.NewReader(source))
}
func ReadFile(filename string) (Etc, error) {
	source, err := ioutil.ReadFile(filename)
	if err != nil {
		return nil, errors.Annotate(err, ErrCannotReadFile, errorMessages, filename)
	}
	return ReadString(string(source))
}
func (e *etc) HasPath(path string) bool {
	fullPath := makeFullPath(path)
	changer := e.values.At(fullPath...)
	return changer.Error() == nil
}
func (e *etc) Do(path string, f func(p string) error) error {
	fullPath := makeFullPath(path)
	changer := e.values.At(fullPath...)
	if changer.Error() != nil {
		return changer.Error()
	}
	kvs, err := changer.List()
	if err != nil {
		return err
	}
	for _, kv := range kvs {
		p := pathToString(append(fullPath, kv.Key))
		err := f(p)
		if err != nil {
			return err
		}
	}
	return nil
}
func (e *etc) ValueAsString(path, dv string) string {
	value := e.valueAt(path)
	return defaulter.AsString(value, dv)
}
func (e *etc) ValueAsBool(path string, dv bool) bool {
	value := e.valueAt(path)
	return defaulter.AsBool(value, dv)
}
func (e *etc) ValueAsInt(path string, dv int) int {
	value := e.valueAt(path)
	return defaulter.AsInt(value, dv)
}
func (e *etc) ValueAsFloat64(path string, dv float64) float64 {
	value := e.valueAt(path)
	return defaulter.AsFloat64(value, dv)
}
func (e *etc) ValueAsTime(path, format string, dv time.Time) time.Time {
	value := e.valueAt(path)
	return defaulter.AsTime(value, format, dv)
}
func (e *etc) ValueAsDuration(path string, dv time.Duration) time.Duration {
	value := e.valueAt(path)
	return defaulter.AsDuration(value, dv)
}
func (e *etc) Split(path string) (Etc, error) {
	if !e.HasPath(path) {
		return ReadString("{etc}")
	}
	fullPath := makeFullPath(path)
	values, err := e.values.CopyAt(fullPath...)
	if err != nil {
		return nil, errors.Annotate(err, ErrCannotSplit, errorMessages)
	}
	values.At(fullPath[len(fullPath)-1:]...).SetKey("etc")
	es := &etc{
		values: values,
	}
	return es, nil
}
func (e *etc) Dump() (Application, error) {
	appl := Application{}
	err := e.values.DoAllDeep(func(ks []string, v string) error {
		if len(ks) == 1 {
			return nil
		}
		path := strings.Join(ks[1:], "/")
		appl[path] = v
		return nil
	})
	if err != nil {
		return nil, err
	}
	return appl, nil
}
func (e *etc) Apply(appl Application) (Etc, error) {
	ec := &etc{
		values: e.values.Copy(),
	}
	for path, value := range appl {
		fullPath := makeFullPath(path)
		_, err := ec.values.Create(fullPath...).SetValue(value)
		if err != nil {
			return nil, errors.Annotate(err, ErrCannotApply, errorMessages)
		}
	}
	return ec, nil
}
func (e *etc) Write(target io.Writer, prettyPrint bool) error {
	builder := sml.NewNodeBuilder()
	depth := 0
	err := e.values.DoAllDeep(func(ks []string, v string) error {
		doDepth := len(ks)
		tag := ks[doDepth-1]
		for i := depth; i > doDepth; i-- {
			builder.EndTagNode()
		}
		switch {
		case doDepth > depth:
			builder.BeginTagNode(tag)
			builder.TextNode(v)
			depth = doDepth
		case doDepth == depth:
			builder.EndTagNode()
			builder.BeginTagNode(tag)
			builder.TextNode(v)
		case doDepth < depth:
			builder.EndTagNode()
			builder.BeginTagNode(tag)
			builder.TextNode(v)
			depth = doDepth
		}
		return nil
	})
	if err != nil {
		return err
	}
	for i := depth; i > 0; i-- {
		builder.EndTagNode()
	}
	root, err := builder.Root()
	if err != nil {
		return err
	}
	wp := sml.NewStandardSMLWriter()
	wctx := sml.NewWriterContext(wp, target, prettyPrint, "   ")
	return sml.WriteSML(root, wctx)
}
func (e *etc) String() string {
	return fmt.Sprintf("%v", e.values)
}
func (e *etc) valueAt(path string) *value {
	fullPath := makeFullPath(path)
	changer := e.values.At(fullPath...)
	return &value{fullPath, changer}
}
func (e *etc) postProcess() error {
	re := regexp.MustCompile("\\[.+(||.+)\\]")
	changers := e.values.FindAll(func(k, v string) (bool, error) {
		return re.MatchString(v), nil
	})
	for _, changer := range changers {
		value, err := changer.Value()
		if err != nil {
			return err
		}
		found := re.FindString(value)
		sourceDefault := strings.SplitN(found[1:len(found)-1], "||", 2)
		defaultValue := found
		if len(sourceDefault) > 1 {
			defaultValue = sourceDefault[1]
		}
		substitute := ""
		if strings.HasPrefix(sourceDefault[0], "$") {
			if envValue, ok := os.LookupEnv(sourceDefault[0][1:]); ok {
				substitute = envValue
			} else {
				substitute = defaultValue
			}
		} else {
			substitute = e.ValueAsString(sourceDefault[0], defaultValue)
		}
		replaced := strings.Replace(value, found, substitute, -1)
		_, err = changer.SetValue(replaced)
		if err != nil {
			return err
		}
	}
	return nil
}
func NewContext(ctx context.Context, cfg Etc) context.Context {
	return context.WithValue(ctx, etcKey, cfg)
}
func FromContext(ctx context.Context) (Etc, bool) {
	cfg, ok := ctx.Value(etcKey).(Etc)
	return cfg, ok
}