2020-02-23 14:36:57 +00:00
|
|
|
package ini
|
|
|
|
|
|
|
|
import (
|
|
|
|
"fmt"
|
|
|
|
"sort"
|
2021-10-17 17:15:44 +00:00
|
|
|
"strings"
|
2020-02-23 14:36:57 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
// Visitor is an interface used by walkers that will
|
|
|
|
// traverse an array of ASTs.
|
|
|
|
type Visitor interface {
|
|
|
|
VisitExpr(AST) error
|
|
|
|
VisitStatement(AST) error
|
|
|
|
}
|
|
|
|
|
|
|
|
// DefaultVisitor is used to visit statements and expressions
|
|
|
|
// and ensure that they are both of the correct format.
|
|
|
|
// In addition, upon visiting this will build sections and populate
|
|
|
|
// the Sections field which can be used to retrieve profile
|
|
|
|
// configuration.
|
|
|
|
type DefaultVisitor struct {
|
2021-10-17 17:15:44 +00:00
|
|
|
|
|
|
|
// scope is the profile which is being visited
|
|
|
|
scope string
|
|
|
|
|
|
|
|
// path is the file path which the visitor is visiting
|
|
|
|
path string
|
|
|
|
|
|
|
|
// Sections defines list of the profile section
|
2020-02-23 14:36:57 +00:00
|
|
|
Sections Sections
|
|
|
|
}
|
|
|
|
|
2021-10-17 17:15:44 +00:00
|
|
|
// NewDefaultVisitor returns a DefaultVisitor. It takes in a filepath
|
|
|
|
// which points to the file it is visiting.
|
|
|
|
func NewDefaultVisitor(filepath string) *DefaultVisitor {
|
2020-02-23 14:36:57 +00:00
|
|
|
return &DefaultVisitor{
|
|
|
|
Sections: Sections{
|
|
|
|
container: map[string]Section{},
|
|
|
|
},
|
2021-10-17 17:15:44 +00:00
|
|
|
path: filepath,
|
2020-02-23 14:36:57 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// VisitExpr visits expressions...
|
|
|
|
func (v *DefaultVisitor) VisitExpr(expr AST) error {
|
|
|
|
t := v.Sections.container[v.scope]
|
|
|
|
if t.values == nil {
|
|
|
|
t.values = values{}
|
|
|
|
}
|
2021-10-17 17:15:44 +00:00
|
|
|
if t.SourceFile == nil {
|
|
|
|
t.SourceFile = make(map[string]string, 0)
|
|
|
|
}
|
2020-02-23 14:36:57 +00:00
|
|
|
|
|
|
|
switch expr.Kind {
|
|
|
|
case ASTKindExprStatement:
|
|
|
|
opExpr := expr.GetRoot()
|
|
|
|
switch opExpr.Kind {
|
|
|
|
case ASTKindEqualExpr:
|
|
|
|
children := opExpr.GetChildren()
|
|
|
|
if len(children) <= 1 {
|
|
|
|
return NewParseError("unexpected token type")
|
|
|
|
}
|
|
|
|
|
|
|
|
rhs := children[1]
|
|
|
|
|
2021-09-02 10:03:56 +00:00
|
|
|
// The right-hand value side the equality expression is allowed to contain '[', ']', ':', '=' in the values.
|
|
|
|
// If the token is not either a literal or one of the token types that identifies those four additional
|
|
|
|
// tokens then error.
|
|
|
|
if !(rhs.Root.Type() == TokenLit || rhs.Root.Type() == TokenOp || rhs.Root.Type() == TokenSep) {
|
2020-02-23 14:36:57 +00:00
|
|
|
return NewParseError("unexpected token type")
|
|
|
|
}
|
|
|
|
|
|
|
|
key := EqualExprKey(opExpr)
|
2021-10-17 17:15:44 +00:00
|
|
|
val, err := newValue(rhs.Root.ValueType, rhs.Root.base, rhs.Root.Raw())
|
2020-02-23 14:36:57 +00:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2021-10-17 17:15:44 +00:00
|
|
|
// lower case key to standardize
|
|
|
|
k := strings.ToLower(key)
|
|
|
|
|
|
|
|
// identify if the section already had this key, append log on section
|
|
|
|
if t.Has(k) {
|
|
|
|
t.Logs = append(t.Logs,
|
|
|
|
fmt.Sprintf("For profile: %v, overriding %v value, "+
|
|
|
|
"with a %v value found in a duplicate profile defined later in the same file %v. \n",
|
|
|
|
t.Name, k, k, v.path))
|
|
|
|
}
|
|
|
|
|
|
|
|
// assign the value
|
|
|
|
t.values[k] = val
|
|
|
|
// update the source file path for region
|
|
|
|
t.SourceFile[k] = v.path
|
2020-02-23 14:36:57 +00:00
|
|
|
default:
|
|
|
|
return NewParseError(fmt.Sprintf("unsupported expression %v", expr))
|
|
|
|
}
|
|
|
|
default:
|
|
|
|
return NewParseError(fmt.Sprintf("unsupported expression %v", expr))
|
|
|
|
}
|
|
|
|
|
|
|
|
v.Sections.container[v.scope] = t
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// VisitStatement visits statements...
|
|
|
|
func (v *DefaultVisitor) VisitStatement(stmt AST) error {
|
|
|
|
switch stmt.Kind {
|
|
|
|
case ASTKindCompletedSectionStatement:
|
|
|
|
child := stmt.GetRoot()
|
|
|
|
if child.Kind != ASTKindSectionStatement {
|
|
|
|
return NewParseError(fmt.Sprintf("unsupported child statement: %T", child))
|
|
|
|
}
|
|
|
|
|
|
|
|
name := string(child.Root.Raw())
|
2021-10-17 17:15:44 +00:00
|
|
|
|
|
|
|
// trim start and end space
|
|
|
|
name = strings.TrimSpace(name)
|
|
|
|
|
|
|
|
// if has prefix "profile " + [ws+] + "profile-name",
|
|
|
|
// we standardize by removing the [ws+] between prefix and profile-name.
|
|
|
|
if strings.HasPrefix(name, "profile ") {
|
|
|
|
names := strings.SplitN(name, " ", 2)
|
|
|
|
name = names[0] + " " + strings.TrimLeft(names[1], " ")
|
|
|
|
}
|
|
|
|
|
|
|
|
// attach profile name on section
|
|
|
|
if !v.Sections.HasSection(name) {
|
|
|
|
v.Sections.container[name] = NewSection(name)
|
|
|
|
}
|
2020-02-23 14:36:57 +00:00
|
|
|
v.scope = name
|
|
|
|
default:
|
|
|
|
return NewParseError(fmt.Sprintf("unsupported statement: %s", stmt.Kind))
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// Sections is a map of Section structures that represent
|
|
|
|
// a configuration.
|
|
|
|
type Sections struct {
|
|
|
|
container map[string]Section
|
|
|
|
}
|
|
|
|
|
2021-10-17 17:15:44 +00:00
|
|
|
// NewSections returns empty ini Sections
|
|
|
|
func NewSections() Sections {
|
|
|
|
return Sections{
|
|
|
|
container: make(map[string]Section, 0),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-02-23 14:36:57 +00:00
|
|
|
// GetSection will return section p. If section p does not exist,
|
|
|
|
// false will be returned in the second parameter.
|
|
|
|
func (t Sections) GetSection(p string) (Section, bool) {
|
|
|
|
v, ok := t.container[p]
|
|
|
|
return v, ok
|
|
|
|
}
|
|
|
|
|
2021-10-17 17:15:44 +00:00
|
|
|
// HasSection denotes if Sections consist of a section with
|
|
|
|
// provided name.
|
|
|
|
func (t Sections) HasSection(p string) bool {
|
|
|
|
_, ok := t.container[p]
|
|
|
|
return ok
|
|
|
|
}
|
|
|
|
|
|
|
|
// SetSection sets a section value for provided section name.
|
|
|
|
func (t Sections) SetSection(p string, v Section) Sections {
|
|
|
|
t.container[p] = v
|
|
|
|
return t
|
|
|
|
}
|
|
|
|
|
|
|
|
// DeleteSection deletes a section entry/value for provided section name./
|
|
|
|
func (t Sections) DeleteSection(p string) {
|
|
|
|
delete(t.container, p)
|
|
|
|
}
|
|
|
|
|
2020-02-23 14:36:57 +00:00
|
|
|
// values represents a map of union values.
|
|
|
|
type values map[string]Value
|
|
|
|
|
|
|
|
// List will return a list of all sections that were successfully
|
|
|
|
// parsed.
|
|
|
|
func (t Sections) List() []string {
|
|
|
|
keys := make([]string, len(t.container))
|
|
|
|
i := 0
|
|
|
|
for k := range t.container {
|
|
|
|
keys[i] = k
|
|
|
|
i++
|
|
|
|
}
|
|
|
|
|
|
|
|
sort.Strings(keys)
|
|
|
|
return keys
|
|
|
|
}
|
|
|
|
|
|
|
|
// Section contains a name and values. This represent
|
|
|
|
// a sectioned entry in a configuration file.
|
|
|
|
type Section struct {
|
2021-10-17 17:15:44 +00:00
|
|
|
// Name is the Section profile name
|
|
|
|
Name string
|
|
|
|
|
|
|
|
// values are the values within parsed profile
|
2020-02-23 14:36:57 +00:00
|
|
|
values values
|
2021-10-17 17:15:44 +00:00
|
|
|
|
|
|
|
// Errors is the list of errors
|
|
|
|
Errors []error
|
|
|
|
|
|
|
|
// Logs is the list of logs
|
|
|
|
Logs []string
|
|
|
|
|
|
|
|
// SourceFile is the INI Source file from where this section
|
|
|
|
// was retrieved. They key is the property, value is the
|
|
|
|
// source file the property was retrieved from.
|
|
|
|
SourceFile map[string]string
|
|
|
|
}
|
|
|
|
|
|
|
|
// NewSection returns an initialize section for the name
|
|
|
|
func NewSection(name string) Section {
|
|
|
|
return Section{
|
|
|
|
Name: name,
|
|
|
|
values: values{},
|
|
|
|
SourceFile: map[string]string{},
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// UpdateSourceFile updates source file for a property to provided filepath.
|
|
|
|
func (t Section) UpdateSourceFile(property string, filepath string) {
|
|
|
|
t.SourceFile[property] = filepath
|
|
|
|
}
|
|
|
|
|
|
|
|
// UpdateValue updates value for a provided key with provided value
|
|
|
|
func (t Section) UpdateValue(k string, v Value) error {
|
|
|
|
t.values[k] = v
|
|
|
|
return nil
|
2020-02-23 14:36:57 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Has will return whether or not an entry exists in a given section
|
|
|
|
func (t Section) Has(k string) bool {
|
|
|
|
_, ok := t.values[k]
|
|
|
|
return ok
|
|
|
|
}
|
|
|
|
|
|
|
|
// ValueType will returned what type the union is set to. If
|
|
|
|
// k was not found, the NoneType will be returned.
|
|
|
|
func (t Section) ValueType(k string) (ValueType, bool) {
|
|
|
|
v, ok := t.values[k]
|
|
|
|
return v.Type, ok
|
|
|
|
}
|
|
|
|
|
|
|
|
// Bool returns a bool value at k
|
|
|
|
func (t Section) Bool(k string) bool {
|
|
|
|
return t.values[k].BoolValue()
|
|
|
|
}
|
|
|
|
|
|
|
|
// Int returns an integer value at k
|
|
|
|
func (t Section) Int(k string) int64 {
|
|
|
|
return t.values[k].IntValue()
|
|
|
|
}
|
|
|
|
|
|
|
|
// Float64 returns a float value at k
|
|
|
|
func (t Section) Float64(k string) float64 {
|
|
|
|
return t.values[k].FloatValue()
|
|
|
|
}
|
|
|
|
|
|
|
|
// String returns the string value at k
|
|
|
|
func (t Section) String(k string) string {
|
|
|
|
_, ok := t.values[k]
|
|
|
|
if !ok {
|
|
|
|
return ""
|
|
|
|
}
|
|
|
|
return t.values[k].StringValue()
|
|
|
|
}
|