github.com/slspeek/camlistore_namedsearch@v0.0.0-20140519202248-ed6f70f7721a/pkg/jsonconfig/eval.go (about) 1 /* 2 Copyright 2011 Google Inc. 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 package jsonconfig 18 19 import ( 20 "encoding/json" 21 "fmt" 22 "io" 23 "log" 24 "os" 25 "path/filepath" 26 "regexp" 27 "runtime" 28 "strconv" 29 "strings" 30 31 "camlistore.org/pkg/errorutil" 32 "camlistore.org/pkg/osutil" 33 ) 34 35 type stringVector struct { 36 v []string 37 } 38 39 func (v *stringVector) Push(s string) { 40 v.v = append(v.v, s) 41 } 42 43 func (v *stringVector) Pop() { 44 v.v = v.v[:len(v.v)-1] 45 } 46 47 func (v *stringVector) Last() string { 48 return v.v[len(v.v)-1] 49 } 50 51 // A File is the type returned by ConfigParser.Open. 52 type File interface { 53 io.ReadSeeker 54 io.Closer 55 Name() string 56 } 57 58 // ConfigParser specifies the environment for parsing a config file 59 // and evaluating expressions. 60 type ConfigParser struct { 61 rootJSON Obj 62 63 touchedFiles map[string]bool 64 includeStack stringVector 65 66 // Open optionally specifies an opener function. 67 Open func(filename string) (File, error) 68 } 69 70 func (c *ConfigParser) open(filename string) (File, error) { 71 if c.Open == nil { 72 return os.Open(filename) 73 } 74 return c.Open(filename) 75 } 76 77 // Validates variable names for config _env expresssions 78 var envPattern = regexp.MustCompile(`\$\{[A-Za-z0-9_]+\}`) 79 80 func (c *ConfigParser) ReadFile(path string) (m map[string]interface{}, err error) { 81 c.touchedFiles = make(map[string]bool) 82 c.rootJSON, err = c.recursiveReadJSON(path) 83 return c.rootJSON, err 84 } 85 86 // Decodes and evaluates a json config file, watching for include cycles. 87 func (c *ConfigParser) recursiveReadJSON(configPath string) (decodedObject map[string]interface{}, err error) { 88 89 absConfigPath, err := filepath.Abs(configPath) 90 if err != nil { 91 return nil, fmt.Errorf("Failed to expand absolute path for %s", configPath) 92 } 93 if c.touchedFiles[absConfigPath] { 94 return nil, fmt.Errorf("ConfigParser include cycle detected reading config: %v", 95 absConfigPath) 96 } 97 c.touchedFiles[absConfigPath] = true 98 99 c.includeStack.Push(absConfigPath) 100 defer c.includeStack.Pop() 101 102 var f File 103 if f, err = c.open(configPath); err != nil { 104 return nil, fmt.Errorf("Failed to open config: %v", err) 105 } 106 defer f.Close() 107 108 decodedObject = make(map[string]interface{}) 109 dj := json.NewDecoder(f) 110 if err = dj.Decode(&decodedObject); err != nil { 111 extra := "" 112 if serr, ok := err.(*json.SyntaxError); ok { 113 if _, serr := f.Seek(0, os.SEEK_SET); serr != nil { 114 log.Fatalf("seek error: %v", serr) 115 } 116 line, col, highlight := errorutil.HighlightBytePosition(f, serr.Offset) 117 extra = fmt.Sprintf(":\nError at line %d, column %d (file offset %d):\n%s", 118 line, col, serr.Offset, highlight) 119 } 120 return nil, fmt.Errorf("error parsing JSON object in config file %s%s\n%v", 121 f.Name(), extra, err) 122 } 123 124 if err = c.evaluateExpressions(decodedObject, nil, false); err != nil { 125 return nil, fmt.Errorf("error expanding JSON config expressions in %s:\n%v", 126 f.Name(), err) 127 } 128 129 return decodedObject, nil 130 } 131 132 type expanderFunc func(c *ConfigParser, v []interface{}) (interface{}, error) 133 134 func namedExpander(name string) (expanderFunc, bool) { 135 switch name { 136 case "_env": 137 return expanderFunc((*ConfigParser).expandEnv), true 138 case "_fileobj": 139 return expanderFunc((*ConfigParser).expandFile), true 140 } 141 return nil, false 142 } 143 144 func (c *ConfigParser) evalValue(v interface{}) (interface{}, error) { 145 sl, ok := v.([]interface{}) 146 if !ok { 147 return v, nil 148 } 149 if name, ok := sl[0].(string); ok { 150 if expander, ok := namedExpander(name); ok { 151 newval, err := expander(c, sl[1:]) 152 if err != nil { 153 return nil, err 154 } 155 return newval, nil 156 } 157 } 158 for i, oldval := range sl { 159 newval, err := c.evalValue(oldval) 160 if err != nil { 161 return nil, err 162 } 163 sl[i] = newval 164 } 165 return v, nil 166 } 167 168 // CheckTypes parses m and returns an error if it encounters a type or value 169 // that is not supported by this package. 170 func (c *ConfigParser) CheckTypes(m map[string]interface{}) error { 171 return c.evaluateExpressions(m, nil, true) 172 } 173 174 // evaluateExpressions parses recursively m, populating it with the values 175 // that are found, unless testOnly is true. 176 func (c *ConfigParser) evaluateExpressions(m map[string]interface{}, seenKeys []string, testOnly bool) error { 177 for k, ei := range m { 178 thisPath := append(seenKeys, k) 179 switch subval := ei.(type) { 180 case string: 181 continue 182 case bool: 183 continue 184 case float64: 185 continue 186 case []interface{}: 187 if len(subval) == 0 { 188 continue 189 } 190 evaled, err := c.evalValue(subval) 191 if err != nil { 192 return fmt.Errorf("%s: value error %v", strings.Join(thisPath, "."), err) 193 } 194 if !testOnly { 195 m[k] = evaled 196 } 197 case map[string]interface{}: 198 if err := c.evaluateExpressions(subval, thisPath, testOnly); err != nil { 199 return err 200 } 201 default: 202 return fmt.Errorf("%s: unhandled type %T", strings.Join(thisPath, "."), ei) 203 } 204 } 205 return nil 206 } 207 208 // Permit either: 209 // ["_env", "VARIABLE"] (required to be set) 210 // or ["_env", "VARIABLE", "default_value"] 211 func (c *ConfigParser) expandEnv(v []interface{}) (interface{}, error) { 212 hasDefault := false 213 def := "" 214 if len(v) < 1 || len(v) > 2 { 215 return "", fmt.Errorf("_env expansion expected 1 or 2 args, got %d", len(v)) 216 } 217 s, ok := v[0].(string) 218 if !ok { 219 return "", fmt.Errorf("Expected a string after _env expansion; got %#v", v[0]) 220 } 221 boolDefault, wantsBool := false, false 222 if len(v) == 2 { 223 hasDefault = true 224 switch vdef := v[1].(type) { 225 case string: 226 def = vdef 227 case bool: 228 wantsBool = true 229 boolDefault = vdef 230 default: 231 return "", fmt.Errorf("Expected default value in %q _env expansion; got %#v", s, v[1]) 232 } 233 } 234 var err error 235 expanded := envPattern.ReplaceAllStringFunc(s, func(match string) string { 236 envVar := match[2 : len(match)-1] 237 val := os.Getenv(envVar) 238 // Special case: 239 if val == "" && envVar == "USER" && runtime.GOOS == "windows" { 240 val = os.Getenv("USERNAME") 241 } 242 if val == "" { 243 if hasDefault { 244 return def 245 } 246 err = fmt.Errorf("couldn't expand environment variable %q", envVar) 247 } 248 return val 249 }) 250 if wantsBool { 251 if expanded == "" { 252 return boolDefault, nil 253 } 254 return strconv.ParseBool(expanded) 255 } 256 return expanded, err 257 } 258 259 func (c *ConfigParser) expandFile(v []interface{}) (exp interface{}, err error) { 260 if len(v) != 1 { 261 return "", fmt.Errorf("_file expansion expected 1 arg, got %d", len(v)) 262 } 263 var incPath string 264 if incPath, err = osutil.FindCamliInclude(v[0].(string)); err != nil { 265 return "", fmt.Errorf("Included config does not exist: %v", v[0]) 266 } 267 if exp, err = c.recursiveReadJSON(incPath); err != nil { 268 return "", fmt.Errorf("In file included from %s:\n%v", 269 c.includeStack.Last(), err) 270 } 271 return exp, nil 272 }