Codebase list golang-github-go-ini-ini / c7b65ef
New upstream version 1.27.0 Félix Sipma 6 years ago
21 changed file(s) with 2851 addition(s) and 1154 deletion(s). Raw diff Collapse all Expand all
11 ini.sublime-project
22 ini.sublime-workspace
33 testdata/conf_reflect.ini
4 .idea
0 sudo: false
1 language: go
2 go:
3 - 1.4.x
4 - 1.5.x
5 - 1.6.x
6 - 1.7.x
7 - master
8
9 script:
10 - go get golang.org/x/tools/cmd/cover
11 - go get github.com/smartystreets/goconvey
12 - go test -v -cover -race
13
14 notifications:
15 email:
16 - u@gogs.io
0 .PHONY: build test bench vet
1
2 build: vet bench
3
4 test:
5 go test -v -cover -race
6
7 bench:
8 go test -v -cover -race -test.bench=. -test.benchmem
9
10 vet:
11 go vet
0 ini [![Build Status](https://drone.io/github.com/go-ini/ini/status.png)](https://drone.io/github.com/go-ini/ini/latest) [![](http://gocover.io/_badge/github.com/go-ini/ini)](http://gocover.io/github.com/go-ini/ini)
0 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)
11 ===
22
33 ![](https://avatars0.githubusercontent.com/u/10216035?v=3&s=200)
88
99 ## Feature
1010
11 - Load multiple data sources(`[]byte` or file) with overwrites.
11 - Load multiple data sources(`[]byte`, file and `io.ReadCloser`) with overwrites.
1212 - Read with recursion values.
1313 - Read with parent-child sections.
1414 - Read with auto-increment key names.
2929
3030 go get github.com/go-ini/ini
3131
32 Please add `-u` flag to update in the future.
33
3234 ### Testing
3335
3436 If you want to test on your machine, please apply `-t` flag:
3537
3638 go get -t gopkg.in/ini.v1
3739
40 Please add `-u` flag to update in the future.
41
3842 ## Getting Started
3943
4044 ### Loading from data sources
4145
42 A **Data Source** is either raw data in type `[]byte` or a file name with type `string` and you can load **as many as** data sources you want. Passing other types will simply return an error.
43
44 ```go
45 cfg, err := ini.Load([]byte("raw data"), "filename")
46 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.
47
48 ```go
49 cfg, err := ini.Load([]byte("raw data"), "filename", ioutil.NopCloser(bytes.NewReader([]byte("some other data"))))
4650 ```
4751
4852 Or start with an empty object:
5155 cfg := ini.Empty()
5256 ```
5357
54 When you cannot decide how many data sources to load at the beginning, you still able to **Append()** them later.
58 When you cannot decide how many data sources to load at the beginning, you will still be able to **Append()** them later.
5559
5660 ```go
5761 err := cfg.Append("other file", []byte("other raw data"))
5862 ```
5963
64 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.
65
66 ```go
67 cfg, err := ini.LooseLoad("filename", "filename_404")
68 ```
69
70 The cool thing is, whenever the file is available to load while you're calling `Reload` method, it will be counted as usual.
71
72 #### Ignore cases of key name
73
74 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.
75
76 ```go
77 cfg, err := ini.InsensitiveLoad("filename")
78 //...
79
80 // sec1 and sec2 are the exactly same section object
81 sec1, err := cfg.GetSection("Section")
82 sec2, err := cfg.GetSection("SecTIOn")
83
84 // key1 and key2 are the exactly same key object
85 key1, err := cfg.GetKey("Key")
86 key2, err := cfg.GetKey("KeY")
87 ```
88
89 #### MySQL-like boolean key
90
91 MySQL's configuration allows a key without value as follows:
92
93 ```ini
94 [mysqld]
95 ...
96 skip-host-cache
97 skip-name-resolve
98 ```
99
100 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:
101
102 ```go
103 cfg, err := LoadSources(LoadOptions{AllowBooleanKeys: true}, "my.cnf"))
104 ```
105
106 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.
107
108 To generate such keys in your program, you could use `NewBooleanKey`:
109
110 ```go
111 key, err := sec.NewBooleanKey("skip-host-cache")
112 ```
113
114 #### Comment
115
116 Take care that following format will be treated as comment:
117
118 1. Line begins with `#` or `;`
119 2. Words after `#` or `;`
120 3. Words after section name (i.e words after `[some section name]`)
121
122 If you want to save a value with `#` or `;`, please quote them with ``` ` ``` or ``` """ ```.
123
60124 ### Working with sections
61125
62126 To get a section, you would need to:
74138 When you're pretty sure the section exists, following code could make your life easier:
75139
76140 ```go
77 section := cfg.Section("")
141 section := cfg.Section("section name")
78142 ```
79143
80144 What happens when the section somehow does not exist? Don't panic, it automatically creates and returns a new section to you.
128192 To get a clone hash of keys and corresponding values:
129193
130194 ```go
131 hash := cfg.GetSection("").KeysHash()
195 hash := cfg.Section("").KeysHash()
132196 ```
133197
134198 ### Working with values
241305 cfg.Section("advance").Key("lots_of_lines").String() // 1 2 3 4
242306 ```
243307
308 Well, I hate continuation lines, how do I disable that?
309
310 ```go
311 cfg, err := ini.LoadSources(ini.LoadOptions{
312 IgnoreContinuation: true,
313 }, "filename")
314 ```
315
316 Holy crap!
317
244318 Note that single quotes around values will be stripped:
245319
246320 ```ini
279353 vals = cfg.Section("").Key("TIME").RangeTime(time.Now(), minTime, maxTime) // RFC3339
280354 ```
281355
282 To auto-split value into slice:
283
284 ```go
356 ##### Auto-split values into a slice
357
358 To use zero value of type for invalid inputs:
359
360 ```go
361 // Input: 1.1, 2.2, 3.3, 4.4 -> [1.1 2.2 3.3 4.4]
362 // Input: how, 2.2, are, you -> [0.0 2.2 0.0 0.0]
285363 vals = cfg.Section("").Key("STRINGS").Strings(",")
286364 vals = cfg.Section("").Key("FLOAT64S").Float64s(",")
287365 vals = cfg.Section("").Key("INTS").Ints(",")
291369 vals = cfg.Section("").Key("TIMES").Times(",")
292370 ```
293371
372 To exclude invalid values out of result slice:
373
374 ```go
375 // Input: 1.1, 2.2, 3.3, 4.4 -> [1.1 2.2 3.3 4.4]
376 // Input: how, 2.2, are, you -> [2.2]
377 vals = cfg.Section("").Key("FLOAT64S").ValidFloat64s(",")
378 vals = cfg.Section("").Key("INTS").ValidInts(",")
379 vals = cfg.Section("").Key("INT64S").ValidInt64s(",")
380 vals = cfg.Section("").Key("UINTS").ValidUints(",")
381 vals = cfg.Section("").Key("UINT64S").ValidUint64s(",")
382 vals = cfg.Section("").Key("TIMES").ValidTimes(",")
383 ```
384
385 Or to return nothing but error when have invalid inputs:
386
387 ```go
388 // Input: 1.1, 2.2, 3.3, 4.4 -> [1.1 2.2 3.3 4.4]
389 // Input: how, 2.2, are, you -> error
390 vals = cfg.Section("").Key("FLOAT64S").StrictFloat64s(",")
391 vals = cfg.Section("").Key("INTS").StrictInts(",")
392 vals = cfg.Section("").Key("INT64S").StrictInt64s(",")
393 vals = cfg.Section("").Key("UINTS").StrictUints(",")
394 vals = cfg.Section("").Key("UINT64S").StrictUint64s(",")
395 vals = cfg.Section("").Key("TIMES").StrictTimes(",")
396 ```
397
294398 ### Save your configuration
295399
296400 Finally, it's time to save your configuration to somewhere.
311415 cfg.WriteToIndent(writer, "\t")
312416 ```
313417
418 By default, spaces are used to align "=" sign between key and values, to disable that:
419
420 ```go
421 ini.PrettyFormat = false
422 ```
423
314424 ## Advanced Usage
315425
316426 ### Recursive Values
350460
351461 ```go
352462 cfg.Section("package.sub").Key("CLONE_URL").String() // https://gopkg.in/ini.v1
463 ```
464
465 #### Retrieve parent keys available to a child section
466
467 ```go
468 cfg.Section("package.sub").ParentKeys() // ["CLONE_URL"]
469 ```
470
471 ### Unparseable Sections
472
473 Sometimes, you have sections that do not contain key-value pairs but raw content, to handle such case, you can use `LoadOptions.UnparsableSections`:
474
475 ```go
476 cfg, err := LoadSources(LoadOptions{UnparseableSections: []string{"COMMENTS"}}, `[COMMENTS]
477 <1><L.Slide#2> This slide has the fuel listed in the wrong units <e.1>`))
478
479 body := cfg.Section("COMMENTS").Body()
480
481 /* --- start ---
482 <1><L.Slide#2> This slide has the fuel listed in the wrong units <e.1>
483 ------ end --- */
353484 ```
354485
355486 ### Auto-increment Key Names
436567 ```go
437568 type Embeded struct {
438569 Dates []time.Time `delim:"|"`
439 Places []string
440 None []int
570 Places []string `ini:"places,omitempty"`
571 None []int `ini:",omitempty"`
441572 }
442573
443574 type Author struct {
472603
473604 [Embeded]
474605 Dates = 2015-08-07T22:14:22+08:00|2015-08-07T22:14:22+08:00
475 Places = HangZhou,Boston
476 None =
606 places = HangZhou,Boston
477607 ```
478608
479609 #### Name Mapper
507637
508638 Same rules of name mapper apply to `ini.ReflectFromWithMapper` function.
509639
640 #### Value Mapper
641
642 To expand values (e.g. from environment variables), you can use the `ValueMapper` to transform values:
643
644 ```go
645 type Env struct {
646 Foo string `ini:"foo"`
647 }
648
649 func main() {
650 cfg, err := ini.Load([]byte("[env]\nfoo = ${MY_VAR}\n")
651 cfg.ValueMapper = os.ExpandEnv
652 // ...
653 env := &Env{}
654 err = cfg.Section("env").MapTo(env)
655 }
656 ```
657
658 This would set the value of `env.Foo` to the value of the environment variable `MY_VAR`.
659
510660 #### Other Notes On Map/Reflect
511661
512662 Any embedded struct is treated as a section by default, and there is no automatic parent-child relations in map/reflect feature:
11
22 ## 功能特性
33
4 - 支持覆盖加载多个数据源(`[]byte` 或文件)
4 - 支持覆盖加载多个数据源(`[]byte`、文件和 `io.ReadCloser`)
55 - 支持递归读取键值
66 - 支持读取父子分区
77 - 支持读取自增键名
2222
2323 go get github.com/go-ini/ini
2424
25 如需更新请添加 `-u` 选项。
26
2527 ### 测试安装
2628
2729 如果您想要在自己的机器上运行测试,请使用 `-t` 标记:
2830
2931 go get -t gopkg.in/ini.v1
3032
33 如需更新请添加 `-u` 选项。
34
3135 ## 开始使用
3236
3337 ### 从数据源加载
3438
35 一个 **数据源** 可以是 `[]byte` 类型的原始数据,或 `string` 类型的文件路径。您可以加载 **任意多个** 数据源。如果您传递其它类型的数据源,则会直接返回错误。
36
37 ```go
38 cfg, err := ini.Load([]byte("raw data"), "filename")
39 一个 **数据源** 可以是 `[]byte` 类型的原始数据,`string` 类型的文件路径或 `io.ReadCloser`。您可以加载 **任意多个** 数据源。如果您传递其它类型的数据源,则会直接返回错误。
40
41 ```go
42 cfg, err := ini.Load([]byte("raw data"), "filename", ioutil.NopCloser(bytes.NewReader([]byte("some other data"))))
3943 ```
4044
4145 或者从一个空白的文件开始:
5054 err := cfg.Append("other file", []byte("other raw data"))
5155 ```
5256
57 当您想要加载一系列文件,但是不能够确定其中哪些文件是不存在的,可以通过调用函数 `LooseLoad` 来忽略它们(`Load` 会因为文件不存在而返回错误):
58
59 ```go
60 cfg, err := ini.LooseLoad("filename", "filename_404")
61 ```
62
63 更牛逼的是,当那些之前不存在的文件在重新调用 `Reload` 方法的时候突然出现了,那么它们会被正常加载。
64
65 #### 忽略键名的大小写
66
67 有时候分区和键的名称大小写混合非常烦人,这个时候就可以通过 `InsensitiveLoad` 将所有分区和键名在读取里强制转换为小写:
68
69 ```go
70 cfg, err := ini.InsensitiveLoad("filename")
71 //...
72
73 // sec1 和 sec2 指向同一个分区对象
74 sec1, err := cfg.GetSection("Section")
75 sec2, err := cfg.GetSection("SecTIOn")
76
77 // key1 和 key2 指向同一个键对象
78 key1, err := cfg.GetKey("Key")
79 key2, err := cfg.GetKey("KeY")
80 ```
81
82 #### 类似 MySQL 配置中的布尔值键
83
84 MySQL 的配置文件中会出现没有具体值的布尔类型的键:
85
86 ```ini
87 [mysqld]
88 ...
89 skip-host-cache
90 skip-name-resolve
91 ```
92
93 默认情况下这被认为是缺失值而无法完成解析,但可以通过高级的加载选项对它们进行处理:
94
95 ```go
96 cfg, err := LoadSources(LoadOptions{AllowBooleanKeys: true}, "my.cnf"))
97 ```
98
99 这些键的值永远为 `true`,且在保存到文件时也只会输出键名。
100
101 如果您想要通过程序来生成此类键,则可以使用 `NewBooleanKey`:
102
103 ```go
104 key, err := sec.NewBooleanKey("skip-host-cache")
105 ```
106
107 #### 关于注释
108
109 下述几种情况的内容将被视为注释:
110
111 1. 所有以 `#` 或 `;` 开头的行
112 2. 所有在 `#` 或 `;` 之后的内容
113 3. 分区标签后的文字 (即 `[分区名]` 之后的内容)
114
115 如果你希望使用包含 `#` 或 `;` 的值,请使用 ``` ` ``` 或 ``` """ ``` 进行包覆。
116
53117 ### 操作分区(Section)
54118
55119 获取指定分区:
67131 当您非常确定某个分区是存在的,可以使用以下简便方法:
68132
69133 ```go
70 section := cfg.Section("")
134 section := cfg.Section("section name")
71135 ```
72136
73137 如果不小心判断错了,要获取的分区其实是不存在的,那会发生什么呢?没事的,它会自动创建并返回一个对应的分区对象给您。
121185 获取分区下的所有键值对的克隆:
122186
123187 ```go
124 hash := cfg.GetSection("").KeysHash()
188 hash := cfg.Section("").KeysHash()
125189 ```
126190
127191 ### 操作键值(Value)
234298 cfg.Section("advance").Key("lots_of_lines").String() // 1 2 3 4
235299 ```
236300
301 可是我有时候觉得两行连在一起特别没劲,怎么才能不自动连接两行呢?
302
303 ```go
304 cfg, err := ini.LoadSources(ini.LoadOptions{
305 IgnoreContinuation: true,
306 }, "filename")
307 ```
308
309 哇靠给力啊!
310
237311 需要注意的是,值两侧的单引号会被自动剔除:
238312
239313 ```ini
272346 vals = cfg.Section("").Key("TIME").RangeTime(time.Now(), minTime, maxTime) // RFC3339
273347 ```
274348
275 自动分割键值为切片(slice):
276
277 ```go
349 ##### 自动分割键值到切片(slice)
350
351 当存在无效输入时,使用零值代替:
352
353 ```go
354 // Input: 1.1, 2.2, 3.3, 4.4 -> [1.1 2.2 3.3 4.4]
355 // Input: how, 2.2, are, you -> [0.0 2.2 0.0 0.0]
278356 vals = cfg.Section("").Key("STRINGS").Strings(",")
279357 vals = cfg.Section("").Key("FLOAT64S").Float64s(",")
280358 vals = cfg.Section("").Key("INTS").Ints(",")
284362 vals = cfg.Section("").Key("TIMES").Times(",")
285363 ```
286364
365 从结果切片中剔除无效输入:
366
367 ```go
368 // Input: 1.1, 2.2, 3.3, 4.4 -> [1.1 2.2 3.3 4.4]
369 // Input: how, 2.2, are, you -> [2.2]
370 vals = cfg.Section("").Key("FLOAT64S").ValidFloat64s(",")
371 vals = cfg.Section("").Key("INTS").ValidInts(",")
372 vals = cfg.Section("").Key("INT64S").ValidInt64s(",")
373 vals = cfg.Section("").Key("UINTS").ValidUints(",")
374 vals = cfg.Section("").Key("UINT64S").ValidUint64s(",")
375 vals = cfg.Section("").Key("TIMES").ValidTimes(",")
376 ```
377
378 当存在无效输入时,直接返回错误:
379
380 ```go
381 // Input: 1.1, 2.2, 3.3, 4.4 -> [1.1 2.2 3.3 4.4]
382 // Input: how, 2.2, are, you -> error
383 vals = cfg.Section("").Key("FLOAT64S").StrictFloat64s(",")
384 vals = cfg.Section("").Key("INTS").StrictInts(",")
385 vals = cfg.Section("").Key("INT64S").StrictInt64s(",")
386 vals = cfg.Section("").Key("UINTS").StrictUints(",")
387 vals = cfg.Section("").Key("UINT64S").StrictUint64s(",")
388 vals = cfg.Section("").Key("TIMES").StrictTimes(",")
389 ```
390
287391 ### 保存配置
288392
289393 终于到了这个时刻,是时候保存一下配置了。
304408 cfg.WriteToIndent(writer, "\t")
305409 ```
306410
307 ### 高级用法
308
309 #### 递归读取键值
411 默认情况下,空格将被用于对齐键值之间的等号以美化输出结果,以下代码可以禁用该功能:
412
413 ```go
414 ini.PrettyFormat = false
415 ```
416
417 ## 高级用法
418
419 ### 递归读取键值
310420
311421 在获取所有键值的过程中,特殊语法 `%(<name>)s` 会被应用,其中 `<name>` 可以是相同分区或者默认分区下的键名。字符串 `%(<name>)s` 会被相应的键值所替代,如果指定的键不存在,则会用空字符串替代。您可以最多使用 99 层的递归嵌套。
312422
326436 cfg.Section("package").Key("FULL_NAME").String() // github.com/go-ini/ini
327437 ```
328438
329 #### 读取父子分区
439 ### 读取父子分区
330440
331441 您可以在分区名称中使用 `.` 来表示两个或多个分区之间的父子关系。如果某个键在子分区中不存在,则会去它的父分区中再次寻找,直到没有父分区为止。
332442
345455 cfg.Section("package.sub").Key("CLONE_URL").String() // https://gopkg.in/ini.v1
346456 ```
347457
348 #### 读取自增键名
458 #### 获取上级父分区下的所有键名
459
460 ```go
461 cfg.Section("package.sub").ParentKeys() // ["CLONE_URL"]
462 ```
463
464 ### 无法解析的分区
465
466 如果遇到一些比较特殊的分区,它们不包含常见的键值对,而是没有固定格式的纯文本,则可以使用 `LoadOptions.UnparsableSections` 进行处理:
467
468 ```go
469 cfg, err := LoadSources(LoadOptions{UnparseableSections: []string{"COMMENTS"}}, `[COMMENTS]
470 <1><L.Slide#2> This slide has the fuel listed in the wrong units <e.1>`))
471
472 body := cfg.Section("COMMENTS").Body()
473
474 /* --- start ---
475 <1><L.Slide#2> This slide has the fuel listed in the wrong units <e.1>
476 ------ end --- */
477 ```
478
479 ### 读取自增键名
349480
350481 如果数据源中的键名为 `-`,则认为该键使用了自增键名的特殊语法。计数器从 1 开始,并且分区之间是相互独立的。
351482
427558 ```go
428559 type Embeded struct {
429560 Dates []time.Time `delim:"|"`
430 Places []string
431 None []int
561 Places []string `ini:"places,omitempty"`
562 None []int `ini:",omitempty"`
432563 }
433564
434565 type Author struct {
463594
464595 [Embeded]
465596 Dates = 2015-08-07T22:14:22+08:00|2015-08-07T22:14:22+08:00
466 Places = HangZhou,Boston
467 None =
597 places = HangZhou,Boston
468598 ```
469599
470600 #### 名称映射器(Name Mapper)
498628
499629 使用函数 `ini.ReflectFromWithMapper` 时也可应用相同的规则。
500630
631 #### 值映射器(Value Mapper)
632
633 值映射器允许使用一个自定义函数自动展开值的具体内容,例如:运行时获取环境变量:
634
635 ```go
636 type Env struct {
637 Foo string `ini:"foo"`
638 }
639
640 func main() {
641 cfg, err := ini.Load([]byte("[env]\nfoo = ${MY_VAR}\n")
642 cfg.ValueMapper = os.ExpandEnv
643 // ...
644 env := &Env{}
645 err = cfg.Section("env").MapTo(env)
646 }
647 ```
648
649 本例中,`env.Foo` 将会是运行时所获取到环境变量 `MY_VAR` 的值。
650
501651 #### 映射/反射的其它说明
502652
503653 任何嵌入的结构都会被默认认作一个不同的分区,并且不会自动产生所谓的父子分区关联:
0 // Copyright 2016 Unknwon
1 //
2 // Licensed under the Apache License, Version 2.0 (the "License"): you may
3 // not use this file except in compliance with the License. You may obtain
4 // a copy of the License at
5 //
6 // http://www.apache.org/licenses/LICENSE-2.0
7 //
8 // Unless required by applicable law or agreed to in writing, software
9 // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
10 // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
11 // License for the specific language governing permissions and limitations
12 // under the License.
13
14 package ini
15
16 import (
17 "fmt"
18 )
19
20 type ErrDelimiterNotFound struct {
21 Line string
22 }
23
24 func IsErrDelimiterNotFound(err error) bool {
25 _, ok := err.(ErrDelimiterNotFound)
26 return ok
27 }
28
29 func (err ErrDelimiterNotFound) Error() string {
30 return fmt.Sprintf("key-value delimiter not found: %s", err.Line)
31 }
+173
-644
ini.go less more
1919 "errors"
2020 "fmt"
2121 "io"
22 "io/ioutil"
2223 "os"
2324 "regexp"
2425 "runtime"
2930 )
3031
3132 const (
33 // Name for default section. You can use this constant or the string literal.
34 // In most of cases, an empty string is all you need to access the section.
3235 DEFAULT_SECTION = "DEFAULT"
36
3337 // Maximum allowed depth when recursively substituing variable names.
3438 _DEPTH_VALUES = 99
35
36 _VERSION = "1.8.6"
39 _VERSION = "1.27.0"
3740 )
3841
42 // Version returns current package version literal.
3943 func Version() string {
4044 return _VERSION
4145 }
4246
4347 var (
48 // Delimiter to determine or compose a new line.
49 // This variable will be changed to "\r\n" automatically on Windows
50 // at package init time.
4451 LineBreak = "\n"
4552
4653 // Variable regexp pattern: %(variable)s
4754 varPattern = regexp.MustCompile(`%\(([^\)]+)\)s`)
4855
49 // Write spaces around "=" to look better.
56 // Indicate whether to align "=" sign with spaces to produce pretty output
57 // or reduce all possible spaces for compact format.
5058 PrettyFormat = true
59
60 // Explicitly write DEFAULT section header
61 DefaultHeader = false
5162 )
5263
5364 func init() {
6576 return false
6677 }
6778
68 // dataSource is a interface that returns file content.
79 // dataSource is an interface that returns object which can be read and closed.
6980 type dataSource interface {
7081 ReadCloser() (io.ReadCloser, error)
7182 }
7283
84 // sourceFile represents an object that contains content on the local file system.
7385 type sourceFile struct {
7486 name string
7587 }
90102 return nil
91103 }
92104
105 // sourceData represents an object that contains content in memory.
93106 type sourceData struct {
94107 data []byte
95108 }
96109
97110 func (s *sourceData) ReadCloser() (io.ReadCloser, error) {
98 return &bytesReadCloser{bytes.NewReader(s.data)}, nil
99 }
100
101 // ____ __.
102 // | |/ _|____ ___.__.
103 // | <_/ __ < | |
104 // | | \ ___/\___ |
105 // |____|__ \___ > ____|
106 // \/ \/\/
107
108 // Key represents a key under a section.
109 type Key struct {
110 s *Section
111 Comment string
112 name string
113 value string
114 isAutoIncr bool
115 }
116
117 // Name returns name of key.
118 func (k *Key) Name() string {
119 return k.name
120 }
121
122 // Value returns raw value of key for performance purpose.
123 func (k *Key) Value() string {
124 return k.value
125 }
126
127 // String returns string representation of value.
128 func (k *Key) String() string {
129 val := k.value
130 if strings.Index(val, "%") == -1 {
131 return val
132 }
133
134 for i := 0; i < _DEPTH_VALUES; i++ {
135 vr := varPattern.FindString(val)
136 if len(vr) == 0 {
137 break
138 }
139
140 // Take off leading '%(' and trailing ')s'.
141 noption := strings.TrimLeft(vr, "%(")
142 noption = strings.TrimRight(noption, ")s")
143
144 // Search in the same section.
145 nk, err := k.s.GetKey(noption)
146 if err != nil {
147 // Search again in default section.
148 nk, _ = k.s.f.Section("").GetKey(noption)
149 }
150
151 // Substitute by new value and take off leading '%(' and trailing ')s'.
152 val = strings.Replace(val, vr, nk.value, -1)
153 }
154 return val
155 }
156
157 // Validate accepts a validate function which can
158 // return modifed result as key value.
159 func (k *Key) Validate(fn func(string) string) string {
160 return fn(k.String())
161 }
162
163 // parseBool returns the boolean value represented by the string.
164 //
165 // It accepts 1, t, T, TRUE, true, True, YES, yes, Yes, y, ON, on, On,
166 // 0, f, F, FALSE, false, False, NO, no, No, n, OFF, off, Off.
167 // Any other value returns an error.
168 func parseBool(str string) (value bool, err error) {
169 switch str {
170 case "1", "t", "T", "true", "TRUE", "True", "YES", "yes", "Yes", "y", "ON", "on", "On":
171 return true, nil
172 case "0", "f", "F", "false", "FALSE", "False", "NO", "no", "No", "n", "OFF", "off", "Off":
173 return false, nil
174 }
175 return false, fmt.Errorf("parsing \"%s\": invalid syntax", str)
176 }
177
178 // Bool returns bool type value.
179 func (k *Key) Bool() (bool, error) {
180 return parseBool(k.String())
181 }
182
183 // Float64 returns float64 type value.
184 func (k *Key) Float64() (float64, error) {
185 return strconv.ParseFloat(k.String(), 64)
186 }
187
188 // Int returns int type value.
189 func (k *Key) Int() (int, error) {
190 return strconv.Atoi(k.String())
191 }
192
193 // Int64 returns int64 type value.
194 func (k *Key) Int64() (int64, error) {
195 return strconv.ParseInt(k.String(), 10, 64)
196 }
197
198 // Uint returns uint type valued.
199 func (k *Key) Uint() (uint, error) {
200 u, e := strconv.ParseUint(k.String(), 10, 64)
201 return uint(u), e
202 }
203
204 // Uint64 returns uint64 type value.
205 func (k *Key) Uint64() (uint64, error) {
206 return strconv.ParseUint(k.String(), 10, 64)
207 }
208
209 // Duration returns time.Duration type value.
210 func (k *Key) Duration() (time.Duration, error) {
211 return time.ParseDuration(k.String())
212 }
213
214 // TimeFormat parses with given format and returns time.Time type value.
215 func (k *Key) TimeFormat(format string) (time.Time, error) {
216 return time.Parse(format, k.String())
217 }
218
219 // Time parses with RFC3339 format and returns time.Time type value.
220 func (k *Key) Time() (time.Time, error) {
221 return k.TimeFormat(time.RFC3339)
222 }
223
224 // MustString returns default value if key value is empty.
225 func (k *Key) MustString(defaultVal string) string {
226 val := k.String()
227 if len(val) == 0 {
228 return defaultVal
229 }
230 return val
231 }
232
233 // MustBool always returns value without error,
234 // it returns false if error occurs.
235 func (k *Key) MustBool(defaultVal ...bool) bool {
236 val, err := k.Bool()
237 if len(defaultVal) > 0 && err != nil {
238 return defaultVal[0]
239 }
240 return val
241 }
242
243 // MustFloat64 always returns value without error,
244 // it returns 0.0 if error occurs.
245 func (k *Key) MustFloat64(defaultVal ...float64) float64 {
246 val, err := k.Float64()
247 if len(defaultVal) > 0 && err != nil {
248 return defaultVal[0]
249 }
250 return val
251 }
252
253 // MustInt always returns value without error,
254 // it returns 0 if error occurs.
255 func (k *Key) MustInt(defaultVal ...int) int {
256 val, err := k.Int()
257 if len(defaultVal) > 0 && err != nil {
258 return defaultVal[0]
259 }
260 return val
261 }
262
263 // MustInt64 always returns value without error,
264 // it returns 0 if error occurs.
265 func (k *Key) MustInt64(defaultVal ...int64) int64 {
266 val, err := k.Int64()
267 if len(defaultVal) > 0 && err != nil {
268 return defaultVal[0]
269 }
270 return val
271 }
272
273 // MustUint always returns value without error,
274 // it returns 0 if error occurs.
275 func (k *Key) MustUint(defaultVal ...uint) uint {
276 val, err := k.Uint()
277 if len(defaultVal) > 0 && err != nil {
278 return defaultVal[0]
279 }
280 return val
281 }
282
283 // MustUint64 always returns value without error,
284 // it returns 0 if error occurs.
285 func (k *Key) MustUint64(defaultVal ...uint64) uint64 {
286 val, err := k.Uint64()
287 if len(defaultVal) > 0 && err != nil {
288 return defaultVal[0]
289 }
290 return val
291 }
292
293 // MustDuration always returns value without error,
294 // it returns zero value if error occurs.
295 func (k *Key) MustDuration(defaultVal ...time.Duration) time.Duration {
296 val, err := k.Duration()
297 if len(defaultVal) > 0 && err != nil {
298 return defaultVal[0]
299 }
300 return val
301 }
302
303 // MustTimeFormat always parses with given format and returns value without error,
304 // it returns zero value if error occurs.
305 func (k *Key) MustTimeFormat(format string, defaultVal ...time.Time) time.Time {
306 val, err := k.TimeFormat(format)
307 if len(defaultVal) > 0 && err != nil {
308 return defaultVal[0]
309 }
310 return val
311 }
312
313 // MustTime always parses with RFC3339 format and returns value without error,
314 // it returns zero value if error occurs.
315 func (k *Key) MustTime(defaultVal ...time.Time) time.Time {
316 return k.MustTimeFormat(time.RFC3339, defaultVal...)
317 }
318
319 // In always returns value without error,
320 // it returns default value if error occurs or doesn't fit into candidates.
321 func (k *Key) In(defaultVal string, candidates []string) string {
322 val := k.String()
323 for _, cand := range candidates {
324 if val == cand {
325 return val
326 }
327 }
328 return defaultVal
329 }
330
331 // InFloat64 always returns value without error,
332 // it returns default value if error occurs or doesn't fit into candidates.
333 func (k *Key) InFloat64(defaultVal float64, candidates []float64) float64 {
334 val := k.MustFloat64()
335 for _, cand := range candidates {
336 if val == cand {
337 return val
338 }
339 }
340 return defaultVal
341 }
342
343 // InInt always returns value without error,
344 // it returns default value if error occurs or doesn't fit into candidates.
345 func (k *Key) InInt(defaultVal int, candidates []int) int {
346 val := k.MustInt()
347 for _, cand := range candidates {
348 if val == cand {
349 return val
350 }
351 }
352 return defaultVal
353 }
354
355 // InInt64 always returns value without error,
356 // it returns default value if error occurs or doesn't fit into candidates.
357 func (k *Key) InInt64(defaultVal int64, candidates []int64) int64 {
358 val := k.MustInt64()
359 for _, cand := range candidates {
360 if val == cand {
361 return val
362 }
363 }
364 return defaultVal
365 }
366
367 // InUint always returns value without error,
368 // it returns default value if error occurs or doesn't fit into candidates.
369 func (k *Key) InUint(defaultVal uint, candidates []uint) uint {
370 val := k.MustUint()
371 for _, cand := range candidates {
372 if val == cand {
373 return val
374 }
375 }
376 return defaultVal
377 }
378
379 // InUint64 always returns value without error,
380 // it returns default value if error occurs or doesn't fit into candidates.
381 func (k *Key) InUint64(defaultVal uint64, candidates []uint64) uint64 {
382 val := k.MustUint64()
383 for _, cand := range candidates {
384 if val == cand {
385 return val
386 }
387 }
388 return defaultVal
389 }
390
391 // InTimeFormat always parses with given format and returns value without error,
392 // it returns default value if error occurs or doesn't fit into candidates.
393 func (k *Key) InTimeFormat(format string, defaultVal time.Time, candidates []time.Time) time.Time {
394 val := k.MustTimeFormat(format)
395 for _, cand := range candidates {
396 if val == cand {
397 return val
398 }
399 }
400 return defaultVal
401 }
402
403 // InTime always parses with RFC3339 format and returns value without error,
404 // it returns default value if error occurs or doesn't fit into candidates.
405 func (k *Key) InTime(defaultVal time.Time, candidates []time.Time) time.Time {
406 return k.InTimeFormat(time.RFC3339, defaultVal, candidates)
407 }
408
409 // RangeFloat64 checks if value is in given range inclusively,
410 // and returns default value if it's not.
411 func (k *Key) RangeFloat64(defaultVal, min, max float64) float64 {
412 val := k.MustFloat64()
413 if val < min || val > max {
414 return defaultVal
415 }
416 return val
417 }
418
419 // RangeInt checks if value is in given range inclusively,
420 // and returns default value if it's not.
421 func (k *Key) RangeInt(defaultVal, min, max int) int {
422 val := k.MustInt()
423 if val < min || val > max {
424 return defaultVal
425 }
426 return val
427 }
428
429 // RangeInt64 checks if value is in given range inclusively,
430 // and returns default value if it's not.
431 func (k *Key) RangeInt64(defaultVal, min, max int64) int64 {
432 val := k.MustInt64()
433 if val < min || val > max {
434 return defaultVal
435 }
436 return val
437 }
438
439 // RangeTimeFormat checks if value with given format is in given range inclusively,
440 // and returns default value if it's not.
441 func (k *Key) RangeTimeFormat(format string, defaultVal, min, max time.Time) time.Time {
442 val := k.MustTimeFormat(format)
443 if val.Unix() < min.Unix() || val.Unix() > max.Unix() {
444 return defaultVal
445 }
446 return val
447 }
448
449 // RangeTime checks if value with RFC3339 format is in given range inclusively,
450 // and returns default value if it's not.
451 func (k *Key) RangeTime(defaultVal, min, max time.Time) time.Time {
452 return k.RangeTimeFormat(time.RFC3339, defaultVal, min, max)
453 }
454
455 // Strings returns list of string divided by given delimiter.
456 func (k *Key) Strings(delim string) []string {
457 str := k.String()
458 if len(str) == 0 {
459 return []string{}
460 }
461
462 vals := strings.Split(str, delim)
463 for i := range vals {
464 vals[i] = strings.TrimSpace(vals[i])
465 }
466 return vals
467 }
468
469 // Float64s returns list of float64 divided by given delimiter.
470 func (k *Key) Float64s(delim string) []float64 {
471 strs := k.Strings(delim)
472 vals := make([]float64, len(strs))
473 for i := range strs {
474 vals[i], _ = strconv.ParseFloat(strs[i], 64)
475 }
476 return vals
477 }
478
479 // Ints returns list of int divided by given delimiter.
480 func (k *Key) Ints(delim string) []int {
481 strs := k.Strings(delim)
482 vals := make([]int, len(strs))
483 for i := range strs {
484 vals[i], _ = strconv.Atoi(strs[i])
485 }
486 return vals
487 }
488
489 // Int64s returns list of int64 divided by given delimiter.
490 func (k *Key) Int64s(delim string) []int64 {
491 strs := k.Strings(delim)
492 vals := make([]int64, len(strs))
493 for i := range strs {
494 vals[i], _ = strconv.ParseInt(strs[i], 10, 64)
495 }
496 return vals
497 }
498
499 // Uints returns list of uint divided by given delimiter.
500 func (k *Key) Uints(delim string) []uint {
501 strs := k.Strings(delim)
502 vals := make([]uint, len(strs))
503 for i := range strs {
504 u, _ := strconv.ParseUint(strs[i], 10, 0)
505 vals[i] = uint(u)
506 }
507 return vals
508 }
509
510 // Uint64s returns list of uint64 divided by given delimiter.
511 func (k *Key) Uint64s(delim string) []uint64 {
512 strs := k.Strings(delim)
513 vals := make([]uint64, len(strs))
514 for i := range strs {
515 vals[i], _ = strconv.ParseUint(strs[i], 10, 64)
516 }
517 return vals
518 }
519
520 // TimesFormat parses with given format and returns list of time.Time divided by given delimiter.
521 func (k *Key) TimesFormat(format, delim string) []time.Time {
522 strs := k.Strings(delim)
523 vals := make([]time.Time, len(strs))
524 for i := range strs {
525 vals[i], _ = time.Parse(format, strs[i])
526 }
527 return vals
528 }
529
530 // Times parses with RFC3339 format and returns list of time.Time divided by given delimiter.
531 func (k *Key) Times(delim string) []time.Time {
532 return k.TimesFormat(time.RFC3339, delim)
533 }
534
535 // SetValue changes key value.
536 func (k *Key) SetValue(v string) {
537 if k.s.f.BlockMode {
538 k.s.f.lock.Lock()
539 defer k.s.f.lock.Unlock()
540 }
541
542 k.value = v
543 k.s.keysHash[k.name] = v
544 }
545
546 // _________ __ .__
547 // / _____/ ____ _____/ |_|__| ____ ____
548 // \_____ \_/ __ \_/ ___\ __\ |/ _ \ / \
549 // / \ ___/\ \___| | | ( <_> ) | \
550 // /_______ /\___ >\___ >__| |__|\____/|___| /
551 // \/ \/ \/ \/
552
553 // Section represents a config section.
554 type Section struct {
555 f *File
556 Comment string
557 name string
558 keys map[string]*Key
559 keyList []string
560 keysHash map[string]string
561 }
562
563 func newSection(f *File, name string) *Section {
564 return &Section{f, "", name, make(map[string]*Key), make([]string, 0, 10), make(map[string]string)}
565 }
566
567 // Name returns name of Section.
568 func (s *Section) Name() string {
569 return s.name
570 }
571
572 // NewKey creates a new key to given section.
573 func (s *Section) NewKey(name, val string) (*Key, error) {
574 if len(name) == 0 {
575 return nil, errors.New("error creating new key: empty key name")
576 }
577
578 if s.f.BlockMode {
579 s.f.lock.Lock()
580 defer s.f.lock.Unlock()
581 }
582
583 if inSlice(name, s.keyList) {
584 s.keys[name].value = val
585 return s.keys[name], nil
586 }
587
588 s.keyList = append(s.keyList, name)
589 s.keys[name] = &Key{s, "", name, val, false}
590 s.keysHash[name] = val
591 return s.keys[name], nil
592 }
593
594 // GetKey returns key in section by given name.
595 func (s *Section) GetKey(name string) (*Key, error) {
596 // FIXME: change to section level lock?
597 if s.f.BlockMode {
598 s.f.lock.RLock()
599 }
600 key := s.keys[name]
601 if s.f.BlockMode {
602 s.f.lock.RUnlock()
603 }
604
605 if key == nil {
606 // Check if it is a child-section.
607 sname := s.name
608 for {
609 if i := strings.LastIndex(sname, "."); i > -1 {
610 sname = sname[:i]
611 sec, err := s.f.GetSection(sname)
612 if err != nil {
613 continue
614 }
615 return sec.GetKey(name)
616 } else {
617 break
618 }
619 }
620 return nil, fmt.Errorf("error when getting key of section '%s': key '%s' not exists", s.name, name)
621 }
622 return key, nil
623 }
624
625 // HasKey returns true if section contains a key with given name.
626 func (s *Section) HasKey(name string) bool {
627 key, _ := s.GetKey(name)
628 return key != nil
629 }
630
631 // Haskey is a backwards-compatible name for HasKey.
632 func (s *Section) Haskey(name string) bool {
633 return s.HasKey(name)
634 }
635
636 // HasValue returns true if section contains given raw value.
637 func (s *Section) HasValue(value string) bool {
638 if s.f.BlockMode {
639 s.f.lock.RLock()
640 defer s.f.lock.RUnlock()
641 }
642
643 for _, k := range s.keys {
644 if value == k.value {
645 return true
646 }
647 }
648 return false
649 }
650
651 // Key assumes named Key exists in section and returns a zero-value when not.
652 func (s *Section) Key(name string) *Key {
653 key, err := s.GetKey(name)
654 if err != nil {
655 // It's OK here because the only possible error is empty key name,
656 // but if it's empty, this piece of code won't be executed.
657 key, _ = s.NewKey(name, "")
658 return key
659 }
660 return key
661 }
662
663 // Keys returns list of keys of section.
664 func (s *Section) Keys() []*Key {
665 keys := make([]*Key, len(s.keyList))
666 for i := range s.keyList {
667 keys[i] = s.Key(s.keyList[i])
668 }
669 return keys
670 }
671
672 // KeyStrings returns list of key names of section.
673 func (s *Section) KeyStrings() []string {
674 list := make([]string, len(s.keyList))
675 copy(list, s.keyList)
676 return list
677 }
678
679 // KeysHash returns keys hash consisting of names and values.
680 func (s *Section) KeysHash() map[string]string {
681 if s.f.BlockMode {
682 s.f.lock.RLock()
683 defer s.f.lock.RUnlock()
684 }
685
686 hash := map[string]string{}
687 for key, value := range s.keysHash {
688 hash[key] = value
689 }
690 return hash
691 }
692
693 // DeleteKey deletes a key from section.
694 func (s *Section) DeleteKey(name string) {
695 if s.f.BlockMode {
696 s.f.lock.Lock()
697 defer s.f.lock.Unlock()
698 }
699
700 for i, k := range s.keyList {
701 if k == name {
702 s.keyList = append(s.keyList[:i], s.keyList[i+1:]...)
703 delete(s.keys, name)
704 return
705 }
706 }
707 }
708
709 // ___________.__.__
710 // \_ _____/|__| | ____
711 // | __) | | | _/ __ \
712 // | \ | | |_\ ___/
713 // \___ / |__|____/\___ >
714 // \/ \/
111 return ioutil.NopCloser(bytes.NewReader(s.data)), nil
112 }
113
114 // sourceReadCloser represents an input stream with Close method.
115 type sourceReadCloser struct {
116 reader io.ReadCloser
117 }
118
119 func (s *sourceReadCloser) ReadCloser() (io.ReadCloser, error) {
120 return s.reader, nil
121 }
715122
716123 // File represents a combination of a or more INI file(s) in memory.
717124 type File struct {
728135 // To keep data in order.
729136 sectionList []string
730137
138 options LoadOptions
139
731140 NameMapper
141 ValueMapper
732142 }
733143
734144 // newFile initializes File object with given data sources.
735 func newFile(dataSources []dataSource) *File {
145 func newFile(dataSources []dataSource, opts LoadOptions) *File {
736146 return &File{
737147 BlockMode: true,
738148 dataSources: dataSources,
739149 sections: make(map[string]*Section),
740150 sectionList: make([]string, 0, 10),
151 options: opts,
741152 }
742153 }
743154
747158 return sourceFile{s}, nil
748159 case []byte:
749160 return &sourceData{s}, nil
161 case io.ReadCloser:
162 return &sourceReadCloser{s}, nil
750163 default:
751164 return nil, fmt.Errorf("error parsing data source: unknown type '%s'", s)
752165 }
753166 }
754167
755 // Load loads and parses from INI data sources.
756 // Arguments can be mixed of file name with string type, or raw data in []byte.
757 func Load(source interface{}, others ...interface{}) (_ *File, err error) {
168 type LoadOptions struct {
169 // Loose indicates whether the parser should ignore nonexistent files or return error.
170 Loose bool
171 // Insensitive indicates whether the parser forces all section and key names to lowercase.
172 Insensitive bool
173 // IgnoreContinuation indicates whether to ignore continuation lines while parsing.
174 IgnoreContinuation bool
175 // IgnoreInlineComment indicates whether to ignore comments at the end of value and treat it as part of value.
176 IgnoreInlineComment bool
177 // AllowBooleanKeys indicates whether to allow boolean type keys or treat as value is missing.
178 // This type of keys are mostly used in my.cnf.
179 AllowBooleanKeys bool
180 // AllowShadows indicates whether to keep track of keys with same name under same section.
181 AllowShadows bool
182 // Some INI formats allow group blocks that store a block of raw content that doesn't otherwise
183 // conform to key/value pairs. Specify the names of those blocks here.
184 UnparseableSections []string
185 }
186
187 func LoadSources(opts LoadOptions, source interface{}, others ...interface{}) (_ *File, err error) {
758188 sources := make([]dataSource, len(others)+1)
759189 sources[0], err = parseDataSource(source)
760190 if err != nil {
766196 return nil, err
767197 }
768198 }
769 f := newFile(sources)
199 f := newFile(sources, opts)
770200 if err = f.Reload(); err != nil {
771201 return nil, err
772202 }
773203 return f, nil
204 }
205
206 // Load loads and parses from INI data sources.
207 // Arguments can be mixed of file name with string type, or raw data in []byte.
208 // It will return error if list contains nonexistent files.
209 func Load(source interface{}, others ...interface{}) (*File, error) {
210 return LoadSources(LoadOptions{}, source, others...)
211 }
212
213 // LooseLoad has exactly same functionality as Load function
214 // except it ignores nonexistent files instead of returning error.
215 func LooseLoad(source interface{}, others ...interface{}) (*File, error) {
216 return LoadSources(LoadOptions{Loose: true}, source, others...)
217 }
218
219 // InsensitiveLoad has exactly same functionality as Load function
220 // except it forces all section and key names to be lowercased.
221 func InsensitiveLoad(source interface{}, others ...interface{}) (*File, error) {
222 return LoadSources(LoadOptions{Insensitive: true}, source, others...)
223 }
224
225 // InsensitiveLoad has exactly same functionality as Load function
226 // except it allows have shadow keys.
227 func ShadowLoad(source interface{}, others ...interface{}) (*File, error) {
228 return LoadSources(LoadOptions{AllowShadows: true}, source, others...)
774229 }
775230
776231 // Empty returns an empty file object.
784239 func (f *File) NewSection(name string) (*Section, error) {
785240 if len(name) == 0 {
786241 return nil, errors.New("error creating new section: empty section name")
242 } else if f.options.Insensitive && name != DEFAULT_SECTION {
243 name = strings.ToLower(name)
787244 }
788245
789246 if f.BlockMode {
798255 f.sectionList = append(f.sectionList, name)
799256 f.sections[name] = newSection(f, name)
800257 return f.sections[name], nil
258 }
259
260 // NewRawSection creates a new section with an unparseable body.
261 func (f *File) NewRawSection(name, body string) (*Section, error) {
262 section, err := f.NewSection(name)
263 if err != nil {
264 return nil, err
265 }
266
267 section.isRawSection = true
268 section.rawBody = body
269 return section, nil
801270 }
802271
803272 // NewSections creates a list of sections.
814283 func (f *File) GetSection(name string) (*Section, error) {
815284 if len(name) == 0 {
816285 name = DEFAULT_SECTION
286 } else if f.options.Insensitive {
287 name = strings.ToLower(name)
817288 }
818289
819290 if f.BlockMode {
823294
824295 sec := f.sections[name]
825296 if sec == nil {
826 return nil, fmt.Errorf("error when getting section: section '%s' not exists", name)
297 return nil, fmt.Errorf("section '%s' does not exist", name)
827298 }
828299 return sec, nil
829300 }
849320 return sections
850321 }
851322
323 // ChildSections returns a list of child sections of given section name.
324 func (f *File) ChildSections(name string) []*Section {
325 return f.Section(name).ChildSections()
326 }
327
852328 // SectionStrings returns list of section names.
853329 func (f *File) SectionStrings() []string {
854330 list := make([]string, len(f.sectionList))
890366 func (f *File) Reload() (err error) {
891367 for _, s := range f.dataSources {
892368 if err = f.reload(s); err != nil {
369 // In loose mode, we create an empty default section for nonexistent files.
370 if os.IsNotExist(err) && f.options.Loose {
371 f.parse(bytes.NewBuffer(nil))
372 continue
373 }
893374 return err
894375 }
895376 }
913394 return f.Reload()
914395 }
915396
916 // WriteToIndent writes file content into io.Writer with given value indention.
397 // WriteToIndent writes content into io.Writer with given indention.
398 // If PrettyFormat has been set to be true,
399 // it will align "=" sign with spaces under each section.
917400 func (f *File) WriteToIndent(w io.Writer, indent string) (n int64, err error) {
918401 equalSign := "="
919402 if PrettyFormat {
933416 }
934417 }
935418
936 if i > 0 {
419 if i > 0 || DefaultHeader {
937420 if _, err = buf.WriteString("[" + sname + "]" + LineBreak); err != nil {
938421 return 0, err
939422 }
940423 } else {
941 // Write nothing if default section is empty.
424 // Write nothing if default section is empty
942425 if len(sec.keyList) == 0 {
943426 continue
944427 }
945428 }
946429
430 if sec.isRawSection {
431 if _, err = buf.WriteString(sec.rawBody); err != nil {
432 return 0, err
433 }
434 continue
435 }
436
437 // Count and generate alignment length and buffer spaces using the
438 // longest key. Keys may be modifed if they contain certain characters so
439 // we need to take that into account in our calculation.
440 alignLength := 0
441 if PrettyFormat {
442 for _, kname := range sec.keyList {
443 keyLength := len(kname)
444 // First case will surround key by ` and second by """
445 if strings.ContainsAny(kname, "\"=:") {
446 keyLength += 2
447 } else if strings.Contains(kname, "`") {
448 keyLength += 6
449 }
450
451 if keyLength > alignLength {
452 alignLength = keyLength
453 }
454 }
455 }
456 alignSpaces := bytes.Repeat([]byte(" "), alignLength)
457
458 KEY_LIST:
947459 for _, kname := range sec.keyList {
948460 key := sec.Key(kname)
949461 if len(key.Comment) > 0 {
963475 }
964476
965477 switch {
966 case key.isAutoIncr:
478 case key.isAutoIncrement:
967479 kname = "-"
968480 case strings.ContainsAny(kname, "\"=:"):
969481 kname = "`" + kname + "`"
971483 kname = `"""` + kname + `"""`
972484 }
973485
974 val := key.value
975 // In case key value contains "\n", "`", "\"", "#" or ";".
976 if strings.ContainsAny(val, "\n`") {
977 val = `"""` + val + `"""`
978 } else if strings.ContainsAny(val, "#;") {
979 val = "`" + val + "`"
980 }
981 if _, err = buf.WriteString(kname + equalSign + val + LineBreak); err != nil {
982 return 0, err
983 }
984 }
985
986 // Put a line between sections.
486 for _, val := range key.ValueWithShadows() {
487 if _, err = buf.WriteString(kname); err != nil {
488 return 0, err
489 }
490
491 if key.isBooleanType {
492 if kname != sec.keyList[len(sec.keyList)-1] {
493 buf.WriteString(LineBreak)
494 }
495 continue KEY_LIST
496 }
497
498 // Write out alignment spaces before "=" sign
499 if PrettyFormat {
500 buf.Write(alignSpaces[:alignLength-len(kname)])
501 }
502
503 // In case key value contains "\n", "`", "\"", "#" or ";"
504 if strings.ContainsAny(val, "\n`") {
505 val = `"""` + val + `"""`
506 } else if strings.ContainsAny(val, "#;") {
507 val = "`" + val + "`"
508 }
509 if _, err = buf.WriteString(equalSign + val + LineBreak); err != nil {
510 return 0, err
511 }
512 }
513 }
514
515 // Put a line between sections
987516 if _, err = buf.WriteString(LineBreak); err != nil {
988517 return 0, err
989518 }
1515
1616 import (
1717 "bytes"
18 "fmt"
18 "io/ioutil"
1919 "strings"
2020 "testing"
2121 "time"
3131
3232 const _CONF_DATA = `
3333 ; Package name
34 NAME = ini
34 NAME = ini
3535 ; Package version
36 VERSION = v1
36 VERSION = v1
3737 ; Package import path
3838 IMPORT_PATH = gopkg.in/%(NAME)s.%(VERSION)s
3939
4040 # Information about package author
4141 # Bio can be written in multiple lines.
4242 [author]
43 NAME = Unknwon ; Succeeding comment
43 NAME = Unknwon ; Succeeding comment
4444 E-MAIL = fake@localhost
4545 GITHUB = https://github.com/%(NAME)s
46 BIO = """Gopher.
46 BIO = """Gopher.
4747 Coding addict.
4848 Good man.
4949 """ # Succeeding comment
6060 -: Support load multiple files to overwrite key values
6161
6262 [types]
63 STRING = str
64 BOOL = true
63 STRING = str
64 BOOL = true
6565 BOOL_FALSE = false
66 FLOAT64 = 1.25
67 INT = 10
68 TIME = 2015-01-01T20:17:05Z
69 DURATION = 2h45m
70 UINT = 3
66 FLOAT64 = 1.25
67 INT = 10
68 TIME = 2015-01-01T20:17:05Z
69 DURATION = 2h45m
70 UINT = 3
7171
7272 [array]
73 STRINGS = en, zh, de
73 STRINGS = en, zh, de
7474 FLOAT64S = 1.1, 2.2, 3.3
75 INTS = 1, 2, 3
76 UINTS = 1, 2, 3
77 TIMES = 2015-01-01T20:17:05Z,2015-01-01T20:17:05Z,2015-01-01T20:17:05Z
75 INTS = 1, 2, 3
76 UINTS = 1, 2, 3
77 TIMES = 2015-01-01T20:17:05Z,2015-01-01T20:17:05Z,2015-01-01T20:17:05Z
7878
7979 [note]
8080 empty_lines = next line is empty\
8282 ; Comment before the section
8383 [comments] ; This is a comment for the section too
8484 ; Comment before key
85 key = "value"
85 key = "value"
8686 key2 = "value2" ; This is a comment for key2
8787 key3 = "one", "two", "three"
8888
8989 [advance]
9090 value with quotes = "some value"
9191 value quote2 again = 'some value'
92 includes comment sign = ` + "`" + "my#password" + "`" + `
93 includes comment sign2 = ` + "`" + "my;password" + "`" + `
9294 true = 2+3=5
9395 "1+1=2" = true
9496 """6+1=7""" = true
114116 })
115117
116118 Convey("Load with multiple data sources", func() {
117 cfg, err := Load([]byte(_CONF_DATA), "testdata/conf.ini")
119 cfg, err := Load([]byte(_CONF_DATA), "testdata/conf.ini", ioutil.NopCloser(bytes.NewReader([]byte(_CONF_DATA))))
118120 So(err, ShouldBeNil)
119121 So(cfg, ShouldNotBeNil)
122
123 f, err := Load([]byte(_CONF_DATA), "testdata/404.ini")
124 So(err, ShouldNotBeNil)
125 So(f, ShouldBeNil)
126 })
127
128 Convey("Load with io.ReadCloser", func() {
129 cfg, err := Load(ioutil.NopCloser(bytes.NewReader([]byte(_CONF_DATA))))
130 So(err, ShouldBeNil)
131 So(cfg, ShouldNotBeNil)
132
133 So(cfg.Section("").Key("NAME").String(), ShouldEqual, "ini")
120134 })
121135 })
122136
167181 So(err, ShouldNotBeNil)
168182 })
169183 })
170 }
171
172 func Test_Values(t *testing.T) {
173 Convey("Test getting and setting values", t, func() {
174 cfg, err := Load([]byte(_CONF_DATA), "testdata/conf.ini")
175 So(err, ShouldBeNil)
176 So(cfg, ShouldNotBeNil)
177
178 Convey("Get values in default section", func() {
179 sec := cfg.Section("")
180 So(sec, ShouldNotBeNil)
181 So(sec.Key("NAME").Value(), ShouldEqual, "ini")
182 So(sec.Key("NAME").String(), ShouldEqual, "ini")
183 So(sec.Key("NAME").Validate(func(in string) string {
184 return in
185 }), ShouldEqual, "ini")
186 So(sec.Key("NAME").Comment, ShouldEqual, "; Package name")
187 So(sec.Key("IMPORT_PATH").String(), ShouldEqual, "gopkg.in/ini.v1")
188 })
189
190 Convey("Get values in non-default section", func() {
191 sec := cfg.Section("author")
192 So(sec, ShouldNotBeNil)
193 So(sec.Key("NAME").String(), ShouldEqual, "Unknwon")
194 So(sec.Key("GITHUB").String(), ShouldEqual, "https://github.com/Unknwon")
195
196 sec = cfg.Section("package")
197 So(sec, ShouldNotBeNil)
198 So(sec.Key("CLONE_URL").String(), ShouldEqual, "https://gopkg.in/ini.v1")
199 })
200
201 Convey("Get auto-increment key names", func() {
202 keys := cfg.Section("features").Keys()
203 for i, k := range keys {
204 So(k.Name(), ShouldEqual, fmt.Sprintf("#%d", i+1))
184
185 Convey("Get section and key insensitively", t, func() {
186 cfg, err := InsensitiveLoad([]byte(_CONF_DATA), "testdata/conf.ini")
187 So(err, ShouldBeNil)
188 So(cfg, ShouldNotBeNil)
189
190 sec, err := cfg.GetSection("Author")
191 So(err, ShouldBeNil)
192 So(sec, ShouldNotBeNil)
193
194 key, err := sec.GetKey("E-mail")
195 So(err, ShouldBeNil)
196 So(key, ShouldNotBeNil)
197 })
198
199 Convey("Load with ignoring continuation lines", t, func() {
200 cfg, err := LoadSources(LoadOptions{IgnoreContinuation: true}, []byte(`key1=a\b\
201 key2=c\d\`))
202 So(err, ShouldBeNil)
203 So(cfg, ShouldNotBeNil)
204
205 So(cfg.Section("").Key("key1").String(), ShouldEqual, `a\b\`)
206 So(cfg.Section("").Key("key2").String(), ShouldEqual, `c\d\`)
207 })
208
209 Convey("Load with ignoring inline comments", t, func() {
210 cfg, err := LoadSources(LoadOptions{IgnoreInlineComment: true}, []byte(`key1=value ;comment
211 key2=value #comment2`))
212 So(err, ShouldBeNil)
213 So(cfg, ShouldNotBeNil)
214
215 So(cfg.Section("").Key("key1").String(), ShouldEqual, `value ;comment`)
216 So(cfg.Section("").Key("key2").String(), ShouldEqual, `value #comment2`)
217 })
218
219 Convey("Load with boolean type keys", t, func() {
220 cfg, err := LoadSources(LoadOptions{AllowBooleanKeys: true}, []byte(`key1=hello
221 key2
222 #key3
223 key4
224 key5`))
225 So(err, ShouldBeNil)
226 So(cfg, ShouldNotBeNil)
227
228 So(strings.Join(cfg.Section("").KeyStrings(), ","), ShouldEqual, "key1,key2,key4,key5")
229 So(cfg.Section("").Key("key2").MustBool(false), ShouldBeTrue)
230
231 var buf bytes.Buffer
232 cfg.WriteTo(&buf)
233 // there is always a trailing \n at the end of the section
234 So(buf.String(), ShouldEqual, `key1 = hello
235 key2
236 #key3
237 key4
238 key5
239 `)
240 })
241 }
242
243 func Test_File_ChildSections(t *testing.T) {
244 Convey("Find child sections by parent name", t, func() {
245 cfg, err := Load([]byte(`
246 [node]
247
248 [node.biz1]
249
250 [node.biz2]
251
252 [node.biz3]
253
254 [node.bizN]
255 `))
256 So(err, ShouldBeNil)
257 So(cfg, ShouldNotBeNil)
258
259 children := cfg.ChildSections("node")
260 names := make([]string, len(children))
261 for i := range children {
262 names[i] = children[i].name
263 }
264 So(strings.Join(names, ","), ShouldEqual, "node.biz1,node.biz2,node.biz3,node.bizN")
265 })
266 }
267
268 func Test_LooseLoad(t *testing.T) {
269 Convey("Loose load from data sources", t, func() {
270 Convey("Loose load mixed with nonexistent file", func() {
271 cfg, err := LooseLoad("testdata/404.ini")
272 So(err, ShouldBeNil)
273 So(cfg, ShouldNotBeNil)
274 var fake struct {
275 Name string `ini:"name"`
205276 }
206 })
207
208 Convey("Get overwrite value", func() {
209 So(cfg.Section("author").Key("E-MAIL").String(), ShouldEqual, "u@gogs.io")
210 })
211
212 Convey("Get sections", func() {
213 sections := cfg.Sections()
214 for i, name := range []string{DEFAULT_SECTION, "author", "package", "package.sub", "features", "types", "array", "note", "comments", "advance"} {
215 So(sections[i].Name(), ShouldEqual, name)
216 }
217 })
218
219 Convey("Get parent section value", func() {
220 So(cfg.Section("package.sub").Key("CLONE_URL").String(), ShouldEqual, "https://gopkg.in/ini.v1")
221 So(cfg.Section("package.fake.sub").Key("CLONE_URL").String(), ShouldEqual, "https://gopkg.in/ini.v1")
222 })
223
224 Convey("Get multiple line value", func() {
225 So(cfg.Section("author").Key("BIO").String(), ShouldEqual, "Gopher.\nCoding addict.\nGood man.\n")
226 })
227
228 Convey("Get values with type", func() {
229 sec := cfg.Section("types")
230 v1, err := sec.Key("BOOL").Bool()
277 So(cfg.MapTo(&fake), ShouldBeNil)
278
279 cfg, err = LooseLoad([]byte("name=Unknwon"), "testdata/404.ini")
231280 So(err, ShouldBeNil)
232 So(v1, ShouldBeTrue)
233
234 v1, err = sec.Key("BOOL_FALSE").Bool()
235 So(err, ShouldBeNil)
236 So(v1, ShouldBeFalse)
237
238 v2, err := sec.Key("FLOAT64").Float64()
239 So(err, ShouldBeNil)
240 So(v2, ShouldEqual, 1.25)
241
242 v3, err := sec.Key("INT").Int()
243 So(err, ShouldBeNil)
244 So(v3, ShouldEqual, 10)
245
246 v4, err := sec.Key("INT").Int64()
247 So(err, ShouldBeNil)
248 So(v4, ShouldEqual, 10)
249
250 v5, err := sec.Key("UINT").Uint()
251 So(err, ShouldBeNil)
252 So(v5, ShouldEqual, 3)
253
254 v6, err := sec.Key("UINT").Uint64()
255 So(err, ShouldBeNil)
256 So(v6, ShouldEqual, 3)
257
258 t, err := time.Parse(time.RFC3339, "2015-01-01T20:17:05Z")
259 So(err, ShouldBeNil)
260 v7, err := sec.Key("TIME").Time()
261 So(err, ShouldBeNil)
262 So(v7.String(), ShouldEqual, t.String())
263
264 Convey("Must get values with type", func() {
265 So(sec.Key("STRING").MustString("404"), ShouldEqual, "str")
266 So(sec.Key("BOOL").MustBool(), ShouldBeTrue)
267 So(sec.Key("FLOAT64").MustFloat64(), ShouldEqual, 1.25)
268 So(sec.Key("INT").MustInt(), ShouldEqual, 10)
269 So(sec.Key("INT").MustInt64(), ShouldEqual, 10)
270 So(sec.Key("UINT").MustUint(), ShouldEqual, 3)
271 So(sec.Key("UINT").MustUint64(), ShouldEqual, 3)
272 So(sec.Key("TIME").MustTime().String(), ShouldEqual, t.String())
273
274 dur, err := time.ParseDuration("2h45m")
275 So(err, ShouldBeNil)
276 So(sec.Key("DURATION").MustDuration().Seconds(), ShouldEqual, dur.Seconds())
277
278 Convey("Must get values with default value", func() {
279 So(sec.Key("STRING_404").MustString("404"), ShouldEqual, "404")
280 So(sec.Key("BOOL_404").MustBool(true), ShouldBeTrue)
281 So(sec.Key("FLOAT64_404").MustFloat64(2.5), ShouldEqual, 2.5)
282 So(sec.Key("INT_404").MustInt(15), ShouldEqual, 15)
283 So(sec.Key("INT_404").MustInt64(15), ShouldEqual, 15)
284 So(sec.Key("UINT_404").MustUint(6), ShouldEqual, 6)
285 So(sec.Key("UINT_404").MustUint64(6), ShouldEqual, 6)
286
287 t, err := time.Parse(time.RFC3339, "2014-01-01T20:17:05Z")
288 So(err, ShouldBeNil)
289 So(sec.Key("TIME_404").MustTime(t).String(), ShouldEqual, t.String())
290
291 So(sec.Key("DURATION_404").MustDuration(dur).Seconds(), ShouldEqual, dur.Seconds())
292 })
293 })
294 })
295
296 Convey("Get value with candidates", func() {
297 sec := cfg.Section("types")
298 So(sec.Key("STRING").In("", []string{"str", "arr", "types"}), ShouldEqual, "str")
299 So(sec.Key("FLOAT64").InFloat64(0, []float64{1.25, 2.5, 3.75}), ShouldEqual, 1.25)
300 So(sec.Key("INT").InInt(0, []int{10, 20, 30}), ShouldEqual, 10)
301 So(sec.Key("INT").InInt64(0, []int64{10, 20, 30}), ShouldEqual, 10)
302 So(sec.Key("UINT").InUint(0, []uint{3, 6, 9}), ShouldEqual, 3)
303 So(sec.Key("UINT").InUint64(0, []uint64{3, 6, 9}), ShouldEqual, 3)
304
305 zt, err := time.Parse(time.RFC3339, "0001-01-01T01:00:00Z")
306 So(err, ShouldBeNil)
307 t, err := time.Parse(time.RFC3339, "2015-01-01T20:17:05Z")
308 So(err, ShouldBeNil)
309 So(sec.Key("TIME").InTime(zt, []time.Time{t, time.Now(), time.Now().Add(1 * time.Second)}).String(), ShouldEqual, t.String())
310
311 Convey("Get value with candidates and default value", func() {
312 So(sec.Key("STRING_404").In("str", []string{"str", "arr", "types"}), ShouldEqual, "str")
313 So(sec.Key("FLOAT64_404").InFloat64(1.25, []float64{1.25, 2.5, 3.75}), ShouldEqual, 1.25)
314 So(sec.Key("INT_404").InInt(10, []int{10, 20, 30}), ShouldEqual, 10)
315 So(sec.Key("INT64_404").InInt64(10, []int64{10, 20, 30}), ShouldEqual, 10)
316 So(sec.Key("UINT_404").InUint(3, []uint{3, 6, 9}), ShouldEqual, 3)
317 So(sec.Key("UINT_404").InUint64(3, []uint64{3, 6, 9}), ShouldEqual, 3)
318 So(sec.Key("TIME_404").InTime(t, []time.Time{time.Now(), time.Now(), time.Now().Add(1 * time.Second)}).String(), ShouldEqual, t.String())
319 })
320 })
321
322 Convey("Get values in range", func() {
323 sec := cfg.Section("types")
324 So(sec.Key("FLOAT64").RangeFloat64(0, 1, 2), ShouldEqual, 1.25)
325 So(sec.Key("INT").RangeInt(0, 10, 20), ShouldEqual, 10)
326 So(sec.Key("INT").RangeInt64(0, 10, 20), ShouldEqual, 10)
327
328 minT, err := time.Parse(time.RFC3339, "0001-01-01T01:00:00Z")
329 So(err, ShouldBeNil)
330 midT, err := time.Parse(time.RFC3339, "2013-01-01T01:00:00Z")
331 So(err, ShouldBeNil)
332 maxT, err := time.Parse(time.RFC3339, "9999-01-01T01:00:00Z")
333 So(err, ShouldBeNil)
334 t, err := time.Parse(time.RFC3339, "2015-01-01T20:17:05Z")
335 So(err, ShouldBeNil)
336 So(sec.Key("TIME").RangeTime(t, minT, maxT).String(), ShouldEqual, t.String())
337
338 Convey("Get value in range with default value", func() {
339 So(sec.Key("FLOAT64").RangeFloat64(5, 0, 1), ShouldEqual, 5)
340 So(sec.Key("INT").RangeInt(7, 0, 5), ShouldEqual, 7)
341 So(sec.Key("INT").RangeInt64(7, 0, 5), ShouldEqual, 7)
342 So(sec.Key("TIME").RangeTime(t, minT, midT).String(), ShouldEqual, t.String())
343 })
344 })
345
346 Convey("Get values into slice", func() {
347 sec := cfg.Section("array")
348 So(strings.Join(sec.Key("STRINGS").Strings(","), ","), ShouldEqual, "en,zh,de")
349 So(len(sec.Key("STRINGS_404").Strings(",")), ShouldEqual, 0)
350
351 vals1 := sec.Key("FLOAT64S").Float64s(",")
352 for i, v := range []float64{1.1, 2.2, 3.3} {
353 So(vals1[i], ShouldEqual, v)
354 }
355
356 vals2 := sec.Key("INTS").Ints(",")
357 for i, v := range []int{1, 2, 3} {
358 So(vals2[i], ShouldEqual, v)
359 }
360
361 vals3 := sec.Key("INTS").Int64s(",")
362 for i, v := range []int64{1, 2, 3} {
363 So(vals3[i], ShouldEqual, v)
364 }
365
366 vals4 := sec.Key("UINTS").Uints(",")
367 for i, v := range []uint{1, 2, 3} {
368 So(vals4[i], ShouldEqual, v)
369 }
370
371 vals5 := sec.Key("UINTS").Uint64s(",")
372 for i, v := range []uint64{1, 2, 3} {
373 So(vals5[i], ShouldEqual, v)
374 }
375
376 t, err := time.Parse(time.RFC3339, "2015-01-01T20:17:05Z")
377 So(err, ShouldBeNil)
378 vals6 := sec.Key("TIMES").Times(",")
379 for i, v := range []time.Time{t, t, t} {
380 So(vals6[i].String(), ShouldEqual, v.String())
381 }
382 })
383
384 Convey("Get key hash", func() {
385 cfg.Section("").KeysHash()
386 })
387
388 Convey("Set key value", func() {
389 k := cfg.Section("author").Key("NAME")
390 k.SetValue("无闻")
391 So(k.String(), ShouldEqual, "无闻")
392 })
393
394 Convey("Get key strings", func() {
395 So(strings.Join(cfg.Section("types").KeyStrings(), ","), ShouldEqual, "STRING,BOOL,BOOL_FALSE,FLOAT64,INT,TIME,DURATION,UINT")
396 })
397
398 Convey("Delete a key", func() {
399 cfg.Section("package.sub").DeleteKey("UNUSED_KEY")
400 _, err := cfg.Section("package.sub").GetKey("UNUSED_KEY")
401 So(err, ShouldNotBeNil)
402 })
403
404 Convey("Has Key (backwards compatible)", func() {
405 sec := cfg.Section("package.sub")
406 haskey1 := sec.Haskey("UNUSED_KEY")
407 haskey2 := sec.Haskey("CLONE_URL")
408 haskey3 := sec.Haskey("CLONE_URL_NO")
409 So(haskey1, ShouldBeTrue)
410 So(haskey2, ShouldBeTrue)
411 So(haskey3, ShouldBeFalse)
412 })
413
414 Convey("Has Key", func() {
415 sec := cfg.Section("package.sub")
416 haskey1 := sec.HasKey("UNUSED_KEY")
417 haskey2 := sec.HasKey("CLONE_URL")
418 haskey3 := sec.HasKey("CLONE_URL_NO")
419 So(haskey1, ShouldBeTrue)
420 So(haskey2, ShouldBeTrue)
421 So(haskey3, ShouldBeFalse)
422 })
423
424 Convey("Has Value", func() {
425 sec := cfg.Section("author")
426 hasvalue1 := sec.HasValue("Unknwon")
427 hasvalue2 := sec.HasValue("doc")
428 So(hasvalue1, ShouldBeTrue)
429 So(hasvalue2, ShouldBeFalse)
430 })
431
432 Convey("Get section strings", func() {
433 So(strings.Join(cfg.SectionStrings(), ","), ShouldEqual, "DEFAULT,author,package,package.sub,features,types,array,note,comments,advance")
434 })
435
436 Convey("Delete a section", func() {
437 cfg.DeleteSection("")
438 So(cfg.SectionStrings()[0], ShouldNotEqual, DEFAULT_SECTION)
439 })
440
441 Convey("Create new sections", func() {
442 cfg.NewSections("test", "test2")
443 _, err := cfg.GetSection("test")
444 So(err, ShouldBeNil)
445 _, err = cfg.GetSection("test2")
446 So(err, ShouldBeNil)
447 })
448 })
449
450 Convey("Test getting and setting bad values", t, func() {
451 cfg, err := Load([]byte(_CONF_DATA), "testdata/conf.ini")
452 So(err, ShouldBeNil)
453 So(cfg, ShouldNotBeNil)
454
455 Convey("Create new key with empty name", func() {
456 k, err := cfg.Section("").NewKey("", "")
457 So(err, ShouldNotBeNil)
458 So(k, ShouldBeNil)
459 })
460
461 Convey("Create new section with empty name", func() {
462 s, err := cfg.NewSection("")
463 So(err, ShouldNotBeNil)
464 So(s, ShouldBeNil)
465 })
466
467 Convey("Create new sections with empty name", func() {
468 So(cfg.NewSections(""), ShouldNotBeNil)
469 })
470
471 Convey("Get section that not exists", func() {
472 s, err := cfg.GetSection("404")
473 So(err, ShouldNotBeNil)
474 So(s, ShouldBeNil)
475
476 s = cfg.Section("404")
477 So(s, ShouldNotBeNil)
478 })
479 })
480
481 Convey("Test key hash clone", t, func() {
482 cfg, err := Load([]byte(strings.Replace("network=tcp,addr=127.0.0.1:6379,db=4,pool_size=100,idle_timeout=180", ",", "\n", -1)))
483 So(err, ShouldBeNil)
484 for _, v := range cfg.Section("").KeysHash() {
485 So(len(v), ShouldBeGreaterThan, 0)
486 }
487 })
488
489 Convey("Key has empty value", t, func() {
490 _conf := `key1=
491 key2= ; comment`
492 cfg, err := Load([]byte(_conf))
493 So(err, ShouldBeNil)
494 So(cfg.Section("").Key("key1").Value(), ShouldBeEmpty)
495 })
281 So(cfg.Section("").Key("name").String(), ShouldEqual, "Unknwon")
282 So(cfg.MapTo(&fake), ShouldBeNil)
283 So(fake.Name, ShouldEqual, "Unknwon")
284 })
285 })
286
496287 }
497288
498289 func Test_File_Append(t *testing.T) {
518309 })
519310 }
520311
521 func Test_File_SaveTo(t *testing.T) {
312 func Test_File_SaveTo_WriteTo(t *testing.T) {
522313 Convey("Save file", t, func() {
523314 cfg, err := Load([]byte(_CONF_DATA), "testdata/conf.ini")
524315 So(err, ShouldBeNil)
528319 cfg.Section("author").Comment = `Information about package author
529320 # Bio can be written in multiple lines.`
530321 cfg.Section("advanced").Key("val w/ pound").SetValue("my#password")
322 cfg.Section("advanced").Key("longest key has a colon : yes/no").SetValue("yes")
531323 So(cfg.SaveTo("testdata/conf_out.ini"), ShouldBeNil)
532324
533325 cfg.Section("author").Key("NAME").Comment = "This is author name"
326
534327 So(cfg.SaveToIndent("testdata/conf_out.ini", "\t"), ShouldBeNil)
535 })
536 }
537
538 func Benchmark_Key_Value(b *testing.B) {
539 c, _ := Load([]byte(_CONF_DATA))
540 for i := 0; i < b.N; i++ {
541 c.Section("").Key("NAME").Value()
542 }
543 }
544
545 func Benchmark_Key_String(b *testing.B) {
546 c, _ := Load([]byte(_CONF_DATA))
547 for i := 0; i < b.N; i++ {
548 c.Section("").Key("NAME").String()
549 }
550 }
551
552 func Benchmark_Key_Value_NonBlock(b *testing.B) {
553 c, _ := Load([]byte(_CONF_DATA))
554 c.BlockMode = false
555 for i := 0; i < b.N; i++ {
556 c.Section("").Key("NAME").Value()
557 }
558 }
559
560 func Benchmark_Key_String_NonBlock(b *testing.B) {
561 c, _ := Load([]byte(_CONF_DATA))
562 c.BlockMode = false
563 for i := 0; i < b.N; i++ {
564 c.Section("").Key("NAME").String()
565 }
566 }
567
568 func Benchmark_Key_SetValue(b *testing.B) {
569 c, _ := Load([]byte(_CONF_DATA))
570 for i := 0; i < b.N; i++ {
571 c.Section("").Key("NAME").SetValue("10")
572 }
573 }
328
329 var buf bytes.Buffer
330 _, err = cfg.WriteToIndent(&buf, "\t")
331 So(err, ShouldBeNil)
332 So(buf.String(), ShouldEqual, `; Package name
333 NAME = ini
334 ; Package version
335 VERSION = v1
336 ; Package import path
337 IMPORT_PATH = gopkg.in/%(NAME)s.%(VERSION)s
338
339 ; Information about package author
340 # Bio can be written in multiple lines.
341 [author]
342 ; This is author name
343 NAME = Unknwon
344 E-MAIL = u@gogs.io
345 GITHUB = https://github.com/%(NAME)s
346 # Succeeding comment
347 BIO = """Gopher.
348 Coding addict.
349 Good man.
350 """
351
352 [package]
353 CLONE_URL = https://%(IMPORT_PATH)s
354
355 [package.sub]
356 UNUSED_KEY = should be deleted
357
358 [features]
359 - = Support read/write comments of keys and sections
360 - = Support auto-increment of key names
361 - = Support load multiple files to overwrite key values
362
363 [types]
364 STRING = str
365 BOOL = true
366 BOOL_FALSE = false
367 FLOAT64 = 1.25
368 INT = 10
369 TIME = 2015-01-01T20:17:05Z
370 DURATION = 2h45m
371 UINT = 3
372
373 [array]
374 STRINGS = en, zh, de
375 FLOAT64S = 1.1, 2.2, 3.3
376 INTS = 1, 2, 3
377 UINTS = 1, 2, 3
378 TIMES = 2015-01-01T20:17:05Z,2015-01-01T20:17:05Z,2015-01-01T20:17:05Z
379
380 [note]
381 empty_lines = next line is empty
382
383 ; Comment before the section
384 ; This is a comment for the section too
385 [comments]
386 ; Comment before key
387 key = value
388 ; This is a comment for key2
389 key2 = value2
390 key3 = "one", "two", "three"
391
392 [advance]
393 value with quotes = some value
394 value quote2 again = some value
395 includes comment sign = `+"`"+"my#password"+"`"+`
396 includes comment sign2 = `+"`"+"my;password"+"`"+`
397 true = 2+3=5
398 `+"`"+`1+1=2`+"`"+` = true
399 `+"`"+`6+1=7`+"`"+` = true
400 """`+"`"+`5+5`+"`"+`""" = 10
401 `+"`"+`"6+6"`+"`"+` = 12
402 `+"`"+`7-2=4`+"`"+` = false
403 ADDRESS = """404 road,
404 NotFound, State, 50000"""
405 two_lines = how about continuation lines?
406 lots_of_lines = 1 2 3 4
407
408 [advanced]
409 val w/ pound = `+"`"+`my#password`+"`"+`
410 `+"`"+`longest key has a colon : yes/no`+"`"+` = yes
411
412 `)
413 })
414 }
415
416 func Test_File_WriteTo_SectionRaw(t *testing.T) {
417 Convey("Write a INI with a raw section", t, func() {
418 var buf bytes.Buffer
419 cfg, err := LoadSources(
420 LoadOptions{
421 UnparseableSections: []string{"CORE_LESSON", "COMMENTS"},
422 },
423 "testdata/aicc.ini")
424 So(err, ShouldBeNil)
425 So(cfg, ShouldNotBeNil)
426 cfg.WriteToIndent(&buf, "\t")
427 So(buf.String(), ShouldEqual, `[Core]
428 Lesson_Location = 87
429 Lesson_Status = C
430 Score = 3
431 Time = 00:02:30
432
433 [CORE_LESSON]
434 my lesson state data – 1111111111111111111000000000000000001110000
435 111111111111111111100000000000111000000000 – end my lesson state data
436 [COMMENTS]
437 <1><L.Slide#2> This slide has the fuel listed in the wrong units <e.1>
438 `)
439 })
440 }
441
442 // Helpers for slice tests.
443 func float64sEqual(values []float64, expected ...float64) {
444 So(values, ShouldHaveLength, len(expected))
445 for i, v := range expected {
446 So(values[i], ShouldEqual, v)
447 }
448 }
449
450 func intsEqual(values []int, expected ...int) {
451 So(values, ShouldHaveLength, len(expected))
452 for i, v := range expected {
453 So(values[i], ShouldEqual, v)
454 }
455 }
456
457 func int64sEqual(values []int64, expected ...int64) {
458 So(values, ShouldHaveLength, len(expected))
459 for i, v := range expected {
460 So(values[i], ShouldEqual, v)
461 }
462 }
463
464 func uintsEqual(values []uint, expected ...uint) {
465 So(values, ShouldHaveLength, len(expected))
466 for i, v := range expected {
467 So(values[i], ShouldEqual, v)
468 }
469 }
470
471 func uint64sEqual(values []uint64, expected ...uint64) {
472 So(values, ShouldHaveLength, len(expected))
473 for i, v := range expected {
474 So(values[i], ShouldEqual, v)
475 }
476 }
477
478 func timesEqual(values []time.Time, expected ...time.Time) {
479 So(values, ShouldHaveLength, len(expected))
480 for i, v := range expected {
481 So(values[i].String(), ShouldEqual, v.String())
482 }
483 }
0 // Copyright 2014 Unknwon
1 //
2 // Licensed under the Apache License, Version 2.0 (the "License"): you may
3 // not use this file except in compliance with the License. You may obtain
4 // a copy of the License at
5 //
6 // http://www.apache.org/licenses/LICENSE-2.0
7 //
8 // Unless required by applicable law or agreed to in writing, software
9 // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
10 // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
11 // License for the specific language governing permissions and limitations
12 // under the License.
13
14 package ini
15
16 import (
17 "errors"
18 "fmt"
19 "strconv"
20 "strings"
21 "time"
22 )
23
24 // Key represents a key under a section.
25 type Key struct {
26 s *Section
27 name string
28 value string
29 isAutoIncrement bool
30 isBooleanType bool
31
32 isShadow bool
33 shadows []*Key
34
35 Comment string
36 }
37
38 // newKey simply return a key object with given values.
39 func newKey(s *Section, name, val string) *Key {
40 return &Key{
41 s: s,
42 name: name,
43 value: val,
44 }
45 }
46
47 func (k *Key) addShadow(val string) error {
48 if k.isShadow {
49 return errors.New("cannot add shadow to another shadow key")
50 } else if k.isAutoIncrement || k.isBooleanType {
51 return errors.New("cannot add shadow to auto-increment or boolean key")
52 }
53
54 shadow := newKey(k.s, k.name, val)
55 shadow.isShadow = true
56 k.shadows = append(k.shadows, shadow)
57 return nil
58 }
59
60 // AddShadow adds a new shadow key to itself.
61 func (k *Key) AddShadow(val string) error {
62 if !k.s.f.options.AllowShadows {
63 return errors.New("shadow key is not allowed")
64 }
65 return k.addShadow(val)
66 }
67
68 // ValueMapper represents a mapping function for values, e.g. os.ExpandEnv
69 type ValueMapper func(string) string
70
71 // Name returns name of key.
72 func (k *Key) Name() string {
73 return k.name
74 }
75
76 // Value returns raw value of key for performance purpose.
77 func (k *Key) Value() string {
78 return k.value
79 }
80
81 // ValueWithShadows returns raw values of key and its shadows if any.
82 func (k *Key) ValueWithShadows() []string {
83 if len(k.shadows) == 0 {
84 return []string{k.value}
85 }
86 vals := make([]string, len(k.shadows)+1)
87 vals[0] = k.value
88 for i := range k.shadows {
89 vals[i+1] = k.shadows[i].value
90 }
91 return vals
92 }
93
94 // transformValue takes a raw value and transforms to its final string.
95 func (k *Key) transformValue(val string) string {
96 if k.s.f.ValueMapper != nil {
97 val = k.s.f.ValueMapper(val)
98 }
99
100 // Fail-fast if no indicate char found for recursive value
101 if !strings.Contains(val, "%") {
102 return val
103 }
104 for i := 0; i < _DEPTH_VALUES; i++ {
105 vr := varPattern.FindString(val)
106 if len(vr) == 0 {
107 break
108 }
109
110 // Take off leading '%(' and trailing ')s'.
111 noption := strings.TrimLeft(vr, "%(")
112 noption = strings.TrimRight(noption, ")s")
113
114 // Search in the same section.
115 nk, err := k.s.GetKey(noption)
116 if err != nil {
117 // Search again in default section.
118 nk, _ = k.s.f.Section("").GetKey(noption)
119 }
120
121 // Substitute by new value and take off leading '%(' and trailing ')s'.
122 val = strings.Replace(val, vr, nk.value, -1)
123 }
124 return val
125 }
126
127 // String returns string representation of value.
128 func (k *Key) String() string {
129 return k.transformValue(k.value)
130 }
131
132 // Validate accepts a validate function which can
133 // return modifed result as key value.
134 func (k *Key) Validate(fn func(string) string) string {
135 return fn(k.String())
136 }
137
138 // parseBool returns the boolean value represented by the string.
139 //
140 // It accepts 1, t, T, TRUE, true, True, YES, yes, Yes, y, ON, on, On,
141 // 0, f, F, FALSE, false, False, NO, no, No, n, OFF, off, Off.
142 // Any other value returns an error.
143 func parseBool(str string) (value bool, err error) {
144 switch str {
145 case "1", "t", "T", "true", "TRUE", "True", "YES", "yes", "Yes", "y", "ON", "on", "On":
146 return true, nil
147 case "0", "f", "F", "false", "FALSE", "False", "NO", "no", "No", "n", "OFF", "off", "Off":
148 return false, nil
149 }
150 return false, fmt.Errorf("parsing \"%s\": invalid syntax", str)
151 }
152
153 // Bool returns bool type value.
154 func (k *Key) Bool() (bool, error) {
155 return parseBool(k.String())
156 }
157
158 // Float64 returns float64 type value.
159 func (k *Key) Float64() (float64, error) {
160 return strconv.ParseFloat(k.String(), 64)
161 }
162
163 // Int returns int type value.
164 func (k *Key) Int() (int, error) {
165 return strconv.Atoi(k.String())
166 }
167
168 // Int64 returns int64 type value.
169 func (k *Key) Int64() (int64, error) {
170 return strconv.ParseInt(k.String(), 10, 64)
171 }
172
173 // Uint returns uint type valued.
174 func (k *Key) Uint() (uint, error) {
175 u, e := strconv.ParseUint(k.String(), 10, 64)
176 return uint(u), e
177 }
178
179 // Uint64 returns uint64 type value.
180 func (k *Key) Uint64() (uint64, error) {
181 return strconv.ParseUint(k.String(), 10, 64)
182 }
183
184 // Duration returns time.Duration type value.
185 func (k *Key) Duration() (time.Duration, error) {
186 return time.ParseDuration(k.String())
187 }
188
189 // TimeFormat parses with given format and returns time.Time type value.
190 func (k *Key) TimeFormat(format string) (time.Time, error) {
191 return time.Parse(format, k.String())
192 }
193
194 // Time parses with RFC3339 format and returns time.Time type value.
195 func (k *Key) Time() (time.Time, error) {
196 return k.TimeFormat(time.RFC3339)
197 }
198
199 // MustString returns default value if key value is empty.
200 func (k *Key) MustString(defaultVal string) string {
201 val := k.String()
202 if len(val) == 0 {
203 k.value = defaultVal
204 return defaultVal
205 }
206 return val
207 }
208
209 // MustBool always returns value without error,
210 // it returns false if error occurs.
211 func (k *Key) MustBool(defaultVal ...bool) bool {
212 val, err := k.Bool()
213 if len(defaultVal) > 0 && err != nil {
214 k.value = strconv.FormatBool(defaultVal[0])
215 return defaultVal[0]
216 }
217 return val
218 }
219
220 // MustFloat64 always returns value without error,
221 // it returns 0.0 if error occurs.
222 func (k *Key) MustFloat64(defaultVal ...float64) float64 {
223 val, err := k.Float64()
224 if len(defaultVal) > 0 && err != nil {
225 k.value = strconv.FormatFloat(defaultVal[0], 'f', -1, 64)
226 return defaultVal[0]
227 }
228 return val
229 }
230
231 // MustInt always returns value without error,
232 // it returns 0 if error occurs.
233 func (k *Key) MustInt(defaultVal ...int) int {
234 val, err := k.Int()
235 if len(defaultVal) > 0 && err != nil {
236 k.value = strconv.FormatInt(int64(defaultVal[0]), 10)
237 return defaultVal[0]
238 }
239 return val
240 }
241
242 // MustInt64 always returns value without error,
243 // it returns 0 if error occurs.
244 func (k *Key) MustInt64(defaultVal ...int64) int64 {
245 val, err := k.Int64()
246 if len(defaultVal) > 0 && err != nil {
247 k.value = strconv.FormatInt(defaultVal[0], 10)
248 return defaultVal[0]
249 }
250 return val
251 }
252
253 // MustUint always returns value without error,
254 // it returns 0 if error occurs.
255 func (k *Key) MustUint(defaultVal ...uint) uint {
256 val, err := k.Uint()
257 if len(defaultVal) > 0 && err != nil {
258 k.value = strconv.FormatUint(uint64(defaultVal[0]), 10)
259 return defaultVal[0]
260 }
261 return val
262 }
263
264 // MustUint64 always returns value without error,
265 // it returns 0 if error occurs.
266 func (k *Key) MustUint64(defaultVal ...uint64) uint64 {
267 val, err := k.Uint64()
268 if len(defaultVal) > 0 && err != nil {
269 k.value = strconv.FormatUint(defaultVal[0], 10)
270 return defaultVal[0]
271 }
272 return val
273 }
274
275 // MustDuration always returns value without error,
276 // it returns zero value if error occurs.
277 func (k *Key) MustDuration(defaultVal ...time.Duration) time.Duration {
278 val, err := k.Duration()
279 if len(defaultVal) > 0 && err != nil {
280 k.value = defaultVal[0].String()
281 return defaultVal[0]
282 }
283 return val
284 }
285
286 // MustTimeFormat always parses with given format and returns value without error,
287 // it returns zero value if error occurs.
288 func (k *Key) MustTimeFormat(format string, defaultVal ...time.Time) time.Time {
289 val, err := k.TimeFormat(format)
290 if len(defaultVal) > 0 && err != nil {
291 k.value = defaultVal[0].Format(format)
292 return defaultVal[0]
293 }
294 return val
295 }
296
297 // MustTime always parses with RFC3339 format and returns value without error,
298 // it returns zero value if error occurs.
299 func (k *Key) MustTime(defaultVal ...time.Time) time.Time {
300 return k.MustTimeFormat(time.RFC3339, defaultVal...)
301 }
302
303 // In always returns value without error,
304 // it returns default value if error occurs or doesn't fit into candidates.
305 func (k *Key) In(defaultVal string, candidates []string) string {
306 val := k.String()
307 for _, cand := range candidates {
308 if val == cand {
309 return val
310 }
311 }
312 return defaultVal
313 }
314
315 // InFloat64 always returns value without error,
316 // it returns default value if error occurs or doesn't fit into candidates.
317 func (k *Key) InFloat64(defaultVal float64, candidates []float64) float64 {
318 val := k.MustFloat64()
319 for _, cand := range candidates {
320 if val == cand {
321 return val
322 }
323 }
324 return defaultVal
325 }
326
327 // InInt always returns value without error,
328 // it returns default value if error occurs or doesn't fit into candidates.
329 func (k *Key) InInt(defaultVal int, candidates []int) int {
330 val := k.MustInt()
331 for _, cand := range candidates {
332 if val == cand {
333 return val
334 }
335 }
336 return defaultVal
337 }
338
339 // InInt64 always returns value without error,
340 // it returns default value if error occurs or doesn't fit into candidates.
341 func (k *Key) InInt64(defaultVal int64, candidates []int64) int64 {
342 val := k.MustInt64()
343 for _, cand := range candidates {
344 if val == cand {
345 return val
346 }
347 }
348 return defaultVal
349 }
350
351 // InUint always returns value without error,
352 // it returns default value if error occurs or doesn't fit into candidates.
353 func (k *Key) InUint(defaultVal uint, candidates []uint) uint {
354 val := k.MustUint()
355 for _, cand := range candidates {
356 if val == cand {
357 return val
358 }
359 }
360 return defaultVal
361 }
362
363 // InUint64 always returns value without error,
364 // it returns default value if error occurs or doesn't fit into candidates.
365 func (k *Key) InUint64(defaultVal uint64, candidates []uint64) uint64 {
366 val := k.MustUint64()
367 for _, cand := range candidates {
368 if val == cand {
369 return val
370 }
371 }
372 return defaultVal
373 }
374
375 // InTimeFormat always parses with given format and returns value without error,
376 // it returns default value if error occurs or doesn't fit into candidates.
377 func (k *Key) InTimeFormat(format string, defaultVal time.Time, candidates []time.Time) time.Time {
378 val := k.MustTimeFormat(format)
379 for _, cand := range candidates {
380 if val == cand {
381 return val
382 }
383 }
384 return defaultVal
385 }
386
387 // InTime always parses with RFC3339 format and returns value without error,
388 // it returns default value if error occurs or doesn't fit into candidates.
389 func (k *Key) InTime(defaultVal time.Time, candidates []time.Time) time.Time {
390 return k.InTimeFormat(time.RFC3339, defaultVal, candidates)
391 }
392
393 // RangeFloat64 checks if value is in given range inclusively,
394 // and returns default value if it's not.
395 func (k *Key) RangeFloat64(defaultVal, min, max float64) float64 {
396 val := k.MustFloat64()
397 if val < min || val > max {
398 return defaultVal
399 }
400 return val
401 }
402
403 // RangeInt checks if value is in given range inclusively,
404 // and returns default value if it's not.
405 func (k *Key) RangeInt(defaultVal, min, max int) int {
406 val := k.MustInt()
407 if val < min || val > max {
408 return defaultVal
409 }
410 return val
411 }
412
413 // RangeInt64 checks if value is in given range inclusively,
414 // and returns default value if it's not.
415 func (k *Key) RangeInt64(defaultVal, min, max int64) int64 {
416 val := k.MustInt64()
417 if val < min || val > max {
418 return defaultVal
419 }
420 return val
421 }
422
423 // RangeTimeFormat checks if value with given format is in given range inclusively,
424 // and returns default value if it's not.
425 func (k *Key) RangeTimeFormat(format string, defaultVal, min, max time.Time) time.Time {
426 val := k.MustTimeFormat(format)
427 if val.Unix() < min.Unix() || val.Unix() > max.Unix() {
428 return defaultVal
429 }
430 return val
431 }
432
433 // RangeTime checks if value with RFC3339 format is in given range inclusively,
434 // and returns default value if it's not.
435 func (k *Key) RangeTime(defaultVal, min, max time.Time) time.Time {
436 return k.RangeTimeFormat(time.RFC3339, defaultVal, min, max)
437 }
438
439 // Strings returns list of string divided by given delimiter.
440 func (k *Key) Strings(delim string) []string {
441 str := k.String()
442 if len(str) == 0 {
443 return []string{}
444 }
445
446 vals := strings.Split(str, delim)
447 for i := range vals {
448 // vals[i] = k.transformValue(strings.TrimSpace(vals[i]))
449 vals[i] = strings.TrimSpace(vals[i])
450 }
451 return vals
452 }
453
454 // StringsWithShadows returns list of string divided by given delimiter.
455 // Shadows will also be appended if any.
456 func (k *Key) StringsWithShadows(delim string) []string {
457 vals := k.ValueWithShadows()
458 results := make([]string, 0, len(vals)*2)
459 for i := range vals {
460 if len(vals) == 0 {
461 continue
462 }
463
464 results = append(results, strings.Split(vals[i], delim)...)
465 }
466
467 for i := range results {
468 results[i] = k.transformValue(strings.TrimSpace(results[i]))
469 }
470 return results
471 }
472
473 // Float64s returns list of float64 divided by given delimiter. Any invalid input will be treated as zero value.
474 func (k *Key) Float64s(delim string) []float64 {
475 vals, _ := k.parseFloat64s(k.Strings(delim), true, false)
476 return vals
477 }
478
479 // Ints returns list of int divided by given delimiter. Any invalid input will be treated as zero value.
480 func (k *Key) Ints(delim string) []int {
481 vals, _ := k.parseInts(k.Strings(delim), true, false)
482 return vals
483 }
484
485 // Int64s returns list of int64 divided by given delimiter. Any invalid input will be treated as zero value.
486 func (k *Key) Int64s(delim string) []int64 {
487 vals, _ := k.parseInt64s(k.Strings(delim), true, false)
488 return vals
489 }
490
491 // Uints returns list of uint divided by given delimiter. Any invalid input will be treated as zero value.
492 func (k *Key) Uints(delim string) []uint {
493 vals, _ := k.parseUints(k.Strings(delim), true, false)
494 return vals
495 }
496
497 // Uint64s returns list of uint64 divided by given delimiter. Any invalid input will be treated as zero value.
498 func (k *Key) Uint64s(delim string) []uint64 {
499 vals, _ := k.parseUint64s(k.Strings(delim), true, false)
500 return vals
501 }
502
503 // TimesFormat parses with given format and returns list of time.Time divided by given delimiter.
504 // Any invalid input will be treated as zero value (0001-01-01 00:00:00 +0000 UTC).
505 func (k *Key) TimesFormat(format, delim string) []time.Time {
506 vals, _ := k.parseTimesFormat(format, k.Strings(delim), true, false)
507 return vals
508 }
509
510 // Times parses with RFC3339 format and returns list of time.Time divided by given delimiter.
511 // Any invalid input will be treated as zero value (0001-01-01 00:00:00 +0000 UTC).
512 func (k *Key) Times(delim string) []time.Time {
513 return k.TimesFormat(time.RFC3339, delim)
514 }
515
516 // ValidFloat64s returns list of float64 divided by given delimiter. If some value is not float, then
517 // it will not be included to result list.
518 func (k *Key) ValidFloat64s(delim string) []float64 {
519 vals, _ := k.parseFloat64s(k.Strings(delim), false, false)
520 return vals
521 }
522
523 // ValidInts returns list of int divided by given delimiter. If some value is not integer, then it will
524 // not be included to result list.
525 func (k *Key) ValidInts(delim string) []int {
526 vals, _ := k.parseInts(k.Strings(delim), false, false)
527 return vals
528 }
529
530 // ValidInt64s returns list of int64 divided by given delimiter. If some value is not 64-bit integer,
531 // then it will not be included to result list.
532 func (k *Key) ValidInt64s(delim string) []int64 {
533 vals, _ := k.parseInt64s(k.Strings(delim), false, false)
534 return vals
535 }
536
537 // ValidUints returns list of uint divided by given delimiter. If some value is not unsigned integer,
538 // then it will not be included to result list.
539 func (k *Key) ValidUints(delim string) []uint {
540 vals, _ := k.parseUints(k.Strings(delim), false, false)
541 return vals
542 }
543
544 // ValidUint64s returns list of uint64 divided by given delimiter. If some value is not 64-bit unsigned
545 // integer, then it will not be included to result list.
546 func (k *Key) ValidUint64s(delim string) []uint64 {
547 vals, _ := k.parseUint64s(k.Strings(delim), false, false)
548 return vals
549 }
550
551 // ValidTimesFormat parses with given format and returns list of time.Time divided by given delimiter.
552 func (k *Key) ValidTimesFormat(format, delim string) []time.Time {
553 vals, _ := k.parseTimesFormat(format, k.Strings(delim), false, false)
554 return vals
555 }
556
557 // ValidTimes parses with RFC3339 format and returns list of time.Time divided by given delimiter.
558 func (k *Key) ValidTimes(delim string) []time.Time {
559 return k.ValidTimesFormat(time.RFC3339, delim)
560 }
561
562 // StrictFloat64s returns list of float64 divided by given delimiter or error on first invalid input.
563 func (k *Key) StrictFloat64s(delim string) ([]float64, error) {
564 return k.parseFloat64s(k.Strings(delim), false, true)
565 }
566
567 // StrictInts returns list of int divided by given delimiter or error on first invalid input.
568 func (k *Key) StrictInts(delim string) ([]int, error) {
569 return k.parseInts(k.Strings(delim), false, true)
570 }
571
572 // StrictInt64s returns list of int64 divided by given delimiter or error on first invalid input.
573 func (k *Key) StrictInt64s(delim string) ([]int64, error) {
574 return k.parseInt64s(k.Strings(delim), false, true)
575 }
576
577 // StrictUints returns list of uint divided by given delimiter or error on first invalid input.
578 func (k *Key) StrictUints(delim string) ([]uint, error) {
579 return k.parseUints(k.Strings(delim), false, true)
580 }
581
582 // StrictUint64s returns list of uint64 divided by given delimiter or error on first invalid input.
583 func (k *Key) StrictUint64s(delim string) ([]uint64, error) {
584 return k.parseUint64s(k.Strings(delim), false, true)
585 }
586
587 // StrictTimesFormat parses with given format and returns list of time.Time divided by given delimiter
588 // or error on first invalid input.
589 func (k *Key) StrictTimesFormat(format, delim string) ([]time.Time, error) {
590 return k.parseTimesFormat(format, k.Strings(delim), false, true)
591 }
592
593 // StrictTimes parses with RFC3339 format and returns list of time.Time divided by given delimiter
594 // or error on first invalid input.
595 func (k *Key) StrictTimes(delim string) ([]time.Time, error) {
596 return k.StrictTimesFormat(time.RFC3339, delim)
597 }
598
599 // parseFloat64s transforms strings to float64s.
600 func (k *Key) parseFloat64s(strs []string, addInvalid, returnOnInvalid bool) ([]float64, error) {
601 vals := make([]float64, 0, len(strs))
602 for _, str := range strs {
603 val, err := strconv.ParseFloat(str, 64)
604 if err != nil && returnOnInvalid {
605 return nil, err
606 }
607 if err == nil || addInvalid {
608 vals = append(vals, val)
609 }
610 }
611 return vals, nil
612 }
613
614 // parseInts transforms strings to ints.
615 func (k *Key) parseInts(strs []string, addInvalid, returnOnInvalid bool) ([]int, error) {
616 vals := make([]int, 0, len(strs))
617 for _, str := range strs {
618 val, err := strconv.Atoi(str)
619 if err != nil && returnOnInvalid {
620 return nil, err
621 }
622 if err == nil || addInvalid {
623 vals = append(vals, val)
624 }
625 }
626 return vals, nil
627 }
628
629 // parseInt64s transforms strings to int64s.
630 func (k *Key) parseInt64s(strs []string, addInvalid, returnOnInvalid bool) ([]int64, error) {
631 vals := make([]int64, 0, len(strs))
632 for _, str := range strs {
633 val, err := strconv.ParseInt(str, 10, 64)
634 if err != nil && returnOnInvalid {
635 return nil, err
636 }
637 if err == nil || addInvalid {
638 vals = append(vals, val)
639 }
640 }
641 return vals, nil
642 }
643
644 // parseUints transforms strings to uints.
645 func (k *Key) parseUints(strs []string, addInvalid, returnOnInvalid bool) ([]uint, error) {
646 vals := make([]uint, 0, len(strs))
647 for _, str := range strs {
648 val, err := strconv.ParseUint(str, 10, 0)
649 if err != nil && returnOnInvalid {
650 return nil, err
651 }
652 if err == nil || addInvalid {
653 vals = append(vals, uint(val))
654 }
655 }
656 return vals, nil
657 }
658
659 // parseUint64s transforms strings to uint64s.
660 func (k *Key) parseUint64s(strs []string, addInvalid, returnOnInvalid bool) ([]uint64, error) {
661 vals := make([]uint64, 0, len(strs))
662 for _, str := range strs {
663 val, err := strconv.ParseUint(str, 10, 64)
664 if err != nil && returnOnInvalid {
665 return nil, err
666 }
667 if err == nil || addInvalid {
668 vals = append(vals, val)
669 }
670 }
671 return vals, nil
672 }
673
674 // parseTimesFormat transforms strings to times in given format.
675 func (k *Key) parseTimesFormat(format string, strs []string, addInvalid, returnOnInvalid bool) ([]time.Time, error) {
676 vals := make([]time.Time, 0, len(strs))
677 for _, str := range strs {
678 val, err := time.Parse(format, str)
679 if err != nil && returnOnInvalid {
680 return nil, err
681 }
682 if err == nil || addInvalid {
683 vals = append(vals, val)
684 }
685 }
686 return vals, nil
687 }
688
689 // SetValue changes key value.
690 func (k *Key) SetValue(v string) {
691 if k.s.f.BlockMode {
692 k.s.f.lock.Lock()
693 defer k.s.f.lock.Unlock()
694 }
695
696 k.value = v
697 k.s.keysHash[k.name] = v
698 }
0 // Copyright 2014 Unknwon
1 //
2 // Licensed under the Apache License, Version 2.0 (the "License"): you may
3 // not use this file except in compliance with the License. You may obtain
4 // a copy of the License at
5 //
6 // http://www.apache.org/licenses/LICENSE-2.0
7 //
8 // Unless required by applicable law or agreed to in writing, software
9 // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
10 // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
11 // License for the specific language governing permissions and limitations
12 // under the License.
13
14 package ini
15
16 import (
17 "bytes"
18 "fmt"
19 "strings"
20 "testing"
21 "time"
22
23 . "github.com/smartystreets/goconvey/convey"
24 )
25
26 func Test_Key(t *testing.T) {
27 Convey("Test getting and setting values", t, func() {
28 cfg, err := Load([]byte(_CONF_DATA), "testdata/conf.ini")
29 So(err, ShouldBeNil)
30 So(cfg, ShouldNotBeNil)
31
32 Convey("Get values in default section", func() {
33 sec := cfg.Section("")
34 So(sec, ShouldNotBeNil)
35 So(sec.Key("NAME").Value(), ShouldEqual, "ini")
36 So(sec.Key("NAME").String(), ShouldEqual, "ini")
37 So(sec.Key("NAME").Validate(func(in string) string {
38 return in
39 }), ShouldEqual, "ini")
40 So(sec.Key("NAME").Comment, ShouldEqual, "; Package name")
41 So(sec.Key("IMPORT_PATH").String(), ShouldEqual, "gopkg.in/ini.v1")
42 })
43
44 Convey("Get values in non-default section", func() {
45 sec := cfg.Section("author")
46 So(sec, ShouldNotBeNil)
47 So(sec.Key("NAME").String(), ShouldEqual, "Unknwon")
48 So(sec.Key("GITHUB").String(), ShouldEqual, "https://github.com/Unknwon")
49
50 sec = cfg.Section("package")
51 So(sec, ShouldNotBeNil)
52 So(sec.Key("CLONE_URL").String(), ShouldEqual, "https://gopkg.in/ini.v1")
53 })
54
55 Convey("Get auto-increment key names", func() {
56 keys := cfg.Section("features").Keys()
57 for i, k := range keys {
58 So(k.Name(), ShouldEqual, fmt.Sprintf("#%d", i+1))
59 }
60 })
61
62 Convey("Get parent-keys that are available to the child section", func() {
63 parentKeys := cfg.Section("package.sub").ParentKeys()
64 for _, k := range parentKeys {
65 So(k.Name(), ShouldEqual, "CLONE_URL")
66 }
67 })
68
69 Convey("Get overwrite value", func() {
70 So(cfg.Section("author").Key("E-MAIL").String(), ShouldEqual, "u@gogs.io")
71 })
72
73 Convey("Get sections", func() {
74 sections := cfg.Sections()
75 for i, name := range []string{DEFAULT_SECTION, "author", "package", "package.sub", "features", "types", "array", "note", "comments", "advance"} {
76 So(sections[i].Name(), ShouldEqual, name)
77 }
78 })
79
80 Convey("Get parent section value", func() {
81 So(cfg.Section("package.sub").Key("CLONE_URL").String(), ShouldEqual, "https://gopkg.in/ini.v1")
82 So(cfg.Section("package.fake.sub").Key("CLONE_URL").String(), ShouldEqual, "https://gopkg.in/ini.v1")
83 })
84
85 Convey("Get multiple line value", func() {
86 So(cfg.Section("author").Key("BIO").String(), ShouldEqual, "Gopher.\nCoding addict.\nGood man.\n")
87 })
88
89 Convey("Get values with type", func() {
90 sec := cfg.Section("types")
91 v1, err := sec.Key("BOOL").Bool()
92 So(err, ShouldBeNil)
93 So(v1, ShouldBeTrue)
94
95 v1, err = sec.Key("BOOL_FALSE").Bool()
96 So(err, ShouldBeNil)
97 So(v1, ShouldBeFalse)
98
99 v2, err := sec.Key("FLOAT64").Float64()
100 So(err, ShouldBeNil)
101 So(v2, ShouldEqual, 1.25)
102
103 v3, err := sec.Key("INT").Int()
104 So(err, ShouldBeNil)
105 So(v3, ShouldEqual, 10)
106
107 v4, err := sec.Key("INT").Int64()
108 So(err, ShouldBeNil)
109 So(v4, ShouldEqual, 10)
110
111 v5, err := sec.Key("UINT").Uint()
112 So(err, ShouldBeNil)
113 So(v5, ShouldEqual, 3)
114
115 v6, err := sec.Key("UINT").Uint64()
116 So(err, ShouldBeNil)
117 So(v6, ShouldEqual, 3)
118
119 t, err := time.Parse(time.RFC3339, "2015-01-01T20:17:05Z")
120 So(err, ShouldBeNil)
121 v7, err := sec.Key("TIME").Time()
122 So(err, ShouldBeNil)
123 So(v7.String(), ShouldEqual, t.String())
124
125 Convey("Must get values with type", func() {
126 So(sec.Key("STRING").MustString("404"), ShouldEqual, "str")
127 So(sec.Key("BOOL").MustBool(), ShouldBeTrue)
128 So(sec.Key("FLOAT64").MustFloat64(), ShouldEqual, 1.25)
129 So(sec.Key("INT").MustInt(), ShouldEqual, 10)
130 So(sec.Key("INT").MustInt64(), ShouldEqual, 10)
131 So(sec.Key("UINT").MustUint(), ShouldEqual, 3)
132 So(sec.Key("UINT").MustUint64(), ShouldEqual, 3)
133 So(sec.Key("TIME").MustTime().String(), ShouldEqual, t.String())
134
135 dur, err := time.ParseDuration("2h45m")
136 So(err, ShouldBeNil)
137 So(sec.Key("DURATION").MustDuration().Seconds(), ShouldEqual, dur.Seconds())
138
139 Convey("Must get values with default value", func() {
140 So(sec.Key("STRING_404").MustString("404"), ShouldEqual, "404")
141 So(sec.Key("BOOL_404").MustBool(true), ShouldBeTrue)
142 So(sec.Key("FLOAT64_404").MustFloat64(2.5), ShouldEqual, 2.5)
143 So(sec.Key("INT_404").MustInt(15), ShouldEqual, 15)
144 So(sec.Key("INT64_404").MustInt64(15), ShouldEqual, 15)
145 So(sec.Key("UINT_404").MustUint(6), ShouldEqual, 6)
146 So(sec.Key("UINT64_404").MustUint64(6), ShouldEqual, 6)
147
148 t, err := time.Parse(time.RFC3339, "2014-01-01T20:17:05Z")
149 So(err, ShouldBeNil)
150 So(sec.Key("TIME_404").MustTime(t).String(), ShouldEqual, t.String())
151
152 So(sec.Key("DURATION_404").MustDuration(dur).Seconds(), ShouldEqual, dur.Seconds())
153
154 Convey("Must should set default as key value", func() {
155 So(sec.Key("STRING_404").String(), ShouldEqual, "404")
156 So(sec.Key("BOOL_404").String(), ShouldEqual, "true")
157 So(sec.Key("FLOAT64_404").String(), ShouldEqual, "2.5")
158 So(sec.Key("INT_404").String(), ShouldEqual, "15")
159 So(sec.Key("INT64_404").String(), ShouldEqual, "15")
160 So(sec.Key("UINT_404").String(), ShouldEqual, "6")
161 So(sec.Key("UINT64_404").String(), ShouldEqual, "6")
162 So(sec.Key("TIME_404").String(), ShouldEqual, "2014-01-01T20:17:05Z")
163 So(sec.Key("DURATION_404").String(), ShouldEqual, "2h45m0s")
164 })
165 })
166 })
167 })
168
169 Convey("Get value with candidates", func() {
170 sec := cfg.Section("types")
171 So(sec.Key("STRING").In("", []string{"str", "arr", "types"}), ShouldEqual, "str")
172 So(sec.Key("FLOAT64").InFloat64(0, []float64{1.25, 2.5, 3.75}), ShouldEqual, 1.25)
173 So(sec.Key("INT").InInt(0, []int{10, 20, 30}), ShouldEqual, 10)
174 So(sec.Key("INT").InInt64(0, []int64{10, 20, 30}), ShouldEqual, 10)
175 So(sec.Key("UINT").InUint(0, []uint{3, 6, 9}), ShouldEqual, 3)
176 So(sec.Key("UINT").InUint64(0, []uint64{3, 6, 9}), ShouldEqual, 3)
177
178 zt, err := time.Parse(time.RFC3339, "0001-01-01T01:00:00Z")
179 So(err, ShouldBeNil)
180 t, err := time.Parse(time.RFC3339, "2015-01-01T20:17:05Z")
181 So(err, ShouldBeNil)
182 So(sec.Key("TIME").InTime(zt, []time.Time{t, time.Now(), time.Now().Add(1 * time.Second)}).String(), ShouldEqual, t.String())
183
184 Convey("Get value with candidates and default value", func() {
185 So(sec.Key("STRING_404").In("str", []string{"str", "arr", "types"}), ShouldEqual, "str")
186 So(sec.Key("FLOAT64_404").InFloat64(1.25, []float64{1.25, 2.5, 3.75}), ShouldEqual, 1.25)
187 So(sec.Key("INT_404").InInt(10, []int{10, 20, 30}), ShouldEqual, 10)
188 So(sec.Key("INT64_404").InInt64(10, []int64{10, 20, 30}), ShouldEqual, 10)
189 So(sec.Key("UINT_404").InUint(3, []uint{3, 6, 9}), ShouldEqual, 3)
190 So(sec.Key("UINT_404").InUint64(3, []uint64{3, 6, 9}), ShouldEqual, 3)
191 So(sec.Key("TIME_404").InTime(t, []time.Time{time.Now(), time.Now(), time.Now().Add(1 * time.Second)}).String(), ShouldEqual, t.String())
192 })
193 })
194
195 Convey("Get values in range", func() {
196 sec := cfg.Section("types")
197 So(sec.Key("FLOAT64").RangeFloat64(0, 1, 2), ShouldEqual, 1.25)
198 So(sec.Key("INT").RangeInt(0, 10, 20), ShouldEqual, 10)
199 So(sec.Key("INT").RangeInt64(0, 10, 20), ShouldEqual, 10)
200
201 minT, err := time.Parse(time.RFC3339, "0001-01-01T01:00:00Z")
202 So(err, ShouldBeNil)
203 midT, err := time.Parse(time.RFC3339, "2013-01-01T01:00:00Z")
204 So(err, ShouldBeNil)
205 maxT, err := time.Parse(time.RFC3339, "9999-01-01T01:00:00Z")
206 So(err, ShouldBeNil)
207 t, err := time.Parse(time.RFC3339, "2015-01-01T20:17:05Z")
208 So(err, ShouldBeNil)
209 So(sec.Key("TIME").RangeTime(t, minT, maxT).String(), ShouldEqual, t.String())
210
211 Convey("Get value in range with default value", func() {
212 So(sec.Key("FLOAT64").RangeFloat64(5, 0, 1), ShouldEqual, 5)
213 So(sec.Key("INT").RangeInt(7, 0, 5), ShouldEqual, 7)
214 So(sec.Key("INT").RangeInt64(7, 0, 5), ShouldEqual, 7)
215 So(sec.Key("TIME").RangeTime(t, minT, midT).String(), ShouldEqual, t.String())
216 })
217 })
218
219 Convey("Get values into slice", func() {
220 sec := cfg.Section("array")
221 So(strings.Join(sec.Key("STRINGS").Strings(","), ","), ShouldEqual, "en,zh,de")
222 So(len(sec.Key("STRINGS_404").Strings(",")), ShouldEqual, 0)
223
224 vals1 := sec.Key("FLOAT64S").Float64s(",")
225 float64sEqual(vals1, 1.1, 2.2, 3.3)
226
227 vals2 := sec.Key("INTS").Ints(",")
228 intsEqual(vals2, 1, 2, 3)
229
230 vals3 := sec.Key("INTS").Int64s(",")
231 int64sEqual(vals3, 1, 2, 3)
232
233 vals4 := sec.Key("UINTS").Uints(",")
234 uintsEqual(vals4, 1, 2, 3)
235
236 vals5 := sec.Key("UINTS").Uint64s(",")
237 uint64sEqual(vals5, 1, 2, 3)
238
239 t, err := time.Parse(time.RFC3339, "2015-01-01T20:17:05Z")
240 So(err, ShouldBeNil)
241 vals6 := sec.Key("TIMES").Times(",")
242 timesEqual(vals6, t, t, t)
243 })
244
245 Convey("Get valid values into slice", func() {
246 sec := cfg.Section("array")
247 vals1 := sec.Key("FLOAT64S").ValidFloat64s(",")
248 float64sEqual(vals1, 1.1, 2.2, 3.3)
249
250 vals2 := sec.Key("INTS").ValidInts(",")
251 intsEqual(vals2, 1, 2, 3)
252
253 vals3 := sec.Key("INTS").ValidInt64s(",")
254 int64sEqual(vals3, 1, 2, 3)
255
256 vals4 := sec.Key("UINTS").ValidUints(",")
257 uintsEqual(vals4, 1, 2, 3)
258
259 vals5 := sec.Key("UINTS").ValidUint64s(",")
260 uint64sEqual(vals5, 1, 2, 3)
261
262 t, err := time.Parse(time.RFC3339, "2015-01-01T20:17:05Z")
263 So(err, ShouldBeNil)
264 vals6 := sec.Key("TIMES").ValidTimes(",")
265 timesEqual(vals6, t, t, t)
266 })
267
268 Convey("Get values one type into slice of another type", func() {
269 sec := cfg.Section("array")
270 vals1 := sec.Key("STRINGS").ValidFloat64s(",")
271 So(vals1, ShouldBeEmpty)
272
273 vals2 := sec.Key("STRINGS").ValidInts(",")
274 So(vals2, ShouldBeEmpty)
275
276 vals3 := sec.Key("STRINGS").ValidInt64s(",")
277 So(vals3, ShouldBeEmpty)
278
279 vals4 := sec.Key("STRINGS").ValidUints(",")
280 So(vals4, ShouldBeEmpty)
281
282 vals5 := sec.Key("STRINGS").ValidUint64s(",")
283 So(vals5, ShouldBeEmpty)
284
285 vals6 := sec.Key("STRINGS").ValidTimes(",")
286 So(vals6, ShouldBeEmpty)
287 })
288
289 Convey("Get valid values into slice without errors", func() {
290 sec := cfg.Section("array")
291 vals1, err := sec.Key("FLOAT64S").StrictFloat64s(",")
292 So(err, ShouldBeNil)
293 float64sEqual(vals1, 1.1, 2.2, 3.3)
294
295 vals2, err := sec.Key("INTS").StrictInts(",")
296 So(err, ShouldBeNil)
297 intsEqual(vals2, 1, 2, 3)
298
299 vals3, err := sec.Key("INTS").StrictInt64s(",")
300 So(err, ShouldBeNil)
301 int64sEqual(vals3, 1, 2, 3)
302
303 vals4, err := sec.Key("UINTS").StrictUints(",")
304 So(err, ShouldBeNil)
305 uintsEqual(vals4, 1, 2, 3)
306
307 vals5, err := sec.Key("UINTS").StrictUint64s(",")
308 So(err, ShouldBeNil)
309 uint64sEqual(vals5, 1, 2, 3)
310
311 t, err := time.Parse(time.RFC3339, "2015-01-01T20:17:05Z")
312 So(err, ShouldBeNil)
313 vals6, err := sec.Key("TIMES").StrictTimes(",")
314 So(err, ShouldBeNil)
315 timesEqual(vals6, t, t, t)
316 })
317
318 Convey("Get invalid values into slice", func() {
319 sec := cfg.Section("array")
320 vals1, err := sec.Key("STRINGS").StrictFloat64s(",")
321 So(vals1, ShouldBeEmpty)
322 So(err, ShouldNotBeNil)
323
324 vals2, err := sec.Key("STRINGS").StrictInts(",")
325 So(vals2, ShouldBeEmpty)
326 So(err, ShouldNotBeNil)
327
328 vals3, err := sec.Key("STRINGS").StrictInt64s(",")
329 So(vals3, ShouldBeEmpty)
330 So(err, ShouldNotBeNil)
331
332 vals4, err := sec.Key("STRINGS").StrictUints(",")
333 So(vals4, ShouldBeEmpty)
334 So(err, ShouldNotBeNil)
335
336 vals5, err := sec.Key("STRINGS").StrictUint64s(",")
337 So(vals5, ShouldBeEmpty)
338 So(err, ShouldNotBeNil)
339
340 vals6, err := sec.Key("STRINGS").StrictTimes(",")
341 So(vals6, ShouldBeEmpty)
342 So(err, ShouldNotBeNil)
343 })
344
345 Convey("Get key hash", func() {
346 cfg.Section("").KeysHash()
347 })
348
349 Convey("Set key value", func() {
350 k := cfg.Section("author").Key("NAME")
351 k.SetValue("无闻")
352 So(k.String(), ShouldEqual, "无闻")
353 })
354
355 Convey("Get key strings", func() {
356 So(strings.Join(cfg.Section("types").KeyStrings(), ","), ShouldEqual, "STRING,BOOL,BOOL_FALSE,FLOAT64,INT,TIME,DURATION,UINT")
357 })
358
359 Convey("Delete a key", func() {
360 cfg.Section("package.sub").DeleteKey("UNUSED_KEY")
361 _, err := cfg.Section("package.sub").GetKey("UNUSED_KEY")
362 So(err, ShouldNotBeNil)
363 })
364
365 Convey("Has Key (backwards compatible)", func() {
366 sec := cfg.Section("package.sub")
367 haskey1 := sec.Haskey("UNUSED_KEY")
368 haskey2 := sec.Haskey("CLONE_URL")
369 haskey3 := sec.Haskey("CLONE_URL_NO")
370 So(haskey1, ShouldBeTrue)
371 So(haskey2, ShouldBeTrue)
372 So(haskey3, ShouldBeFalse)
373 })
374
375 Convey("Has Key", func() {
376 sec := cfg.Section("package.sub")
377 haskey1 := sec.HasKey("UNUSED_KEY")
378 haskey2 := sec.HasKey("CLONE_URL")
379 haskey3 := sec.HasKey("CLONE_URL_NO")
380 So(haskey1, ShouldBeTrue)
381 So(haskey2, ShouldBeTrue)
382 So(haskey3, ShouldBeFalse)
383 })
384
385 Convey("Has Value", func() {
386 sec := cfg.Section("author")
387 hasvalue1 := sec.HasValue("Unknwon")
388 hasvalue2 := sec.HasValue("doc")
389 So(hasvalue1, ShouldBeTrue)
390 So(hasvalue2, ShouldBeFalse)
391 })
392 })
393
394 Convey("Test getting and setting bad values", t, func() {
395 cfg, err := Load([]byte(_CONF_DATA), "testdata/conf.ini")
396 So(err, ShouldBeNil)
397 So(cfg, ShouldNotBeNil)
398
399 Convey("Create new key with empty name", func() {
400 k, err := cfg.Section("").NewKey("", "")
401 So(err, ShouldNotBeNil)
402 So(k, ShouldBeNil)
403 })
404
405 Convey("Create new section with empty name", func() {
406 s, err := cfg.NewSection("")
407 So(err, ShouldNotBeNil)
408 So(s, ShouldBeNil)
409 })
410
411 Convey("Create new sections with empty name", func() {
412 So(cfg.NewSections(""), ShouldNotBeNil)
413 })
414
415 Convey("Get section that not exists", func() {
416 s, err := cfg.GetSection("404")
417 So(err, ShouldNotBeNil)
418 So(s, ShouldBeNil)
419
420 s = cfg.Section("404")
421 So(s, ShouldNotBeNil)
422 })
423 })
424
425 Convey("Test key hash clone", t, func() {
426 cfg, err := Load([]byte(strings.Replace("network=tcp,addr=127.0.0.1:6379,db=4,pool_size=100,idle_timeout=180", ",", "\n", -1)))
427 So(err, ShouldBeNil)
428 for _, v := range cfg.Section("").KeysHash() {
429 So(len(v), ShouldBeGreaterThan, 0)
430 }
431 })
432
433 Convey("Key has empty value", t, func() {
434 _conf := `key1=
435 key2= ; comment`
436 cfg, err := Load([]byte(_conf))
437 So(err, ShouldBeNil)
438 So(cfg.Section("").Key("key1").Value(), ShouldBeEmpty)
439 })
440 }
441
442 const _CONF_GIT_CONFIG = `
443 [remote "origin"]
444 url = https://github.com/Antergone/test1.git
445 url = https://github.com/Antergone/test2.git
446 `
447
448 func Test_Key_Shadows(t *testing.T) {
449 Convey("Shadows keys", t, func() {
450 Convey("Disable shadows", func() {
451 cfg, err := Load([]byte(_CONF_GIT_CONFIG))
452 So(err, ShouldBeNil)
453 So(cfg.Section(`remote "origin"`).Key("url").String(), ShouldEqual, "https://github.com/Antergone/test2.git")
454 })
455
456 Convey("Enable shadows", func() {
457 cfg, err := ShadowLoad([]byte(_CONF_GIT_CONFIG))
458 So(err, ShouldBeNil)
459 So(cfg.Section(`remote "origin"`).Key("url").String(), ShouldEqual, "https://github.com/Antergone/test1.git")
460 So(strings.Join(cfg.Section(`remote "origin"`).Key("url").ValueWithShadows(), " "), ShouldEqual,
461 "https://github.com/Antergone/test1.git https://github.com/Antergone/test2.git")
462
463 Convey("Save with shadows", func() {
464 var buf bytes.Buffer
465 _, err := cfg.WriteTo(&buf)
466 So(err, ShouldBeNil)
467 So(buf.String(), ShouldEqual, `[remote "origin"]
468 url = https://github.com/Antergone/test1.git
469 url = https://github.com/Antergone/test2.git
470
471 `)
472 })
473 })
474 })
475 }
476
477 func newTestFile(block bool) *File {
478 c, _ := Load([]byte(_CONF_DATA))
479 c.BlockMode = block
480 return c
481 }
482
483 func Benchmark_Key_Value(b *testing.B) {
484 c := newTestFile(true)
485 for i := 0; i < b.N; i++ {
486 c.Section("").Key("NAME").Value()
487 }
488 }
489
490 func Benchmark_Key_Value_NonBlock(b *testing.B) {
491 c := newTestFile(false)
492 for i := 0; i < b.N; i++ {
493 c.Section("").Key("NAME").Value()
494 }
495 }
496
497 func Benchmark_Key_Value_ViaSection(b *testing.B) {
498 c := newTestFile(true)
499 sec := c.Section("")
500 for i := 0; i < b.N; i++ {
501 sec.Key("NAME").Value()
502 }
503 }
504
505 func Benchmark_Key_Value_ViaSection_NonBlock(b *testing.B) {
506 c := newTestFile(false)
507 sec := c.Section("")
508 for i := 0; i < b.N; i++ {
509 sec.Key("NAME").Value()
510 }
511 }
512
513 func Benchmark_Key_Value_Direct(b *testing.B) {
514 c := newTestFile(true)
515 key := c.Section("").Key("NAME")
516 for i := 0; i < b.N; i++ {
517 key.Value()
518 }
519 }
520
521 func Benchmark_Key_Value_Direct_NonBlock(b *testing.B) {
522 c := newTestFile(false)
523 key := c.Section("").Key("NAME")
524 for i := 0; i < b.N; i++ {
525 key.Value()
526 }
527 }
528
529 func Benchmark_Key_String(b *testing.B) {
530 c := newTestFile(true)
531 for i := 0; i < b.N; i++ {
532 _ = c.Section("").Key("NAME").String()
533 }
534 }
535
536 func Benchmark_Key_String_NonBlock(b *testing.B) {
537 c := newTestFile(false)
538 for i := 0; i < b.N; i++ {
539 _ = c.Section("").Key("NAME").String()
540 }
541 }
542
543 func Benchmark_Key_String_ViaSection(b *testing.B) {
544 c := newTestFile(true)
545 sec := c.Section("")
546 for i := 0; i < b.N; i++ {
547 _ = sec.Key("NAME").String()
548 }
549 }
550
551 func Benchmark_Key_String_ViaSection_NonBlock(b *testing.B) {
552 c := newTestFile(false)
553 sec := c.Section("")
554 for i := 0; i < b.N; i++ {
555 _ = sec.Key("NAME").String()
556 }
557 }
558
559 func Benchmark_Key_SetValue(b *testing.B) {
560 c := newTestFile(true)
561 for i := 0; i < b.N; i++ {
562 c.Section("").Key("NAME").SetValue("10")
563 }
564 }
565
566 func Benchmark_Key_SetValue_VisSection(b *testing.B) {
567 c := newTestFile(true)
568 sec := c.Section("")
569 for i := 0; i < b.N; i++ {
570 sec.Key("NAME").SetValue("10")
571 }
572 }
4747 }
4848 }
4949
50 // BOM handles header of BOM-UTF8 format.
50 // BOM handles header of UTF-8, UTF-16 LE and UTF-16 BE's BOM format.
5151 // http://en.wikipedia.org/wiki/Byte_order_mark#Representations_of_byte_order_marks_by_encoding
5252 func (p *parser) BOM() error {
53 mask, err := p.buf.Peek(3)
53 mask, err := p.buf.Peek(2)
5454 if err != nil && err != io.EOF {
5555 return err
56 } else if len(mask) < 3 {
56 } else if len(mask) < 2 {
5757 return nil
58 } else if mask[0] == 239 && mask[1] == 187 && mask[2] == 191 {
58 }
59
60 switch {
61 case mask[0] == 254 && mask[1] == 255:
62 fallthrough
63 case mask[0] == 255 && mask[1] == 254:
5964 p.buf.Read(mask)
65 case mask[0] == 239 && mask[1] == 187:
66 mask, err := p.buf.Peek(3)
67 if err != nil && err != io.EOF {
68 return err
69 } else if len(mask) < 3 {
70 return nil
71 }
72 if mask[2] == 191 {
73 p.buf.Read(mask)
74 }
6075 }
6176 return nil
6277 }
110125 // Find key-value delimiter
111126 i := strings.IndexAny(line[pos+startIdx:], "=:")
112127 if i < 0 {
113 return "", -1, fmt.Errorf("key-value delimiter not found: %s", line)
128 return "", -1, ErrDelimiterNotFound{line}
114129 }
115130 endIdx = pos + i
116131 return strings.TrimSpace(line[startIdx:pos]), endIdx + startIdx + 1, nil
118133
119134 endIdx = strings.IndexAny(line, "=:")
120135 if endIdx < 0 {
121 return "", -1, fmt.Errorf("key-value delimiter not found: %s", line)
136 return "", -1, ErrDelimiterNotFound{line}
122137 }
123138 return strings.TrimSpace(line[0:endIdx]), endIdx + 1, nil
124139 }
177192 strings.IndexByte(in[1:], quote) == len(in)-2
178193 }
179194
180 func (p *parser) readValue(in []byte) (string, error) {
195 func (p *parser) readValue(in []byte, ignoreContinuation, ignoreInlineComment bool) (string, error) {
181196 line := strings.TrimLeftFunc(string(in), unicode.IsSpace)
182197 if len(line) == 0 {
183198 return "", nil
201216 return line[startIdx : pos+startIdx], nil
202217 }
203218
204 // Won't be able to reach here if value only contains whitespace.
219 // Won't be able to reach here if value only contains whitespace
205220 line = strings.TrimSpace(line)
206221
207 // Check continuation lines
208 if line[len(line)-1] == '\\' {
222 // Check continuation lines when desired
223 if !ignoreContinuation && line[len(line)-1] == '\\' {
209224 return p.readContinuationLines(line[:len(line)-1])
210225 }
211226
212 i := strings.IndexAny(line, "#;")
213 if i > -1 {
214 p.comment.WriteString(line[i:])
215 line = strings.TrimSpace(line[:i])
227 // Check if ignore inline comment
228 if !ignoreInlineComment {
229 i := strings.IndexAny(line, "#;")
230 if i > -1 {
231 p.comment.WriteString(line[i:])
232 line = strings.TrimSpace(line[:i])
233 }
216234 }
217235
218236 // Trim single quotes
234252 section, _ := f.NewSection(DEFAULT_SECTION)
235253
236254 var line []byte
255 var inUnparseableSection bool
237256 for !p.isEOF {
238257 line, err = p.readUntil('\n')
239258 if err != nil {
257276 // Section
258277 if line[0] == '[' {
259278 // Read to the next ']' (TODO: support quoted strings)
260 closeIdx := bytes.IndexByte(line, ']')
279 // TODO(unknwon): use LastIndexByte when stop supporting Go1.4
280 closeIdx := bytes.LastIndex(line, []byte("]"))
261281 if closeIdx == -1 {
262282 return fmt.Errorf("unclosed section: %s", line)
263283 }
264284
265 section, err = f.NewSection(string(line[1:closeIdx]))
285 name := string(line[1:closeIdx])
286 section, err = f.NewSection(name)
266287 if err != nil {
267288 return err
268289 }
277298 // Reset aotu-counter and comments
278299 p.comment.Reset()
279300 p.count = 1
301
302 inUnparseableSection = false
303 for i := range f.options.UnparseableSections {
304 if f.options.UnparseableSections[i] == name ||
305 (f.options.Insensitive && strings.ToLower(f.options.UnparseableSections[i]) == strings.ToLower(name)) {
306 inUnparseableSection = true
307 continue
308 }
309 }
280310 continue
281311 }
282312
313 if inUnparseableSection {
314 section.isRawSection = true
315 section.rawBody += string(line)
316 continue
317 }
318
283319 kname, offset, err := readKeyName(line)
284320 if err != nil {
321 // Treat as boolean key when desired, and whole line is key name.
322 if IsErrDelimiterNotFound(err) && f.options.AllowBooleanKeys {
323 kname, err := p.readValue(line, f.options.IgnoreContinuation, f.options.IgnoreInlineComment)
324 if err != nil {
325 return err
326 }
327 key, err := section.NewBooleanKey(kname)
328 if err != nil {
329 return err
330 }
331 key.Comment = strings.TrimSpace(p.comment.String())
332 p.comment.Reset()
333 continue
334 }
285335 return err
286336 }
287337
293343 p.count++
294344 }
295345
296 key, err := section.NewKey(kname, "")
297 if err != nil {
298 return err
299 }
300 key.isAutoIncr = isAutoIncr
301
302 value, err := p.readValue(line[offset:])
303 if err != nil {
304 return err
305 }
306 key.SetValue(value)
346 value, err := p.readValue(line[offset:], f.options.IgnoreContinuation, f.options.IgnoreInlineComment)
347 if err != nil {
348 return err
349 }
350
351 key, err := section.NewKey(kname, value)
352 if err != nil {
353 return err
354 }
355 key.isAutoIncrement = isAutoIncr
307356 key.Comment = strings.TrimSpace(p.comment.String())
308357 p.comment.Reset()
309358 }
0 // Copyright 2016 Unknwon
1 //
2 // Licensed under the Apache License, Version 2.0 (the "License"): you may
3 // not use this file except in compliance with the License. You may obtain
4 // a copy of the License at
5 //
6 // http://www.apache.org/licenses/LICENSE-2.0
7 //
8 // Unless required by applicable law or agreed to in writing, software
9 // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
10 // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
11 // License for the specific language governing permissions and limitations
12 // under the License.
13
14 package ini
15
16 import (
17 "testing"
18
19 . "github.com/smartystreets/goconvey/convey"
20 )
21
22 func Test_BOM(t *testing.T) {
23 Convey("Test handling BOM", t, func() {
24 Convey("UTF-8-BOM", func() {
25 cfg, err := Load("testdata/UTF-8-BOM.ini")
26 So(err, ShouldBeNil)
27 So(cfg, ShouldNotBeNil)
28
29 So(cfg.Section("author").Key("E-MAIL").String(), ShouldEqual, "u@gogs.io")
30 })
31
32 Convey("UTF-16-LE-BOM", func() {
33 cfg, err := Load("testdata/UTF-16-LE-BOM.ini")
34 So(err, ShouldBeNil)
35 So(cfg, ShouldNotBeNil)
36 })
37
38 Convey("UTF-16-BE-BOM", func() {
39 })
40 })
41 }
0 // Copyright 2014 Unknwon
1 //
2 // Licensed under the Apache License, Version 2.0 (the "License"): you may
3 // not use this file except in compliance with the License. You may obtain
4 // a copy of the License at
5 //
6 // http://www.apache.org/licenses/LICENSE-2.0
7 //
8 // Unless required by applicable law or agreed to in writing, software
9 // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
10 // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
11 // License for the specific language governing permissions and limitations
12 // under the License.
13
14 package ini
15
16 import (
17 "errors"
18 "fmt"
19 "strings"
20 )
21
22 // Section represents a config section.
23 type Section struct {
24 f *File
25 Comment string
26 name string
27 keys map[string]*Key
28 keyList []string
29 keysHash map[string]string
30
31 isRawSection bool
32 rawBody string
33 }
34
35 func newSection(f *File, name string) *Section {
36 return &Section{
37 f: f,
38 name: name,
39 keys: make(map[string]*Key),
40 keyList: make([]string, 0, 10),
41 keysHash: make(map[string]string),
42 }
43 }
44
45 // Name returns name of Section.
46 func (s *Section) Name() string {
47 return s.name
48 }
49
50 // Body returns rawBody of Section if the section was marked as unparseable.
51 // It still follows the other rules of the INI format surrounding leading/trailing whitespace.
52 func (s *Section) Body() string {
53 return strings.TrimSpace(s.rawBody)
54 }
55
56 // NewKey creates a new key to given section.
57 func (s *Section) NewKey(name, val string) (*Key, error) {
58 if len(name) == 0 {
59 return nil, errors.New("error creating new key: empty key name")
60 } else if s.f.options.Insensitive {
61 name = strings.ToLower(name)
62 }
63
64 if s.f.BlockMode {
65 s.f.lock.Lock()
66 defer s.f.lock.Unlock()
67 }
68
69 if inSlice(name, s.keyList) {
70 if s.f.options.AllowShadows {
71 if err := s.keys[name].addShadow(val); err != nil {
72 return nil, err
73 }
74 } else {
75 s.keys[name].value = val
76 }
77 return s.keys[name], nil
78 }
79
80 s.keyList = append(s.keyList, name)
81 s.keys[name] = newKey(s, name, val)
82 s.keysHash[name] = val
83 return s.keys[name], nil
84 }
85
86 // NewBooleanKey creates a new boolean type key to given section.
87 func (s *Section) NewBooleanKey(name string) (*Key, error) {
88 key, err := s.NewKey(name, "true")
89 if err != nil {
90 return nil, err
91 }
92
93 key.isBooleanType = true
94 return key, nil
95 }
96
97 // GetKey returns key in section by given name.
98 func (s *Section) GetKey(name string) (*Key, error) {
99 // FIXME: change to section level lock?
100 if s.f.BlockMode {
101 s.f.lock.RLock()
102 }
103 if s.f.options.Insensitive {
104 name = strings.ToLower(name)
105 }
106 key := s.keys[name]
107 if s.f.BlockMode {
108 s.f.lock.RUnlock()
109 }
110
111 if key == nil {
112 // Check if it is a child-section.
113 sname := s.name
114 for {
115 if i := strings.LastIndex(sname, "."); i > -1 {
116 sname = sname[:i]
117 sec, err := s.f.GetSection(sname)
118 if err != nil {
119 continue
120 }
121 return sec.GetKey(name)
122 } else {
123 break
124 }
125 }
126 return nil, fmt.Errorf("error when getting key of section '%s': key '%s' not exists", s.name, name)
127 }
128 return key, nil
129 }
130
131 // HasKey returns true if section contains a key with given name.
132 func (s *Section) HasKey(name string) bool {
133 key, _ := s.GetKey(name)
134 return key != nil
135 }
136
137 // Haskey is a backwards-compatible name for HasKey.
138 func (s *Section) Haskey(name string) bool {
139 return s.HasKey(name)
140 }
141
142 // HasValue returns true if section contains given raw value.
143 func (s *Section) HasValue(value string) bool {
144 if s.f.BlockMode {
145 s.f.lock.RLock()
146 defer s.f.lock.RUnlock()
147 }
148
149 for _, k := range s.keys {
150 if value == k.value {
151 return true
152 }
153 }
154 return false
155 }
156
157 // Key assumes named Key exists in section and returns a zero-value when not.
158 func (s *Section) Key(name string) *Key {
159 key, err := s.GetKey(name)
160 if err != nil {
161 // It's OK here because the only possible error is empty key name,
162 // but if it's empty, this piece of code won't be executed.
163 key, _ = s.NewKey(name, "")
164 return key
165 }
166 return key
167 }
168
169 // Keys returns list of keys of section.
170 func (s *Section) Keys() []*Key {
171 keys := make([]*Key, len(s.keyList))
172 for i := range s.keyList {
173 keys[i] = s.Key(s.keyList[i])
174 }
175 return keys
176 }
177
178 // ParentKeys returns list of keys of parent section.
179 func (s *Section) ParentKeys() []*Key {
180 var parentKeys []*Key
181 sname := s.name
182 for {
183 if i := strings.LastIndex(sname, "."); i > -1 {
184 sname = sname[:i]
185 sec, err := s.f.GetSection(sname)
186 if err != nil {
187 continue
188 }
189 parentKeys = append(parentKeys, sec.Keys()...)
190 } else {
191 break
192 }
193
194 }
195 return parentKeys
196 }
197
198 // KeyStrings returns list of key names of section.
199 func (s *Section) KeyStrings() []string {
200 list := make([]string, len(s.keyList))
201 copy(list, s.keyList)
202 return list
203 }
204
205 // KeysHash returns keys hash consisting of names and values.
206 func (s *Section) KeysHash() map[string]string {
207 if s.f.BlockMode {
208 s.f.lock.RLock()
209 defer s.f.lock.RUnlock()
210 }
211
212 hash := map[string]string{}
213 for key, value := range s.keysHash {
214 hash[key] = value
215 }
216 return hash
217 }
218
219 // DeleteKey deletes a key from section.
220 func (s *Section) DeleteKey(name string) {
221 if s.f.BlockMode {
222 s.f.lock.Lock()
223 defer s.f.lock.Unlock()
224 }
225
226 for i, k := range s.keyList {
227 if k == name {
228 s.keyList = append(s.keyList[:i], s.keyList[i+1:]...)
229 delete(s.keys, name)
230 return
231 }
232 }
233 }
234
235 // ChildSections returns a list of child sections of current section.
236 // For example, "[parent.child1]" and "[parent.child12]" are child sections
237 // of section "[parent]".
238 func (s *Section) ChildSections() []*Section {
239 prefix := s.name + "."
240 children := make([]*Section, 0, 3)
241 for _, name := range s.f.sectionList {
242 if strings.HasPrefix(name, prefix) {
243 children = append(children, s.f.sections[name])
244 }
245 }
246 return children
247 }
0 // Copyright 2014 Unknwon
1 //
2 // Licensed under the Apache License, Version 2.0 (the "License"): you may
3 // not use this file except in compliance with the License. You may obtain
4 // a copy of the License at
5 //
6 // http://www.apache.org/licenses/LICENSE-2.0
7 //
8 // Unless required by applicable law or agreed to in writing, software
9 // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
10 // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
11 // License for the specific language governing permissions and limitations
12 // under the License.
13
14 package ini
15
16 import (
17 "strings"
18 "testing"
19
20 . "github.com/smartystreets/goconvey/convey"
21 )
22
23 func Test_Section(t *testing.T) {
24 Convey("Test CRD sections", t, func() {
25 cfg, err := Load([]byte(_CONF_DATA), "testdata/conf.ini")
26 So(err, ShouldBeNil)
27 So(cfg, ShouldNotBeNil)
28
29 Convey("Get section strings", func() {
30 So(strings.Join(cfg.SectionStrings(), ","), ShouldEqual, "DEFAULT,author,package,package.sub,features,types,array,note,comments,advance")
31 })
32
33 Convey("Delete a section", func() {
34 cfg.DeleteSection("")
35 So(cfg.SectionStrings()[0], ShouldNotEqual, DEFAULT_SECTION)
36 })
37
38 Convey("Create new sections", func() {
39 cfg.NewSections("test", "test2")
40 _, err := cfg.GetSection("test")
41 So(err, ShouldBeNil)
42 _, err = cfg.GetSection("test2")
43 So(err, ShouldBeNil)
44 })
45 })
46 }
47
48 func Test_SectionRaw(t *testing.T) {
49 Convey("Test section raw string", t, func() {
50 cfg, err := LoadSources(
51 LoadOptions{
52 Insensitive: true,
53 UnparseableSections: []string{"core_lesson", "comments"},
54 },
55 "testdata/aicc.ini")
56 So(err, ShouldBeNil)
57 So(cfg, ShouldNotBeNil)
58
59 Convey("Get section strings", func() {
60 So(strings.Join(cfg.SectionStrings(), ","), ShouldEqual, "DEFAULT,core,core_lesson,comments")
61 })
62
63 Convey("Validate non-raw section", func() {
64 val, err := cfg.Section("core").GetKey("lesson_status")
65 So(err, ShouldBeNil)
66 So(val.String(), ShouldEqual, "C")
67 })
68
69 Convey("Validate raw section", func() {
70 So(cfg.Section("core_lesson").Body(), ShouldEqual, `my lesson state data – 1111111111111111111000000000000000001110000
71 111111111111111111100000000000111000000000 – end my lesson state data`)
72 })
73 })
74 }
1818 "errors"
1919 "fmt"
2020 "reflect"
21 "strings"
2122 "time"
2223 "unicode"
2324 )
7576
7677 var reflectTime = reflect.TypeOf(time.Now()).Kind()
7778
79 // setSliceWithProperType sets proper values to slice based on its type.
80 func setSliceWithProperType(key *Key, field reflect.Value, delim string, allowShadow bool) error {
81 var strs []string
82 if allowShadow {
83 strs = key.StringsWithShadows(delim)
84 } else {
85 strs = key.Strings(delim)
86 }
87
88 numVals := len(strs)
89 if numVals == 0 {
90 return nil
91 }
92
93 var vals interface{}
94
95 sliceOf := field.Type().Elem().Kind()
96 switch sliceOf {
97 case reflect.String:
98 vals = strs
99 case reflect.Int:
100 vals, _ = key.parseInts(strs, true, false)
101 case reflect.Int64:
102 vals, _ = key.parseInt64s(strs, true, false)
103 case reflect.Uint:
104 vals, _ = key.parseUints(strs, true, false)
105 case reflect.Uint64:
106 vals, _ = key.parseUint64s(strs, true, false)
107 case reflect.Float64:
108 vals, _ = key.parseFloat64s(strs, true, false)
109 case reflectTime:
110 vals, _ = key.parseTimesFormat(time.RFC3339, strs, true, false)
111 default:
112 return fmt.Errorf("unsupported type '[]%s'", sliceOf)
113 }
114
115 slice := reflect.MakeSlice(field.Type(), numVals, numVals)
116 for i := 0; i < numVals; i++ {
117 switch sliceOf {
118 case reflect.String:
119 slice.Index(i).Set(reflect.ValueOf(vals.([]string)[i]))
120 case reflect.Int:
121 slice.Index(i).Set(reflect.ValueOf(vals.([]int)[i]))
122 case reflect.Int64:
123 slice.Index(i).Set(reflect.ValueOf(vals.([]int64)[i]))
124 case reflect.Uint:
125 slice.Index(i).Set(reflect.ValueOf(vals.([]uint)[i]))
126 case reflect.Uint64:
127 slice.Index(i).Set(reflect.ValueOf(vals.([]uint64)[i]))
128 case reflect.Float64:
129 slice.Index(i).Set(reflect.ValueOf(vals.([]float64)[i]))
130 case reflectTime:
131 slice.Index(i).Set(reflect.ValueOf(vals.([]time.Time)[i]))
132 }
133 }
134 field.Set(slice)
135 return nil
136 }
137
78138 // setWithProperType sets proper value to field based on its type,
79139 // but it does not return error for failing parsing,
80140 // because we want to use default value that is already assigned to strcut.
81 func setWithProperType(t reflect.Type, key *Key, field reflect.Value, delim string) error {
141 func setWithProperType(t reflect.Type, key *Key, field reflect.Value, delim string, allowShadow bool) error {
82142 switch t.Kind() {
83143 case reflect.String:
84144 if len(key.String()) == 0 {
107167 // byte is an alias for uint8, so supporting uint8 breaks support for byte
108168 case reflect.Uint, reflect.Uint16, reflect.Uint32, reflect.Uint64:
109169 durationVal, err := key.Duration()
110 if err == nil {
170 // Skip zero value
171 if err == nil && int(durationVal) > 0 {
111172 field.Set(reflect.ValueOf(durationVal))
112173 return nil
113174 }
118179 }
119180 field.SetUint(uintVal)
120181
121 case reflect.Float64:
182 case reflect.Float32, reflect.Float64:
122183 floatVal, err := key.Float64()
123184 if err != nil {
124185 return nil
131192 }
132193 field.Set(reflect.ValueOf(timeVal))
133194 case reflect.Slice:
134 vals := key.Strings(delim)
135 numVals := len(vals)
136 if numVals == 0 {
137 return nil
138 }
139
140 sliceOf := field.Type().Elem().Kind()
141
142 var times []time.Time
143 if sliceOf == reflectTime {
144 times = key.Times(delim)
145 }
146
147 slice := reflect.MakeSlice(field.Type(), numVals, numVals)
148 for i := 0; i < numVals; i++ {
149 switch sliceOf {
150 case reflectTime:
151 slice.Index(i).Set(reflect.ValueOf(times[i]))
152 default:
153 slice.Index(i).Set(reflect.ValueOf(vals[i]))
154 }
155 }
156 field.Set(slice)
195 return setSliceWithProperType(key, field, delim, allowShadow)
157196 default:
158197 return fmt.Errorf("unsupported type '%s'", t)
159198 }
160199 return nil
200 }
201
202 func parseTagOptions(tag string) (rawName string, omitEmpty bool, allowShadow bool) {
203 opts := strings.SplitN(tag, ",", 3)
204 rawName = opts[0]
205 if len(opts) > 1 {
206 omitEmpty = opts[1] == "omitempty"
207 }
208 if len(opts) > 2 {
209 allowShadow = opts[2] == "allowshadow"
210 }
211 return rawName, omitEmpty, allowShadow
161212 }
162213
163214 func (s *Section) mapTo(val reflect.Value) error {
175226 continue
176227 }
177228
178 fieldName := s.parseFieldName(tpField.Name, tag)
229 rawName, _, allowShadow := parseTagOptions(tag)
230 fieldName := s.parseFieldName(tpField.Name, rawName)
179231 if len(fieldName) == 0 || !field.CanSet() {
180232 continue
181233 }
196248 }
197249
198250 if key, err := s.GetKey(fieldName); err == nil {
199 if err = setWithProperType(tpField.Type, key, field, parseDelim(tpField.Tag.Get("delim"))); err != nil {
251 delim := parseDelim(tpField.Tag.Get("delim"))
252 if err = setWithProperType(tpField.Type, key, field, delim, allowShadow); err != nil {
200253 return fmt.Errorf("error mapping field(%s): %v", fieldName, err)
201254 }
202255 }
238291 return MapToWithMapper(v, nil, source, others...)
239292 }
240293
241 // reflectWithProperType does the opposite thing with setWithProperType.
294 // reflectSliceWithProperType does the opposite thing as setSliceWithProperType.
295 func reflectSliceWithProperType(key *Key, field reflect.Value, delim string) error {
296 slice := field.Slice(0, field.Len())
297 if field.Len() == 0 {
298 return nil
299 }
300
301 var buf bytes.Buffer
302 sliceOf := field.Type().Elem().Kind()
303 for i := 0; i < field.Len(); i++ {
304 switch sliceOf {
305 case reflect.String:
306 buf.WriteString(slice.Index(i).String())
307 case reflect.Int, reflect.Int64:
308 buf.WriteString(fmt.Sprint(slice.Index(i).Int()))
309 case reflect.Uint, reflect.Uint64:
310 buf.WriteString(fmt.Sprint(slice.Index(i).Uint()))
311 case reflect.Float64:
312 buf.WriteString(fmt.Sprint(slice.Index(i).Float()))
313 case reflectTime:
314 buf.WriteString(slice.Index(i).Interface().(time.Time).Format(time.RFC3339))
315 default:
316 return fmt.Errorf("unsupported type '[]%s'", sliceOf)
317 }
318 buf.WriteString(delim)
319 }
320 key.SetValue(buf.String()[:buf.Len()-1])
321 return nil
322 }
323
324 // reflectWithProperType does the opposite thing as setWithProperType.
242325 func reflectWithProperType(t reflect.Type, key *Key, field reflect.Value, delim string) error {
243326 switch t.Kind() {
244327 case reflect.String:
245328 key.SetValue(field.String())
246 case reflect.Bool,
247 reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64,
248 reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64,
249 reflect.Float64,
250 reflectTime:
251 key.SetValue(fmt.Sprint(field))
329 case reflect.Bool:
330 key.SetValue(fmt.Sprint(field.Bool()))
331 case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
332 key.SetValue(fmt.Sprint(field.Int()))
333 case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
334 key.SetValue(fmt.Sprint(field.Uint()))
335 case reflect.Float32, reflect.Float64:
336 key.SetValue(fmt.Sprint(field.Float()))
337 case reflectTime:
338 key.SetValue(fmt.Sprint(field.Interface().(time.Time).Format(time.RFC3339)))
252339 case reflect.Slice:
253 vals := field.Slice(0, field.Len())
254 if field.Len() == 0 {
255 return nil
256 }
257
258 var buf bytes.Buffer
259 isTime := fmt.Sprint(field.Type()) == "[]time.Time"
260 for i := 0; i < field.Len(); i++ {
261 if isTime {
262 buf.WriteString(vals.Index(i).Interface().(time.Time).Format(time.RFC3339))
263 } else {
264 buf.WriteString(fmt.Sprint(vals.Index(i)))
265 }
266 buf.WriteString(delim)
267 }
268 key.SetValue(buf.String()[:buf.Len()-1])
340 return reflectSliceWithProperType(key, field, delim)
269341 default:
270342 return fmt.Errorf("unsupported type '%s'", t)
271343 }
272344 return nil
345 }
346
347 // CR: copied from encoding/json/encode.go with modifications of time.Time support.
348 // TODO: add more test coverage.
349 func isEmptyValue(v reflect.Value) bool {
350 switch v.Kind() {
351 case reflect.Array, reflect.Map, reflect.Slice, reflect.String:
352 return v.Len() == 0
353 case reflect.Bool:
354 return !v.Bool()
355 case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
356 return v.Int() == 0
357 case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
358 return v.Uint() == 0
359 case reflect.Float32, reflect.Float64:
360 return v.Float() == 0
361 case reflectTime:
362 return v.Interface().(time.Time).IsZero()
363 case reflect.Interface, reflect.Ptr:
364 return v.IsNil()
365 }
366 return false
273367 }
274368
275369 func (s *Section) reflectFrom(val reflect.Value) error {
287381 continue
288382 }
289383
290 fieldName := s.parseFieldName(tpField.Name, tag)
384 opts := strings.SplitN(tag, ",", 2)
385 if len(opts) == 2 && opts[1] == "omitempty" && isEmptyValue(field) {
386 continue
387 }
388
389 fieldName := s.parseFieldName(tpField.Name, opts[0])
291390 if len(fieldName) == 0 || !field.CanSet() {
292391 continue
293392 }
294393
295394 if (tpField.Type.Kind() == reflect.Ptr && tpField.Anonymous) ||
296 (tpField.Type.Kind() == reflect.Struct) {
395 (tpField.Type.Kind() == reflect.Struct && tpField.Type.Name() != "Time") {
297396 // Note: The only error here is section doesn't exist.
298397 sec, err := s.f.GetSection(fieldName)
299398 if err != nil {
301400 sec, _ = s.f.NewSection(fieldName)
302401 }
303402 if err = sec.reflectFrom(field); err != nil {
304 return fmt.Errorf("error reflecting field(%s): %v", fieldName, err)
403 return fmt.Errorf("error reflecting field (%s): %v", fieldName, err)
305404 }
306405 continue
307406 }
312411 key, _ = s.NewKey(fieldName, "")
313412 }
314413 if err = reflectWithProperType(tpField.Type, key, field, parseDelim(tpField.Tag.Get("delim"))); err != nil {
315 return fmt.Errorf("error reflecting field(%s): %v", fieldName, err)
414 return fmt.Errorf("error reflecting field (%s): %v", fieldName, err)
316415 }
317416
318417 }
1414 package ini
1515
1616 import (
17 "bytes"
18 "fmt"
1719 "strings"
1820 "testing"
1921 "time"
2224 )
2325
2426 type testNested struct {
25 Cities []string `delim:"|"`
26 Visits []time.Time
27 Note string
28 Unused int `ini:"-"`
27 Cities []string `delim:"|"`
28 Visits []time.Time
29 Years []int
30 Numbers []int64
31 Ages []uint
32 Populations []uint64
33 Coordinates []float64
34 Note string
35 Unused int `ini:"-"`
2936 }
3037
3138 type testEmbeded struct {
4350 *testEmbeded `ini:"grade"`
4451 Unused int `ini:"-"`
4552 Unsigned uint
53 Omitted bool `ini:"omitthis,omitempty"`
54 Shadows []string `ini:",,allowshadow"`
55 ShadowInts []int `ini:"Shadows,,allowshadow"`
4656 }
4757
4858 const _CONF_DATA_STRUCT = `
5363 Born = 1993-10-07T20:17:05Z
5464 Duration = 2h45m
5565 Unsigned = 3
66 omitthis = true
67 Shadows = 1, 2
68 Shadows = 3, 4
5669
5770 [Others]
5871 Cities = HangZhou|Boston
5972 Visits = 1993-10-07T20:17:05Z, 1993-10-07T20:17:05Z
73 Years = 1993,1994
74 Numbers = 10010,10086
75 Ages = 18,19
76 Populations = 12345678,98765432
77 Coordinates = 192.168,10.11
6078 Note = Hello world!
6179
6280 [grade]
129147
130148 So(strings.Join(ts.Others.Cities, ","), ShouldEqual, "HangZhou,Boston")
131149 So(ts.Others.Visits[0].String(), ShouldEqual, t.String())
150 So(fmt.Sprint(ts.Others.Years), ShouldEqual, "[1993 1994]")
151 So(fmt.Sprint(ts.Others.Numbers), ShouldEqual, "[10010 10086]")
152 So(fmt.Sprint(ts.Others.Ages), ShouldEqual, "[18 19]")
153 So(fmt.Sprint(ts.Others.Populations), ShouldEqual, "[12345678 98765432]")
154 So(fmt.Sprint(ts.Others.Coordinates), ShouldEqual, "[192.168 10.11]")
132155 So(ts.Others.Note, ShouldEqual, "Hello world!")
133156 So(ts.testEmbeded.GPA, ShouldEqual, 2.8)
134157 })
167190 So(cfg.MapTo(&unsupport4{}), ShouldNotBeNil)
168191 })
169192
193 Convey("Map to omitempty field", func() {
194 ts := new(testStruct)
195 So(MapTo(ts, []byte(_CONF_DATA_STRUCT)), ShouldBeNil)
196
197 So(ts.Omitted, ShouldEqual, true)
198 })
199
200 Convey("Map with shadows", func() {
201 cfg, err := LoadSources(LoadOptions{AllowShadows: true}, []byte(_CONF_DATA_STRUCT))
202 So(err, ShouldBeNil)
203 ts := new(testStruct)
204 So(cfg.MapTo(ts), ShouldBeNil)
205
206 So(strings.Join(ts.Shadows, " "), ShouldEqual, "1 2 3 4")
207 So(fmt.Sprintf("%v", ts.ShadowInts), ShouldEqual, "[1 2 3 4]")
208 })
209
170210 Convey("Map from invalid data source", func() {
171211 So(MapTo(&testStruct{}, "hi"), ShouldNotBeNil)
172212 })
190230
191231 Convey("Reflect from struct", t, func() {
192232 type Embeded struct {
193 Dates []time.Time `delim:"|"`
194 Places []string
195 None []int
233 Dates []time.Time `delim:"|"`
234 Places []string
235 Years []int
236 Numbers []int64
237 Ages []uint
238 Populations []uint64
239 Coordinates []float64
240 None []int
196241 }
197242 type Author struct {
198243 Name string `ini:"NAME"`
199244 Male bool
200245 Age int
246 Height uint
201247 GPA float64
248 Date time.Time
202249 NeverMind string `ini:"-"`
203250 *Embeded `ini:"infos"`
204251 }
205 a := &Author{"Unknwon", true, 21, 2.8, "",
252
253 t, err := time.Parse(time.RFC3339, "1993-10-07T20:17:05Z")
254 So(err, ShouldBeNil)
255 a := &Author{"Unknwon", true, 21, 100, 2.8, t, "",
206256 &Embeded{
207 []time.Time{time.Now(), time.Now()},
257 []time.Time{t, t},
208258 []string{"HangZhou", "Boston"},
259 []int{1993, 1994},
260 []int64{10010, 10086},
261 []uint{18, 19},
262 []uint64{12345678, 98765432},
263 []float64{192.168, 10.11},
209264 []int{},
210265 }}
211266 cfg := Empty()
212267 So(ReflectFrom(cfg, a), ShouldBeNil)
213 cfg.SaveTo("testdata/conf_reflect.ini")
268
269 var buf bytes.Buffer
270 _, err = cfg.WriteTo(&buf)
271 So(err, ShouldBeNil)
272 So(buf.String(), ShouldEqual, `NAME = Unknwon
273 Male = true
274 Age = 21
275 Height = 100
276 GPA = 2.8
277 Date = 1993-10-07T20:17:05Z
278
279 [infos]
280 Dates = 1993-10-07T20:17:05Z|1993-10-07T20:17:05Z
281 Places = HangZhou,Boston
282 Years = 1993,1994
283 Numbers = 10010,10086
284 Ages = 18,19
285 Populations = 12345678,98765432
286 Coordinates = 192.168,10.11
287 None =
288
289 `)
214290
215291 Convey("Reflect from non-point struct", func() {
216292 So(ReflectFrom(cfg, Author{}), ShouldNotBeNil)
293 })
294
295 Convey("Reflect from struct with omitempty", func() {
296 cfg := Empty()
297 type SpecialStruct struct {
298 FirstName string `ini:"first_name"`
299 LastName string `ini:"last_name"`
300 JustOmitMe string `ini:"omitempty"`
301 LastLogin time.Time `ini:"last_login,omitempty"`
302 LastLogin2 time.Time `ini:",omitempty"`
303 NotEmpty int `ini:"omitempty"`
304 }
305
306 So(ReflectFrom(cfg, &SpecialStruct{FirstName: "John", LastName: "Doe", NotEmpty: 9}), ShouldBeNil)
307
308 var buf bytes.Buffer
309 _, err = cfg.WriteTo(&buf)
310 So(buf.String(), ShouldEqual, `first_name = John
311 last_name = Doe
312 omitempty = 9
313
314 `)
217315 })
218316 })
219317 }
Binary diff not shown
Binary diff not shown
0 [author]
1 E-MAIL = u@gogs.io
0 [Core]
1 Lesson_Location = 87
2 Lesson_Status = C
3 Score = 3
4 Time = 00:02:30
5
6 [CORE_LESSON]
7 my lesson state data – 1111111111111111111000000000000000001110000
8 111111111111111111100000000000111000000000 – end my lesson state data
9 [COMMENTS]
10 <1><L.Slide#2> This slide has the fuel listed in the wrong units <e.1>
0 [author]
0 [author]
11 E-MAIL = u@gogs.io