You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

703 lines
17 KiB

  1. INI [![Build Status](https://travis-ci.org/go-ini/ini.svg?branch=master)](https://travis-ci.org/go-ini/ini)
  2. ===
  3. ![](https://avatars0.githubusercontent.com/u/10216035?v=3&s=200)
  4. Package ini provides INI file read and write functionality in Go.
  5. [简体中文](README_ZH.md)
  6. ## Feature
  7. - Load multiple data sources(`[]byte` or file) with overwrites.
  8. - Read with recursion values.
  9. - Read with parent-child sections.
  10. - Read with auto-increment key names.
  11. - Read with multiple-line values.
  12. - Read with tons of helper methods.
  13. - Read and convert values to Go types.
  14. - Read and **WRITE** comments of sections and keys.
  15. - Manipulate sections, keys and comments with ease.
  16. - Keep sections and keys in order as you parse and save.
  17. ## Installation
  18. To use a tagged revision:
  19. go get gopkg.in/ini.v1
  20. To use with latest changes:
  21. go get github.com/go-ini/ini
  22. Please add `-u` flag to update in the future.
  23. ### Testing
  24. If you want to test on your machine, please apply `-t` flag:
  25. go get -t gopkg.in/ini.v1
  26. Please add `-u` flag to update in the future.
  27. ## Getting Started
  28. ### Loading from data sources
  29. A **Data Source** is either raw data in type `[]byte` or a file name with type `string` and you can load **as many data sources as you want**. Passing other types will simply return an error.
  30. ```go
  31. cfg, err := ini.Load([]byte("raw data"), "filename")
  32. ```
  33. Or start with an empty object:
  34. ```go
  35. cfg := ini.Empty()
  36. ```
  37. When you cannot decide how many data sources to load at the beginning, you will still be able to **Append()** them later.
  38. ```go
  39. err := cfg.Append("other file", []byte("other raw data"))
  40. ```
  41. 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.
  42. ```go
  43. cfg, err := ini.LooseLoad("filename", "filename_404")
  44. ```
  45. The cool thing is, whenever the file is available to load while you're calling `Reload` method, it will be counted as usual.
  46. #### Ignore cases of key name
  47. 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.
  48. ```go
  49. cfg, err := ini.InsensitiveLoad("filename")
  50. //...
  51. // sec1 and sec2 are the exactly same section object
  52. sec1, err := cfg.GetSection("Section")
  53. sec2, err := cfg.GetSection("SecTIOn")
  54. // key1 and key2 are the exactly same key object
  55. key1, err := cfg.GetKey("Key")
  56. key2, err := cfg.GetKey("KeY")
  57. ```
  58. #### MySQL-like boolean key
  59. MySQL's configuration allows a key without value as follows:
  60. ```ini
  61. [mysqld]
  62. ...
  63. skip-host-cache
  64. skip-name-resolve
  65. ```
  66. 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:
  67. ```go
  68. cfg, err := LoadSources(LoadOptions{AllowBooleanKeys: true}, "my.cnf"))
  69. ```
  70. 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.
  71. ### Working with sections
  72. To get a section, you would need to:
  73. ```go
  74. section, err := cfg.GetSection("section name")
  75. ```
  76. For a shortcut for default section, just give an empty string as name:
  77. ```go
  78. section, err := cfg.GetSection("")
  79. ```
  80. When you're pretty sure the section exists, following code could make your life easier:
  81. ```go
  82. section := cfg.Section("")
  83. ```
  84. What happens when the section somehow does not exist? Don't panic, it automatically creates and returns a new section to you.
  85. To create a new section:
  86. ```go
  87. err := cfg.NewSection("new section")
  88. ```
  89. To get a list of sections or section names:
  90. ```go
  91. sections := cfg.Sections()
  92. names := cfg.SectionStrings()
  93. ```
  94. ### Working with keys
  95. To get a key under a section:
  96. ```go
  97. key, err := cfg.Section("").GetKey("key name")
  98. ```
  99. Same rule applies to key operations:
  100. ```go
  101. key := cfg.Section("").Key("key name")
  102. ```
  103. To check if a key exists:
  104. ```go
  105. yes := cfg.Section("").HasKey("key name")
  106. ```
  107. To create a new key:
  108. ```go
  109. err := cfg.Section("").NewKey("name", "value")
  110. ```
  111. To get a list of keys or key names:
  112. ```go
  113. keys := cfg.Section("").Keys()
  114. names := cfg.Section("").KeyStrings()
  115. ```
  116. To get a clone hash of keys and corresponding values:
  117. ```go
  118. hash := cfg.Section("").KeysHash()
  119. ```
  120. ### Working with values
  121. To get a string value:
  122. ```go
  123. val := cfg.Section("").Key("key name").String()
  124. ```
  125. To validate key value on the fly:
  126. ```go
  127. val := cfg.Section("").Key("key name").Validate(func(in string) string {
  128. if len(in) == 0 {
  129. return "default"
  130. }
  131. return in
  132. })
  133. ```
  134. 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):
  135. ```go
  136. val := cfg.Section("").Key("key name").Value()
  137. ```
  138. To check if raw value exists:
  139. ```go
  140. yes := cfg.Section("").HasValue("test value")
  141. ```
  142. To get value with types:
  143. ```go
  144. // For boolean values:
  145. // true when value is: 1, t, T, TRUE, true, True, YES, yes, Yes, y, ON, on, On
  146. // false when value is: 0, f, F, FALSE, false, False, NO, no, No, n, OFF, off, Off
  147. v, err = cfg.Section("").Key("BOOL").Bool()
  148. v, err = cfg.Section("").Key("FLOAT64").Float64()
  149. v, err = cfg.Section("").Key("INT").Int()
  150. v, err = cfg.Section("").Key("INT64").Int64()
  151. v, err = cfg.Section("").Key("UINT").Uint()
  152. v, err = cfg.Section("").Key("UINT64").Uint64()
  153. v, err = cfg.Section("").Key("TIME").TimeFormat(time.RFC3339)
  154. v, err = cfg.Section("").Key("TIME").Time() // RFC3339
  155. v = cfg.Section("").Key("BOOL").MustBool()
  156. v = cfg.Section("").Key("FLOAT64").MustFloat64()
  157. v = cfg.Section("").Key("INT").MustInt()
  158. v = cfg.Section("").Key("INT64").MustInt64()
  159. v = cfg.Section("").Key("UINT").MustUint()
  160. v = cfg.Section("").Key("UINT64").MustUint64()
  161. v = cfg.Section("").Key("TIME").MustTimeFormat(time.RFC3339)
  162. v = cfg.Section("").Key("TIME").MustTime() // RFC3339
  163. // Methods start with Must also accept one argument for default value
  164. // when key not found or fail to parse value to given type.
  165. // Except method MustString, which you have to pass a default value.
  166. v = cfg.Section("").Key("String").MustString("default")
  167. v = cfg.Section("").Key("BOOL").MustBool(true)
  168. v = cfg.Section("").Key("FLOAT64").MustFloat64(1.25)
  169. v = cfg.Section("").Key("INT").MustInt(10)
  170. v = cfg.Section("").Key("INT64").MustInt64(99)
  171. v = cfg.Section("").Key("UINT").MustUint(3)
  172. v = cfg.Section("").Key("UINT64").MustUint64(6)
  173. v = cfg.Section("").Key("TIME").MustTimeFormat(time.RFC3339, time.Now())
  174. v = cfg.Section("").Key("TIME").MustTime(time.Now()) // RFC3339
  175. ```
  176. What if my value is three-line long?
  177. ```ini
  178. [advance]
  179. ADDRESS = """404 road,
  180. NotFound, State, 5000
  181. Earth"""
  182. ```
  183. Not a problem!
  184. ```go
  185. cfg.Section("advance").Key("ADDRESS").String()
  186. /* --- start ---
  187. 404 road,
  188. NotFound, State, 5000
  189. Earth
  190. ------ end --- */
  191. ```
  192. That's cool, how about continuation lines?
  193. ```ini
  194. [advance]
  195. two_lines = how about \
  196. continuation lines?
  197. lots_of_lines = 1 \
  198. 2 \
  199. 3 \
  200. 4
  201. ```
  202. Piece of cake!
  203. ```go
  204. cfg.Section("advance").Key("two_lines").String() // how about continuation lines?
  205. cfg.Section("advance").Key("lots_of_lines").String() // 1 2 3 4
  206. ```
  207. Well, I hate continuation lines, how do I disable that?
  208. ```go
  209. cfg, err := ini.LoadSources(ini.LoadOptions{
  210. IgnoreContinuation: true,
  211. }, "filename")
  212. ```
  213. Holy crap!
  214. Note that single quotes around values will be stripped:
  215. ```ini
  216. foo = "some value" // foo: some value
  217. bar = 'some value' // bar: some value
  218. ```
  219. That's all? Hmm, no.
  220. #### Helper methods of working with values
  221. To get value with given candidates:
  222. ```go
  223. v = cfg.Section("").Key("STRING").In("default", []string{"str", "arr", "types"})
  224. v = cfg.Section("").Key("FLOAT64").InFloat64(1.1, []float64{1.25, 2.5, 3.75})
  225. v = cfg.Section("").Key("INT").InInt(5, []int{10, 20, 30})
  226. v = cfg.Section("").Key("INT64").InInt64(10, []int64{10, 20, 30})
  227. v = cfg.Section("").Key("UINT").InUint(4, []int{3, 6, 9})
  228. v = cfg.Section("").Key("UINT64").InUint64(8, []int64{3, 6, 9})
  229. v = cfg.Section("").Key("TIME").InTimeFormat(time.RFC3339, time.Now(), []time.Time{time1, time2, time3})
  230. v = cfg.Section("").Key("TIME").InTime(time.Now(), []time.Time{time1, time2, time3}) // RFC3339
  231. ```
  232. 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.
  233. To validate value in a given range:
  234. ```go
  235. vals = cfg.Section("").Key("FLOAT64").RangeFloat64(0.0, 1.1, 2.2)
  236. vals = cfg.Section("").Key("INT").RangeInt(0, 10, 20)
  237. vals = cfg.Section("").Key("INT64").RangeInt64(0, 10, 20)
  238. vals = cfg.Section("").Key("UINT").RangeUint(0, 3, 9)
  239. vals = cfg.Section("").Key("UINT64").RangeUint64(0, 3, 9)
  240. vals = cfg.Section("").Key("TIME").RangeTimeFormat(time.RFC3339, time.Now(), minTime, maxTime)
  241. vals = cfg.Section("").Key("TIME").RangeTime(time.Now(), minTime, maxTime) // RFC3339
  242. ```
  243. ##### Auto-split values into a slice
  244. To use zero value of type for invalid inputs:
  245. ```go
  246. // Input: 1.1, 2.2, 3.3, 4.4 -> [1.1 2.2 3.3 4.4]
  247. // Input: how, 2.2, are, you -> [0.0 2.2 0.0 0.0]
  248. vals = cfg.Section("").Key("STRINGS").Strings(",")
  249. vals = cfg.Section("").Key("FLOAT64S").Float64s(",")
  250. vals = cfg.Section("").Key("INTS").Ints(",")
  251. vals = cfg.Section("").Key("INT64S").Int64s(",")
  252. vals = cfg.Section("").Key("UINTS").Uints(",")
  253. vals = cfg.Section("").Key("UINT64S").Uint64s(",")
  254. vals = cfg.Section("").Key("TIMES").Times(",")
  255. ```
  256. To exclude invalid values out of result slice:
  257. ```go
  258. // Input: 1.1, 2.2, 3.3, 4.4 -> [1.1 2.2 3.3 4.4]
  259. // Input: how, 2.2, are, you -> [2.2]
  260. vals = cfg.Section("").Key("FLOAT64S").ValidFloat64s(",")
  261. vals = cfg.Section("").Key("INTS").ValidInts(",")
  262. vals = cfg.Section("").Key("INT64S").ValidInt64s(",")
  263. vals = cfg.Section("").Key("UINTS").ValidUints(",")
  264. vals = cfg.Section("").Key("UINT64S").ValidUint64s(",")
  265. vals = cfg.Section("").Key("TIMES").ValidTimes(",")
  266. ```
  267. Or to return nothing but error when have invalid inputs:
  268. ```go
  269. // Input: 1.1, 2.2, 3.3, 4.4 -> [1.1 2.2 3.3 4.4]
  270. // Input: how, 2.2, are, you -> error
  271. vals = cfg.Section("").Key("FLOAT64S").StrictFloat64s(",")
  272. vals = cfg.Section("").Key("INTS").StrictInts(",")
  273. vals = cfg.Section("").Key("INT64S").StrictInt64s(",")
  274. vals = cfg.Section("").Key("UINTS").StrictUints(",")
  275. vals = cfg.Section("").Key("UINT64S").StrictUint64s(",")
  276. vals = cfg.Section("").Key("TIMES").StrictTimes(",")
  277. ```
  278. ### Save your configuration
  279. Finally, it's time to save your configuration to somewhere.
  280. A typical way to save configuration is writing it to a file:
  281. ```go
  282. // ...
  283. err = cfg.SaveTo("my.ini")
  284. err = cfg.SaveToIndent("my.ini", "\t")
  285. ```
  286. Another way to save is writing to a `io.Writer` interface:
  287. ```go
  288. // ...
  289. cfg.WriteTo(writer)
  290. cfg.WriteToIndent(writer, "\t")
  291. ```
  292. ## Advanced Usage
  293. ### Recursive Values
  294. 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.
  295. ```ini
  296. NAME = ini
  297. [author]
  298. NAME = Unknwon
  299. GITHUB = https://github.com/%(NAME)s
  300. [package]
  301. FULL_NAME = github.com/go-ini/%(NAME)s
  302. ```
  303. ```go
  304. cfg.Section("author").Key("GITHUB").String() // https://github.com/Unknwon
  305. cfg.Section("package").Key("FULL_NAME").String() // github.com/go-ini/ini
  306. ```
  307. ### Parent-child Sections
  308. 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.
  309. ```ini
  310. NAME = ini
  311. VERSION = v1
  312. IMPORT_PATH = gopkg.in/%(NAME)s.%(VERSION)s
  313. [package]
  314. CLONE_URL = https://%(IMPORT_PATH)s
  315. [package.sub]
  316. ```
  317. ```go
  318. cfg.Section("package.sub").Key("CLONE_URL").String() // https://gopkg.in/ini.v1
  319. ```
  320. #### Retrieve parent keys available to a child section
  321. ```go
  322. cfg.Section("package.sub").ParentKeys() // ["CLONE_URL"]
  323. ```
  324. ### Auto-increment Key Names
  325. 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.
  326. ```ini
  327. [features]
  328. -: Support read/write comments of keys and sections
  329. -: Support auto-increment of key names
  330. -: Support load multiple files to overwrite key values
  331. ```
  332. ```go
  333. cfg.Section("features").KeyStrings() // []{"#1", "#2", "#3"}
  334. ```
  335. ### Map To Struct
  336. Want more objective way to play with INI? Cool.
  337. ```ini
  338. Name = Unknwon
  339. age = 21
  340. Male = true
  341. Born = 1993-01-01T20:17:05Z
  342. [Note]
  343. Content = Hi is a good man!
  344. Cities = HangZhou, Boston
  345. ```
  346. ```go
  347. type Note struct {
  348. Content string
  349. Cities []string
  350. }
  351. type Person struct {
  352. Name string
  353. Age int `ini:"age"`
  354. Male bool
  355. Born time.Time
  356. Note
  357. Created time.Time `ini:"-"`
  358. }
  359. func main() {
  360. cfg, err := ini.Load("path/to/ini")
  361. // ...
  362. p := new(Person)
  363. err = cfg.MapTo(p)
  364. // ...
  365. // Things can be simpler.
  366. err = ini.MapTo(p, "path/to/ini")
  367. // ...
  368. // Just map a section? Fine.
  369. n := new(Note)
  370. err = cfg.Section("Note").MapTo(n)
  371. // ...
  372. }
  373. ```
  374. Can I have default value for field? Absolutely.
  375. 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.
  376. ```go
  377. // ...
  378. p := &Person{
  379. Name: "Joe",
  380. }
  381. // ...
  382. ```
  383. It's really cool, but what's the point if you can't give me my file back from struct?
  384. ### Reflect From Struct
  385. Why not?
  386. ```go
  387. type Embeded struct {
  388. Dates []time.Time `delim:"|"`
  389. Places []string `ini:"places,omitempty"`
  390. None []int `ini:",omitempty"`
  391. }
  392. type Author struct {
  393. Name string `ini:"NAME"`
  394. Male bool
  395. Age int
  396. GPA float64
  397. NeverMind string `ini:"-"`
  398. *Embeded
  399. }
  400. func main() {
  401. a := &Author{"Unknwon", true, 21, 2.8, "",
  402. &Embeded{
  403. []time.Time{time.Now(), time.Now()},
  404. []string{"HangZhou", "Boston"},
  405. []int{},
  406. }}
  407. cfg := ini.Empty()
  408. err = ini.ReflectFrom(cfg, a)
  409. // ...
  410. }
  411. ```
  412. So, what do I get?
  413. ```ini
  414. NAME = Unknwon
  415. Male = true
  416. Age = 21
  417. GPA = 2.8
  418. [Embeded]
  419. Dates = 2015-08-07T22:14:22+08:00|2015-08-07T22:14:22+08:00
  420. places = HangZhou,Boston
  421. ```
  422. #### Name Mapper
  423. 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.
  424. There are 2 built-in name mappers:
  425. - `AllCapsUnderscore`: it converts to format `ALL_CAPS_UNDERSCORE` then match section or key.
  426. - `TitleUnderscore`: it converts to format `title_underscore` then match section or key.
  427. To use them:
  428. ```go
  429. type Info struct {
  430. PackageName string
  431. }
  432. func main() {
  433. err = ini.MapToWithMapper(&Info{}, ini.TitleUnderscore, []byte("package_name=ini"))
  434. // ...
  435. cfg, err := ini.Load([]byte("PACKAGE_NAME=ini"))
  436. // ...
  437. info := new(Info)
  438. cfg.NameMapper = ini.AllCapsUnderscore
  439. err = cfg.MapTo(info)
  440. // ...
  441. }
  442. ```
  443. Same rules of name mapper apply to `ini.ReflectFromWithMapper` function.
  444. #### Value Mapper
  445. To expand values (e.g. from environment variables), you can use the `ValueMapper` to transform values:
  446. ```go
  447. type Env struct {
  448. Foo string `ini:"foo"`
  449. }
  450. func main() {
  451. cfg, err := ini.Load([]byte("[env]\nfoo = ${MY_VAR}\n")
  452. cfg.ValueMapper = os.ExpandEnv
  453. // ...
  454. env := &Env{}
  455. err = cfg.Section("env").MapTo(env)
  456. }
  457. ```
  458. This would set the value of `env.Foo` to the value of the environment variable `MY_VAR`.
  459. #### Other Notes On Map/Reflect
  460. Any embedded struct is treated as a section by default, and there is no automatic parent-child relations in map/reflect feature:
  461. ```go
  462. type Child struct {
  463. Age string
  464. }
  465. type Parent struct {
  466. Name string
  467. Child
  468. }
  469. type Config struct {
  470. City string
  471. Parent
  472. }
  473. ```
  474. Example configuration:
  475. ```ini
  476. City = Boston
  477. [Parent]
  478. Name = Unknwon
  479. [Child]
  480. Age = 21
  481. ```
  482. What if, yes, I'm paranoid, I want embedded struct to be in the same section. Well, all roads lead to Rome.
  483. ```go
  484. type Child struct {
  485. Age string
  486. }
  487. type Parent struct {
  488. Name string
  489. Child `ini:"Parent"`
  490. }
  491. type Config struct {
  492. City string
  493. Parent
  494. }
  495. ```
  496. Example configuration:
  497. ```ini
  498. City = Boston
  499. [Parent]
  500. Name = Unknwon
  501. Age = 21
  502. ```
  503. ## Getting Help
  504. - [API Documentation](https://gowalker.org/gopkg.in/ini.v1)
  505. - [File An Issue](https://github.com/go-ini/ini/issues/new)
  506. ## FAQs
  507. ### What does `BlockMode` field do?
  508. 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.
  509. ### Why another INI library?
  510. 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.
  511. 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)
  512. ## License
  513. This project is under Apache v2 License. See the [LICENSE](LICENSE) file for the full license text.