code.dwrz.net

Go monorepo.
Log | Files | Refs

visitor.go (6909B)


      1 package ini
      2 
      3 import (
      4 	"fmt"
      5 	"sort"
      6 	"strings"
      7 )
      8 
      9 // Visitor is an interface used by walkers that will
     10 // traverse an array of ASTs.
     11 type Visitor interface {
     12 	VisitExpr(AST) error
     13 	VisitStatement(AST) error
     14 }
     15 
     16 // DefaultVisitor is used to visit statements and expressions
     17 // and ensure that they are both of the correct format.
     18 // In addition, upon visiting this will build sections and populate
     19 // the Sections field which can be used to retrieve profile
     20 // configuration.
     21 type DefaultVisitor struct {
     22 
     23 	// scope is the profile which is being visited
     24 	scope string
     25 
     26 	// path is the file path which the visitor is visiting
     27 	path string
     28 
     29 	// Sections defines list of the profile section
     30 	Sections Sections
     31 }
     32 
     33 // NewDefaultVisitor returns a DefaultVisitor. It takes in a filepath
     34 // which points to the file it is visiting.
     35 func NewDefaultVisitor(filepath string) *DefaultVisitor {
     36 	return &DefaultVisitor{
     37 		Sections: Sections{
     38 			container: map[string]Section{},
     39 		},
     40 		path: filepath,
     41 	}
     42 }
     43 
     44 // VisitExpr visits expressions...
     45 func (v *DefaultVisitor) VisitExpr(expr AST) error {
     46 	t := v.Sections.container[v.scope]
     47 	if t.values == nil {
     48 		t.values = values{}
     49 	}
     50 	if t.SourceFile == nil {
     51 		t.SourceFile = make(map[string]string, 0)
     52 	}
     53 
     54 	switch expr.Kind {
     55 	case ASTKindExprStatement:
     56 		opExpr := expr.GetRoot()
     57 		switch opExpr.Kind {
     58 		case ASTKindEqualExpr:
     59 			children := opExpr.GetChildren()
     60 			if len(children) <= 1 {
     61 				return NewParseError("unexpected token type")
     62 			}
     63 
     64 			rhs := children[1]
     65 
     66 			// The right-hand value side the equality expression is allowed to contain '[', ']', ':', '=' in the values.
     67 			// If the token is not either a literal or one of the token types that identifies those four additional
     68 			// tokens then error.
     69 			if !(rhs.Root.Type() == TokenLit || rhs.Root.Type() == TokenOp || rhs.Root.Type() == TokenSep) {
     70 				return NewParseError("unexpected token type")
     71 			}
     72 
     73 			key := EqualExprKey(opExpr)
     74 			val, err := newValue(rhs.Root.ValueType, rhs.Root.base, rhs.Root.Raw())
     75 			if err != nil {
     76 				return err
     77 			}
     78 
     79 			// lower case key to standardize
     80 			k := strings.ToLower(key)
     81 
     82 			// identify if the section already had this key, append log on section
     83 			if t.Has(k) {
     84 				t.Logs = append(t.Logs,
     85 					fmt.Sprintf("For profile: %v, overriding %v value, "+
     86 						"with a %v value found in a duplicate profile defined later in the same file %v. \n",
     87 						t.Name, k, k, v.path))
     88 			}
     89 
     90 			// assign the value
     91 			t.values[k] = val
     92 			// update the source file path for region
     93 			t.SourceFile[k] = v.path
     94 		default:
     95 			return NewParseError(fmt.Sprintf("unsupported expression %v", expr))
     96 		}
     97 	default:
     98 		return NewParseError(fmt.Sprintf("unsupported expression %v", expr))
     99 	}
    100 
    101 	v.Sections.container[v.scope] = t
    102 	return nil
    103 }
    104 
    105 // VisitStatement visits statements...
    106 func (v *DefaultVisitor) VisitStatement(stmt AST) error {
    107 	switch stmt.Kind {
    108 	case ASTKindCompletedSectionStatement:
    109 		child := stmt.GetRoot()
    110 		if child.Kind != ASTKindSectionStatement {
    111 			return NewParseError(fmt.Sprintf("unsupported child statement: %T", child))
    112 		}
    113 
    114 		name := string(child.Root.Raw())
    115 
    116 		// trim start and end space
    117 		name = strings.TrimSpace(name)
    118 
    119 		// if has prefix "profile " + [ws+] + "profile-name",
    120 		// we standardize by removing the [ws+] between prefix and profile-name.
    121 		if strings.HasPrefix(name, "profile ") {
    122 			names := strings.SplitN(name, " ", 2)
    123 			name = names[0] + " " + strings.TrimLeft(names[1], " ")
    124 		}
    125 
    126 		// attach profile name on section
    127 		if !v.Sections.HasSection(name) {
    128 			v.Sections.container[name] = NewSection(name)
    129 		}
    130 		v.scope = name
    131 	default:
    132 		return NewParseError(fmt.Sprintf("unsupported statement: %s", stmt.Kind))
    133 	}
    134 
    135 	return nil
    136 }
    137 
    138 // Sections is a map of Section structures that represent
    139 // a configuration.
    140 type Sections struct {
    141 	container map[string]Section
    142 }
    143 
    144 // NewSections returns empty ini Sections
    145 func NewSections() Sections {
    146 	return Sections{
    147 		container: make(map[string]Section, 0),
    148 	}
    149 }
    150 
    151 // GetSection will return section p. If section p does not exist,
    152 // false will be returned in the second parameter.
    153 func (t Sections) GetSection(p string) (Section, bool) {
    154 	v, ok := t.container[p]
    155 	return v, ok
    156 }
    157 
    158 // HasSection denotes if Sections consist of a section with
    159 // provided name.
    160 func (t Sections) HasSection(p string) bool {
    161 	_, ok := t.container[p]
    162 	return ok
    163 }
    164 
    165 // SetSection sets a section value for provided section name.
    166 func (t Sections) SetSection(p string, v Section) Sections {
    167 	t.container[p] = v
    168 	return t
    169 }
    170 
    171 // DeleteSection deletes a section entry/value for provided section name./
    172 func (t Sections) DeleteSection(p string) {
    173 	delete(t.container, p)
    174 }
    175 
    176 // values represents a map of union values.
    177 type values map[string]Value
    178 
    179 // List will return a list of all sections that were successfully
    180 // parsed.
    181 func (t Sections) List() []string {
    182 	keys := make([]string, len(t.container))
    183 	i := 0
    184 	for k := range t.container {
    185 		keys[i] = k
    186 		i++
    187 	}
    188 
    189 	sort.Strings(keys)
    190 	return keys
    191 }
    192 
    193 // Section contains a name and values. This represent
    194 // a sectioned entry in a configuration file.
    195 type Section struct {
    196 	// Name is the Section profile name
    197 	Name string
    198 
    199 	// values are the values within parsed profile
    200 	values values
    201 
    202 	// Errors is the list of errors
    203 	Errors []error
    204 
    205 	// Logs is the list of logs
    206 	Logs []string
    207 
    208 	// SourceFile is the INI Source file from where this section
    209 	// was retrieved. They key is the property, value is the
    210 	// source file the property was retrieved from.
    211 	SourceFile map[string]string
    212 }
    213 
    214 // NewSection returns an initialize section for the name
    215 func NewSection(name string) Section {
    216 	return Section{
    217 		Name:       name,
    218 		values:     values{},
    219 		SourceFile: map[string]string{},
    220 	}
    221 }
    222 
    223 // UpdateSourceFile updates source file for a property to provided filepath.
    224 func (t Section) UpdateSourceFile(property string, filepath string) {
    225 	t.SourceFile[property] = filepath
    226 }
    227 
    228 // UpdateValue updates value for a provided key with provided value
    229 func (t Section) UpdateValue(k string, v Value) error {
    230 	t.values[k] = v
    231 	return nil
    232 }
    233 
    234 // Has will return whether or not an entry exists in a given section
    235 func (t Section) Has(k string) bool {
    236 	_, ok := t.values[k]
    237 	return ok
    238 }
    239 
    240 // ValueType will returned what type the union is set to. If
    241 // k was not found, the NoneType will be returned.
    242 func (t Section) ValueType(k string) (ValueType, bool) {
    243 	v, ok := t.values[k]
    244 	return v.Type, ok
    245 }
    246 
    247 // Bool returns a bool value at k
    248 func (t Section) Bool(k string) bool {
    249 	return t.values[k].BoolValue()
    250 }
    251 
    252 // Int returns an integer value at k
    253 func (t Section) Int(k string) int64 {
    254 	return t.values[k].IntValue()
    255 }
    256 
    257 // Float64 returns a float value at k
    258 func (t Section) Float64(k string) float64 {
    259 	return t.values[k].FloatValue()
    260 }
    261 
    262 // String returns the string value at k
    263 func (t Section) String(k string) string {
    264 	_, ok := t.values[k]
    265 	if !ok {
    266 		return ""
    267 	}
    268 	return t.values[k].StringValue()
    269 }