package forge
|
|
|
|
import (
|
|
"encoding/json"
|
|
"errors"
|
|
"fmt"
|
|
"sort"
|
|
"strings"
|
|
)
|
|
|
|
// Section struct holds a map of values
|
|
type Section struct {
|
|
comments []string
|
|
includes []string
|
|
parent *Section
|
|
values map[string]Value
|
|
}
|
|
|
|
// NewSection will create and initialize a new Section
|
|
func NewSection() *Section {
|
|
return &Section{
|
|
comments: make([]string, 0),
|
|
includes: make([]string, 0),
|
|
values: make(map[string]Value),
|
|
}
|
|
}
|
|
|
|
func newChildSection(parent *Section) *Section {
|
|
return &Section{
|
|
comments: make([]string, 0),
|
|
includes: make([]string, 0),
|
|
parent: parent,
|
|
values: make(map[string]Value),
|
|
}
|
|
}
|
|
|
|
// AddComment will append a new comment into the section
|
|
func (section *Section) AddComment(comment string) {
|
|
section.comments = append(section.comments, comment)
|
|
}
|
|
|
|
// AddInclude will append a new filename into the section
|
|
func (section *Section) AddInclude(filename string) {
|
|
section.includes = append(section.includes, filename)
|
|
}
|
|
|
|
// GetComments will return all the comments were defined for this Section
|
|
func (section *Section) GetComments() []string {
|
|
return section.comments
|
|
}
|
|
|
|
// GetIncludes will return the filenames of all the includes were parsed for this Section
|
|
func (section *Section) GetIncludes() []string {
|
|
return section.includes
|
|
}
|
|
|
|
// GetType will respond with the ValueType of this Section (hint, always SECTION)
|
|
func (section *Section) GetType() ValueType {
|
|
return SECTION
|
|
}
|
|
|
|
// GetValue retrieves the raw underlying value stored in this Section
|
|
func (section *Section) GetValue() interface{} {
|
|
return section.values
|
|
}
|
|
|
|
// UpdateValue updates the raw underlying value stored in this Section
|
|
func (section *Section) UpdateValue(value interface{}) error {
|
|
switch value.(type) {
|
|
case map[string]Value:
|
|
section.values = value.(map[string]Value)
|
|
return nil
|
|
}
|
|
|
|
msg := fmt.Sprintf("unsupported type, %s must be of type `map[string]Value`", value)
|
|
return errors.New(msg)
|
|
}
|
|
|
|
// AddSection adds a new child section to this Section with the provided name
|
|
func (section *Section) AddSection(name string) *Section {
|
|
childSection := newChildSection(section)
|
|
section.values[name] = childSection
|
|
return childSection
|
|
}
|
|
|
|
// Exists returns true when a value stored under the key exists
|
|
func (section *Section) Exists(name string) bool {
|
|
_, err := section.Get(name)
|
|
return err == nil
|
|
}
|
|
|
|
// Get the value (Primative or Section) stored under the name
|
|
// will respond with an error if the value does not exist
|
|
func (section *Section) Get(name string) (Value, error) {
|
|
value, ok := section.values[name]
|
|
var err error
|
|
if ok == false {
|
|
err = errors.New("value does not exist")
|
|
}
|
|
return value, err
|
|
}
|
|
|
|
// GetBoolean will try to get the value stored under name as a bool
|
|
// will respond with an error if the value does not exist or cannot be converted to a bool
|
|
func (section *Section) GetBoolean(name string) (bool, error) {
|
|
value, err := section.Get(name)
|
|
if err != nil {
|
|
return false, err
|
|
}
|
|
|
|
switch value.(type) {
|
|
case *Primative:
|
|
return value.(*Primative).AsBoolean()
|
|
case *Section:
|
|
return true, nil
|
|
}
|
|
|
|
return false, errors.New("could not convert unknown value to boolean")
|
|
}
|
|
|
|
// GetFloat will try to get the value stored under name as a float64
|
|
// will respond with an error if the value does not exist or cannot be converted to a float64
|
|
func (section *Section) GetFloat(name string) (float64, error) {
|
|
value, err := section.Get(name)
|
|
if err != nil {
|
|
return float64(0), err
|
|
}
|
|
|
|
switch value.(type) {
|
|
case *Primative:
|
|
return value.(*Primative).AsFloat()
|
|
}
|
|
|
|
return float64(0), errors.New("could not convert non-primative value to float")
|
|
}
|
|
|
|
// GetInteger will try to get the value stored under name as a int64
|
|
// will respond with an error if the value does not exist or cannot be converted to a int64
|
|
func (section *Section) GetInteger(name string) (int64, error) {
|
|
value, err := section.Get(name)
|
|
if err != nil {
|
|
return int64(0), err
|
|
}
|
|
|
|
switch value.(type) {
|
|
case *Primative:
|
|
return value.(*Primative).AsInteger()
|
|
}
|
|
|
|
return int64(0), errors.New("could not convert non-primative value to integer")
|
|
}
|
|
|
|
// GetList will try to get the value stored under name as a List
|
|
// will respond with an error if the value does not exist or is not a List
|
|
func (section *Section) GetList(name string) (*List, error) {
|
|
value, err := section.Get(name)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if value.GetType() == LIST {
|
|
return value.(*List), nil
|
|
}
|
|
|
|
return nil, errors.New("could not fetch value as list")
|
|
}
|
|
|
|
// GetSection will try to get the value stored under name as a Section
|
|
// will respond with an error if the value does not exist or is not a Section
|
|
func (section *Section) GetSection(name string) (*Section, error) {
|
|
value, err := section.Get(name)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if value.GetType() == SECTION {
|
|
return value.(*Section), nil
|
|
}
|
|
return nil, errors.New("could not fetch value as section")
|
|
}
|
|
|
|
// GetString will try to get the value stored under name as a string
|
|
// will respond with an error if the value does not exist or cannot be converted to a string
|
|
func (section *Section) GetString(name string) (string, error) {
|
|
value, err := section.Get(name)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
|
|
switch value.(type) {
|
|
case *Primative:
|
|
return value.(*Primative).AsString()
|
|
}
|
|
|
|
return "", errors.New("could not convert non-primative value to string")
|
|
}
|
|
|
|
// GetParent will get the parent section associated with this Section or nil
|
|
// if it does not have one
|
|
func (section *Section) GetParent() *Section {
|
|
return section.parent
|
|
}
|
|
|
|
// HasParent will return true if this Section has a parent
|
|
func (section *Section) HasParent() bool {
|
|
return section.parent != nil
|
|
}
|
|
|
|
// Keys will return back a list of all setting names in this Section
|
|
func (section *Section) Keys() []string {
|
|
var keys []string
|
|
for key := range section.values {
|
|
keys = append(keys, key)
|
|
}
|
|
|
|
sort.Strings(keys)
|
|
return keys
|
|
}
|
|
|
|
// Set will set a value (Primative or Section) to the provided name
|
|
func (section *Section) Set(name string, value Value) {
|
|
section.values[name] = value
|
|
}
|
|
|
|
// SetBoolean will set the value for name as a bool
|
|
func (section *Section) SetBoolean(name string, value bool) {
|
|
current, err := section.Get(name)
|
|
|
|
// Exists just update the value/type
|
|
if err == nil {
|
|
current.UpdateValue(value)
|
|
} else {
|
|
section.values[name] = NewBoolean(value)
|
|
}
|
|
}
|
|
|
|
// SetFloat will set the value for name as a float64
|
|
func (section *Section) SetFloat(name string, value float64) {
|
|
current, err := section.Get(name)
|
|
|
|
// Exists just update the value/type
|
|
if err == nil {
|
|
current.UpdateValue(value)
|
|
} else {
|
|
section.values[name] = NewFloat(value)
|
|
}
|
|
}
|
|
|
|
// SetInteger will set the value for name as a int64
|
|
func (section *Section) SetInteger(name string, value int64) {
|
|
current, err := section.Get(name)
|
|
|
|
// Exists just update the value/type
|
|
if err == nil {
|
|
current.UpdateValue(value)
|
|
} else {
|
|
section.values[name] = NewInteger(value)
|
|
}
|
|
}
|
|
|
|
// SetNull will set the value for name as nil
|
|
func (section *Section) SetNull(name string) {
|
|
current, err := section.Get(name)
|
|
|
|
// Already is a Null, nothing to do
|
|
if err == nil && current.GetType() == NULL {
|
|
return
|
|
}
|
|
section.Set(name, NewNull())
|
|
}
|
|
|
|
// SetString will set the value for name as a string
|
|
func (section *Section) SetString(name string, value string) {
|
|
current, err := section.Get(name)
|
|
|
|
// Exists just update the value/type
|
|
if err == nil {
|
|
current.UpdateValue(value)
|
|
} else {
|
|
section.Set(name, NewString(value))
|
|
}
|
|
}
|
|
|
|
// Resolve will recursively try to fetch the provided value and will respond
|
|
// with an error if the name does not exist or tries to be resolved through
|
|
// a non-section value
|
|
func (section *Section) Resolve(name string) (Value, error) {
|
|
// Used only in error state return value
|
|
var value Value
|
|
|
|
parts := strings.Split(name, ".")
|
|
if len(parts) == 0 {
|
|
return value, errors.New("no name provided")
|
|
}
|
|
|
|
var current Value
|
|
current = section
|
|
for _, part := range parts {
|
|
if current.GetType() != SECTION {
|
|
return value, errors.New("trying to resolve value from non-section")
|
|
}
|
|
|
|
nextCurrent, err := current.(*Section).Get(part)
|
|
if err != nil {
|
|
return value, errors.New("could not find value in section")
|
|
}
|
|
current = nextCurrent
|
|
}
|
|
return current, nil
|
|
}
|
|
|
|
// Merge merges the given section to current section. Settings from source
|
|
// section overwites the values in the current section
|
|
func (section *Section) Merge(source *Section) error {
|
|
for _, key := range source.Keys() {
|
|
sourceValue, _ := source.Get(key)
|
|
targetValue, err := section.Get(key)
|
|
|
|
// not found, so add it
|
|
if err != nil {
|
|
section.Set(key, sourceValue)
|
|
continue
|
|
}
|
|
|
|
// found existing one and it's type SECTION, merge it
|
|
if targetValue.GetType() == SECTION {
|
|
// Source value have to be SECTION type here
|
|
if sourceValue.GetType() != SECTION {
|
|
return fmt.Errorf("source (%v) and target (%v) type doesn't match: %v",
|
|
sourceValue.GetType(),
|
|
targetValue.GetType(),
|
|
key)
|
|
}
|
|
|
|
if err = targetValue.(*Section).Merge(sourceValue.(*Section)); err != nil {
|
|
return err
|
|
}
|
|
|
|
continue
|
|
}
|
|
|
|
// found existing one, update it
|
|
if err = targetValue.UpdateValue(sourceValue.GetValue()); err != nil {
|
|
return fmt.Errorf("%v: %v", err, key)
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// ToJSON will convert this Section and all it's underlying values and Sections
|
|
// into JSON as a []byte
|
|
func (section *Section) ToJSON() ([]byte, error) {
|
|
data := section.ToMap()
|
|
return json.Marshal(data)
|
|
}
|
|
|
|
// ToMap will convert this Section and all it's underlying values and Sections into
|
|
// a map[string]interface{}
|
|
func (section *Section) ToMap() map[string]interface{} {
|
|
output := make(map[string]interface{})
|
|
|
|
for key, value := range section.values {
|
|
if value.GetType() == SECTION {
|
|
output[key] = value.(*Section).ToMap()
|
|
} else {
|
|
output[key] = value.GetValue()
|
|
}
|
|
}
|
|
return output
|
|
}
|