package forge import ( "encoding/json" "errors" "fmt" "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") } // 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 } // 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 } // 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 }