1 INI [![Build Status](https://travis-ci.org/go-ini/ini.svg?branch=master)](https://travis-ci.org/go-ini/ini) [![Sourcegraph](https://sourcegraph.com/github.com/go-ini/ini/-/badge.svg)](https://sourcegraph.com/github.com/go-ini/ini?badge)
4 ![](https://avatars0.githubusercontent.com/u/10216035?v=3&s=200)
6 Package ini provides INI file read and write functionality in Go.
12 - Load multiple data sources(`[]byte`, file and `io.ReadCloser`) with overwrites.
13 - Read with recursion values.
14 - Read with parent-child sections.
15 - Read with auto-increment key names.
16 - Read with multiple-line values.
17 - Read with tons of helper methods.
18 - Read and convert values to Go types.
19 - Read and **WRITE** comments of sections and keys.
20 - Manipulate sections, keys and comments with ease.
21 - Keep sections and keys in order as you parse and save.
25 To use a tagged revision:
27 go get gopkg.in/ini.v1
29 To use with latest changes:
31 go get github.com/go-ini/ini
33 Please add `-u` flag to update in the future.
37 If you want to test on your machine, please apply `-t` flag:
39 go get -t gopkg.in/ini.v1
41 Please add `-u` flag to update in the future.
45 ### Loading from data sources
47 A **Data Source** is either raw data in type `[]byte`, a file name with type `string` or `io.ReadCloser`. You can load **as many data sources as you want**. Passing other types will simply return an error.
50 cfg, err := ini.Load([]byte("raw data"), "filename", ioutil.NopCloser(bytes.NewReader([]byte("some other data"))))
53 Or start with an empty object:
59 When you cannot decide how many data sources to load at the beginning, you will still be able to **Append()** them later.
62 err := cfg.Append("other file", []byte("other raw data"))
65 If you have a list of files with possibilities that some of them may not available at the time, and you don't know exactly which ones, you can use `LooseLoad` to ignore nonexistent files without returning error.
68 cfg, err := ini.LooseLoad("filename", "filename_404")
71 The cool thing is, whenever the file is available to load while you're calling `Reload` method, it will be counted as usual.
73 #### Ignore cases of key name
75 When you do not care about cases of section and key names, you can use `InsensitiveLoad` to force all names to be lowercased while parsing.
78 cfg, err := ini.InsensitiveLoad("filename")
81 // sec1 and sec2 are the exactly same section object
82 sec1, err := cfg.GetSection("Section")
83 sec2, err := cfg.GetSection("SecTIOn")
85 // key1 and key2 are the exactly same key object
86 key1, err := cfg.GetKey("Key")
87 key2, err := cfg.GetKey("KeY")
90 #### MySQL-like boolean key
92 MySQL's configuration allows a key without value as follows:
101 By default, this is considered as missing value. But if you know you're going to deal with those cases, you can assign advanced load options:
104 cfg, err := LoadSources(LoadOptions{AllowBooleanKeys: true}, "my.cnf"))
107 The value of those keys are always `true`, and when you save to a file, it will keep in the same foramt as you read.
109 To generate such keys in your program, you could use `NewBooleanKey`:
112 key, err := sec.NewBooleanKey("skip-host-cache")
117 Take care that following format will be treated as comment:
119 1. Line begins with `#` or `;`
120 2. Words after `#` or `;`
121 3. Words after section name (i.e words after `[some section name]`)
123 If you want to save a value with `#` or `;`, please quote them with ``` ` ``` or ``` """ ```.
125 ### Working with sections
127 To get a section, you would need to:
130 section, err := cfg.GetSection("section name")
133 For a shortcut for default section, just give an empty string as name:
136 section, err := cfg.GetSection("")
139 When you're pretty sure the section exists, following code could make your life easier:
142 section := cfg.Section("section name")
145 What happens when the section somehow does not exist? Don't panic, it automatically creates and returns a new section to you.
147 To create a new section:
150 err := cfg.NewSection("new section")
153 To get a list of sections or section names:
156 sections := cfg.Sections()
157 names := cfg.SectionStrings()
160 ### Working with keys
162 To get a key under a section:
165 key, err := cfg.Section("").GetKey("key name")
168 Same rule applies to key operations:
171 key := cfg.Section("").Key("key name")
174 To check if a key exists:
177 yes := cfg.Section("").HasKey("key name")
183 err := cfg.Section("").NewKey("name", "value")
186 To get a list of keys or key names:
189 keys := cfg.Section("").Keys()
190 names := cfg.Section("").KeyStrings()
193 To get a clone hash of keys and corresponding values:
196 hash := cfg.Section("").KeysHash()
199 ### Working with values
201 To get a string value:
204 val := cfg.Section("").Key("key name").String()
207 To validate key value on the fly:
210 val := cfg.Section("").Key("key name").Validate(func(in string) string {
218 If you do not want any auto-transformation (such as recursive read) for the values, you can get raw value directly (this way you get much better performance):
221 val := cfg.Section("").Key("key name").Value()
224 To check if raw value exists:
227 yes := cfg.Section("").HasValue("test value")
230 To get value with types:
233 // For boolean values:
234 // true when value is: 1, t, T, TRUE, true, True, YES, yes, Yes, y, ON, on, On
235 // false when value is: 0, f, F, FALSE, false, False, NO, no, No, n, OFF, off, Off
236 v, err = cfg.Section("").Key("BOOL").Bool()
237 v, err = cfg.Section("").Key("FLOAT64").Float64()
238 v, err = cfg.Section("").Key("INT").Int()
239 v, err = cfg.Section("").Key("INT64").Int64()
240 v, err = cfg.Section("").Key("UINT").Uint()
241 v, err = cfg.Section("").Key("UINT64").Uint64()
242 v, err = cfg.Section("").Key("TIME").TimeFormat(time.RFC3339)
243 v, err = cfg.Section("").Key("TIME").Time() // RFC3339
245 v = cfg.Section("").Key("BOOL").MustBool()
246 v = cfg.Section("").Key("FLOAT64").MustFloat64()
247 v = cfg.Section("").Key("INT").MustInt()
248 v = cfg.Section("").Key("INT64").MustInt64()
249 v = cfg.Section("").Key("UINT").MustUint()
250 v = cfg.Section("").Key("UINT64").MustUint64()
251 v = cfg.Section("").Key("TIME").MustTimeFormat(time.RFC3339)
252 v = cfg.Section("").Key("TIME").MustTime() // RFC3339
254 // Methods start with Must also accept one argument for default value
255 // when key not found or fail to parse value to given type.
256 // Except method MustString, which you have to pass a default value.
258 v = cfg.Section("").Key("String").MustString("default")
259 v = cfg.Section("").Key("BOOL").MustBool(true)
260 v = cfg.Section("").Key("FLOAT64").MustFloat64(1.25)
261 v = cfg.Section("").Key("INT").MustInt(10)
262 v = cfg.Section("").Key("INT64").MustInt64(99)
263 v = cfg.Section("").Key("UINT").MustUint(3)
264 v = cfg.Section("").Key("UINT64").MustUint64(6)
265 v = cfg.Section("").Key("TIME").MustTimeFormat(time.RFC3339, time.Now())
266 v = cfg.Section("").Key("TIME").MustTime(time.Now()) // RFC3339
269 What if my value is three-line long?
273 ADDRESS = """404 road,
274 NotFound, State, 5000
281 cfg.Section("advance").Key("ADDRESS").String()
285 NotFound, State, 5000
290 That's cool, how about continuation lines?
294 two_lines = how about \
305 cfg.Section("advance").Key("two_lines").String() // how about continuation lines?
306 cfg.Section("advance").Key("lots_of_lines").String() // 1 2 3 4
309 Well, I hate continuation lines, how do I disable that?
312 cfg, err := ini.LoadSources(ini.LoadOptions{
313 IgnoreContinuation: true,
319 Note that single quotes around values will be stripped:
322 foo = "some value" // foo: some value
323 bar = 'some value' // bar: some value
328 #### Helper methods of working with values
330 To get value with given candidates:
333 v = cfg.Section("").Key("STRING").In("default", []string{"str", "arr", "types"})
334 v = cfg.Section("").Key("FLOAT64").InFloat64(1.1, []float64{1.25, 2.5, 3.75})
335 v = cfg.Section("").Key("INT").InInt(5, []int{10, 20, 30})
336 v = cfg.Section("").Key("INT64").InInt64(10, []int64{10, 20, 30})
337 v = cfg.Section("").Key("UINT").InUint(4, []int{3, 6, 9})
338 v = cfg.Section("").Key("UINT64").InUint64(8, []int64{3, 6, 9})
339 v = cfg.Section("").Key("TIME").InTimeFormat(time.RFC3339, time.Now(), []time.Time{time1, time2, time3})
340 v = cfg.Section("").Key("TIME").InTime(time.Now(), []time.Time{time1, time2, time3}) // RFC3339
343 Default value will be presented if value of key is not in candidates you given, and default value does not need be one of candidates.
345 To validate value in a given range:
348 vals = cfg.Section("").Key("FLOAT64").RangeFloat64(0.0, 1.1, 2.2)
349 vals = cfg.Section("").Key("INT").RangeInt(0, 10, 20)
350 vals = cfg.Section("").Key("INT64").RangeInt64(0, 10, 20)
351 vals = cfg.Section("").Key("UINT").RangeUint(0, 3, 9)
352 vals = cfg.Section("").Key("UINT64").RangeUint64(0, 3, 9)
353 vals = cfg.Section("").Key("TIME").RangeTimeFormat(time.RFC3339, time.Now(), minTime, maxTime)
354 vals = cfg.Section("").Key("TIME").RangeTime(time.Now(), minTime, maxTime) // RFC3339
357 ##### Auto-split values into a slice
359 To use zero value of type for invalid inputs:
362 // Input: 1.1, 2.2, 3.3, 4.4 -> [1.1 2.2 3.3 4.4]
363 // Input: how, 2.2, are, you -> [0.0 2.2 0.0 0.0]
364 vals = cfg.Section("").Key("STRINGS").Strings(",")
365 vals = cfg.Section("").Key("FLOAT64S").Float64s(",")
366 vals = cfg.Section("").Key("INTS").Ints(",")
367 vals = cfg.Section("").Key("INT64S").Int64s(",")
368 vals = cfg.Section("").Key("UINTS").Uints(",")
369 vals = cfg.Section("").Key("UINT64S").Uint64s(",")
370 vals = cfg.Section("").Key("TIMES").Times(",")
373 To exclude invalid values out of result slice:
376 // Input: 1.1, 2.2, 3.3, 4.4 -> [1.1 2.2 3.3 4.4]
377 // Input: how, 2.2, are, you -> [2.2]
378 vals = cfg.Section("").Key("FLOAT64S").ValidFloat64s(",")
379 vals = cfg.Section("").Key("INTS").ValidInts(",")
380 vals = cfg.Section("").Key("INT64S").ValidInt64s(",")
381 vals = cfg.Section("").Key("UINTS").ValidUints(",")
382 vals = cfg.Section("").Key("UINT64S").ValidUint64s(",")
383 vals = cfg.Section("").Key("TIMES").ValidTimes(",")
386 Or to return nothing but error when have invalid inputs:
389 // Input: 1.1, 2.2, 3.3, 4.4 -> [1.1 2.2 3.3 4.4]
390 // Input: how, 2.2, are, you -> error
391 vals = cfg.Section("").Key("FLOAT64S").StrictFloat64s(",")
392 vals = cfg.Section("").Key("INTS").StrictInts(",")
393 vals = cfg.Section("").Key("INT64S").StrictInt64s(",")
394 vals = cfg.Section("").Key("UINTS").StrictUints(",")
395 vals = cfg.Section("").Key("UINT64S").StrictUint64s(",")
396 vals = cfg.Section("").Key("TIMES").StrictTimes(",")
399 ### Save your configuration
401 Finally, it's time to save your configuration to somewhere.
403 A typical way to save configuration is writing it to a file:
407 err = cfg.SaveTo("my.ini")
408 err = cfg.SaveToIndent("my.ini", "\t")
411 Another way to save is writing to a `io.Writer` interface:
416 cfg.WriteToIndent(writer, "\t")
419 By default, spaces are used to align "=" sign between key and values, to disable that:
422 ini.PrettyFormat = false
429 For all value of keys, there is a special syntax `%(<name>)s`, where `<name>` is the key name in same section or default section, and `%(<name>)s` will be replaced by corresponding value(empty string if key not found). You can use this syntax at most 99 level of recursions.
436 GITHUB = https://github.com/%(NAME)s
439 FULL_NAME = github.com/go-ini/%(NAME)s
443 cfg.Section("author").Key("GITHUB").String() // https://github.com/Unknwon
444 cfg.Section("package").Key("FULL_NAME").String() // github.com/go-ini/ini
447 ### Parent-child Sections
449 You can use `.` in section name to indicate parent-child relationship between two or more sections. If the key not found in the child section, library will try again on its parent section until there is no parent section.
454 IMPORT_PATH = gopkg.in/%(NAME)s.%(VERSION)s
457 CLONE_URL = https://%(IMPORT_PATH)s
463 cfg.Section("package.sub").Key("CLONE_URL").String() // https://gopkg.in/ini.v1
466 #### Retrieve parent keys available to a child section
469 cfg.Section("package.sub").ParentKeys() // ["CLONE_URL"]
472 ### Unparseable Sections
474 Sometimes, you have sections that do not contain key-value pairs but raw content, to handle such case, you can use `LoadOptions.UnparsableSections`:
477 cfg, err := LoadSources(LoadOptions{UnparseableSections: []string{"COMMENTS"}}, `[COMMENTS]
478 <1><L.Slide#2> This slide has the fuel listed in the wrong units <e.1>`))
480 body := cfg.Section("COMMENTS").Body()
483 <1><L.Slide#2> This slide has the fuel listed in the wrong units <e.1>
487 ### Auto-increment Key Names
489 If key name is `-` in data source, then it would be seen as special syntax for auto-increment key name start from 1, and every section is independent on counter.
493 -: Support read/write comments of keys and sections
494 -: Support auto-increment of key names
495 -: Support load multiple files to overwrite key values
499 cfg.Section("features").KeyStrings() // []{"#1", "#2", "#3"}
504 Want more objective way to play with INI? Cool.
510 Born = 1993-01-01T20:17:05Z
513 Content = Hi is a good man!
514 Cities = HangZhou, Boston
529 Created time.Time `ini:"-"`
533 cfg, err := ini.Load("path/to/ini")
539 // Things can be simpler.
540 err = ini.MapTo(p, "path/to/ini")
543 // Just map a section? Fine.
545 err = cfg.Section("Note").MapTo(n)
550 Can I have default value for field? Absolutely.
552 Assign it before you map to struct. It will keep the value as it is if the key is not presented or got wrong type.
562 It's really cool, but what's the point if you can't give me my file back from struct?
564 ### Reflect From Struct
569 type Embeded struct {
570 Dates []time.Time `delim:"|"`
571 Places []string `ini:"places,omitempty"`
572 None []int `ini:",omitempty"`
576 Name string `ini:"NAME"`
580 NeverMind string `ini:"-"`
585 a := &Author{"Unknwon", true, 21, 2.8, "",
587 []time.Time{time.Now(), time.Now()},
588 []string{"HangZhou", "Boston"},
592 err = ini.ReflectFrom(cfg, a)
606 Dates = 2015-08-07T22:14:22+08:00|2015-08-07T22:14:22+08:00
607 places = HangZhou,Boston
612 To save your time and make your code cleaner, this library supports [`NameMapper`](https://gowalker.org/gopkg.in/ini.v1#NameMapper) between struct field and actual section and key name.
614 There are 2 built-in name mappers:
616 - `AllCapsUnderscore`: it converts to format `ALL_CAPS_UNDERSCORE` then match section or key.
617 - `TitleUnderscore`: it converts to format `title_underscore` then match section or key.
627 err = ini.MapToWithMapper(&Info{}, ini.TitleUnderscore, []byte("package_name=ini"))
630 cfg, err := ini.Load([]byte("PACKAGE_NAME=ini"))
633 cfg.NameMapper = ini.AllCapsUnderscore
634 err = cfg.MapTo(info)
639 Same rules of name mapper apply to `ini.ReflectFromWithMapper` function.
643 To expand values (e.g. from environment variables), you can use the `ValueMapper` to transform values:
647 Foo string `ini:"foo"`
651 cfg, err := ini.Load([]byte("[env]\nfoo = ${MY_VAR}\n")
652 cfg.ValueMapper = os.ExpandEnv
655 err = cfg.Section("env").MapTo(env)
659 This would set the value of `env.Foo` to the value of the environment variable `MY_VAR`.
661 #### Other Notes On Map/Reflect
663 Any embedded struct is treated as a section by default, and there is no automatic parent-child relations in map/reflect feature:
681 Example configuration:
693 What if, yes, I'm paranoid, I want embedded struct to be in the same section. Well, all roads lead to Rome.
711 Example configuration:
723 - [API Documentation](https://gowalker.org/gopkg.in/ini.v1)
724 - [File An Issue](https://github.com/go-ini/ini/issues/new)
728 ### What does `BlockMode` field do?
730 By default, library lets you read and write values so we need a locker to make sure your data is safe. But in cases that you are very sure about only reading data through the library, you can set `cfg.BlockMode = false` to speed up read operations about **50-70%** faster.
732 ### Why another INI library?
734 Many people are using my another INI library [goconfig](https://github.com/Unknwon/goconfig), so the reason for this one is I would like to make more Go style code. Also when you set `cfg.BlockMode = false`, this one is about **10-30%** faster.
736 To make those changes I have to confirm API broken, so it's safer to keep it in another place and start using `gopkg.in` to version my package at this time.(PS: shorter import path)
740 This project is under Apache v2 License. See the [LICENSE](LICENSE) file for the full license text.