github.com/eframework-cn/EP.GO.UTIL@v1.0.0/xconfig/ini.go (about) 1 // Copyright 2014 beego Author. All Rights Reserved. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package xconfig 16 17 import ( 18 "bufio" 19 "bytes" 20 "errors" 21 "io" 22 "io/ioutil" 23 "os" 24 "os/user" 25 "path/filepath" 26 "strconv" 27 "strings" 28 "sync" 29 ) 30 31 var ( 32 defaultSection = "default" // default section means if some ini items not in a section, make them in default section, 33 bNumComment = []byte{'#'} // number signal 34 bSemComment = []byte{';'} // semicolon signal 35 bEmpty = []byte{} 36 bEqual = []byte{'='} // equal signal 37 bDQuote = []byte{'"'} // quote signal 38 sectionStart = []byte{'['} // section start signal 39 sectionEnd = []byte{']'} // section end signal 40 lineBreak = "\n" 41 ) 42 43 // IniConfig implements Config to parse ini file. 44 type IniConfig struct { 45 } 46 47 // Parse creates a new Config and parses the file configuration from the named file. 48 func (ini *IniConfig) Parse(name string) (Configer, error) { 49 return ini.parseFile(name) 50 } 51 52 func (ini *IniConfig) parseFile(name string) (*IniConfigContainer, error) { 53 data, err := ioutil.ReadFile(name) 54 if err != nil { 55 return nil, err 56 } 57 58 return ini.parseData(filepath.Dir(name), data) 59 } 60 61 func (ini *IniConfig) parseData(dir string, data []byte) (*IniConfigContainer, error) { 62 cfg := &IniConfigContainer{ 63 data: make(map[string]map[string]string), 64 sectionComment: make(map[string]string), 65 keyComment: make(map[string]string), 66 RWMutex: sync.RWMutex{}, 67 } 68 cfg.Lock() 69 defer cfg.Unlock() 70 71 var comment bytes.Buffer 72 buf := bufio.NewReader(bytes.NewBuffer(data)) 73 // check the BOM 74 head, err := buf.Peek(3) 75 if err == nil && head[0] == 239 && head[1] == 187 && head[2] == 191 { 76 for i := 1; i <= 3; i++ { 77 buf.ReadByte() 78 } 79 } 80 section := defaultSection 81 tmpBuf := bytes.NewBuffer(nil) 82 for { 83 tmpBuf.Reset() 84 85 shouldBreak := false 86 for { 87 tmp, isPrefix, err := buf.ReadLine() 88 if err == io.EOF { 89 shouldBreak = true 90 break 91 } 92 93 //It might be a good idea to throw a error on all unknonw errors? 94 if _, ok := err.(*os.PathError); ok { 95 return nil, err 96 } 97 98 tmpBuf.Write(tmp) 99 if isPrefix { 100 continue 101 } 102 103 if !isPrefix { 104 break 105 } 106 } 107 if shouldBreak { 108 break 109 } 110 111 line := tmpBuf.Bytes() 112 line = bytes.TrimSpace(line) 113 if bytes.Equal(line, bEmpty) { 114 continue 115 } 116 var bComment []byte 117 switch { 118 case bytes.HasPrefix(line, bNumComment): 119 bComment = bNumComment 120 case bytes.HasPrefix(line, bSemComment): 121 bComment = bSemComment 122 } 123 if bComment != nil { 124 line = bytes.TrimLeft(line, string(bComment)) 125 // Need append to a new line if multi-line comments. 126 if comment.Len() > 0 { 127 comment.WriteByte('\n') 128 } 129 comment.Write(line) 130 continue 131 } 132 133 if bytes.HasPrefix(line, sectionStart) && bytes.HasSuffix(line, sectionEnd) { 134 section = strings.ToLower(string(line[1 : len(line)-1])) // section name case insensitive 135 if comment.Len() > 0 { 136 cfg.sectionComment[section] = comment.String() 137 comment.Reset() 138 } 139 if _, ok := cfg.data[section]; !ok { 140 cfg.data[section] = make(map[string]string) 141 } 142 continue 143 } 144 145 if _, ok := cfg.data[section]; !ok { 146 cfg.data[section] = make(map[string]string) 147 } 148 keyValue := bytes.SplitN(line, bEqual, 2) 149 150 key := string(bytes.TrimSpace(keyValue[0])) // key name case insensitive 151 key = strings.ToLower(key) 152 153 // handle include "other.conf" 154 if len(keyValue) == 1 && strings.HasPrefix(key, "include") { 155 156 includefiles := strings.Fields(key) 157 if includefiles[0] == "include" && len(includefiles) == 2 { 158 159 otherfile := strings.Trim(includefiles[1], "\"") 160 if !filepath.IsAbs(otherfile) { 161 otherfile = filepath.Join(dir, otherfile) 162 } 163 164 i, err := ini.parseFile(otherfile) 165 if err != nil { 166 return nil, err 167 } 168 169 for sec, dt := range i.data { 170 if _, ok := cfg.data[sec]; !ok { 171 cfg.data[sec] = make(map[string]string) 172 } 173 for k, v := range dt { 174 cfg.data[sec][k] = v 175 } 176 } 177 178 for sec, comm := range i.sectionComment { 179 cfg.sectionComment[sec] = comm 180 } 181 182 for k, comm := range i.keyComment { 183 cfg.keyComment[k] = comm 184 } 185 186 continue 187 } 188 } 189 190 if len(keyValue) != 2 { 191 return nil, errors.New("read the content error: \"" + string(line) + "\", should key = val") 192 } 193 val := bytes.TrimSpace(keyValue[1]) 194 if bytes.HasPrefix(val, bDQuote) { 195 val = bytes.Trim(val, `"`) 196 } 197 198 cfg.data[section][key] = ExpandValueEnv(string(val)) 199 if comment.Len() > 0 { 200 cfg.keyComment[section+"."+key] = comment.String() 201 comment.Reset() 202 } 203 204 } 205 return cfg, nil 206 } 207 208 // ParseData parse ini the data 209 // When include other.conf,other.conf is either absolute directory 210 // or under beego in default temporary directory(/tmp/beego[-username]). 211 func (ini *IniConfig) ParseData(data []byte) (Configer, error) { 212 dir := "beego" 213 currentUser, err := user.Current() 214 if err == nil { 215 dir = "beego-" + currentUser.Username 216 } 217 dir = filepath.Join(os.TempDir(), dir) 218 if err = os.MkdirAll(dir, os.ModePerm); err != nil { 219 return nil, err 220 } 221 222 return ini.parseData(dir, data) 223 } 224 225 // IniConfigContainer A Config represents the ini configuration. 226 // When set and get value, support key as section:name type. 227 type IniConfigContainer struct { 228 data map[string]map[string]string // section=> key:val 229 sectionComment map[string]string // section : comment 230 keyComment map[string]string // id: []{comment, key...}; id 1 is for main comment. 231 sync.RWMutex 232 } 233 234 // Bool returns the boolean value for a given key. 235 func (c *IniConfigContainer) Bool(key string) (bool, error) { 236 return ParseBool(c.getdata(key)) 237 } 238 239 // DefaultBool returns the boolean value for a given key. 240 // if err != nil return defaultval 241 func (c *IniConfigContainer) DefaultBool(key string, defaultval bool) bool { 242 v, err := c.Bool(key) 243 if err != nil { 244 return defaultval 245 } 246 return v 247 } 248 249 // Int returns the integer value for a given key. 250 func (c *IniConfigContainer) Int(key string) (int, error) { 251 return strconv.Atoi(c.getdata(key)) 252 } 253 254 // DefaultInt returns the integer value for a given key. 255 // if err != nil return defaultval 256 func (c *IniConfigContainer) DefaultInt(key string, defaultval int) int { 257 v, err := c.Int(key) 258 if err != nil { 259 return defaultval 260 } 261 return v 262 } 263 264 // Int64 returns the int64 value for a given key. 265 func (c *IniConfigContainer) Int64(key string) (int64, error) { 266 return strconv.ParseInt(c.getdata(key), 10, 64) 267 } 268 269 // DefaultInt64 returns the int64 value for a given key. 270 // if err != nil return defaultval 271 func (c *IniConfigContainer) DefaultInt64(key string, defaultval int64) int64 { 272 v, err := c.Int64(key) 273 if err != nil { 274 return defaultval 275 } 276 return v 277 } 278 279 // Float returns the float value for a given key. 280 func (c *IniConfigContainer) Float(key string) (float64, error) { 281 return strconv.ParseFloat(c.getdata(key), 64) 282 } 283 284 // DefaultFloat returns the float64 value for a given key. 285 // if err != nil return defaultval 286 func (c *IniConfigContainer) DefaultFloat(key string, defaultval float64) float64 { 287 v, err := c.Float(key) 288 if err != nil { 289 return defaultval 290 } 291 return v 292 } 293 294 // String returns the string value for a given key. 295 func (c *IniConfigContainer) String(key string) string { 296 return c.getdata(key) 297 } 298 299 // DefaultString returns the string value for a given key. 300 // if err != nil return defaultval 301 func (c *IniConfigContainer) DefaultString(key string, defaultval string) string { 302 v := c.String(key) 303 if v == "" { 304 return defaultval 305 } 306 return v 307 } 308 309 // Strings returns the []string value for a given key. 310 // Return nil if config value does not exist or is empty. 311 func (c *IniConfigContainer) Strings(key string) []string { 312 v := c.String(key) 313 if v == "" { 314 return nil 315 } 316 return strings.Split(v, ";") 317 } 318 319 // DefaultStrings returns the []string value for a given key. 320 // if err != nil return defaultval 321 func (c *IniConfigContainer) DefaultStrings(key string, defaultval []string) []string { 322 v := c.Strings(key) 323 if v == nil { 324 return defaultval 325 } 326 return v 327 } 328 329 // GetSection returns map for the given section 330 func (c *IniConfigContainer) GetSection(section string) (map[string]string, error) { 331 if v, ok := c.data[section]; ok { 332 return v, nil 333 } 334 return nil, errors.New("not exist section") 335 } 336 337 // SaveConfigFile save the config into file. 338 // 339 // BUG(env): The environment variable config item will be saved with real value in SaveConfigFile Function. 340 func (c *IniConfigContainer) SaveConfigFile(filename string) (err error) { 341 // Write configuration file by filename. 342 f, err := os.Create(filename) 343 if err != nil { 344 return err 345 } 346 defer f.Close() 347 348 // Get section or key comments. Fixed #1607 349 getCommentStr := func(section, key string) string { 350 var ( 351 comment string 352 ok bool 353 ) 354 if len(key) == 0 { 355 comment, ok = c.sectionComment[section] 356 } else { 357 comment, ok = c.keyComment[section+"."+key] 358 } 359 360 if ok { 361 // Empty comment 362 if len(comment) == 0 || len(strings.TrimSpace(comment)) == 0 { 363 return string(bNumComment) 364 } 365 prefix := string(bNumComment) 366 // Add the line head character "#" 367 return prefix + strings.Replace(comment, lineBreak, lineBreak+prefix, -1) 368 } 369 return "" 370 } 371 372 buf := bytes.NewBuffer(nil) 373 // Save default section at first place 374 if dt, ok := c.data[defaultSection]; ok { 375 for key, val := range dt { 376 if key != " " { 377 // Write key comments. 378 if v := getCommentStr(defaultSection, key); len(v) > 0 { 379 if _, err = buf.WriteString(v + lineBreak); err != nil { 380 return err 381 } 382 } 383 384 // Write key and value. 385 if _, err = buf.WriteString(key + string(bEqual) + val + lineBreak); err != nil { 386 return err 387 } 388 } 389 } 390 391 // Put a line between sections. 392 if _, err = buf.WriteString(lineBreak); err != nil { 393 return err 394 } 395 } 396 // Save named sections 397 for section, dt := range c.data { 398 if section != defaultSection { 399 // Write section comments. 400 if v := getCommentStr(section, ""); len(v) > 0 { 401 if _, err = buf.WriteString(v + lineBreak); err != nil { 402 return err 403 } 404 } 405 406 // Write section name. 407 if _, err = buf.WriteString(string(sectionStart) + section + string(sectionEnd) + lineBreak); err != nil { 408 return err 409 } 410 411 for key, val := range dt { 412 if key != " " { 413 // Write key comments. 414 if v := getCommentStr(section, key); len(v) > 0 { 415 if _, err = buf.WriteString(v + lineBreak); err != nil { 416 return err 417 } 418 } 419 420 // Write key and value. 421 if _, err = buf.WriteString(key + string(bEqual) + val + lineBreak); err != nil { 422 return err 423 } 424 } 425 } 426 427 // Put a line between sections. 428 if _, err = buf.WriteString(lineBreak); err != nil { 429 return err 430 } 431 } 432 } 433 _, err = buf.WriteTo(f) 434 return err 435 } 436 437 // Set writes a new value for key. 438 // if write to one section, the key need be "section::key". 439 // if the section is not existed, it panics. 440 func (c *IniConfigContainer) Set(key, value string) error { 441 c.Lock() 442 defer c.Unlock() 443 if len(key) == 0 { 444 return errors.New("key is empty") 445 } 446 447 var ( 448 section, k string 449 sectionKey = strings.Split(strings.ToLower(key), "::") 450 ) 451 452 if len(sectionKey) >= 2 { 453 section = sectionKey[0] 454 k = sectionKey[1] 455 } else { 456 section = defaultSection 457 k = sectionKey[0] 458 } 459 460 if _, ok := c.data[section]; !ok { 461 c.data[section] = make(map[string]string) 462 } 463 c.data[section][k] = value 464 return nil 465 } 466 467 // DIY returns the raw value by a given key. 468 func (c *IniConfigContainer) DIY(key string) (v interface{}, err error) { 469 if v, ok := c.data[strings.ToLower(key)]; ok { 470 return v, nil 471 } 472 return v, errors.New("key not find") 473 } 474 475 // section.key or key 476 func (c *IniConfigContainer) getdata(key string) string { 477 if len(key) == 0 { 478 return "" 479 } 480 c.RLock() 481 defer c.RUnlock() 482 483 var ( 484 section, k string 485 sectionKey = strings.Split(strings.ToLower(key), "::") 486 ) 487 if len(sectionKey) >= 2 { 488 section = sectionKey[0] 489 k = sectionKey[1] 490 } else { 491 section = defaultSection 492 k = sectionKey[0] 493 } 494 if v, ok := c.data[section]; ok { 495 if vv, ok := v[k]; ok { 496 return vv 497 } 498 } 499 return "" 500 } 501 502 func init() { 503 Register("ini", &IniConfig{}) 504 }