├── LICENSE ├── README.md ├── README.original.md ├── doc.go ├── mini.go └── mini_test.go /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Fog Creek Software, Inc 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | fogcreek/mini is no longer actively maintained 2 | 3 | The new official fork can be found at [https://github.com/sasbury/mini](https://github.com/sasbury/mini) 4 | 5 | See [README.original.md](README.original.md) for the documentation for this fork. 6 | -------------------------------------------------------------------------------- /README.original.md: -------------------------------------------------------------------------------- 1 | [![GoDoc](https://godoc.org/github.com/FogCreek/mini?status.svg)](https://godoc.org/github.com/FogCreek/mini) 2 | 3 | Mini is a simple [ini configuration file](http://en.wikipedia.org/wiki/INI_file) parser. 4 | 5 | The ini syntax supported includes: 6 | 7 | * The standard name=value 8 | * Comments on new lines starting with # or ; 9 | * Blank lines 10 | * Sections labelled with [sectionname] 11 | * Split sections, using the same section in more than one place 12 | * Encoded strings, strings containing \n, \t, etc... 13 | * Array values using repeated keys named in the form key[]=value 14 | * Global key/value pairs that appear before the first section 15 | 16 | Repeated keys, that aren't array keys, replace their previous value. 17 | 18 | To use simply: 19 | 20 | % go get github.com/fogcreek/mini 21 | 22 | copyright © 2015 Fog Creek Software, Inc. 23 | -------------------------------------------------------------------------------- /doc.go: -------------------------------------------------------------------------------- 1 | /* 2 | Package mini implements a simple ini file parser. 3 | 4 | The ini syntax supported includes: 5 | 6 | * The standard name=value 7 | * Comments on new lines starting with # or ; 8 | * Blank lines 9 | * Sections labelled with [sectionname] 10 | * Split sections, using the same section in more than one place 11 | * Encoded strings, strings containing \n, \t, etc... 12 | * Array values using repeated keys named in the form key[]=value 13 | * Global key/value pairs that appear before the first section 14 | 15 | Repeated keys, that aren't array keys, replace their previous value. 16 | 17 | copyright © 2015 Fog Creek Software, Inc. 18 | */ 19 | package mini 20 | -------------------------------------------------------------------------------- /mini.go: -------------------------------------------------------------------------------- 1 | package mini 2 | 3 | import ( 4 | "bufio" 5 | "errors" 6 | "fmt" 7 | "io" 8 | "os" 9 | "reflect" 10 | "sort" 11 | "strconv" 12 | "strings" 13 | ) 14 | 15 | type configSection struct { 16 | name string 17 | values map[string]interface{} 18 | } 19 | 20 | /* 21 | Config holds the contents of an ini file organized into sections. 22 | */ 23 | type Config struct { 24 | configSection 25 | sections map[string]*configSection 26 | } 27 | 28 | /* 29 | LoadConfiguration takes a path, treats it as a file and scans it for an ini configuration. 30 | */ 31 | func LoadConfiguration(path string) (*Config, error) { 32 | 33 | config := new(Config) 34 | err := config.InitializeFromPath(path) 35 | 36 | if err != nil { 37 | return nil, err 38 | } 39 | return config, nil 40 | } 41 | 42 | /* 43 | LoadConfigurationFromReader takes a reader and scans it for an ini configuration. 44 | The caller should close the reader. 45 | */ 46 | func LoadConfigurationFromReader(input io.Reader) (*Config, error) { 47 | 48 | config := new(Config) 49 | err := config.InitializeFromReader(input) 50 | 51 | if err != nil { 52 | return nil, err 53 | } 54 | return config, nil 55 | } 56 | 57 | /* 58 | InitializeFromPath takes a path, treats it as a file and scans it for an ini configuration. 59 | */ 60 | func (config *Config) InitializeFromPath(path string) error { 61 | 62 | f, err := os.Open(path) 63 | 64 | if err != nil { 65 | return err 66 | } 67 | 68 | defer f.Close() 69 | 70 | return config.InitializeFromReader(bufio.NewReader(f)) 71 | } 72 | 73 | /* 74 | InitializeFromReader takes a reader and scans it for an ini configuration. 75 | The caller should close the reader. 76 | */ 77 | func (config *Config) InitializeFromReader(input io.Reader) error { 78 | 79 | var currentSection *configSection 80 | 81 | scanner := bufio.NewScanner(input) 82 | config.values = make(map[string]interface{}) 83 | config.sections = make(map[string]*configSection) 84 | 85 | for scanner.Scan() { 86 | curLine := scanner.Text() 87 | 88 | curLine = strings.TrimSpace(curLine) 89 | 90 | if len(curLine) == 0 { 91 | continue // ignore empty lines 92 | } 93 | 94 | if strings.HasPrefix(curLine, ";") || strings.HasPrefix(curLine, "#") { 95 | continue // comment 96 | } 97 | 98 | if strings.HasPrefix(curLine, "[") { 99 | 100 | if !strings.HasSuffix(curLine, "]") { 101 | return errors.New("mini: section names must be surrounded by [ and ], as in [section]") 102 | } 103 | 104 | sectionName := curLine[1 : len(curLine)-1] 105 | 106 | if sect, ok := config.sections[sectionName]; !ok { //reuse sections 107 | currentSection = new(configSection) 108 | currentSection.name = sectionName 109 | currentSection.values = make(map[string]interface{}) 110 | config.sections[currentSection.name] = currentSection 111 | } else { 112 | currentSection = sect 113 | } 114 | 115 | continue 116 | } 117 | 118 | index := strings.Index(curLine, "=") 119 | 120 | if index <= 0 { 121 | return errors.New("mini: configuration format requires an equals between the key and value") 122 | } 123 | 124 | key := strings.ToLower(strings.TrimSpace(curLine[0:index])) 125 | isArray := strings.HasSuffix(key, "[]") 126 | 127 | if isArray { 128 | key = key[0 : len(key)-2] 129 | } 130 | 131 | value := strings.TrimSpace(curLine[index+1:]) 132 | value = strings.Trim(value, "\"'") //clear quotes 133 | 134 | valueMap := config.values 135 | 136 | if currentSection != nil { 137 | valueMap = currentSection.values 138 | } 139 | 140 | if isArray { 141 | arr := valueMap[key] 142 | 143 | if arr == nil { 144 | arr = make([]interface{}, 0) 145 | valueMap[key] = arr 146 | } 147 | 148 | valueMap[key] = append(arr.([]interface{}), value) 149 | } else { 150 | valueMap[key] = value 151 | } 152 | } 153 | 154 | return scanner.Err() 155 | } 156 | 157 | /* 158 | SetName sets the config's name, which allows it to be returned in SectionNames, or in get functions that take a name. 159 | */ 160 | func (config *Config) SetName(name string) { 161 | config.name = name 162 | } 163 | 164 | //Return non-array values 165 | func get(values map[string]interface{}, key string) interface{} { 166 | if len(key) == 0 || values == nil { 167 | return nil 168 | } 169 | 170 | key = strings.ToLower(key) 171 | val, ok := values[key] 172 | 173 | if ok { 174 | switch val.(type) { 175 | case []interface{}: 176 | return nil 177 | default: 178 | return val 179 | } 180 | } 181 | 182 | return nil 183 | } 184 | 185 | //Return array values 186 | func getArray(values map[string]interface{}, key string) []interface{} { 187 | if len(key) == 0 || values == nil { 188 | return nil 189 | } 190 | 191 | key = strings.ToLower(key) 192 | val, ok := values[key] 193 | 194 | if ok { 195 | switch v := val.(type) { 196 | case []interface{}: 197 | return v 198 | default: 199 | retVal := make([]interface{}, 1) 200 | retVal[0] = val 201 | return retVal 202 | } 203 | } 204 | 205 | return nil 206 | } 207 | 208 | func getString(values map[string]interface{}, key string, def string) string { 209 | 210 | val := get(values, key) 211 | 212 | if val != nil { 213 | str, err := strconv.Unquote(fmt.Sprintf("\"%v\"", val)) 214 | 215 | if err == nil { 216 | return str 217 | } 218 | 219 | return def 220 | } 221 | 222 | return def 223 | } 224 | 225 | func getBoolean(values map[string]interface{}, key string, def bool) bool { 226 | 227 | val := get(values, key) 228 | 229 | if val != nil { 230 | retVal, err := strconv.ParseBool(fmt.Sprint(val)) 231 | 232 | if err != nil { 233 | return def 234 | } 235 | return retVal 236 | } 237 | 238 | return def 239 | } 240 | 241 | func getInteger(values map[string]interface{}, key string, def int64) int64 { 242 | 243 | val := get(values, key) 244 | 245 | if val != nil { 246 | retVal, err := strconv.ParseInt(fmt.Sprint(val), 0, 64) 247 | 248 | if err != nil { 249 | return def 250 | } 251 | return retVal 252 | } 253 | 254 | return def 255 | } 256 | 257 | func getFloat(values map[string]interface{}, key string, def float64) float64 { 258 | 259 | val := get(values, key) 260 | 261 | if val != nil { 262 | retVal, err := strconv.ParseFloat(fmt.Sprint(val), 64) 263 | 264 | if err != nil { 265 | return def 266 | } 267 | return retVal 268 | } 269 | 270 | return def 271 | } 272 | 273 | func getStrings(values map[string]interface{}, key string) []string { 274 | 275 | val := getArray(values, key) 276 | 277 | if val != nil { 278 | retVal := make([]string, len(val)) 279 | 280 | var err error 281 | for i, v := range val { 282 | retVal[i], err = strconv.Unquote(fmt.Sprintf("\"%v\"", v)) 283 | if err != nil { 284 | return nil 285 | } 286 | } 287 | return retVal 288 | } 289 | 290 | return nil 291 | } 292 | 293 | func getIntegers(values map[string]interface{}, key string) []int64 { 294 | 295 | val := getArray(values, key) 296 | 297 | if val != nil { 298 | retVal := make([]int64, len(val)) 299 | 300 | var err error 301 | for i, v := range val { 302 | retVal[i], err = strconv.ParseInt(fmt.Sprint(v), 0, 64) 303 | if err != nil { 304 | return nil 305 | } 306 | } 307 | return retVal 308 | } 309 | 310 | return nil 311 | } 312 | 313 | func getFloats(values map[string]interface{}, key string) []float64 { 314 | 315 | val := getArray(values, key) 316 | 317 | if val != nil { 318 | retVal := make([]float64, len(val)) 319 | 320 | var err error 321 | for i, v := range val { 322 | retVal[i], err = strconv.ParseFloat(fmt.Sprint(v), 64) 323 | if err != nil { 324 | return nil 325 | } 326 | } 327 | return retVal 328 | } 329 | 330 | return nil 331 | } 332 | 333 | /* 334 | String looks for the specified key and returns it as a string. If not found the default value def is returned. 335 | */ 336 | func (config *Config) String(key string, def string) string { 337 | return getString(config.values, key, def) 338 | } 339 | 340 | /* 341 | Boolean looks for the specified key and returns it as a bool. If not found the default value def is returned. 342 | */ 343 | func (config *Config) Boolean(key string, def bool) bool { 344 | return getBoolean(config.values, key, def) 345 | } 346 | 347 | /* 348 | Integer looks for the specified key and returns it as an int. If not found the default value def is returned. 349 | */ 350 | func (config *Config) Integer(key string, def int64) int64 { 351 | return getInteger(config.values, key, def) 352 | } 353 | 354 | /* 355 | Float looks for the specified key and returns it as a float. If not found the default value def is returned. 356 | */ 357 | func (config *Config) Float(key string, def float64) float64 { 358 | return getFloat(config.values, key, def) 359 | } 360 | 361 | /* 362 | Strings looks for an array of strings under the provided key. 363 | If no matches are found nil is returned. If only one matches an array of 1 is returned. 364 | */ 365 | func (config *Config) Strings(key string) []string { 366 | return getStrings(config.values, key) 367 | } 368 | 369 | /* 370 | Integers looks for an array of ints under the provided key. 371 | If no matches are found nil is returned. 372 | */ 373 | func (config *Config) Integers(key string) []int64 { 374 | return getIntegers(config.values, key) 375 | } 376 | 377 | /* 378 | Floats looks for an array of floats under the provided key. 379 | If no matches are found nil is returned. 380 | */ 381 | func (config *Config) Floats(key string) []float64 { 382 | return getFloats(config.values, key) 383 | } 384 | 385 | func (config *Config) sectionForName(sectionName string) *configSection { 386 | if len(sectionName) == 0 || sectionName == config.name { 387 | return &(config.configSection) 388 | } 389 | 390 | return config.sections[sectionName] 391 | } 392 | 393 | /* 394 | StringFromSection looks for the specified key and returns it as a string. If not found the default value def is returned. 395 | 396 | If the section name matches the config.name or "" the global data is searched. 397 | */ 398 | func (config *Config) StringFromSection(sectionName string, key string, def string) string { 399 | section := config.sectionForName(sectionName) 400 | 401 | if section != nil { 402 | return getString(section.values, key, def) 403 | } 404 | 405 | return def 406 | } 407 | 408 | /* 409 | BooleanFromSection looks for the specified key and returns it as a boolean. If not found the default value def is returned. 410 | 411 | If the section name matches the config.name or "" the global data is searched. 412 | */ 413 | func (config *Config) BooleanFromSection(sectionName string, key string, def bool) bool { 414 | section := config.sectionForName(sectionName) 415 | 416 | if section != nil { 417 | return getBoolean(section.values, key, def) 418 | } 419 | 420 | return def 421 | } 422 | 423 | /* 424 | IntegerFromSection looks for the specified key and returns it as an int64. If not found the default value def is returned. 425 | 426 | If the section name matches the config.name or "" the global data is searched. 427 | */ 428 | func (config *Config) IntegerFromSection(sectionName string, key string, def int64) int64 { 429 | section := config.sectionForName(sectionName) 430 | 431 | if section != nil { 432 | return getInteger(section.values, key, def) 433 | } 434 | 435 | return def 436 | } 437 | 438 | /* 439 | FloatFromSection looks for the specified key and returns it as a float. If not found the default value def is returned. 440 | 441 | If the section name matches the config.name or "" the global data is searched. 442 | */ 443 | func (config *Config) FloatFromSection(sectionName string, key string, def float64) float64 { 444 | section := config.sectionForName(sectionName) 445 | 446 | if section != nil { 447 | return getFloat(section.values, key, def) 448 | } 449 | 450 | return def 451 | } 452 | 453 | /* 454 | StringsFromSection returns the value of an array key, if the value of the key is a non-array, then 455 | that value is returned in an array of length 1. 456 | 457 | If the section name matches the config.name or "" the global data is searched. 458 | */ 459 | func (config *Config) StringsFromSection(sectionName string, key string) []string { 460 | section := config.sectionForName(sectionName) 461 | 462 | if section != nil { 463 | return getStrings(section.values, key) 464 | } 465 | 466 | return nil 467 | } 468 | 469 | /* 470 | IntegersFromSection looks for an array of integers in the provided section and under the provided key. 471 | If no matches are found nil is returned. 472 | */ 473 | func (config *Config) IntegersFromSection(sectionName string, key string) []int64 { 474 | section := config.sectionForName(sectionName) 475 | 476 | if section != nil { 477 | return getIntegers(section.values, key) 478 | } 479 | 480 | return nil 481 | } 482 | 483 | /* 484 | FloatsFromSection looks for an array of floats in the provided section and under the provided key. 485 | If no matches are found nil is returned. 486 | 487 | If the section name matches the config.name or "" the global data is searched. 488 | */ 489 | func (config *Config) FloatsFromSection(sectionName string, key string) []float64 { 490 | section := config.sectionForName(sectionName) 491 | 492 | if section != nil { 493 | return getFloats(section.values, key) 494 | } 495 | 496 | return nil 497 | } 498 | 499 | /* 500 | DataFromSection reads the values of a section into a struct. The values should be of the types: 501 | bool 502 | string 503 | []string 504 | int64 505 | []int64 506 | float64 507 | []float64 508 | Values that are missing in the section are not set, and values that are missing in the 509 | struct but present in the section are ignored. 510 | 511 | If the section name matches the config.name or "" the global data is searched. 512 | */ 513 | func (config *Config) DataFromSection(sectionName string, data interface{}) bool { 514 | section := config.sectionForName(sectionName) 515 | 516 | if section == nil { 517 | return false 518 | } 519 | 520 | values := section.values 521 | 522 | fields := reflect.ValueOf(data).Elem() 523 | dataType := fields.Type() 524 | 525 | for i := 0; i < fields.NumField(); i++ { 526 | field := fields.Field(i) 527 | 528 | if !field.CanSet() { 529 | continue 530 | } 531 | 532 | fieldType := dataType.Field(i) 533 | fieldName := fieldType.Name 534 | 535 | switch field.Type().Kind() { 536 | case reflect.Bool: 537 | field.SetBool(getBoolean(values, fieldName, field.Interface().(bool))) 538 | case reflect.Int64: 539 | field.SetInt(getInteger(values, fieldName, field.Interface().(int64))) 540 | case reflect.Float64: 541 | field.SetFloat(getFloat(values, fieldName, field.Interface().(float64))) 542 | case reflect.String: 543 | field.SetString(getString(values, fieldName, field.Interface().(string))) 544 | case reflect.Array, reflect.Slice: 545 | switch fieldType.Type.Elem().Kind() { 546 | case reflect.Int64: 547 | ints := getIntegers(values, fieldName) 548 | if ints != nil { 549 | field.Set(reflect.ValueOf(ints)) 550 | } 551 | case reflect.Float64: 552 | floats := getFloats(values, fieldName) 553 | if floats != nil { 554 | field.Set(reflect.ValueOf(floats)) 555 | } 556 | case reflect.String: 557 | strings := getStrings(values, fieldName) 558 | if strings != nil { 559 | field.Set(reflect.ValueOf(strings)) 560 | } 561 | } 562 | } 563 | } 564 | return true 565 | } 566 | 567 | /* 568 | Keys returns all of the global keys in the config. 569 | */ 570 | func (config *Config) Keys() []string { 571 | keys := make([]string, 0, len(config.values)) 572 | for key := range config.values { 573 | keys = append(keys, key) 574 | } 575 | sort.Strings(keys) 576 | return keys 577 | } 578 | 579 | /* 580 | KeysForSection returns all of the keys found in the section named sectionName. 581 | 582 | If the section name matches the config.name or "" the global data is searched. 583 | */ 584 | func (config *Config) KeysForSection(sectionName string) []string { 585 | section := config.sectionForName(sectionName) 586 | 587 | if section != nil { 588 | keys := make([]string, 0, len(section.values)) 589 | for key := range section.values { 590 | keys = append(keys, key) 591 | } 592 | sort.Strings(keys) 593 | return keys 594 | } 595 | 596 | return nil 597 | } 598 | 599 | /* 600 | SectionNames returns the names for each of the sections in a config structure. If the config was assigned 601 | a name, that name is included in the list. If the name is not set, then only explicitely named sections are returned. 602 | */ 603 | func (config *Config) SectionNames() []string { 604 | sectionNames := make([]string, 0, len(config.sections)) 605 | for name := range config.sections { 606 | sectionNames = append(sectionNames, name) 607 | } 608 | 609 | if len(config.name) > 0 { 610 | sectionNames = append(sectionNames, config.name) 611 | } 612 | 613 | sort.Strings(sectionNames) 614 | 615 | return sectionNames 616 | } 617 | -------------------------------------------------------------------------------- /mini_test.go: -------------------------------------------------------------------------------- 1 | package mini 2 | 3 | import ( 4 | "github.com/stretchr/testify/assert" 5 | "os" 6 | "path" 7 | "strings" 8 | "testing" 9 | ) 10 | 11 | func TestSimpleIniFile(t *testing.T) { 12 | 13 | simpleIni := `first=alpha 14 | second=beta 15 | third="gamma bamma" 16 | fourth = 'delta' 17 | int=32 18 | float=3.14 19 | true=true 20 | false=false 21 | #comment 22 | ; comment` 23 | 24 | filepath := path.Join(os.TempDir(), "simpleini.txt") 25 | f, err := os.Create(filepath) 26 | if err != nil { 27 | t.Fatal(err) 28 | } 29 | defer os.Remove(filepath) 30 | if _, err := f.WriteString(simpleIni); err != nil { 31 | t.Fatal(err) 32 | } 33 | if err := f.Close(); err != nil { 34 | t.Fatal(err) 35 | } 36 | 37 | config, err := LoadConfiguration(filepath) 38 | 39 | assert.Nil(t, err, "Simple configuration should load without error.") 40 | 41 | assert.Equal(t, config.String("first", ""), "alpha", "Read value of first wrong") 42 | assert.Equal(t, config.String("second", ""), "beta", "Read value of second wrong") 43 | assert.Equal(t, config.String("third", ""), "gamma bamma", "Read value of third wrong") 44 | assert.Equal(t, config.String("fourth", ""), "delta", "Read value of fourth wrong") 45 | assert.Equal(t, config.Integer("int", 0), 32, "Read value of int wrong") 46 | assert.Equal(t, config.Float("float", 0), 3.14, "Read value of float wrong") 47 | assert.Equal(t, config.Boolean("true", false), true, "Read true wrong") 48 | assert.Equal(t, config.Boolean("false", true), false, "Read false wrong") 49 | 50 | assert.Equal(t, len(config.Keys()), 8, "Simple ini contains 8 fields") 51 | } 52 | 53 | func TestSimpleIniFileFromReader(t *testing.T) { 54 | 55 | simpleIni := `first=alpha 56 | second=beta 57 | third="gamma bamma" 58 | fourth = 'delta' 59 | int=32 60 | float=3.14 61 | true=true 62 | false=false 63 | #comment 64 | ; comment` 65 | 66 | config, err := LoadConfigurationFromReader(strings.NewReader(simpleIni)) 67 | 68 | assert.Nil(t, err, "Simple configuration should load without error.") 69 | 70 | assert.Equal(t, config.String("first", ""), "alpha", "Read value of first wrong") 71 | assert.Equal(t, config.String("second", ""), "beta", "Read value of second wrong") 72 | assert.Equal(t, config.String("third", ""), "gamma bamma", "Read value of third wrong") 73 | assert.Equal(t, config.String("fourth", ""), "delta", "Read value of fourth wrong") 74 | assert.Equal(t, config.Integer("int", 0), 32, "Read value of int wrong") 75 | assert.Equal(t, config.Float("float", 0), 3.14, "Read value of float wrong") 76 | assert.Equal(t, config.Boolean("true", false), true, "Read true wrong") 77 | assert.Equal(t, config.Boolean("false", true), false, "Read false wrong") 78 | 79 | assert.Equal(t, len(config.Keys()), 8, "Simple ini contains 8 fields") 80 | } 81 | 82 | func TestCaseInsensitive(t *testing.T) { 83 | 84 | simpleIni := `fIrst=alpha 85 | SECOND=beta 86 | Third="gamma bamma" 87 | FourTh = 'delta'` 88 | 89 | config, err := LoadConfigurationFromReader(strings.NewReader(simpleIni)) 90 | 91 | assert.Nil(t, err, "Case insensitive configuration should load without error.") 92 | 93 | assert.Equal(t, config.String("first", ""), "alpha", "Read value of first wrong") 94 | assert.Equal(t, config.String("second", ""), "beta", "Read value of second wrong") 95 | assert.Equal(t, config.String("THIRD", ""), "gamma bamma", "Read value of third wrong") 96 | assert.Equal(t, config.String("fourth", ""), "delta", "Read value of fourth wrong") 97 | 98 | assert.Equal(t, len(config.Keys()), 4, "Case ins ini contains 4 fields") 99 | } 100 | 101 | func TestArrayOfStrings(t *testing.T) { 102 | 103 | simpleIni := `key[]=one 104 | key[]=two 105 | noarray=three` 106 | 107 | config, err := LoadConfigurationFromReader(strings.NewReader(simpleIni)) 108 | 109 | assert.Nil(t, err, "Simple configuration should load without error.") 110 | 111 | val := config.Strings("key") 112 | 113 | assert.Equal(t, len(val), 2, "Array for keys should have 2 values") 114 | assert.Equal(t, val[0], "one", "Read value of first wrong") 115 | assert.Equal(t, val[1], "two", "Read value of second wrong") 116 | 117 | val = config.Strings("noarray") 118 | assert.Equal(t, len(val), 1, "Array for noarray should have 1 value") 119 | assert.Equal(t, val[0], "three", "Read value of noarray wrong") 120 | 121 | assert.Equal(t, len(config.Keys()), 2, "StringArray test contains 2 fields") 122 | } 123 | 124 | func TestArrayOfIntegers(t *testing.T) { 125 | 126 | simpleIni := `key[]=1 127 | key[]=2 128 | noarray=3` 129 | 130 | config, err := LoadConfigurationFromReader(strings.NewReader(simpleIni)) 131 | 132 | assert.Nil(t, err, "Simple configuration should load without error.") 133 | 134 | val := config.Integers("key") 135 | 136 | assert.Equal(t, len(val), 2, "Array for keys should have 2 values") 137 | assert.Equal(t, val[0], 1, "Read value of first wrong") 138 | assert.Equal(t, val[1], 2, "Read value of second wrong") 139 | 140 | val = config.Integers("noarray") 141 | assert.Equal(t, len(val), 1, "Array for noarray should have 1 value") 142 | assert.Equal(t, val[0], 3, "Read value of noarray wrong") 143 | 144 | assert.Equal(t, len(config.Keys()), 2, "IntArray test contains 2 fields") 145 | } 146 | 147 | func TestArrayOfFloats(t *testing.T) { 148 | 149 | simpleIni := `key[]=1.1 150 | key[]=2.2 151 | noarray=3.3` 152 | 153 | config, err := LoadConfigurationFromReader(strings.NewReader(simpleIni)) 154 | 155 | assert.Nil(t, err, "Simple configuration should load without error.") 156 | 157 | val := config.Floats("key") 158 | 159 | assert.Equal(t, len(val), 2, "Array for keys should have 2 values") 160 | assert.Equal(t, val[0], 1.1, "Read value of first wrong") 161 | assert.Equal(t, val[1], 2.2, "Read value of second wrong") 162 | 163 | val = config.Floats("noarray") 164 | assert.Equal(t, len(val), 1, "Array for noarray should have 1 value") 165 | assert.Equal(t, val[0], 3.3, "Read value of noarray wrong") 166 | 167 | assert.Equal(t, len(config.Keys()), 2, "FloatArray test contains 2 fields") 168 | } 169 | 170 | func TestSectionedIniFile(t *testing.T) { 171 | 172 | simpleIni := `first=alpha 173 | second=beta 174 | third="gamma bamma" 175 | fourth = 'delta' 176 | int=32 177 | float=3.14 178 | true=true 179 | false=false 180 | 181 | [section] 182 | first=raz 183 | second=dba 184 | int=124 185 | float=1222.7 186 | true=false 187 | false=true 188 | #comment 189 | ; comment` 190 | 191 | config, err := LoadConfigurationFromReader(strings.NewReader(simpleIni)) 192 | 193 | assert.Nil(t, err, "Sectioned configuration should load without error.") 194 | 195 | assert.Equal(t, config.String("first", ""), "alpha", "Read value of first wrong") 196 | assert.Equal(t, config.String("second", ""), "beta", "Read value of second wrong") 197 | assert.Equal(t, config.String("third", ""), "gamma bamma", "Read value of third wrong") 198 | assert.Equal(t, config.String("fourth", ""), "delta", "Read value of fourth wrong") 199 | assert.Equal(t, config.Integer("int", 0), 32, "Read value of int wrong") 200 | assert.Equal(t, config.Float("float", 0), 3.14, "Read value of float wrong") 201 | assert.Equal(t, config.Boolean("true", false), true, "Read true wrong") 202 | assert.Equal(t, config.Boolean("false", true), false, "Read false wrong") 203 | 204 | assert.Equal(t, config.StringFromSection("section", "first", ""), "raz", "Read value of first from section wrong") 205 | assert.Equal(t, config.StringFromSection("section", "second", ""), "dba", "Read value of second from section wrong") 206 | assert.Equal(t, config.IntegerFromSection("section", "int", 0), 124, "Read value of int in section wrong") 207 | assert.Equal(t, config.FloatFromSection("section", "float", 0), 1222.7, "Read value of float in section wrong") 208 | assert.Equal(t, config.BooleanFromSection("section", "true", true), false, "Read true in section wrong") 209 | assert.Equal(t, config.BooleanFromSection("section", "false", false), true, "Read false in section wrong") 210 | 211 | assert.Equal(t, len(config.Keys()), 8, "Section ini contains 6 fields") 212 | assert.Equal(t, len(config.KeysForSection("section")), 6, "Section in ini contains 4 fields") 213 | } 214 | 215 | func TestArrayOfStringsInSection(t *testing.T) { 216 | 217 | simpleIni := `key=nope 218 | noarray=nope 219 | [section] 220 | key[]=one 221 | key[]=two 222 | noarray=three` 223 | 224 | config, err := LoadConfigurationFromReader(strings.NewReader(simpleIni)) 225 | 226 | assert.Nil(t, err, "Configuration should load without error.") 227 | 228 | val := config.StringsFromSection("section", "key") 229 | 230 | assert.Equal(t, len(val), 2, "Array for keys should have 2 values") 231 | assert.Equal(t, val[0], "one", "Read value of first wrong") 232 | assert.Equal(t, val[1], "two", "Read value of second wrong") 233 | 234 | val = config.StringsFromSection("section", "noarray") 235 | assert.Equal(t, len(val), 1, "Array for noarray should have 1 value") 236 | assert.Equal(t, val[0], "three", "Read value of noarray wrong") 237 | 238 | assert.Equal(t, len(config.Keys()), 2, "StringArray section test contains 2 fields") 239 | } 240 | 241 | func TestArrayOfIntegersInSection(t *testing.T) { 242 | 243 | simpleIni := `key=nope 244 | noarray=nope 245 | [section] 246 | key[]=1 247 | key[]=2 248 | noarray=3` 249 | 250 | config, err := LoadConfigurationFromReader(strings.NewReader(simpleIni)) 251 | 252 | assert.Nil(t, err, "Configuration should load without error.") 253 | 254 | val := config.IntegersFromSection("section", "key") 255 | 256 | assert.Equal(t, len(val), 2, "Array for keys should have 2 values") 257 | assert.Equal(t, val[0], 1, "Read value of first wrong") 258 | assert.Equal(t, val[1], 2, "Read value of second wrong") 259 | 260 | val = config.IntegersFromSection("section", "noarray") 261 | assert.Equal(t, len(val), 1, "Array for noarray should have 1 value") 262 | assert.Equal(t, val[0], 3, "Read value of noarray wrong") 263 | 264 | assert.Equal(t, len(config.KeysForSection("section")), 2, "IntArray section test contains 2 fields") 265 | } 266 | 267 | func TestArrayOfFloatsInSection(t *testing.T) { 268 | 269 | simpleIni := `key=nope 270 | noarray=nope 271 | [section] 272 | key[]=1.1 273 | key[]=2.2 274 | noarray=3.3` 275 | 276 | config, err := LoadConfigurationFromReader(strings.NewReader(simpleIni)) 277 | 278 | assert.Nil(t, err, "Configuration should load without error.") 279 | 280 | val := config.FloatsFromSection("section", "key") 281 | 282 | assert.Equal(t, len(val), 2, "Array for keys should have 2 values") 283 | assert.Equal(t, val[0], 1.1, "Read value of first wrong") 284 | assert.Equal(t, val[1], 2.2, "Read value of second wrong") 285 | 286 | val = config.FloatsFromSection("section", "noarray") 287 | assert.Equal(t, len(val), 1, "Array for noarray should have 1 value") 288 | assert.Equal(t, val[0], 3.3, "Read value of noarray wrong") 289 | 290 | assert.Equal(t, len(config.Keys()), 2, "FloatArray section test contains 2 fields") 291 | } 292 | 293 | func TestMultipleSections(t *testing.T) { 294 | 295 | simpleIni := `first=alpha 296 | int=32 297 | float=3.14 298 | 299 | [section_one] 300 | first=raz 301 | int=124 302 | float=1222.7 303 | 304 | [section_two] 305 | first=one 306 | int=555 307 | float=124.3` 308 | 309 | config, err := LoadConfigurationFromReader(strings.NewReader(simpleIni)) 310 | 311 | assert.Nil(t, err, "Sectioned configuration should load without error.") 312 | 313 | assert.Equal(t, config.String("first", ""), "alpha", "Read value of first wrong") 314 | assert.Equal(t, config.Integer("int", 0), 32, "Read value of int wrong") 315 | assert.Equal(t, config.Float("float", 0), 3.14, "Read value of float wrong") 316 | 317 | assert.Equal(t, config.StringFromSection("", "first", ""), "alpha", "Read value of first wrong") 318 | assert.Equal(t, config.IntegerFromSection("", "int", 0), 32, "Read value of int wrong") 319 | assert.Equal(t, config.FloatFromSection("", "float", 0), 3.14, "Read value of float wrong") 320 | 321 | config.SetName("section_zero") 322 | 323 | assert.Equal(t, config.StringFromSection("section_zero", "first", ""), "alpha", "Read value of first wrong") 324 | assert.Equal(t, config.IntegerFromSection("section_zero", "int", 0), 32, "Read value of int wrong") 325 | assert.Equal(t, config.FloatFromSection("section_zero", "float", 0), 3.14, "Read value of float wrong") 326 | 327 | assert.Equal(t, config.StringFromSection("section_one", "first", ""), "raz", "Read value of first from section wrong") 328 | assert.Equal(t, config.IntegerFromSection("section_one", "int", 0), 124, "Read value of int in section wrong") 329 | assert.Equal(t, config.FloatFromSection("section_one", "float", 0), 1222.7, "Read value of float in section wrong") 330 | 331 | assert.Equal(t, config.StringFromSection("section_two", "first", ""), "one", "Read value of first from section wrong") 332 | assert.Equal(t, config.IntegerFromSection("section_two", "int", 0), 555, "Read value of int in section wrong") 333 | assert.Equal(t, config.FloatFromSection("section_two", "float", 0), 124.3, "Read value of float in section wrong") 334 | 335 | assert.Equal(t, len(config.Keys()), 3, "Section ini contains 3 fields") 336 | assert.Equal(t, len(config.KeysForSection("section_one")), 3, "Section in ini contains 3 fields") 337 | assert.Equal(t, len(config.KeysForSection("section_two")), 3, "Section in ini contains 3 fields") 338 | } 339 | 340 | func TestSectionNames(t *testing.T) { 341 | 342 | simpleIni := `first=alpha 343 | int=32 344 | float=3.14 345 | 346 | [section_one] 347 | first=raz 348 | int=124 349 | float=1222.7 350 | 351 | [section_two] 352 | first=one 353 | 354 | [section_three] 355 | int=555 356 | float=124.3` 357 | 358 | config, err := LoadConfigurationFromReader(strings.NewReader(simpleIni)) 359 | 360 | assert.Nil(t, err, "Sectioned configuration should load without error.") 361 | 362 | sectionNames := config.SectionNames() 363 | 364 | assert.Equal(t, len(sectionNames), 3, "Read section name array wrong") 365 | 366 | //Section names should be sorted, alphabetically 367 | assert.Equal(t, sectionNames[0], "section_one", "Read section name wrong") 368 | assert.Equal(t, sectionNames[1], "section_three", "Read section name wrong") 369 | assert.Equal(t, sectionNames[2], "section_two", "Read section name wrong") 370 | 371 | config.SetName("section_zero") 372 | 373 | sectionNames = config.SectionNames() 374 | 375 | assert.Equal(t, len(sectionNames), 4, "Read section name array wrong") 376 | 377 | //Section names should be sorted, alphabetically 378 | assert.Equal(t, sectionNames[0], "section_one", "Read section name wrong") 379 | assert.Equal(t, sectionNames[1], "section_three", "Read section name wrong") 380 | assert.Equal(t, sectionNames[2], "section_two", "Read section name wrong") 381 | assert.Equal(t, sectionNames[3], "section_zero", "Read section name wrong") 382 | 383 | } 384 | 385 | func TestSplitSection(t *testing.T) { 386 | 387 | simpleIni := `first=alpha 388 | int=32 389 | float=3.14 390 | 391 | [section_one] 392 | first=raz 393 | 394 | [section_two] 395 | first=one 396 | int=555 397 | float=124.3 398 | 399 | [section_one] 400 | int=124 401 | float=1222.7` 402 | 403 | config, err := LoadConfigurationFromReader(strings.NewReader(simpleIni)) 404 | 405 | assert.Nil(t, err, "Sectioned configuration should load without error.") 406 | 407 | assert.Equal(t, config.String("first", ""), "alpha", "Read value of first wrong") 408 | assert.Equal(t, config.Integer("int", 0), 32, "Read value of int wrong") 409 | assert.Equal(t, config.Float("float", 0), 3.14, "Read value of float wrong") 410 | 411 | assert.Equal(t, config.StringFromSection("section_one", "first", ""), "raz", "Read value of first from section wrong") 412 | assert.Equal(t, config.IntegerFromSection("section_one", "int", 0), 124, "Read value of int in section wrong") 413 | assert.Equal(t, config.FloatFromSection("section_one", "float", 0), 1222.7, "Read value of float in section wrong") 414 | 415 | assert.Equal(t, config.StringFromSection("section_two", "first", ""), "one", "Read value of first from section wrong") 416 | assert.Equal(t, config.IntegerFromSection("section_two", "int", 0), 555, "Read value of int in section wrong") 417 | assert.Equal(t, config.FloatFromSection("section_two", "float", 0), 124.3, "Read value of float in section wrong") 418 | 419 | assert.Equal(t, len(config.Keys()), 3, "Section ini contains 3 fields") 420 | assert.Equal(t, len(config.KeysForSection("section_one")), 3, "Section in ini contains 3 fields") 421 | assert.Equal(t, len(config.KeysForSection("section_two")), 3, "Section in ini contains 3 fields") 422 | } 423 | 424 | func TestRepeatedKey(t *testing.T) { 425 | 426 | simpleIni := `first=alpha 427 | first=beta` 428 | 429 | config, err := LoadConfigurationFromReader(strings.NewReader(simpleIni)) 430 | 431 | assert.Nil(t, err, "Configuration should load without error.") 432 | 433 | assert.Equal(t, config.String("first", ""), "beta", "Read value of first wrong") 434 | 435 | assert.Equal(t, len(config.Keys()), 1, "ini contains 1 fields") 436 | } 437 | 438 | func TestDefaults(t *testing.T) { 439 | 440 | simpleIni := `first=alpha 441 | third=\` 442 | 443 | config, err := LoadConfigurationFromReader(strings.NewReader(simpleIni)) 444 | 445 | assert.Nil(t, err, "Configuration should load without error.") 446 | 447 | assert.Equal(t, config.String("second", "beta"), "beta", "Read default value of first wrong") 448 | assert.Equal(t, config.String("third", "gamma"), "gamma", "Read default value of too short a string") 449 | assert.Equal(t, config.Integer("int", 32), 32, "Default value of int wrong") 450 | assert.Equal(t, config.Float("float", 3.14), 3.14, "Default value of float wrong") 451 | assert.Equal(t, config.Boolean("bool", true), true, "Default value of bool wrong") 452 | 453 | assert.Equal(t, config.String("", "test"), "test", "Nil key should result in empty value") 454 | assert.Equal(t, config.Integer("", 32), 32, "Default value of int wrong for empty key") 455 | assert.Equal(t, config.Float("", 3.14), 3.14, "Default value of float wrong for empty key") 456 | assert.Equal(t, config.Boolean("", true), true, "Default value of bool wrong for empty key") 457 | 458 | assert.Equal(t, len(config.Keys()), 2, "ini contains 2 fields") 459 | } 460 | 461 | func TestDefaultsOnParseError(t *testing.T) { 462 | 463 | simpleIni := `first=alpha 464 | int=yex 465 | float=blap 466 | bool=zipzap 467 | intarray[]=blip 468 | floatarray[]=blap` 469 | 470 | config, err := LoadConfigurationFromReader(strings.NewReader(simpleIni)) 471 | 472 | assert.Nil(t, err, "Configuration should load without error.") 473 | 474 | assert.Equal(t, config.String("second", "beta"), "beta", "Read default value of first wrong") 475 | assert.Equal(t, config.Integer("int", 32), 32, "Default value of int wrong") 476 | assert.Equal(t, config.Float("float", 3.14), 3.14, "Default value of float wrong") 477 | assert.Equal(t, config.Boolean("bool", true), true, "Default value of bool wrong") 478 | 479 | assert.Nil(t, config.Integers("intarray"), "Default value of ints wrong on parse error") 480 | assert.Nil(t, config.Floats("floatarray"), "Default value of floats wrong on parse error") 481 | 482 | assert.Equal(t, len(config.Keys()), 6, "ini contains 4 fields") 483 | } 484 | 485 | func TestMissingArray(t *testing.T) { 486 | 487 | simpleIni := `first=alpha` 488 | 489 | config, err := LoadConfigurationFromReader(strings.NewReader(simpleIni)) 490 | 491 | assert.Nil(t, err, "Configuration should load without error.") 492 | 493 | assert.Nil(t, config.Strings("second"), "Read default value of strings wrong") 494 | assert.Nil(t, config.Integers("int"), "Default value of ints wrong") 495 | assert.Nil(t, config.Floats("float"), "Default value of floats wrong") 496 | 497 | assert.Nil(t, config.Strings(""), "Read default value of strings wrong for empty key") 498 | assert.Nil(t, config.Integers(""), "Default value of ints wrong for empty key") 499 | assert.Nil(t, config.Floats(""), "Default value of floats wrong for empty key") 500 | 501 | assert.Equal(t, len(config.Keys()), 1, "ini contains 1 fields") 502 | } 503 | 504 | func TestDefaultsWithSection(t *testing.T) { 505 | 506 | simpleIni := `[section] 507 | first=alpha` 508 | 509 | config, err := LoadConfigurationFromReader(strings.NewReader(simpleIni)) 510 | 511 | assert.Nil(t, err, "Configuration should load without error.") 512 | 513 | assert.Equal(t, config.StringFromSection("section", "second", "beta"), "beta", "Read default value of first wrong") 514 | assert.Equal(t, config.IntegerFromSection("section", "int", 32), 32, "Default value of int wrong") 515 | assert.Equal(t, config.FloatFromSection("section", "float", 3.14), 3.14, "Default value of float wrong") 516 | assert.Equal(t, config.BooleanFromSection("section", "bool", true), true, "Default value of bool wrong") 517 | 518 | assert.Equal(t, config.StringFromSection("section-1", "second", "beta"), "beta", "Missing section for first wrong") 519 | assert.Equal(t, config.IntegerFromSection("section-1", "int", 32), 32, "Missing section for int wrong") 520 | assert.Equal(t, config.FloatFromSection("section-1", "float", 3.14), 3.14, "Missing section for float wrong") 521 | assert.Equal(t, config.BooleanFromSection("section-1", "bool", true), true, "Missing section for bool wrong") 522 | 523 | assert.Equal(t, len(config.Keys()), 0, "ini contains 0 fields") 524 | assert.Equal(t, len(config.KeysForSection("section")), 1, "section contains 1 field") 525 | assert.Nil(t, config.KeysForSection("section-1"), "missing section should have no keys") 526 | } 527 | 528 | type testStruct struct { 529 | First string 530 | Second string 531 | L int64 532 | F64 float64 533 | Flag bool 534 | Strings []string 535 | LS []int64 536 | F64s []float64 537 | Missing string 538 | MissingInt int64 539 | MissingArray []string 540 | Flags []bool 541 | U uint64 542 | private string 543 | } 544 | 545 | func TestLoadStructFile(t *testing.T) { 546 | 547 | simpleIni := `[section] 548 | first=alpha 549 | second=beta 550 | third="gamma bamma" 551 | fourth = 'delta' 552 | l=-32 553 | f64=3.14 554 | flag=true 555 | unflag=false 556 | strings[]=one 557 | strings[]=two 558 | LS[]=1 559 | LS[]=2 560 | F64s[]=11.0 561 | F64s[]=22.0 562 | #comment 563 | ; comment` 564 | 565 | config, err := LoadConfigurationFromReader(strings.NewReader(simpleIni)) 566 | 567 | assert.Nil(t, err, "Simple configuration should load without error.") 568 | 569 | var data testStruct 570 | 571 | data.MissingInt = 33 572 | data.Missing = "hello world" 573 | 574 | ok := config.DataFromSection("section", &data) 575 | 576 | assert.Equal(t, ok, true, "load should succeed") 577 | 578 | assert.Equal(t, data.First, "alpha", "Read value of first wrong") 579 | assert.Equal(t, data.Second, "beta", "Read value of second wrong") 580 | assert.Equal(t, data.L, -32, "Read value of int wrong") 581 | assert.Equal(t, data.F64, 3.14, "Read value of float wrong") 582 | assert.Equal(t, data.Flag, true, "Read true wrong") 583 | assert.Equal(t, data.Missing, "hello world", "Read value of missing wrong") 584 | assert.Equal(t, data.MissingInt, 33, "Read false wrong") 585 | assert.Nil(t, data.MissingArray, "Missing array Should be nil") 586 | assert.Equal(t, data.private, "", "private value in struct should be ignored") 587 | 588 | strings := data.Strings 589 | assert.NotNil(t, strings, "strings should not be nil") 590 | assert.Equal(t, len(strings), 2, "Read wrong length of string array") 591 | assert.Equal(t, strings[0], "one", "Read string array wrong") 592 | assert.Equal(t, strings[1], "two", "Read string array wrong") 593 | 594 | assert.NotNil(t, data.LS, "ints should not be nil") 595 | assert.Equal(t, len(data.LS), 2, "Read wrong length of ints array") 596 | assert.Equal(t, data.LS[0], 1, "Read ints array wrong") 597 | assert.Equal(t, data.LS[1], 2, "Read ints array wrong") 598 | 599 | assert.NotNil(t, data.F64s, "floats should not be nil") 600 | assert.Equal(t, len(data.F64s), 2, "Read wrong length of floats array") 601 | assert.Equal(t, data.F64s[0], 11.0, "Read floats array wrong") 602 | assert.Equal(t, data.F64s[1], 22.0, "Read floats array wrong") 603 | } 604 | 605 | func TestLoadStructMissingSection(t *testing.T) { 606 | 607 | simpleIni := `` 608 | 609 | config, err := LoadConfigurationFromReader(strings.NewReader(simpleIni)) 610 | 611 | assert.Nil(t, err, "Simple configuration should load without error.") 612 | 613 | var data testStruct 614 | 615 | ok := config.DataFromSection("section", &data) 616 | 617 | assert.Equal(t, ok, false, "section is missing so ok should be false") 618 | } 619 | 620 | func TestMissingSection(t *testing.T) { 621 | simpleIni := `[section] 622 | first=alpha` 623 | 624 | config, err := LoadConfigurationFromReader(strings.NewReader(simpleIni)) 625 | 626 | assert.Nil(t, err, "Simple configuration should load without error.") 627 | 628 | var data testStruct 629 | 630 | val := config.DataFromSection("missing_section", &data) 631 | assert.False(t, val) 632 | } 633 | 634 | func TestMissingArrayInSection(t *testing.T) { 635 | 636 | simpleIni := `[section] 637 | first=alpha` 638 | 639 | config, err := LoadConfigurationFromReader(strings.NewReader(simpleIni)) 640 | 641 | assert.Nil(t, err, "Configuration should load without error.") 642 | 643 | assert.Nil(t, config.StringsFromSection("section", "second"), "Read default value of strings wrong") 644 | assert.Nil(t, config.IntegersFromSection("section", "int"), "Default value of ints wrong") 645 | assert.Nil(t, config.FloatsFromSection("section", "float"), "Default value of floats wrong") 646 | 647 | assert.Nil(t, config.StringsFromSection("section-1", "second"), "Missing section for strings wrong") 648 | assert.Nil(t, config.IntegersFromSection("section-1", "int"), "Missing section for ints wrong") 649 | assert.Nil(t, config.FloatsFromSection("section-1", "float"), "Missing section for floats wrong") 650 | 651 | assert.Equal(t, len(config.Keys()), 0, "ini contains 0 fields") 652 | assert.Equal(t, len(config.KeysForSection("section")), 1, "section contains 1 field") 653 | assert.Nil(t, config.KeysForSection("section-1"), "missing section should have no keys") 654 | } 655 | 656 | func TestBadSection(t *testing.T) { 657 | 658 | simpleIni := `key=nope 659 | noarray=nope 660 | [section 661 | key[]=one 662 | key[]=two 663 | noarray=three` 664 | 665 | _, err := LoadConfigurationFromReader(strings.NewReader(simpleIni)) 666 | 667 | assert.NotNil(t, err, "Configuration should load with error.") 668 | } 669 | 670 | func TestBadKeyValue(t *testing.T) { 671 | 672 | simpleIni := `key=nope 673 | noarray:nope` 674 | 675 | _, err := LoadConfigurationFromReader(strings.NewReader(simpleIni)) 676 | 677 | assert.NotNil(t, err, "Configuration should load with error.") 678 | } 679 | 680 | func TestBadArrayAsSingle(t *testing.T) { 681 | 682 | simpleIni := `key=nope 683 | noarray=nope 684 | [section] 685 | key[]=one 686 | key[]=two 687 | noarray=three` 688 | 689 | config, err := LoadConfigurationFromReader(strings.NewReader(simpleIni)) 690 | 691 | assert.Nil(t, err, "Configuration should load without error.") 692 | 693 | assert.Equal(t, config.StringFromSection("section", "key", "default"), "default", "Read default value of strings wrong") 694 | } 695 | 696 | func TestBadFile(t *testing.T) { 697 | const filepath = "/no.such.dir/xxx.no-such-file.txt" 698 | config, err := LoadConfiguration(filepath) 699 | 700 | assert.NotNil(t, err, "No valid file is an error.") 701 | assert.True(t, os.IsNotExist(err), "No valid file errors is of expected type.") 702 | assert.Nil(t, config, "Configuration should be nil.") 703 | 704 | } 705 | 706 | func TestNullValuesInGet(t *testing.T) { 707 | 708 | assert.Nil(t, get(nil, "foo"), "Configuration should be nil.") 709 | 710 | } 711 | 712 | func TestStringEscape(t *testing.T) { 713 | 714 | simpleIni := `first=\n\t\rhello` 715 | 716 | config, err := LoadConfigurationFromReader(strings.NewReader(simpleIni)) 717 | 718 | assert.Nil(t, err, "Simple configuration should load without error.") 719 | 720 | assert.Equal(t, config.String("first", ""), "\n\t\rhello", "Read value of first wrong") 721 | 722 | assert.Equal(t, len(config.Keys()), 1, "ini contains 1 fields") 723 | } 724 | 725 | func TestBadStringArray(t *testing.T) { 726 | 727 | simpleIni := `first[]=\` 728 | 729 | config, err := LoadConfigurationFromReader(strings.NewReader(simpleIni)) 730 | 731 | assert.Nil(t, err, "Simple configuration should load without error.") 732 | assert.Nil(t, config.Strings("first"), "Read value of first wrong") 733 | } 734 | 735 | func BenchmarkLoadConfiguration(b *testing.B) { 736 | 737 | simpleIni := `first=alpha 738 | int=32 739 | float=3.14 740 | 741 | [section_one] 742 | first=raz 743 | 744 | [section_two] 745 | first=one 746 | int=555 747 | float=124.3 748 | 749 | [section_one] 750 | int=124 751 | float=1222.7` 752 | 753 | b.ReportAllocs() 754 | r := strings.NewReader(simpleIni) 755 | for i := 0; i < b.N; i++ { 756 | r.Seek(0, os.SEEK_SET) 757 | _, err := LoadConfigurationFromReader(r) 758 | if err != nil { 759 | b.Fatal(err) 760 | } 761 | } 762 | } 763 | --------------------------------------------------------------------------------