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 }