github.com/olivere/camlistore@v0.0.0-20140121221811-1b7ac2da0199/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 30 "camlistore.org/pkg/errorutil" 31 "camlistore.org/pkg/osutil" 32 ) 33 34 type stringVector struct { 35 v []string 36 } 37 38 func (v *stringVector) Push(s string) { 39 v.v = append(v.v, s) 40 } 41 42 func (v *stringVector) Pop() { 43 v.v = v.v[:len(v.v)-1] 44 } 45 46 func (v *stringVector) Last() string { 47 return v.v[len(v.v)-1] 48 } 49 50 // A File is the type returned by ConfigParser.Open. 51 type File interface { 52 io.ReadSeeker 53 io.Closer 54 Name() string 55 } 56 57 // ConfigParser specifies the environment for parsing a config file 58 // and evaluating expressions. 59 type ConfigParser struct { 60 rootJSON Obj 61 62 touchedFiles map[string]bool 63 includeStack stringVector 64 65 // Open optionally specifies an opener function. 66 Open func(filename string) (File, error) 67 } 68 69 func (c *ConfigParser) open(filename string) (File, error) { 70 if c.Open == nil { 71 return os.Open(filename) 72 } 73 return c.Open(filename) 74 } 75 76 // Validates variable names for config _env expresssions 77 var envPattern = regexp.MustCompile(`\$\{[A-Za-z0-9_]+\}`) 78 79 func (c *ConfigParser) ReadFile(path string) (m map[string]interface{}, err error) { 80 c.touchedFiles = make(map[string]bool) 81 c.rootJSON, err = c.recursiveReadJSON(path) 82 return c.rootJSON, err 83 } 84 85 // Decodes and evaluates a json config file, watching for include cycles. 86 func (c *ConfigParser) recursiveReadJSON(configPath string) (decodedObject map[string]interface{}, err error) { 87 88 absConfigPath, err := filepath.Abs(configPath) 89 if err != nil { 90 return nil, fmt.Errorf("Failed to expand absolute path for %s", configPath) 91 } 92 if c.touchedFiles[absConfigPath] { 93 return nil, fmt.Errorf("ConfigParser include cycle detected reading config: %v", 94 absConfigPath) 95 } 96 c.touchedFiles[absConfigPath] = true 97 98 c.includeStack.Push(absConfigPath) 99 defer c.includeStack.Pop() 100 101 var f File 102 if f, err = c.open(configPath); err != nil { 103 return nil, fmt.Errorf("Failed to open config: %v", err) 104 } 105 defer f.Close() 106 107 decodedObject = make(map[string]interface{}) 108 dj := json.NewDecoder(f) 109 if err = dj.Decode(&decodedObject); err != nil { 110 extra := "" 111 if serr, ok := err.(*json.SyntaxError); ok { 112 if _, serr := f.Seek(0, os.SEEK_SET); serr != nil { 113 log.Fatalf("seek error: %v", serr) 114 } 115 line, col, highlight := errorutil.HighlightBytePosition(f, serr.Offset) 116 extra = fmt.Sprintf(":\nError at line %d, column %d (file offset %d):\n%s", 117 line, col, serr.Offset, highlight) 118 } 119 return nil, fmt.Errorf("error parsing JSON object in config file %s%s\n%v", 120 f.Name(), extra, err) 121 } 122 123 if err = c.evaluateExpressions(decodedObject); err != nil { 124 return nil, fmt.Errorf("error expanding JSON config expressions in %s:\n%v", 125 f.Name(), err) 126 } 127 128 return decodedObject, nil 129 } 130 131 type expanderFunc func(c *ConfigParser, v []interface{}) (interface{}, error) 132 133 func namedExpander(name string) (expanderFunc, bool) { 134 switch name { 135 case "_env": 136 return expanderFunc((*ConfigParser).expandEnv), true 137 case "_fileobj": 138 return expanderFunc((*ConfigParser).expandFile), true 139 } 140 return nil, false 141 } 142 143 func (c *ConfigParser) evalValue(v interface{}) (interface{}, error) { 144 sl, ok := v.([]interface{}) 145 if !ok { 146 return v, nil 147 } 148 if name, ok := sl[0].(string); ok { 149 if expander, ok := namedExpander(name); ok { 150 newval, err := expander(c, sl[1:]) 151 if err != nil { 152 return nil, err 153 } 154 return newval, nil 155 } 156 } 157 for i, oldval := range sl { 158 newval, err := c.evalValue(oldval) 159 if err != nil { 160 return nil, err 161 } 162 sl[i] = newval 163 } 164 return v, nil 165 } 166 167 func (c *ConfigParser) evaluateExpressions(m map[string]interface{}) error { 168 for k, ei := range m { 169 switch subval := ei.(type) { 170 case string: 171 continue 172 case bool: 173 continue 174 case float64: 175 continue 176 case []interface{}: 177 if len(subval) == 0 { 178 continue 179 } 180 var err error 181 m[k], err = c.evalValue(subval) 182 if err != nil { 183 return err 184 } 185 case map[string]interface{}: 186 if err := c.evaluateExpressions(subval); err != nil { 187 return err 188 } 189 default: 190 return fmt.Errorf("Unhandled type %T", ei) 191 } 192 } 193 return nil 194 } 195 196 // Permit either: 197 // ["_env", "VARIABLE"] (required to be set) 198 // or ["_env", "VARIABLE", "default_value"] 199 func (c *ConfigParser) expandEnv(v []interface{}) (interface{}, error) { 200 hasDefault := false 201 def := "" 202 if len(v) < 1 || len(v) > 2 { 203 return "", fmt.Errorf("_env expansion expected 1 or 2 args, got %d", len(v)) 204 } 205 s, ok := v[0].(string) 206 if !ok { 207 return "", fmt.Errorf("Expected a string after _env expansion; got %#v", v[0]) 208 } 209 boolDefault, wantsBool := false, false 210 if len(v) == 2 { 211 hasDefault = true 212 switch vdef := v[1].(type) { 213 case string: 214 def = vdef 215 case bool: 216 wantsBool = true 217 boolDefault = vdef 218 default: 219 return "", fmt.Errorf("Expected default value in %q _env expansion; got %#v", s, v[1]) 220 } 221 } 222 var err error 223 expanded := envPattern.ReplaceAllStringFunc(s, func(match string) string { 224 envVar := match[2 : len(match)-1] 225 val := os.Getenv(envVar) 226 // Special case: 227 if val == "" && envVar == "USER" && runtime.GOOS == "windows" { 228 val = os.Getenv("USERNAME") 229 } 230 if val == "" { 231 if hasDefault { 232 return def 233 } 234 err = fmt.Errorf("couldn't expand environment variable %q", envVar) 235 } 236 return val 237 }) 238 if wantsBool { 239 if expanded == "" { 240 return boolDefault, nil 241 } 242 return strconv.ParseBool(expanded) 243 } 244 return expanded, err 245 } 246 247 func (c *ConfigParser) expandFile(v []interface{}) (exp interface{}, err error) { 248 if len(v) != 1 { 249 return "", fmt.Errorf("_file expansion expected 1 arg, got %d", len(v)) 250 } 251 var incPath string 252 if incPath, err = osutil.FindCamliInclude(v[0].(string)); err != nil { 253 return "", fmt.Errorf("Included config does not exist: %v", v[0]) 254 } 255 if exp, err = c.recursiveReadJSON(incPath); err != nil { 256 return "", fmt.Errorf("In file included from %s:\n%v", 257 c.includeStack.Last(), err) 258 } 259 return exp, nil 260 }