Codebase list golang-github-go-ini-ini / fresh-releases/main
New upstream release. Debian Janitor 2 years ago
36 changed file(s) with 3012 addition(s) and 2054 deletion(s). Raw diff Collapse all Expand all
0 # http://editorconfig.org
1
2 root = true
3
4 [*]
5 charset = utf-8
6 end_of_line = lf
7 insert_final_newline = true
8 trim_trailing_whitespace = true
9
10 [*_test.go]
11 trim_trailing_whitespace = false
+0
-23
.github/ISSUE_TEMPLATE/bug_report.md less more
0 ---
1 name: Bug report
2 about: Create a report to help us improve
3 title: ''
4 labels: ''
5 assignees: ''
6
7 ---
8
9 **Describe the bug**
10 A clear and concise description of what the bug is.
11
12 **To Reproduce**
13 A code snippet to reproduce the problem described above.
14
15 **Expected behavior**
16 A clear and concise description of what you expected to happen.
17
18 **Screenshots**
19 If applicable, add screenshots to help explain your problem.
20
21 **Additional context**
22 Add any other context about the problem here, or any suggestion to fix the problem.
0 name: Bug report
1 description: File a bug report to help us improve
2 labels: ["bug"]
3 body:
4 - type: markdown
5 attributes:
6 value: |
7 Thanks for taking the time to fill out this bug report!
8
9 - Before you file an issue read the [Contributing guide](https://github.com/go-ini/ini/blob/main/.github/contributing.md).
10 - Check to make sure someone hasn't already opened a similar [issue](https://github.com/go-ini/ini/issues).
11 - type: input
12 attributes:
13 label: Version
14 description: Please specify the exact Go module version you're reporting for.
15 validations:
16 required: true
17 - type: textarea
18 attributes:
19 label: Describe the bug
20 description: A clear and concise description of what the bug is.
21 validations:
22 required: true
23 - type: textarea
24 attributes:
25 label: To reproduce
26 description: A code snippet to reproduce the problem described above.
27 validations:
28 required: true
29 - type: textarea
30 attributes:
31 label: Expected behavior
32 description: A clear and concise description of what you expected to happen.
33 validations:
34 required: true
35 - type: textarea
36 attributes:
37 label: Additional context
38 description: |
39 Links? References? Suggestions? Anything that will give us more context about the issue you are encountering!
40
41 Tip: You can attach images or log files by clicking this area to highlight it and then dragging files in.
42 validations:
43 required: false
44 - type: checkboxes
45 attributes:
46 label: Code of Conduct
47 description: By submitting this issue, you agree to follow our [Code of Conduct](https://go.dev/conduct)
48 options:
49 - label: I agree to follow this project's Code of Conduct
50 required: true
0 blank_issues_enabled: false
0 name: Improve documentation
1 description: Suggest an idea or a patch for documentation
2 labels: ["documentation"]
3 body:
4 - type: markdown
5 attributes:
6 value: |
7 Thanks for taking the time to fill out this form!
8
9 - Before you file an issue read the [Contributing guide](https://github.com/go-ini/ini/blob/main/.github/contributing.md).
10 - Check to make sure someone hasn't already opened a similar [issue](https://github.com/go-ini/ini/issues).
11 - type: textarea
12 attributes:
13 label: What needs to be improved? Please describe
14 description: A clear and concise description of what is wrong or missing.
15 validations:
16 required: true
17 - type: textarea
18 attributes:
19 label: Why do you think it is important?
20 description: A clear and concise explanation of the rationale.
21 validations:
22 required: true
23 - type: checkboxes
24 attributes:
25 label: Code of Conduct
26 description: By submitting this issue, you agree to follow our [Code of Conduct](https://go.dev/conduct)
27 options:
28 - label: I agree to follow this project's Code of Conduct
29 required: true
+0
-20
.github/ISSUE_TEMPLATE/feature_request.md less more
0 ---
1 name: Feature request
2 about: Suggest an idea for this project
3 title: ''
4 labels: ''
5 assignees: ''
6
7 ---
8
9 **Is your feature request related to a problem? Please describe.**
10 A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
11
12 **Describe the solution you'd like**
13 A clear and concise description of what you want to happen.
14
15 **Describe alternatives you've considered**
16 A clear and concise description of any alternative solutions or features you've considered.
17
18 **Additional context**
19 Add any other context or screenshots about the feature request here.
0 name: Feature request
1 description: Suggest an idea for this project
2 labels: ["feature"]
3 body:
4 - type: markdown
5 attributes:
6 value: |
7 Thanks for taking the time to fill out this form!
8
9 - Before you file an issue read the [Contributing guide](https://github.com/go-ini/ini/blob/main/.github/contributing.md).
10 - Check to make sure someone hasn't already opened a similar [issue](https://github.com/go-ini/ini/issues).
11 - type: textarea
12 attributes:
13 label: Describe the feature
14 description: A clear and concise description of what the problem is, e.g. I'm always frustrated when [...]
15 validations:
16 required: true
17 - type: textarea
18 attributes:
19 label: Describe the solution you'd like
20 description: A clear and concise description of what you want to happen.
21 validations:
22 required: true
23 - type: textarea
24 attributes:
25 label: Describe alternatives you've considered
26 description: A clear and concise description of any alternative solutions or features you've considered.
27 validations:
28 required: true
29 - type: textarea
30 attributes:
31 label: Additional context
32 description: |
33 Links? References? Suggestions? Anything that will give us more context about the feature you are requesting!
34
35 Tip: You can attach images or log files by clicking this area to highlight it and then dragging files in.
36 validations:
37 required: false
38 - type: checkboxes
39 attributes:
40 label: Code of Conduct
41 description: By submitting this issue, you agree to follow our [Code of Conduct](https://go.dev/conduct)
42 options:
43 - label: I agree to follow this project's Code of Conduct
44 required: true
0 ### What problem should be fixed?
0 ### Describe the pull request
11
2 ### Have you added test cases to catch the problem?
2 A clear and concise description of what the pull request is about, i.e. what problem should be fixed?
3
4 Link to the issue: <!-- paste the issue link here, or put "n/a" if not applicable -->
5
6 ### Checklist
7
8 - [ ] I agree to follow the [Code of Conduct](https://go.dev/conduct) by submitting this pull request.
9 - [ ] I have read and acknowledge the [Contributing guide](https://github.com/go-ini/ini/blob/main/.github/contributing.md).
10 - [ ] I have added test cases to cover the new code.
0 # Welcome to go-ini contributing guide
1
2 Thank you for investing your time in contributing to our projects!
3
4 Read our [Code of Conduct](https://go.dev/conduct) to keep our community approachable and respectable.
5
6 In this guide you will get an overview of the contribution workflow from opening an issue, creating a PR, reviewing, and merging the PR.
7
8 Use the table of contents icon <img src="https://github.com/github/docs/raw/50561895328b8f369694973252127b7d93899d83/assets/images/table-of-contents.png" width="25" height="25" /> on the top left corner of this document to get to a specific section of this guide quickly.
9
10 ## New contributor guide
11
12 To get an overview of the project, read the [README](/README.md). Here are some resources to help you get started with open source contributions:
13
14 - [Finding ways to contribute to open source on GitHub](https://docs.github.com/en/get-started/exploring-projects-on-github/finding-ways-to-contribute-to-open-source-on-github)
15 - [Set up Git](https://docs.github.com/en/get-started/quickstart/set-up-git)
16 - [GitHub flow](https://docs.github.com/en/get-started/quickstart/github-flow)
17 - [Collaborating with pull requests](https://docs.github.com/en/github/collaborating-with-pull-requests)
18
19 In addition to the general guides with open source contributions, you would also need to:
20
21 - Have basic knowledge about INI configuration format and programming in [Go](https://go.dev/).
22 - Have a working local development setup with a reasonable good IDE or editor like [Visual Studio Code](https://code.visualstudio.com/docs/languages/go), [GoLand](https://www.jetbrains.com/go/) or [Vim](https://github.com/fatih/vim-go).
23
24 ## Issues
25
26 ### Create a new issue
27
28 - [Check to make sure](https://docs.github.com/en/github/searching-for-information-on-github/searching-on-github/searching-issues-and-pull-requests#search-by-the-title-body-or-comments) someone hasn't already opened a similar [issue](https://github.com/go-ini/ini/issues).
29 - If a similar issue doesn't exist, open a new issue using a relevant [issue form](https://github.com/go-ini/ini/issues/new/choose).
30
31 ### Pick up an issue to solve
32
33 - Scan through our [existing issues](https://github.com/go-ini/ini/issues) to find one that interests you.
34 - The [good first issue](https://github.com/go-ini/ini/labels/good%20first%20issue) is a good place to start exploring issues that are well-groomed for newcomers.
35 - Do not hesitate to ask for more details or clarifying questions on the issue!
36 - Communicate on the issue you are intended to pick up _before_ starting working on it.
37 - Every issue that gets picked up will have an expected timeline for the implementation, the issue may be reassigned after the expected timeline. Please be responsible and proactive on the communication 🙇‍♂️
38
39 ## Pull requests
40
41 When you're finished with the changes, create a pull request, or a series of pull requests if necessary.
42
43 Contributing to another codebase is not as simple as code changes, it is also about contributing influence to the design. Therefore, we kindly ask you that:
44
45 - Please acknowledge that no pull request is guaranteed to be merged.
46 - Please always do a self-review before requesting reviews from others.
47 - Please expect code review to be strict and may have multiple rounds.
48 - Please make self-contained incremental changes, pull requests with huge diff may be rejected for review.
49 - Please use English in code comments and docstring.
50 - Please do not force push unless absolutely necessary. Force pushes make review much harder in multiple rounds, and we use [Squash and merge](https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/incorporating-changes-from-a-pull-request/about-pull-request-merges#squash-and-merge-your-pull-request-commits) so you don't need to worry about messy commits and just focus on the changes.
51
52 ## Your PR is merged!
53
54 Congratulations 🎉🎉 Thanks again for taking the effort to have this journey with us 🌟
0 name: Go
1 on:
2 push:
3 branches: [ main ]
4 paths:
5 - '**.go'
6 - 'go.mod'
7 - '.golangci.yml'
8 - '.github/workflows/go.yml'
9 pull_request:
10 paths:
11 - '**.go'
12 - 'go.mod'
13 - '.golangci.yml'
14 - '.github/workflows/go.yml'
15 env:
16 GOPROXY: "https://proxy.golang.org"
17
18 jobs:
19 lint:
20 name: Lint
21 runs-on: ubuntu-latest
22 steps:
23 - name: Checkout code
24 uses: actions/checkout@v2
25 - name: Init Go Modules
26 run: |
27 go mod init github.com/go-ini/ini
28 go mod tidy
29 - name: Run golangci-lint
30 uses: golangci/golangci-lint-action@v2
31 with:
32 version: latest
33 args: --timeout=30m
34 skip-pkg-cache: true # Wrokaround of the "tar" problem: https://github.com/golangci/golangci-lint-action/issues/244
35
36 test:
37 name: Test
38 strategy:
39 matrix:
40 go-version: [ 1.15.x, 1.16.x, 1.17.x ]
41 platform: [ ubuntu-latest, macos-latest, windows-latest ]
42 runs-on: ${{ matrix.platform }}
43 steps:
44 - name: Install Go
45 uses: actions/setup-go@v2
46 with:
47 go-version: ${{ matrix.go-version }}
48 - name: Checkout code
49 uses: actions/checkout@v2
50 - name: Run tests with coverage
51 run: |
52 go mod init github.com/go-ini/ini
53 go mod tidy
54 go test -v -race -coverprofile=coverage -covermode=atomic ./...
55 - name: Upload coverage report to Codecov
56 uses: codecov/codecov-action@v1.5.0
57 with:
58 file: ./coverage
59 flags: unittests
00 name: LSIF
1 on: [push]
1 on:
2 push:
3 paths:
4 - '**.go'
5 - 'go.mod'
6 - '.github/workflows/lsif.yml'
7 env:
8 GOPROXY: "https://proxy.golang.org"
9
210 jobs:
3 build:
11 lsif-go:
12 if: github.repository == 'go-ini/ini'
413 runs-on: ubuntu-latest
514 steps:
6 - uses: actions/checkout@v1
15 - uses: actions/checkout@v2
716 - name: Generate LSIF data
817 uses: sourcegraph/lsif-go-action@master
18 - name: Upload LSIF data to sourcegraph.com
19 continue-on-error: true
20 uses: docker://sourcegraph/src-cli:latest
921 with:
10 verbose: 'true'
11 - name: Upload LSIF data
12 uses: sourcegraph/lsif-upload-action@master
22 args: lsif upload -github-token=${{ secrets.GITHUB_TOKEN }}
23 - name: Upload LSIF data to sourcegraph.unknwon.cn
1324 continue-on-error: true
25 uses: docker://sourcegraph/src-cli:latest
1426 with:
15 endpoint: https://sourcegraph.com
16 github_token: ${{ secrets.GITHUB_TOKEN }}
27 args: -endpoint=https://sourcegraph.unknwon.cn lsif upload -github-token=${{ secrets.GITHUB_TOKEN }}
33 testdata/conf_reflect.ini
44 .idea
55 /.vscode
6 .DS_Store
0 linters-settings:
1 nakedret:
2 max-func-lines: 0 # Disallow any unnamed return statement
3
4 linters:
5 enable:
6 - deadcode
7 - errcheck
8 - gosimple
9 - govet
10 - ineffassign
11 - staticcheck
12 - structcheck
13 - typecheck
14 - unused
15 - varcheck
16 - nakedret
17 - gofmt
18 - rowserrcheck
19 - unconvert
20 - goimports
+0
-21
.travis.yml less more
0 language: go
1 os: linux
2 dist: xenial
3 go:
4 - 1.6.x
5 - 1.7.x
6 - 1.8.x
7 - 1.9.x
8 - 1.10.x
9 - 1.11.x
10 - 1.12.x
11 - 1.13.x
12 - 1.14.x
13 install: skip
14 script:
15 - go get golang.org/x/tools/cmd/cover
16 - go get github.com/smartystreets/goconvey
17 - mkdir -p $HOME/gopath/src/gopkg.in
18 - ln -s $HOME/gopath/src/github.com/go-ini/ini $HOME/gopath/src/gopkg.in/ini.v1
19 - cd $HOME/gopath/src/gopkg.in/ini.v1
20 - go test -v -cover -race
00 # INI
11
2 [![Build Status](https://img.shields.io/travis/go-ini/ini/master.svg?style=for-the-badge&logo=travis)](https://travis-ci.org/go-ini/ini) [![Sourcegraph](https://img.shields.io/badge/view%20on-Sourcegraph-brightgreen.svg?style=for-the-badge&logo=sourcegraph)](https://sourcegraph.com/github.com/go-ini/ini)
2 [![GitHub Workflow Status](https://img.shields.io/github/workflow/status/go-ini/ini/Go?logo=github&style=for-the-badge)](https://github.com/go-ini/ini/actions?query=workflow%3AGo)
3 [![codecov](https://img.shields.io/codecov/c/github/go-ini/ini/master?logo=codecov&style=for-the-badge)](https://codecov.io/gh/go-ini/ini)
4 [![GoDoc](https://img.shields.io/badge/GoDoc-Reference-blue?style=for-the-badge&logo=go)](https://pkg.go.dev/github.com/go-ini/ini?tab=doc)
5 [![Sourcegraph](https://img.shields.io/badge/view%20on-Sourcegraph-brightgreen.svg?style=for-the-badge&logo=sourcegraph)](https://sourcegraph.com/github.com/go-ini/ini)
36
47 ![](https://avatars0.githubusercontent.com/u/10216035?v=3&s=200)
58
2023
2124 ## Installation
2225
23 The minimum requirement of Go is **1.6**.
26 The minimum requirement of Go is **1.12**.
2427
2528 ```sh
2629 $ go get gopkg.in/ini.v1
3235
3336 - [Getting Started](https://ini.unknwon.io/docs/intro/getting_started)
3437 - [API Documentation](https://gowalker.org/gopkg.in/ini.v1)
38 - 中国大陆镜像:https://ini.unknwon.cn
3539
3640 ## License
3741
1111 // License for the specific language governing permissions and limitations
1212 // under the License.
1313
14 package ini_test
14 package ini
1515
1616 import (
1717 "testing"
18
19 "gopkg.in/ini.v1"
2018 )
2119
22 func newTestFile(block bool) *ini.File {
23 c, _ := ini.Load([]byte(confData))
20 func newTestFile(block bool) *File {
21 c, _ := Load([]byte(confData))
2422 c.BlockMode = block
2523 return c
2624 }
0 coverage:
1 range: "60...95"
2 status:
3 project:
4 default:
5 threshold: 1%
6
7 comment:
8 layout: 'diff'
6565 return sourceFile{s}, nil
6666 case []byte:
6767 return &sourceData{s}, nil
68 case io.ReadCloser:
69 return &sourceReadCloser{s}, nil
6870 case io.Reader:
6971 return &sourceReadCloser{ioutil.NopCloser(s)}, nil
70 case io.ReadCloser:
71 return &sourceReadCloser{s}, nil
7272 default:
7373 return nil, fmt.Errorf("error parsing data source: unknown type %q", s)
7474 }
0 golang-github-go-ini-ini (1.66.4-1) UNRELEASED; urgency=low
1
2 * New upstream release.
3
4 -- Debian Janitor <janitor@jelmer.uk> Mon, 07 Mar 2022 11:15:38 -0000
5
06 golang-github-go-ini-ini (1.55.0-1) unstable; urgency=medium
17
28 * Team Upload.
5454 if len(opts.KeyValueDelimiterOnWrite) == 0 {
5555 opts.KeyValueDelimiterOnWrite = "="
5656 }
57 if len(opts.ChildSectionDelimiter) == 0 {
58 opts.ChildSectionDelimiter = "."
59 }
5760
5861 return &File{
5962 BlockMode: true,
8184 return nil, errors.New("empty section name")
8285 }
8386
84 if f.options.Insensitive && name != DefaultSection {
87 if (f.options.Insensitive || f.options.InsensitiveSections) && name != DefaultSection {
8588 name = strings.ToLower(name)
8689 }
8790
138141 return secs[0], err
139142 }
140143
144 // HasSection returns true if the file contains a section with given name.
145 func (f *File) HasSection(name string) bool {
146 section, _ := f.GetSection(name)
147 return section != nil
148 }
149
141150 // SectionsByName returns all sections with given name.
142151 func (f *File) SectionsByName(name string) ([]*Section, error) {
143152 if len(name) == 0 {
144153 name = DefaultSection
145154 }
146 if f.options.Insensitive {
155 if f.options.Insensitive || f.options.InsensitiveSections {
147156 name = strings.ToLower(name)
148157 }
149158
164173 func (f *File) Section(name string) *Section {
165174 sec, err := f.GetSection(name)
166175 if err != nil {
167 // Note: It's OK here because the only possible error is empty section name,
168 // but if it's empty, this piece of code won't be executed.
176 if name == "" {
177 name = DefaultSection
178 }
169179 sec, _ = f.NewSection(name)
170180 return sec
171181 }
235245 if len(name) == 0 {
236246 name = DefaultSection
237247 }
238 if f.options.Insensitive {
248 if f.options.Insensitive || f.options.InsensitiveSections {
239249 name = strings.ToLower(name)
240250 }
241251
297307 continue
298308 }
299309 return err
310 }
311 if f.options.ShortCircuit {
312 return nil
300313 }
301314 }
302315 return nil
346359 }
347360 }
348361
349 if i > 0 || DefaultHeader {
362 if i > 0 || DefaultHeader || (i == 0 && strings.ToUpper(sec.name) != DefaultSection) {
350363 if _, err := buf.WriteString("[" + sname + "]" + LineBreak); err != nil {
351364 return nil, err
352365 }
428441 kname = `"""` + kname + `"""`
429442 }
430443
431 for _, val := range key.ValueWithShadows() {
444 writeKeyValue := func(val string) (bool, error) {
432445 if _, err := buf.WriteString(kname); err != nil {
433 return nil, err
446 return false, err
434447 }
435448
436449 if key.isBooleanType {
437450 if kname != sec.keyList[len(sec.keyList)-1] {
438451 buf.WriteString(LineBreak)
439452 }
440 continue KeyList
453 return true, nil
441454 }
442455
443456 // Write out alignment spaces before "=" sign
450463 val = `"""` + val + `"""`
451464 } else if !f.options.IgnoreInlineComment && strings.ContainsAny(val, "#;") {
452465 val = "`" + val + "`"
466 } else if len(strings.TrimSpace(val)) != len(val) {
467 val = `"` + val + `"`
453468 }
454469 if _, err := buf.WriteString(equalSign + val + LineBreak); err != nil {
470 return false, err
471 }
472 return false, nil
473 }
474
475 shadows := key.ValueWithShadows()
476 if len(shadows) == 0 {
477 if _, err := writeKeyValue(""); err != nil {
455478 return nil, err
479 }
480 }
481
482 for _, val := range shadows {
483 exitLoop, err := writeKeyValue(val)
484 if err != nil {
485 return nil, err
486 } else if exitLoop {
487 continue KeyList
456488 }
457489 }
458490
493525 // SaveToIndent writes content to file system with given value indention.
494526 func (f *File) SaveToIndent(filename, indent string) error {
495527 // Note: Because we are truncating with os.Create,
496 // so it's safer to save to a temporary file location and rename afte done.
528 // so it's safer to save to a temporary file location and rename after done.
497529 buf, err := f.writeToBuffer(indent)
498530 if err != nil {
499531 return err
1111 // License for the specific language governing permissions and limitations
1212 // under the License.
1313
14 package ini_test
14 package ini
1515
1616 import (
1717 "bytes"
1818 "io/ioutil"
19 "runtime"
20 "sort"
1921 "testing"
2022
21 . "github.com/smartystreets/goconvey/convey"
22 "gopkg.in/ini.v1"
23 "github.com/stretchr/testify/assert"
24 "github.com/stretchr/testify/require"
2325 )
2426
2527 func TestEmpty(t *testing.T) {
26 Convey("Create an empty object", t, func() {
27 f := ini.Empty()
28 So(f, ShouldNotBeNil)
29
30 // Should only have the default section
31 So(len(f.Sections()), ShouldEqual, 1)
32
33 // Default section should not contain any key
34 So(len(f.Section("").Keys()), ShouldBeZeroValue)
35 })
28 f := Empty()
29 require.NotNil(t, f)
30
31 // Should only have the default section
32 assert.Len(t, f.Sections(), 1)
33
34 // Default section should not contain any key
35 assert.Len(t, f.Section("").Keys(), 0)
3636 }
3737
3838 func TestFile_NewSection(t *testing.T) {
39 Convey("Create a new section", t, func() {
40 f := ini.Empty()
41 So(f, ShouldNotBeNil)
42
39 f := Empty()
40 require.NotNil(t, f)
41
42 sec, err := f.NewSection("author")
43 require.NoError(t, err)
44 require.NotNil(t, sec)
45 assert.Equal(t, "author", sec.Name())
46
47 assert.Equal(t, []string{DefaultSection, "author"}, f.SectionStrings())
48
49 t.Run("with duplicated name", func(t *testing.T) {
4350 sec, err := f.NewSection("author")
44 So(err, ShouldBeNil)
45 So(sec, ShouldNotBeNil)
46 So(sec.Name(), ShouldEqual, "author")
47
48 So(f.SectionStrings(), ShouldResemble, []string{ini.DefaultSection, "author"})
49
50 Convey("With duplicated name", func() {
51 sec, err := f.NewSection("author")
52 So(err, ShouldBeNil)
53 So(sec, ShouldNotBeNil)
54
55 // Does nothing if section already exists
56 So(f.SectionStrings(), ShouldResemble, []string{ini.DefaultSection, "author"})
57 })
58
59 Convey("With empty string", func() {
60 _, err := f.NewSection("")
61 So(err, ShouldNotBeNil)
62 })
51 require.NoError(t, err)
52 require.NotNil(t, sec)
53
54 // Does nothing if section already exists
55 assert.Equal(t, []string{DefaultSection, "author"}, f.SectionStrings())
56 })
57
58 t.Run("with empty string", func(t *testing.T) {
59 _, err := f.NewSection("")
60 require.Error(t, err)
6361 })
6462 }
6563
6664 func TestFile_NonUniqueSection(t *testing.T) {
67 Convey("Read and write non-unique sections", t, func() {
68 f, err := ini.LoadSources(ini.LoadOptions{
65 t.Run("read and write non-unique sections", func(t *testing.T) {
66 f, err := LoadSources(LoadOptions{
6967 AllowNonUniqueSections: true,
7068 }, []byte(`[Interface]
7169 Address = 192.168.2.1
7977 [Peer]
8078 PublicKey = <client2's publickey>
8179 AllowedIPs = 192.168.2.3/32`))
82 So(err, ShouldBeNil)
83 So(f, ShouldNotBeNil)
80 require.NoError(t, err)
81 require.NotNil(t, f)
8482
8583 sec, err := f.NewSection("Peer")
86 So(err, ShouldBeNil)
87 So(f, ShouldNotBeNil)
88
89 sec.NewKey("PublicKey", "<client3's publickey>")
90 sec.NewKey("AllowedIPs", "192.168.2.4/32")
84 require.NoError(t, err)
85 require.NotNil(t, f)
86
87 _, _ = sec.NewKey("PublicKey", "<client3's publickey>")
88 _, _ = sec.NewKey("AllowedIPs", "192.168.2.4/32")
9189
9290 var buf bytes.Buffer
9391 _, err = f.WriteTo(&buf)
94 So(err, ShouldBeNil)
92 require.NoError(t, err)
9593 str := buf.String()
96 So(str, ShouldEqual, `[Interface]
94 assert.Equal(t, `[Interface]
9795 Address = 192.168.2.1
9896 PrivateKey = <server's privatekey>
9997 ListenPort = 51820
110108 PublicKey = <client3's publickey>
111109 AllowedIPs = 192.168.2.4/32
112110
113 `)
114 })
115
116 Convey("Delete non-unique section", t, func() {
117 f, err := ini.LoadSources(ini.LoadOptions{
111 `, str)
112 })
113
114 t.Run("delete non-unique section", func(t *testing.T) {
115 f, err := LoadSources(LoadOptions{
118116 AllowNonUniqueSections: true,
119117 }, []byte(`[Interface]
120118 Address = 192.168.2.1
134132 AllowedIPs = 192.168.2.4/32
135133
136134 `))
137 So(err, ShouldBeNil)
138 So(f, ShouldNotBeNil)
135 require.NoError(t, err)
136 require.NotNil(t, f)
139137
140138 err = f.DeleteSectionWithIndex("Peer", 1)
141 So(err, ShouldBeNil)
139 require.NoError(t, err)
142140
143141 var buf bytes.Buffer
144142 _, err = f.WriteTo(&buf)
145 So(err, ShouldBeNil)
143 require.NoError(t, err)
146144 str := buf.String()
147 So(str, ShouldEqual, `[Interface]
145 assert.Equal(t, `[Interface]
148146 Address = 192.168.2.1
149147 PrivateKey = <server's privatekey>
150148 ListenPort = 51820
157155 PublicKey = <client3's publickey>
158156 AllowedIPs = 192.168.2.4/32
159157
160 `)
161 })
162
163 Convey("Delete all sections", t, func() {
164 f := ini.Empty(ini.LoadOptions{
158 `, str)
159 })
160
161 t.Run("delete all sections", func(t *testing.T) {
162 f := Empty(LoadOptions{
165163 AllowNonUniqueSections: true,
166164 })
167 So(f, ShouldNotBeNil)
168
169 f.NewSections("Interface", "Peer", "Peer")
170 So(f.SectionStrings(), ShouldResemble, []string{ini.DefaultSection, "Interface", "Peer", "Peer"})
165 require.NotNil(t, f)
166
167 _ = f.NewSections("Interface", "Peer", "Peer")
168 assert.Equal(t, []string{DefaultSection, "Interface", "Peer", "Peer"}, f.SectionStrings())
171169 f.DeleteSection("Peer")
172 So(f.SectionStrings(), ShouldResemble, []string{ini.DefaultSection, "Interface"})
170 assert.Equal(t, []string{DefaultSection, "Interface"}, f.SectionStrings())
173171 })
174172 }
175173
176174 func TestFile_NewRawSection(t *testing.T) {
177 Convey("Create a new raw section", t, func() {
178 f := ini.Empty()
179 So(f, ShouldNotBeNil)
180
181 sec, err := f.NewRawSection("comments", `1111111111111111111000000000000000001110000
175 f := Empty()
176 require.NotNil(t, f)
177
178 sec, err := f.NewRawSection("comments", `1111111111111111111000000000000000001110000
182179 111111111111111111100000000000111000000000`)
183 So(err, ShouldBeNil)
184 So(sec, ShouldNotBeNil)
185 So(sec.Name(), ShouldEqual, "comments")
186
187 So(f.SectionStrings(), ShouldResemble, []string{ini.DefaultSection, "comments"})
188 So(f.Section("comments").Body(), ShouldEqual, `1111111111111111111000000000000000001110000
189 111111111111111111100000000000111000000000`)
190
191 Convey("With duplicated name", func() {
192 sec, err := f.NewRawSection("comments", `1111111111111111111000000000000000001110000`)
193 So(err, ShouldBeNil)
194 So(sec, ShouldNotBeNil)
195 So(f.SectionStrings(), ShouldResemble, []string{ini.DefaultSection, "comments"})
196
197 // Overwrite previous existed section
198 So(f.Section("comments").Body(), ShouldEqual, `1111111111111111111000000000000000001110000`)
180 require.NoError(t, err)
181 require.NotNil(t, sec)
182 assert.Equal(t, "comments", sec.Name())
183
184 assert.Equal(t, []string{DefaultSection, "comments"}, f.SectionStrings())
185 assert.Equal(t, `1111111111111111111000000000000000001110000
186 111111111111111111100000000000111000000000`, f.Section("comments").Body())
187
188 t.Run("with duplicated name", func(t *testing.T) {
189 sec, err := f.NewRawSection("comments", `1111111111111111111000000000000000001110000`)
190 require.NoError(t, err)
191 require.NotNil(t, sec)
192 assert.Equal(t, []string{DefaultSection, "comments"}, f.SectionStrings())
193
194 // Overwrite previous existed section
195 assert.Equal(t, `1111111111111111111000000000000000001110000`, f.Section("comments").Body())
196 })
197
198 t.Run("with empty string", func(t *testing.T) {
199 _, err := f.NewRawSection("", "")
200 require.Error(t, err)
201 })
202 }
203
204 func TestFile_NewSections(t *testing.T) {
205 f := Empty()
206 require.NotNil(t, f)
207
208 assert.NoError(t, f.NewSections("package", "author"))
209 assert.Equal(t, []string{DefaultSection, "package", "author"}, f.SectionStrings())
210
211 t.Run("with duplicated name", func(t *testing.T) {
212 assert.NoError(t, f.NewSections("author", "features"))
213
214 // Ignore section already exists
215 assert.Equal(t, []string{DefaultSection, "package", "author", "features"}, f.SectionStrings())
216 })
217
218 t.Run("with empty string", func(t *testing.T) {
219 assert.Error(t, f.NewSections("", ""))
220 })
221 }
222
223 func TestFile_GetSection(t *testing.T) {
224 f, err := Load(fullConf)
225 require.NoError(t, err)
226 require.NotNil(t, f)
227
228 sec, err := f.GetSection("author")
229 require.NoError(t, err)
230 require.NotNil(t, sec)
231 assert.Equal(t, "author", sec.Name())
232
233 t.Run("section not exists", func(t *testing.T) {
234 _, err := f.GetSection("404")
235 require.Error(t, err)
236 })
237 }
238
239 func TestFile_HasSection(t *testing.T) {
240 f, err := Load(fullConf)
241 require.NoError(t, err)
242 require.NotNil(t, f)
243
244 sec := f.HasSection("author")
245 assert.True(t, sec)
246
247 t.Run("section not exists", func(t *testing.T) {
248 nonexistent := f.HasSection("404")
249 assert.False(t, nonexistent)
250 })
251 }
252
253 func TestFile_Section(t *testing.T) {
254 t.Run("get a section", func(t *testing.T) {
255 f, err := Load(fullConf)
256 require.NoError(t, err)
257 require.NotNil(t, f)
258
259 sec := f.Section("author")
260 require.NotNil(t, sec)
261 assert.Equal(t, "author", sec.Name())
262
263 t.Run("section not exists", func(t *testing.T) {
264 sec := f.Section("404")
265 require.NotNil(t, sec)
266 assert.Equal(t, "404", sec.Name())
199267 })
200
201 Convey("With empty string", func() {
202 _, err := f.NewRawSection("", "")
203 So(err, ShouldNotBeNil)
204 })
205 })
206 }
207
208 func TestFile_NewSections(t *testing.T) {
209 Convey("Create new sections", t, func() {
210 f := ini.Empty()
211 So(f, ShouldNotBeNil)
212
213 So(f.NewSections("package", "author"), ShouldBeNil)
214 So(f.SectionStrings(), ShouldResemble, []string{ini.DefaultSection, "package", "author"})
215
216 Convey("With duplicated name", func() {
217 So(f.NewSections("author", "features"), ShouldBeNil)
218
219 // Ignore section already exists
220 So(f.SectionStrings(), ShouldResemble, []string{ini.DefaultSection, "package", "author", "features"})
221 })
222
223 Convey("With empty string", func() {
224 So(f.NewSections("", ""), ShouldNotBeNil)
225 })
226 })
227 }
228
229 func TestFile_GetSection(t *testing.T) {
230 Convey("Get a section", t, func() {
231 f, err := ini.Load(fullConf)
232 So(err, ShouldBeNil)
233 So(f, ShouldNotBeNil)
234
235 sec, err := f.GetSection("author")
236 So(err, ShouldBeNil)
237 So(sec, ShouldNotBeNil)
238 So(sec.Name(), ShouldEqual, "author")
239
240 Convey("Section not exists", func() {
241 _, err := f.GetSection("404")
242 So(err, ShouldNotBeNil)
243 })
244 })
245 }
246
247 func TestFile_Section(t *testing.T) {
248 Convey("Get a section", t, func() {
249 f, err := ini.Load(fullConf)
250 So(err, ShouldBeNil)
251 So(f, ShouldNotBeNil)
252
253 sec := f.Section("author")
254 So(sec, ShouldNotBeNil)
255 So(sec.Name(), ShouldEqual, "author")
256
257 Convey("Section not exists", func() {
258 sec := f.Section("404")
259 So(sec, ShouldNotBeNil)
260 So(sec.Name(), ShouldEqual, "404")
261 })
262 })
263
264 Convey("Get default section in lower case with insensitive load", t, func() {
265 f, err := ini.InsensitiveLoad([]byte(`
268 })
269
270 t.Run("get default section in lower case with insensitive load", func(t *testing.T) {
271 f, err := InsensitiveLoad([]byte(`
266272 [default]
267273 NAME = ini
268274 VERSION = v1`))
269 So(err, ShouldBeNil)
270 So(f, ShouldNotBeNil)
271
272 So(f.Section("").Key("name").String(), ShouldEqual, "ini")
273 So(f.Section("").Key("version").String(), ShouldEqual, "v1")
274 })
275 require.NoError(t, err)
276 require.NotNil(t, f)
277
278 assert.Equal(t, "ini", f.Section("").Key("name").String())
279 assert.Equal(t, "v1", f.Section("").Key("version").String())
280 })
281
282 t.Run("get sections after deletion", func(t *testing.T) {
283 f, err := Load([]byte(`
284 [RANDOM]
285 `))
286 require.NoError(t, err)
287 require.NotNil(t, f)
288
289 sectionNames := f.SectionStrings()
290 sort.Strings(sectionNames)
291 assert.Equal(t, []string{DefaultSection, "RANDOM"}, sectionNames)
292
293 for _, currentSection := range sectionNames {
294 f.DeleteSection(currentSection)
295 }
296
297 for sectionParam, expectedSectionName := range map[string]string{
298 "": DefaultSection,
299 "RANDOM": "RANDOM",
300 } {
301 sec := f.Section(sectionParam)
302 require.NotNil(t, sec)
303 assert.Equal(t, expectedSectionName, sec.Name())
304 }
305 })
306
275307 }
276308
277309 func TestFile_Sections(t *testing.T) {
278 Convey("Get all sections", t, func() {
279 f, err := ini.Load(fullConf)
280 So(err, ShouldBeNil)
281 So(f, ShouldNotBeNil)
282
283 secs := f.Sections()
284 names := []string{ini.DefaultSection, "author", "package", "package.sub", "features", "types", "array", "note", "comments", "string escapes", "advance"}
285 So(len(secs), ShouldEqual, len(names))
286 for i, name := range names {
287 So(secs[i].Name(), ShouldEqual, name)
288 }
289 })
310 f, err := Load(fullConf)
311 require.NoError(t, err)
312 require.NotNil(t, f)
313
314 secs := f.Sections()
315 names := []string{DefaultSection, "author", "package", "package.sub", "features", "types", "array", "note", "comments", "string escapes", "advance"}
316 assert.Len(t, secs, len(names))
317 for i, name := range names {
318 assert.Equal(t, name, secs[i].Name())
319 }
290320 }
291321
292322 func TestFile_ChildSections(t *testing.T) {
293 Convey("Get child sections by parent name", t, func() {
294 f, err := ini.Load([]byte(`
323 f, err := Load([]byte(`
295324 [node]
296325 [node.biz1]
297326 [node.biz2]
298327 [node.biz3]
299328 [node.bizN]
300329 `))
301 So(err, ShouldBeNil)
302 So(f, ShouldNotBeNil)
303
304 children := f.ChildSections("node")
305 names := []string{"node.biz1", "node.biz2", "node.biz3", "node.bizN"}
306 So(len(children), ShouldEqual, len(names))
307 for i, name := range names {
308 So(children[i].Name(), ShouldEqual, name)
309 }
310 })
330 require.NoError(t, err)
331 require.NotNil(t, f)
332
333 children := f.ChildSections("node")
334 names := []string{"node.biz1", "node.biz2", "node.biz3", "node.bizN"}
335 assert.Len(t, children, len(names))
336 for i, name := range names {
337 assert.Equal(t, name, children[i].Name())
338 }
311339 }
312340
313341 func TestFile_SectionStrings(t *testing.T) {
314 Convey("Get all section names", t, func() {
315 f, err := ini.Load(fullConf)
316 So(err, ShouldBeNil)
317 So(f, ShouldNotBeNil)
318
319 So(f.SectionStrings(), ShouldResemble, []string{ini.DefaultSection, "author", "package", "package.sub", "features", "types", "array", "note", "comments", "string escapes", "advance"})
320 })
342 f, err := Load(fullConf)
343 require.NoError(t, err)
344 require.NotNil(t, f)
345
346 assert.Equal(t, []string{DefaultSection, "author", "package", "package.sub", "features", "types", "array", "note", "comments", "string escapes", "advance"}, f.SectionStrings())
321347 }
322348
323349 func TestFile_DeleteSection(t *testing.T) {
324 Convey("Delete a section", t, func() {
325 f := ini.Empty()
326 So(f, ShouldNotBeNil)
327
328 f.NewSections("author", "package", "features")
350 t.Run("delete a section", func(t *testing.T) {
351 f := Empty()
352 require.NotNil(t, f)
353
354 _ = f.NewSections("author", "package", "features")
329355 f.DeleteSection("features")
330356 f.DeleteSection("")
331 So(f.SectionStrings(), ShouldResemble, []string{"author", "package"})
357 assert.Equal(t, []string{"author", "package"}, f.SectionStrings())
358 })
359
360 t.Run("delete default section", func(t *testing.T) {
361 f := Empty()
362 require.NotNil(t, f)
363
364 f.Section("").Key("foo").SetValue("bar")
365 f.Section("section1").Key("key1").SetValue("value1")
366 f.DeleteSection("")
367 assert.Equal(t, []string{"section1"}, f.SectionStrings())
368
369 var buf bytes.Buffer
370 _, err := f.WriteTo(&buf)
371 require.NoError(t, err)
372
373 assert.Equal(t, `[section1]
374 key1 = value1
375
376 `, buf.String())
377 })
378
379 t.Run("delete a section with InsensitiveSections", func(t *testing.T) {
380 f := Empty(LoadOptions{InsensitiveSections: true})
381 require.NotNil(t, f)
382
383 _ = f.NewSections("author", "package", "features")
384 f.DeleteSection("FEATURES")
385 f.DeleteSection("")
386 assert.Equal(t, []string{"author", "package"}, f.SectionStrings())
332387 })
333388 }
334389
335390 func TestFile_Append(t *testing.T) {
336 Convey("Append a data source", t, func() {
337 f := ini.Empty()
338 So(f, ShouldNotBeNil)
339
340 So(f.Append(minimalConf, []byte(`
391 f := Empty()
392 require.NotNil(t, f)
393
394 assert.NoError(t, f.Append(minimalConf, []byte(`
341395 [author]
342 NAME = Unknwon`)), ShouldBeNil)
343
344 Convey("With bad input", func() {
345 So(f.Append(123), ShouldNotBeNil)
346 So(f.Append(minimalConf, 123), ShouldNotBeNil)
347 })
396 NAME = Unknwon`)))
397
398 t.Run("with bad input", func(t *testing.T) {
399 assert.Error(t, f.Append(123))
400 assert.Error(t, f.Append(minimalConf, 123))
348401 })
349402 }
350403
351404 func TestFile_WriteTo(t *testing.T) {
352 Convey("Write content to somewhere", t, func() {
353 f, err := ini.Load(fullConf)
354 So(err, ShouldBeNil)
355 So(f, ShouldNotBeNil)
405 if runtime.GOOS == "windows" {
406 t.Skip("Skipping testing on Windows")
407 }
408
409 t.Run("write content to somewhere", func(t *testing.T) {
410 f, err := Load(fullConf)
411 require.NoError(t, err)
412 require.NotNil(t, f)
356413
357414 f.Section("author").Comment = `Information about package author
358415 # Bio can be written in multiple lines.`
359416 f.Section("author").Key("NAME").Comment = "This is author name"
360 f.Section("note").NewBooleanKey("boolean_key")
361 f.Section("note").NewKey("more", "notes")
417 _, _ = f.Section("note").NewBooleanKey("boolean_key")
418 _, _ = f.Section("note").NewKey("more", "notes")
362419
363420 var buf bytes.Buffer
364421 _, err = f.WriteTo(&buf)
365 So(err, ShouldBeNil)
422 require.NoError(t, err)
366423
367424 golden := "testdata/TestFile_WriteTo.golden"
368425 if *update {
369 ioutil.WriteFile(golden, buf.Bytes(), 0644)
426 require.NoError(t, ioutil.WriteFile(golden, buf.Bytes(), 0644))
370427 }
371428
372429 expected, err := ioutil.ReadFile(golden)
373 So(err, ShouldBeNil)
374 So(buf.String(), ShouldEqual, string(expected))
375 })
376
377 Convey("Support multiline comments", t, func() {
378 f, err := ini.Load([]byte(`
430 require.NoError(t, err)
431 assert.Equal(t, string(expected), buf.String())
432 })
433
434 t.Run("support multiline comments", func(t *testing.T) {
435 f, err := Load([]byte(`
379436 #
380437 # general.domain
381438 #
382439 # Domain name of XX system.
383440 domain = mydomain.com
384441 `))
385 So(err, ShouldBeNil)
442 require.NoError(t, err)
386443
387444 f.Section("").Key("test").Comment = "Multiline\nComment"
388445
389446 var buf bytes.Buffer
390447 _, err = f.WriteTo(&buf)
391 So(err, ShouldBeNil)
392
393 So(buf.String(), ShouldEqual, `#
448 require.NoError(t, err)
449
450 assert.Equal(t, `#
394451 # general.domain
395452 #
396453 # Domain name of XX system.
399456 ; Comment
400457 test =
401458
402 `)
403
459 `, buf.String())
460
461 })
462
463 t.Run("keep leading and trailing spaces in value", func(t *testing.T) {
464 f, _ := Load([]byte(`[foo]
465 bar1 = ' val ue1 '
466 bar2 = """ val ue2 """
467 bar3 = " val ue3 "
468 `))
469 require.NotNil(t, f)
470
471 var buf bytes.Buffer
472 _, err := f.WriteTo(&buf)
473 require.NoError(t, err)
474 assert.Equal(t, `[foo]
475 bar1 = " val ue1 "
476 bar2 = " val ue2 "
477 bar3 = " val ue3 "
478
479 `, buf.String())
404480 })
405481 }
406482
407483 func TestFile_SaveTo(t *testing.T) {
408 Convey("Write content to somewhere", t, func() {
409 f, err := ini.Load(fullConf)
410 So(err, ShouldBeNil)
411 So(f, ShouldNotBeNil)
412
413 So(f.SaveTo("testdata/conf_out.ini"), ShouldBeNil)
414 So(f.SaveToIndent("testdata/conf_out.ini", "\t"), ShouldBeNil)
415 })
484 f, err := Load(fullConf)
485 require.NoError(t, err)
486 require.NotNil(t, f)
487
488 assert.NoError(t, f.SaveTo("testdata/conf_out.ini"))
489 assert.NoError(t, f.SaveToIndent("testdata/conf_out.ini", "\t"))
416490 }
417491
418492 func TestFile_WriteToWithOutputDelimiter(t *testing.T) {
419 Convey("Write content to somewhere using a custom output delimiter", t, func() {
420 f, err := ini.LoadSources(ini.LoadOptions{
421 KeyValueDelimiterOnWrite: "->",
422 }, []byte(`[Others]
493 f, err := LoadSources(LoadOptions{
494 KeyValueDelimiterOnWrite: "->",
495 }, []byte(`[Others]
423496 Cities = HangZhou|Boston
424497 Visits = 1993-10-07T20:17:05Z, 1993-10-07T20:17:05Z
425498 Years = 1993,1994
429502 Coordinates = 192.168,10.11
430503 Flags = true,false
431504 Note = Hello world!`))
432 So(err, ShouldBeNil)
433 So(f, ShouldNotBeNil)
434
435 var actual bytes.Buffer
436 var expected = []byte(`[Others]
505 require.NoError(t, err)
506 require.NotNil(t, f)
507
508 var actual bytes.Buffer
509 var expected = []byte(`[Others]
437510 Cities -> HangZhou|Boston
438511 Visits -> 1993-10-07T20:17:05Z, 1993-10-07T20:17:05Z
439512 Years -> 1993,1994
445518 Note -> Hello world!
446519
447520 `)
448 _, err = f.WriteTo(&actual)
449 So(err, ShouldBeNil)
450
451 So(bytes.Equal(expected, actual.Bytes()), ShouldBeTrue)
452 })
521 _, err = f.WriteTo(&actual)
522 require.NoError(t, err)
523
524 assert.Equal(t, expected, actual.Bytes())
453525 }
454526
455527 // Inspired by https://github.com/go-ini/ini/issues/207
456528 func TestReloadAfterShadowLoad(t *testing.T) {
457 Convey("Reload file after ShadowLoad", t, func() {
458 f, err := ini.ShadowLoad([]byte(`
529 f, err := ShadowLoad([]byte(`
459530 [slice]
460531 v = 1
461532 v = 2
462533 v = 3
463534 `))
464 So(err, ShouldBeNil)
465 So(f, ShouldNotBeNil)
466
467 So(f.Section("slice").Key("v").ValueWithShadows(), ShouldResemble, []string{"1", "2", "3"})
468
469 So(f.Reload(), ShouldBeNil)
470 So(f.Section("slice").Key("v").ValueWithShadows(), ShouldResemble, []string{"1", "2", "3"})
471 })
472 }
535 require.NoError(t, err)
536 require.NotNil(t, f)
537
538 assert.Equal(t, []string{"1", "2", "3"}, f.Section("slice").Key("v").ValueWithShadows())
539
540 require.NoError(t, f.Reload())
541 assert.Equal(t, []string{"1", "2", "3"}, f.Section("slice").Key("v").ValueWithShadows())
542 }
1616 import (
1717 "testing"
1818
19 . "github.com/smartystreets/goconvey/convey"
19 "github.com/stretchr/testify/assert"
2020 )
2121
22 func Test_isSlice(t *testing.T) {
23 Convey("Check if a string is in the slice", t, func() {
24 ss := []string{"a", "b", "c"}
25 So(inSlice("a", ss), ShouldBeTrue)
26 So(inSlice("d", ss), ShouldBeFalse)
27 })
22 func TestIsInSlice(t *testing.T) {
23 ss := []string{"a", "b", "c"}
24 assert.True(t, inSlice("a", ss))
25 assert.False(t, inSlice("d", ss))
2826 }
0 // +build go1.6
1
20 // Copyright 2014 Unknwon
31 //
42 // Licensed under the Apache License, Version 2.0 (the "License"): you may
1715 package ini
1816
1917 import (
18 "os"
2019 "regexp"
2120 "runtime"
21 "strings"
2222 )
2323
2424 const (
5454 DefaultFormatRight = ""
5555 )
5656
57 var inTest = len(os.Args) > 0 && strings.HasSuffix(strings.TrimSuffix(os.Args[0], ".exe"), ".test")
58
5759 func init() {
58 if runtime.GOOS == "windows" {
60 if runtime.GOOS == "windows" && !inTest {
5961 LineBreak = "\r\n"
6062 }
6163 }
6668 Loose bool
6769 // Insensitive indicates whether the parser forces all section and key names to lowercase.
6870 Insensitive bool
71 // InsensitiveSections indicates whether the parser forces all section to lowercase.
72 InsensitiveSections bool
73 // InsensitiveKeys indicates whether the parser forces all key names to lowercase.
74 InsensitiveKeys bool
6975 // IgnoreContinuation indicates whether to ignore continuation lines while parsing.
7076 IgnoreContinuation bool
7177 // IgnoreInlineComment indicates whether to ignore comments at the end of value and treat it as part of value.
7278 IgnoreInlineComment bool
7379 // SkipUnrecognizableLines indicates whether to skip unrecognizable lines that do not conform to key/value pairs.
7480 SkipUnrecognizableLines bool
81 // ShortCircuit indicates whether to ignore other configuration sources after loaded the first available configuration source.
82 ShortCircuit bool
7583 // AllowBooleanKeys indicates whether to allow boolean type keys or treat as value is missing.
7684 // This type of keys are mostly used in my.cnf.
7785 AllowBooleanKeys bool
102110 UnparseableSections []string
103111 // KeyValueDelimiters is the sequence of delimiters that are used to separate key and value. By default, it is "=:".
104112 KeyValueDelimiters string
105 // KeyValueDelimiters is the delimiter that are used to separate key and value output. By default, it is "=".
113 // KeyValueDelimiterOnWrite is the delimiter that are used to separate key and value output. By default, it is "=".
106114 KeyValueDelimiterOnWrite string
115 // ChildSectionDelimiter is the delimiter that is used to separate child sections. By default, it is ".".
116 ChildSectionDelimiter string
107117 // PreserveSurroundedQuote indicates whether to preserve surrounded quote (single and double quotes).
108118 PreserveSurroundedQuote bool
109119 // DebugFunc is called to collect debug information (currently only useful to debug parsing Python-style multiline values).
112122 ReaderBufferSize int
113123 // AllowNonUniqueSections indicates whether to allow sections with the same name multiple times.
114124 AllowNonUniqueSections bool
125 // AllowDuplicateShadowValues indicates whether values for shadowed keys should be deduplicated.
126 AllowDuplicateShadowValues bool
115127 }
116128
117129 // DebugFunc is the type of function called to log parse events.
+0
-65
ini_python_multiline_test.go less more
0 package ini_test
1
2 import (
3 "path/filepath"
4 "testing"
5
6 . "github.com/smartystreets/goconvey/convey"
7 "gopkg.in/ini.v1"
8 )
9
10 type testData struct {
11 Value1 string `ini:"value1"`
12 Value2 string `ini:"value2"`
13 Value3 string `ini:"value3"`
14 }
15
16 func TestMultiline(t *testing.T) {
17 Convey("Parse Python-style multiline values", t, func() {
18 path := filepath.Join("testdata", "multiline.ini")
19 f, err := ini.LoadSources(ini.LoadOptions{
20 AllowPythonMultilineValues: true,
21 ReaderBufferSize: 64 * 1024,
22 /*
23 Debug: func(m string) {
24 fmt.Println(m)
25 },
26 */
27 }, path)
28 So(err, ShouldBeNil)
29 So(f, ShouldNotBeNil)
30 So(len(f.Sections()), ShouldEqual, 1)
31
32 defaultSection := f.Section("")
33 So(f.Section(""), ShouldNotBeNil)
34
35 var testData testData
36 err = defaultSection.MapTo(&testData)
37 So(err, ShouldBeNil)
38 So(testData.Value1, ShouldEqual, "some text here\nsome more text here\n\nthere is an empty line above and below\n")
39 So(testData.Value2, ShouldEqual, "there is an empty line above\nthat is not indented so it should not be part\nof the value")
40 So(testData.Value3, ShouldEqual, `.
41
42 Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Eu consequat ac felis donec et odio pellentesque diam volutpat. Mauris commodo quis imperdiet massa tincidunt nunc. Interdum velit euismod in pellentesque. Nisl condimentum id venenatis a condimentum vitae sapien pellentesque. Nascetur ridiculus mus mauris vitae. Posuere urna nec tincidunt praesent semper feugiat. Lorem donec massa sapien faucibus et molestie ac feugiat sed. Ipsum dolor sit amet consectetur adipiscing elit. Enim sed faucibus turpis in eu mi. A diam sollicitudin tempor id. Quam nulla porttitor massa id neque aliquam vestibulum morbi blandit.
43
44 Lectus sit amet est placerat in egestas. At risus viverra adipiscing at in tellus integer. Tristique senectus et netus et malesuada fames ac. In hac habitasse platea dictumst. Purus in mollis nunc sed. Pellentesque sit amet porttitor eget dolor morbi. Elit at imperdiet dui accumsan sit amet nulla. Cursus in hac habitasse platea dictumst. Bibendum arcu vitae elementum curabitur. Faucibus ornare suspendisse sed nisi lacus. In vitae turpis massa sed. Libero nunc consequat interdum varius sit amet. Molestie a iaculis at erat pellentesque.
45
46 Dui faucibus in ornare quam viverra orci sagittis eu. Purus in mollis nunc sed id semper. Sed arcu non odio euismod lacinia at. Quis commodo odio aenean sed adipiscing diam donec. Quisque id diam vel quam elementum pulvinar. Lorem ipsum dolor sit amet. Purus ut faucibus pulvinar elementum integer enim neque volutpat ac. Fermentum posuere urna nec tincidunt praesent semper feugiat nibh sed. Gravida rutrum quisque non tellus orci. Ipsum dolor sit amet consectetur adipiscing elit pellentesque habitant. Et sollicitudin ac orci phasellus egestas tellus rutrum tellus pellentesque. Eget gravida cum sociis natoque penatibus et magnis. Elementum eu facilisis sed odio morbi quis commodo. Mollis nunc sed id semper risus in hendrerit gravida rutrum. Lorem dolor sed viverra ipsum.
47
48 Pellentesque adipiscing commodo elit at imperdiet dui accumsan sit amet. Justo eget magna fermentum iaculis eu non diam. Condimentum mattis pellentesque id nibh tortor id aliquet lectus. Tellus molestie nunc non blandit massa enim. Mauris ultrices eros in cursus turpis. Purus viverra accumsan in nisl nisi scelerisque. Quis lectus nulla at volutpat. Purus ut faucibus pulvinar elementum integer enim. In pellentesque massa placerat duis ultricies lacus sed turpis. Elit sed vulputate mi sit amet mauris commodo. Tellus elementum sagittis vitae et. Duis tristique sollicitudin nibh sit amet commodo nulla facilisi nullam. Lectus vestibulum mattis ullamcorper velit sed ullamcorper morbi tincidunt ornare. Libero id faucibus nisl tincidunt eget nullam. Mattis aliquam faucibus purus in massa tempor. Fames ac turpis egestas sed tempus urna. Gravida in fermentum et sollicitudin ac orci phasellus egestas.
49
50 Blandit turpis cursus in hac habitasse. Sed id semper risus in. Amet porttitor eget dolor morbi non arcu. Rhoncus est pellentesque elit ullamcorper dignissim cras tincidunt. Ut morbi tincidunt augue interdum velit. Lorem mollis aliquam ut porttitor leo a. Nunc eget lorem dolor sed viverra. Scelerisque mauris pellentesque pulvinar pellentesque. Elit at imperdiet dui accumsan sit amet. Eget magna fermentum iaculis eu non diam phasellus vestibulum lorem. Laoreet non curabitur gravida arcu ac tortor dignissim. Tortor pretium viverra suspendisse potenti nullam ac tortor vitae purus. Lacus sed viverra tellus in hac habitasse platea dictumst vestibulum. Viverra adipiscing at in tellus. Duis at tellus at urna condimentum. Eget gravida cum sociis natoque penatibus et magnis dis parturient. Pharetra massa massa ultricies mi quis hendrerit.
51
52 Mauris pellentesque pulvinar pellentesque habitant morbi tristique. Maecenas volutpat blandit aliquam etiam. Sed turpis tincidunt id aliquet. Eget duis at tellus at urna condimentum. Pellentesque habitant morbi tristique senectus et. Amet aliquam id diam maecenas. Volutpat est velit egestas dui id. Vulputate eu scelerisque felis imperdiet proin fermentum leo vel orci. Massa sed elementum tempus egestas sed sed risus pretium. Quam quisque id diam vel quam elementum pulvinar etiam non. Sapien faucibus et molestie ac. Ipsum dolor sit amet consectetur adipiscing. Viverra orci sagittis eu volutpat. Leo urna molestie at elementum. Commodo viverra maecenas accumsan lacus. Non sodales neque sodales ut etiam sit amet. Habitant morbi tristique senectus et netus et malesuada fames. Habitant morbi tristique senectus et netus et malesuada. Blandit aliquam etiam erat velit scelerisque in. Varius duis at consectetur lorem donec massa sapien faucibus et.
53
54 Augue mauris augue neque gravida in. Odio ut sem nulla pharetra diam sit amet nisl suscipit. Nulla aliquet enim tortor at auctor urna nunc id. Morbi tristique senectus et netus et malesuada fames ac. Quam id leo in vitae turpis massa sed elementum tempus. Ipsum faucibus vitae aliquet nec ullamcorper sit amet risus nullam. Maecenas volutpat blandit aliquam etiam erat velit scelerisque in. Sagittis nisl rhoncus mattis rhoncus urna neque viverra justo. Massa tempor nec feugiat nisl pretium. Vulputate sapien nec sagittis aliquam malesuada bibendum arcu vitae elementum. Enim lobortis scelerisque fermentum dui faucibus in ornare. Faucibus ornare suspendisse sed nisi lacus. Morbi tristique senectus et netus et malesuada fames. Malesuada pellentesque elit eget gravida cum sociis natoque penatibus et. Dictum non consectetur a erat nam at. Leo urna molestie at elementum eu facilisis sed odio morbi. Quam id leo in vitae turpis massa. Neque egestas congue quisque egestas diam in arcu. Varius morbi enim nunc faucibus a pellentesque sit. Aliquet enim tortor at auctor urna.
55
56 Elit scelerisque mauris pellentesque pulvinar pellentesque habitant morbi tristique. Luctus accumsan tortor posuere ac. Eu ultrices vitae auctor eu augue ut lectus arcu bibendum. Pretium nibh ipsum consequat nisl vel pretium lectus. Aliquam etiam erat velit scelerisque in dictum. Sem et tortor consequat id porta nibh venenatis cras sed. A scelerisque purus semper eget duis at tellus at urna. At auctor urna nunc id. Ornare quam viverra orci sagittis eu volutpat odio. Nisl purus in mollis nunc sed id semper. Ornare suspendisse sed nisi lacus sed. Consectetur lorem donec massa sapien faucibus et. Ipsum dolor sit amet consectetur adipiscing elit ut. Porta nibh venenatis cras sed. Dignissim diam quis enim lobortis scelerisque. Quam nulla porttitor massa id. Tellus molestie nunc non blandit massa.
57
58 Malesuada fames ac turpis egestas. Suscipit tellus mauris a diam maecenas. Turpis in eu mi bibendum neque egestas. Venenatis tellus in metus vulputate eu scelerisque felis imperdiet. Quis imperdiet massa tincidunt nunc pulvinar sapien et. Urna duis convallis convallis tellus id. Velit egestas dui id ornare arcu odio. Consectetur purus ut faucibus pulvinar elementum integer enim neque. Aenean sed adipiscing diam donec adipiscing tristique. Tortor aliquam nulla facilisi cras fermentum odio eu. Diam in arcu cursus euismod quis viverra nibh cras.
59
60 Id ornare arcu odio ut sem. Arcu dictum varius duis at consectetur lorem donec massa sapien. Proin libero nunc consequat interdum varius sit. Ut eu sem integer vitae justo. Vitae elementum curabitur vitae nunc. Diam quam nulla porttitor massa. Lectus mauris ultrices eros in cursus turpis massa tincidunt dui. Natoque penatibus et magnis dis parturient montes. Pellentesque habitant morbi tristique senectus et netus et malesuada fames. Libero nunc consequat interdum varius sit. Rhoncus dolor purus non enim praesent. Pellentesque sit amet porttitor eget. Nibh tortor id aliquet lectus proin nibh. Fermentum iaculis eu non diam phasellus vestibulum lorem sed.
61
62 Eu feugiat pretium nibh ipsum consequat nisl vel pretium lectus. Habitant morbi tristique senectus et netus et malesuada fames ac. Urna condimentum mattis pellentesque id. Lorem sed risus ultricies tristique nulla aliquet enim tortor at. Ipsum dolor sit amet consectetur adipiscing elit. Convallis a cras semper auctor neque vitae tempus quam. A diam sollicitudin tempor id eu nisl nunc mi ipsum. Maecenas sed enim ut sem viverra aliquet eget. Massa enim nec dui nunc mattis enim. Nam aliquam sem et tortor consequat. Adipiscing commodo elit at imperdiet dui accumsan sit amet nulla. Nullam eget felis eget nunc lobortis. Mauris a diam maecenas sed enim ut sem viverra. Ornare massa eget egestas purus. In hac habitasse platea dictumst. Ut tortor pretium viverra suspendisse potenti nullam ac tortor. Nisl nunc mi ipsum faucibus. At varius vel pharetra vel. Mauris ultrices eros in cursus turpis massa tincidunt.`)
63 })
64 }
1111 // License for the specific language governing permissions and limitations
1212 // under the License.
1313
14 package ini_test
14 package ini
1515
1616 import (
1717 "bytes"
1818 "flag"
1919 "io/ioutil"
20 "path/filepath"
21 "runtime"
2022 "testing"
2123
22 . "github.com/smartystreets/goconvey/convey"
23 "gopkg.in/ini.v1"
24 "github.com/stretchr/testify/assert"
25 "github.com/stretchr/testify/require"
2426 )
2527
2628 const (
5052 var update = flag.Bool("update", false, "Update .golden files")
5153
5254 func TestLoad(t *testing.T) {
53 Convey("Load from good data sources", t, func() {
54 f, err := ini.Load(
55 t.Run("load from good data sources", func(t *testing.T) {
56 f, err := Load(
5557 "testdata/minimal.ini",
5658 []byte("NAME = ini\nIMPORT_PATH = gopkg.in/%(NAME)s.%(VERSION)s"),
5759 bytes.NewReader([]byte(`VERSION = v1`)),
5860 ioutil.NopCloser(bytes.NewReader([]byte("[author]\nNAME = Unknwon"))),
5961 )
60 So(err, ShouldBeNil)
61 So(f, ShouldNotBeNil)
62 require.NoError(t, err)
63 require.NotNil(t, f)
6264
6365 // Validate values make sure all sources are loaded correctly
6466 sec := f.Section("")
65 So(sec.Key("NAME").String(), ShouldEqual, "ini")
66 So(sec.Key("VERSION").String(), ShouldEqual, "v1")
67 So(sec.Key("IMPORT_PATH").String(), ShouldEqual, "gopkg.in/ini.v1")
67 assert.Equal(t, "ini", sec.Key("NAME").String())
68 assert.Equal(t, "v1", sec.Key("VERSION").String())
69 assert.Equal(t, "gopkg.in/ini.v1", sec.Key("IMPORT_PATH").String())
6870
6971 sec = f.Section("author")
70 So(sec.Key("NAME").String(), ShouldEqual, "Unknwon")
71 So(sec.Key("E-MAIL").String(), ShouldEqual, "u@gogs.io")
72 })
73
74 Convey("Load from bad data sources", t, func() {
75 Convey("Invalid input", func() {
76 _, err := ini.Load(notFoundConf)
77 So(err, ShouldNotBeNil)
78 })
79
80 Convey("Unsupported type", func() {
81 _, err := ini.Load(123)
82 So(err, ShouldNotBeNil)
83 })
84 })
85
86 Convey("Can't properly parse INI files containing `#` or `;` in value", t, func() {
87 f, err := ini.Load([]byte(`
72 assert.Equal(t, "Unknwon", sec.Key("NAME").String())
73 assert.Equal(t, "u@gogs.io", sec.Key("E-MAIL").String())
74 })
75
76 t.Run("load from bad data sources", func(t *testing.T) {
77 t.Run("invalid input", func(t *testing.T) {
78 _, err := Load(notFoundConf)
79 require.Error(t, err)
80 })
81
82 t.Run("unsupported type", func(t *testing.T) {
83 _, err := Load(123)
84 require.Error(t, err)
85 })
86 })
87
88 t.Run("cannot properly parse INI files containing `#` or `;` in value", func(t *testing.T) {
89 f, err := Load([]byte(`
8890 [author]
8991 NAME = U#n#k#n#w#o#n
9092 GITHUB = U;n;k;n;w;o;n
9193 `))
92 So(err, ShouldBeNil)
93 So(f, ShouldNotBeNil)
94 require.NoError(t, err)
95 require.NotNil(t, f)
9496
9597 sec := f.Section("author")
9698 nameValue := sec.Key("NAME").String()
9799 githubValue := sec.Key("GITHUB").String()
98 So(nameValue, ShouldEqual, "U")
99 So(githubValue, ShouldEqual, "U")
100 })
101
102 Convey("Can't parse small python-compatible INI files", t, func() {
103 f, err := ini.Load([]byte(`
100 assert.Equal(t, "U", nameValue)
101 assert.Equal(t, "U", githubValue)
102 })
103
104 t.Run("cannot parse small python-compatible INI files", func(t *testing.T) {
105 f, err := Load([]byte(`
104106 [long]
105107 long_rsa_private_key = -----BEGIN RSA PRIVATE KEY-----
106108 foo
109111 barfoo
110112 -----END RSA PRIVATE KEY-----
111113 `))
112 So(err, ShouldNotBeNil)
113 So(f, ShouldBeNil)
114 So(err.Error(), ShouldEqual, "key-value delimiter not found: foo\n")
115 })
116
117 Convey("Can't parse big python-compatible INI files", t, func() {
118 f, err := ini.Load([]byte(`
114 require.Error(t, err)
115 assert.Nil(t, f)
116 assert.Equal(t, "key-value delimiter not found: foo\n", err.Error())
117 })
118
119 t.Run("cannot parse big python-compatible INI files", func(t *testing.T) {
120 f, err := Load([]byte(`
119121 [long]
120122 long_rsa_private_key = -----BEGIN RSA PRIVATE KEY-----
121123 1foo
216218 96barfoo
217219 -----END RSA PRIVATE KEY-----
218220 `))
219 So(err, ShouldNotBeNil)
220 So(f, ShouldBeNil)
221 So(err.Error(), ShouldEqual, "key-value delimiter not found: 1foo\n")
221 require.Error(t, err)
222 assert.Nil(t, f)
223 assert.Equal(t, "key-value delimiter not found: 1foo\n", err.Error())
222224 })
223225 }
224226
225227 func TestLooseLoad(t *testing.T) {
226 Convey("Load from data sources with option `Loose` true", t, func() {
227 f, err := ini.LoadSources(ini.LoadOptions{Loose: true}, notFoundConf, minimalConf)
228 So(err, ShouldBeNil)
229 So(f, ShouldNotBeNil)
230
231 Convey("Inverse case", func() {
232 _, err = ini.Load(notFoundConf)
233 So(err, ShouldNotBeNil)
234 })
228 f, err := LoadSources(LoadOptions{Loose: true}, notFoundConf, minimalConf)
229 require.NoError(t, err)
230 require.NotNil(t, f)
231
232 t.Run("inverse case", func(t *testing.T) {
233 _, err = Load(notFoundConf)
234 require.Error(t, err)
235235 })
236236 }
237237
238238 func TestInsensitiveLoad(t *testing.T) {
239 Convey("Insensitive to section and key names", t, func() {
240 f, err := ini.InsensitiveLoad(minimalConf)
241 So(err, ShouldBeNil)
242 So(f, ShouldNotBeNil)
243
244 So(f.Section("Author").Key("e-mail").String(), ShouldEqual, "u@gogs.io")
245
246 Convey("Write out", func() {
239 t.Run("insensitive to section and key names", func(t *testing.T) {
240 f, err := InsensitiveLoad(minimalConf)
241 require.NoError(t, err)
242 require.NotNil(t, f)
243
244 assert.Equal(t, "u@gogs.io", f.Section("Author").Key("e-mail").String())
245
246 t.Run("write out", func(t *testing.T) {
247247 var buf bytes.Buffer
248248 _, err := f.WriteTo(&buf)
249 So(err, ShouldBeNil)
250 So(buf.String(), ShouldEqual, `[author]
249 require.NoError(t, err)
250 assert.Equal(t, `[author]
251251 e-mail = u@gogs.io
252252
253 `)
254 })
255
256 Convey("Inverse case", func() {
257 f, err := ini.Load(minimalConf)
258 So(err, ShouldBeNil)
259 So(f, ShouldNotBeNil)
260
261 So(f.Section("Author").Key("e-mail").String(), ShouldBeEmpty)
253 `,
254 buf.String(),
255 )
256 })
257
258 t.Run("inverse case", func(t *testing.T) {
259 f, err := Load(minimalConf)
260 require.NoError(t, err)
261 require.NotNil(t, f)
262
263 assert.Empty(t, f.Section("Author").Key("e-mail").String())
262264 })
263265 })
264266
265267 // Ref: https://github.com/go-ini/ini/issues/198
266 Convey("Insensitive load with default section", t, func() {
267 f, err := ini.InsensitiveLoad([]byte(`
268 t.Run("insensitive load with default section", func(t *testing.T) {
269 f, err := InsensitiveLoad([]byte(`
268270 user = unknwon
269271 [profile]
270272 email = unknwon@local
271273 `))
272 So(err, ShouldBeNil)
273 So(f, ShouldNotBeNil)
274
275 So(f.Section(ini.DefaultSection).Key("user").String(), ShouldEqual, "unknwon")
274 require.NoError(t, err)
275 require.NotNil(t, f)
276
277 assert.Equal(t, "unknwon", f.Section(DefaultSection).Key("user").String())
276278 })
277279 }
278280
279281 func TestLoadSources(t *testing.T) {
280 Convey("Load from data sources with options", t, func() {
281 Convey("with true `AllowPythonMultilineValues`", func() {
282 Convey("Ignore nonexistent files", func() {
283 f, err := ini.LoadSources(ini.LoadOptions{AllowPythonMultilineValues: true, Loose: true}, notFoundConf, minimalConf)
284 So(err, ShouldBeNil)
285 So(f, ShouldNotBeNil)
286
287 Convey("Inverse case", func() {
288 _, err = ini.LoadSources(ini.LoadOptions{AllowPythonMultilineValues: true}, notFoundConf)
289 So(err, ShouldNotBeNil)
290 })
291 })
292
293 Convey("Insensitive to section and key names", func() {
294 f, err := ini.LoadSources(ini.LoadOptions{AllowPythonMultilineValues: true, Insensitive: true}, minimalConf)
295 So(err, ShouldBeNil)
296 So(f, ShouldNotBeNil)
297
298 So(f.Section("Author").Key("e-mail").String(), ShouldEqual, "u@gogs.io")
299
300 Convey("Write out", func() {
301 var buf bytes.Buffer
302 _, err := f.WriteTo(&buf)
303 So(err, ShouldBeNil)
304 So(buf.String(), ShouldEqual, `[author]
282 t.Run("with true `AllowPythonMultilineValues`", func(t *testing.T) {
283 t.Run("ignore nonexistent files", func(t *testing.T) {
284 f, err := LoadSources(LoadOptions{AllowPythonMultilineValues: true, Loose: true}, notFoundConf, minimalConf)
285 require.NoError(t, err)
286 require.NotNil(t, f)
287
288 t.Run("inverse case", func(t *testing.T) {
289 _, err = LoadSources(LoadOptions{AllowPythonMultilineValues: true}, notFoundConf)
290 require.Error(t, err)
291 })
292 })
293
294 t.Run("insensitive to section and key names", func(t *testing.T) {
295 f, err := LoadSources(LoadOptions{AllowPythonMultilineValues: true, Insensitive: true}, minimalConf)
296 require.NoError(t, err)
297 require.NotNil(t, f)
298
299 assert.Equal(t, "u@gogs.io", f.Section("Author").Key("e-mail").String())
300
301 t.Run("write out", func(t *testing.T) {
302 var buf bytes.Buffer
303 _, err := f.WriteTo(&buf)
304 require.NoError(t, err)
305 assert.Equal(t, `[author]
305306 e-mail = u@gogs.io
306307
307 `)
308 })
309
310 Convey("Inverse case", func() {
311 f, err := ini.LoadSources(ini.LoadOptions{AllowPythonMultilineValues: true}, minimalConf)
312 So(err, ShouldBeNil)
313 So(f, ShouldNotBeNil)
314
315 So(f.Section("Author").Key("e-mail").String(), ShouldBeEmpty)
316 })
317 })
318
319 Convey("Ignore continuation lines", func() {
320 f, err := ini.LoadSources(ini.LoadOptions{
321 AllowPythonMultilineValues: true,
322 IgnoreContinuation: true,
323 }, []byte(`
308 `,
309 buf.String(),
310 )
311 })
312
313 t.Run("inverse case", func(t *testing.T) {
314 f, err := LoadSources(LoadOptions{AllowPythonMultilineValues: true}, minimalConf)
315 require.NoError(t, err)
316 require.NotNil(t, f)
317
318 assert.Empty(t, f.Section("Author").Key("e-mail").String())
319 })
320 })
321
322 t.Run("insensitive to sections and sensitive to key names", func(t *testing.T) {
323 f, err := LoadSources(LoadOptions{InsensitiveSections: true}, minimalConf)
324 require.NoError(t, err)
325 require.NotNil(t, f)
326
327 assert.Equal(t, "u@gogs.io", f.Section("Author").Key("E-MAIL").String())
328
329 t.Run("write out", func(t *testing.T) {
330 var buf bytes.Buffer
331 _, err := f.WriteTo(&buf)
332 require.NoError(t, err)
333 assert.Equal(t, `[author]
334 E-MAIL = u@gogs.io
335
336 `,
337 buf.String(),
338 )
339 })
340
341 t.Run("inverse case", func(t *testing.T) {
342 f, err := LoadSources(LoadOptions{}, minimalConf)
343 require.NoError(t, err)
344 require.NotNil(t, f)
345
346 assert.Empty(t, f.Section("Author").Key("e-mail").String())
347 })
348 })
349
350 t.Run("sensitive to sections and insensitive to key names", func(t *testing.T) {
351 f, err := LoadSources(LoadOptions{InsensitiveKeys: true}, minimalConf)
352 require.NoError(t, err)
353 require.NotNil(t, f)
354
355 assert.Equal(t, "u@gogs.io", f.Section("author").Key("e-mail").String())
356
357 t.Run("write out", func(t *testing.T) {
358 var buf bytes.Buffer
359 _, err := f.WriteTo(&buf)
360 require.NoError(t, err)
361 assert.Equal(t, `[author]
362 e-mail = u@gogs.io
363
364 `,
365 buf.String(),
366 )
367 })
368
369 t.Run("inverse case", func(t *testing.T) {
370 f, err := LoadSources(LoadOptions{}, minimalConf)
371 require.NoError(t, err)
372 require.NotNil(t, f)
373
374 assert.Empty(t, f.Section("Author").Key("e-mail").String())
375 })
376 })
377
378 t.Run("ignore continuation lines", func(t *testing.T) {
379 f, err := LoadSources(LoadOptions{
380 AllowPythonMultilineValues: true,
381 IgnoreContinuation: true,
382 }, []byte(`
324383 key1=a\b\
325384 key2=c\d\
326385 key3=value`))
327 So(err, ShouldBeNil)
328 So(f, ShouldNotBeNil)
329
330 So(f.Section("").Key("key1").String(), ShouldEqual, `a\b\`)
331 So(f.Section("").Key("key2").String(), ShouldEqual, `c\d\`)
332 So(f.Section("").Key("key3").String(), ShouldEqual, "value")
333
334 Convey("Inverse case", func() {
335 f, err := ini.LoadSources(ini.LoadOptions{AllowPythonMultilineValues: true}, []byte(`
386 require.NoError(t, err)
387 require.NotNil(t, f)
388
389 assert.Equal(t, `a\b\`, f.Section("").Key("key1").String())
390 assert.Equal(t, `c\d\`, f.Section("").Key("key2").String())
391 assert.Equal(t, "value", f.Section("").Key("key3").String())
392
393 t.Run("inverse case", func(t *testing.T) {
394 f, err := LoadSources(LoadOptions{AllowPythonMultilineValues: true}, []byte(`
336395 key1=a\b\
337396 key2=c\d\`))
338 So(err, ShouldBeNil)
339 So(f, ShouldNotBeNil)
340
341 So(f.Section("").Key("key1").String(), ShouldEqual, `a\bkey2=c\d`)
342 })
343 })
344
345 Convey("Ignore inline comments", func() {
346 f, err := ini.LoadSources(ini.LoadOptions{
347 AllowPythonMultilineValues: true,
348 IgnoreInlineComment: true,
349 }, []byte(`
397 require.NoError(t, err)
398 require.NotNil(t, f)
399
400 assert.Equal(t, `a\bkey2=c\d`, f.Section("").Key("key1").String())
401 })
402 })
403
404 t.Run("ignore inline comments", func(t *testing.T) {
405 f, err := LoadSources(LoadOptions{
406 AllowPythonMultilineValues: true,
407 IgnoreInlineComment: true,
408 }, []byte(`
350409 key1=value ;comment
351410 key2=value2 #comment2
352411 key3=val#ue #comment3`))
353 So(err, ShouldBeNil)
354 So(f, ShouldNotBeNil)
355
356 So(f.Section("").Key("key1").String(), ShouldEqual, `value ;comment`)
357 So(f.Section("").Key("key2").String(), ShouldEqual, `value2 #comment2`)
358 So(f.Section("").Key("key3").String(), ShouldEqual, `val#ue #comment3`)
359
360 Convey("Inverse case", func() {
361 f, err := ini.LoadSources(ini.LoadOptions{AllowPythonMultilineValues: true}, []byte(`
412 require.NoError(t, err)
413 require.NotNil(t, f)
414
415 assert.Equal(t, `value ;comment`, f.Section("").Key("key1").String())
416 assert.Equal(t, `value2 #comment2`, f.Section("").Key("key2").String())
417 assert.Equal(t, `val#ue #comment3`, f.Section("").Key("key3").String())
418
419 t.Run("inverse case", func(t *testing.T) {
420 f, err := LoadSources(LoadOptions{AllowPythonMultilineValues: true}, []byte(`
362421 key1=value ;comment
363422 key2=value2 #comment2`))
364 So(err, ShouldBeNil)
365 So(f, ShouldNotBeNil)
366
367 So(f.Section("").Key("key1").String(), ShouldEqual, `value`)
368 So(f.Section("").Key("key1").Comment, ShouldEqual, `;comment`)
369 So(f.Section("").Key("key2").String(), ShouldEqual, `value2`)
370 So(f.Section("").Key("key2").Comment, ShouldEqual, `#comment2`)
371 })
372 })
373
374 Convey("Skip unrecognizable lines", func() {
375 f, err := ini.LoadSources(ini.LoadOptions{
376 SkipUnrecognizableLines: true,
377 }, []byte(`
423 require.NoError(t, err)
424 require.NotNil(t, f)
425
426 assert.Equal(t, `value`, f.Section("").Key("key1").String())
427 assert.Equal(t, `;comment`, f.Section("").Key("key1").Comment)
428 assert.Equal(t, `value2`, f.Section("").Key("key2").String())
429 assert.Equal(t, `#comment2`, f.Section("").Key("key2").Comment)
430 })
431 })
432
433 t.Run("skip unrecognizable lines", func(t *testing.T) {
434 f, err := LoadSources(LoadOptions{
435 SkipUnrecognizableLines: true,
436 }, []byte(`
378437 GenerationDepth: 13
379438
380439 BiomeRarityScale: 100
386445 BiomeGroup(NormalBiomes, 3, 99, RoofedForestEnchanted, ForestSakura, FloatingJungle
387446 BiomeGroup(IceBiomes, 4, 85, Ice Plains)
388447 `))
389 So(err, ShouldBeNil)
390 So(f, ShouldNotBeNil)
391
392 So(f.Section("").Key("GenerationDepth").String(), ShouldEqual, "13")
393 So(f.Section("").Key("BiomeRarityScale").String(), ShouldEqual, "100")
394 So(f.Section("").HasKey("BiomeGroup"), ShouldBeFalse)
395 })
396
397 Convey("Allow boolean type keys", func() {
398 f, err := ini.LoadSources(ini.LoadOptions{
399 AllowPythonMultilineValues: true,
400 AllowBooleanKeys: true,
401 }, []byte(`
448 require.NoError(t, err)
449 require.NotNil(t, f)
450
451 assert.Equal(t, "13", f.Section("").Key("GenerationDepth").String())
452 assert.Equal(t, "100", f.Section("").Key("BiomeRarityScale").String())
453 assert.False(t, f.Section("").HasKey("BiomeGroup"))
454 })
455
456 t.Run("allow boolean type keys", func(t *testing.T) {
457 f, err := LoadSources(LoadOptions{
458 AllowPythonMultilineValues: true,
459 AllowBooleanKeys: true,
460 }, []byte(`
402461 key1=hello
403462 #key2
404463 key3`))
405 So(err, ShouldBeNil)
406 So(f, ShouldNotBeNil)
407
408 So(f.Section("").KeyStrings(), ShouldResemble, []string{"key1", "key3"})
409 So(f.Section("").Key("key3").MustBool(false), ShouldBeTrue)
410
411 Convey("Write out", func() {
412 var buf bytes.Buffer
413 _, err := f.WriteTo(&buf)
414 So(err, ShouldBeNil)
415 So(buf.String(), ShouldEqual, `key1 = hello
464 require.NoError(t, err)
465 require.NotNil(t, f)
466
467 assert.Equal(t, []string{"key1", "key3"}, f.Section("").KeyStrings())
468 assert.True(t, f.Section("").Key("key3").MustBool(false))
469
470 t.Run("write out", func(t *testing.T) {
471 var buf bytes.Buffer
472 _, err := f.WriteTo(&buf)
473 require.NoError(t, err)
474 assert.Equal(t, `key1 = hello
416475 # key2
417476 key3
418 `)
419 })
420
421 Convey("Inverse case", func() {
422 _, err := ini.LoadSources(ini.LoadOptions{AllowPythonMultilineValues: true}, []byte(`
477 `,
478 buf.String(),
479 )
480 })
481
482 t.Run("inverse case", func(t *testing.T) {
483 _, err := LoadSources(LoadOptions{AllowPythonMultilineValues: true}, []byte(`
423484 key1=hello
424485 #key2
425486 key3`))
426 So(err, ShouldNotBeNil)
427 })
428 })
429
430 Convey("Allow shadow keys", func() {
431 f, err := ini.LoadSources(ini.LoadOptions{AllowShadows: true, AllowPythonMultilineValues: true}, []byte(`
487 require.Error(t, err)
488 })
489 })
490
491 t.Run("allow shadow keys", func(t *testing.T) {
492 f, err := LoadSources(LoadOptions{AllowShadows: true, AllowPythonMultilineValues: true}, []byte(`
432493 [remote "origin"]
433494 url = https://github.com/Antergone/test1.git
434495 url = https://github.com/Antergone/test2.git
435496 fetch = +refs/heads/*:refs/remotes/origin/*`))
436 So(err, ShouldBeNil)
437 So(f, ShouldNotBeNil)
438
439 So(f.Section(`remote "origin"`).Key("url").String(), ShouldEqual, "https://github.com/Antergone/test1.git")
440 So(f.Section(`remote "origin"`).Key("url").ValueWithShadows(), ShouldResemble, []string{
497 require.NoError(t, err)
498 require.NotNil(t, f)
499
500 assert.Equal(t, "https://github.com/Antergone/test1.git", f.Section(`remote "origin"`).Key("url").String())
501 assert.Equal(
502 t,
503 []string{
441504 "https://github.com/Antergone/test1.git",
442505 "https://github.com/Antergone/test2.git",
443 })
444 So(f.Section(`remote "origin"`).Key("fetch").String(), ShouldEqual, "+refs/heads/*:refs/remotes/origin/*")
445
446 Convey("Write out", func() {
447 var buf bytes.Buffer
448 _, err := f.WriteTo(&buf)
449 So(err, ShouldBeNil)
450 So(buf.String(), ShouldEqual, `[remote "origin"]
506 },
507 f.Section(`remote "origin"`).Key("url").ValueWithShadows(),
508 )
509 assert.Equal(t, "+refs/heads/*:refs/remotes/origin/*", f.Section(`remote "origin"`).Key("fetch").String())
510
511 t.Run("write out", func(t *testing.T) {
512 var buf bytes.Buffer
513 _, err := f.WriteTo(&buf)
514 require.NoError(t, err)
515 assert.Equal(t, `[remote "origin"]
451516 url = https://github.com/Antergone/test1.git
452517 url = https://github.com/Antergone/test2.git
453518 fetch = +refs/heads/*:refs/remotes/origin/*
454519
455 `)
456 })
457
458 Convey("Inverse case", func() {
459 f, err := ini.LoadSources(ini.LoadOptions{AllowPythonMultilineValues: true}, []byte(`
520 `,
521 buf.String(),
522 )
523 })
524
525 t.Run("inverse case", func(t *testing.T) {
526 f, err := LoadSources(LoadOptions{AllowPythonMultilineValues: true}, []byte(`
460527 [remote "origin"]
461528 url = https://github.com/Antergone/test1.git
462529 url = https://github.com/Antergone/test2.git`))
463 So(err, ShouldBeNil)
464 So(f, ShouldNotBeNil)
465
466 So(f.Section(`remote "origin"`).Key("url").String(), ShouldEqual, "https://github.com/Antergone/test2.git")
467 })
468 })
469
470 Convey("Unescape double quotes inside value", func() {
471 f, err := ini.LoadSources(ini.LoadOptions{
472 AllowPythonMultilineValues: true,
473 UnescapeValueDoubleQuotes: true,
474 }, []byte(`
530 require.NoError(t, err)
531 require.NotNil(t, f)
532
533 assert.Equal(t, "https://github.com/Antergone/test2.git", f.Section(`remote "origin"`).Key("url").String())
534 })
535 })
536
537 t.Run("unescape double quotes inside value", func(t *testing.T) {
538 f, err := LoadSources(LoadOptions{
539 AllowPythonMultilineValues: true,
540 UnescapeValueDoubleQuotes: true,
541 }, []byte(`
475542 create_repo="创建了仓库 <a href=\"%s\">%s</a>"`))
476 So(err, ShouldBeNil)
477 So(f, ShouldNotBeNil)
478
479 So(f.Section("").Key("create_repo").String(), ShouldEqual, `创建了仓库 <a href="%s">%s</a>`)
480
481 Convey("Inverse case", func() {
482 f, err := ini.LoadSources(ini.LoadOptions{AllowPythonMultilineValues: true}, []byte(`
543 require.NoError(t, err)
544 require.NotNil(t, f)
545
546 assert.Equal(t, `创建了仓库 <a href="%s">%s</a>`, f.Section("").Key("create_repo").String())
547
548 t.Run("inverse case", func(t *testing.T) {
549 f, err := LoadSources(LoadOptions{AllowPythonMultilineValues: true}, []byte(`
483550 create_repo="创建了仓库 <a href=\"%s\">%s</a>"`))
484 So(err, ShouldBeNil)
485 So(f, ShouldNotBeNil)
486
487 So(f.Section("").Key("create_repo").String(), ShouldEqual, `"创建了仓库 <a href=\"%s\">%s</a>"`)
488 })
489 })
490
491 Convey("Unescape comment symbols inside value", func() {
492 f, err := ini.LoadSources(ini.LoadOptions{
493 AllowPythonMultilineValues: true,
494 IgnoreInlineComment: true,
495 UnescapeValueCommentSymbols: true,
496 }, []byte(`
551 require.NoError(t, err)
552 require.NotNil(t, f)
553
554 assert.Equal(t, `"创建了仓库 <a href=\"%s\">%s</a>"`, f.Section("").Key("create_repo").String())
555 })
556 })
557
558 t.Run("unescape comment symbols inside value", func(t *testing.T) {
559 f, err := LoadSources(LoadOptions{
560 AllowPythonMultilineValues: true,
561 IgnoreInlineComment: true,
562 UnescapeValueCommentSymbols: true,
563 }, []byte(`
497564 key = test value <span style="color: %s\; background: %s">more text</span>
498565 `))
499 So(err, ShouldBeNil)
500 So(f, ShouldNotBeNil)
501
502 So(f.Section("").Key("key").String(), ShouldEqual, `test value <span style="color: %s; background: %s">more text</span>`)
503 })
504
505 Convey("Can parse small python-compatible INI files", func() {
506 f, err := ini.LoadSources(ini.LoadOptions{
507 AllowPythonMultilineValues: true,
508 Insensitive: true,
509 UnparseableSections: []string{"core_lesson", "comments"},
510 }, []byte(`
566 require.NoError(t, err)
567 require.NotNil(t, f)
568
569 assert.Equal(t, `test value <span style="color: %s; background: %s">more text</span>`, f.Section("").Key("key").String())
570 })
571
572 t.Run("can parse small python-compatible INI files", func(t *testing.T) {
573 f, err := LoadSources(LoadOptions{
574 AllowPythonMultilineValues: true,
575 Insensitive: true,
576 UnparseableSections: []string{"core_lesson", "comments"},
577 }, []byte(`
511578 [long]
512579 long_rsa_private_key = -----BEGIN RSA PRIVATE KEY-----
513580 foo
520587 second
521588 third
522589 `))
523 So(err, ShouldBeNil)
524 So(f, ShouldNotBeNil)
525
526 So(f.Section("long").Key("long_rsa_private_key").String(), ShouldEqual, "-----BEGIN RSA PRIVATE KEY-----\nfoo\nbar\nfoobar\nbarfoo\n-----END RSA PRIVATE KEY-----")
527 So(f.Section("long").Key("multiline_list").String(), ShouldEqual, "\nfirst\nsecond\nthird")
528 })
529
530 Convey("Can parse big python-compatible INI files", func() {
531 f, err := ini.LoadSources(ini.LoadOptions{
532 AllowPythonMultilineValues: true,
533 Insensitive: true,
534 UnparseableSections: []string{"core_lesson", "comments"},
535 }, []byte(`
590 require.NoError(t, err)
591 require.NotNil(t, f)
592
593 assert.Equal(t, "-----BEGIN RSA PRIVATE KEY-----\n foo\n bar\n foobar\n barfoo\n -----END RSA PRIVATE KEY-----", f.Section("long").Key("long_rsa_private_key").String())
594 assert.Equal(t, "\n first\n second\n third", f.Section("long").Key("multiline_list").String())
595 })
596
597 t.Run("can parse big python-compatible INI files", func(t *testing.T) {
598 f, err := LoadSources(LoadOptions{
599 AllowPythonMultilineValues: true,
600 Insensitive: true,
601 UnparseableSections: []string{"core_lesson", "comments"},
602 }, []byte(`
536603 [long]
537604 long_rsa_private_key = -----BEGIN RSA PRIVATE KEY-----
538605 1foo
633700 96barfoo
634701 -----END RSA PRIVATE KEY-----
635702 `))
636 So(err, ShouldBeNil)
637 So(f, ShouldNotBeNil)
638
639 So(f.Section("long").Key("long_rsa_private_key").String(), ShouldEqual, `-----BEGIN RSA PRIVATE KEY-----
640 1foo
641 2bar
642 3foobar
643 4barfoo
644 5foo
645 6bar
646 7foobar
647 8barfoo
648 9foo
649 10bar
650 11foobar
651 12barfoo
652 13foo
653 14bar
654 15foobar
655 16barfoo
656 17foo
657 18bar
658 19foobar
659 20barfoo
660 21foo
661 22bar
662 23foobar
663 24barfoo
664 25foo
665 26bar
666 27foobar
667 28barfoo
668 29foo
669 30bar
670 31foobar
671 32barfoo
672 33foo
673 34bar
674 35foobar
675 36barfoo
676 37foo
677 38bar
678 39foobar
679 40barfoo
680 41foo
681 42bar
682 43foobar
683 44barfoo
684 45foo
685 46bar
686 47foobar
687 48barfoo
688 49foo
689 50bar
690 51foobar
691 52barfoo
692 53foo
693 54bar
694 55foobar
695 56barfoo
696 57foo
697 58bar
698 59foobar
699 60barfoo
700 61foo
701 62bar
702 63foobar
703 64barfoo
704 65foo
705 66bar
706 67foobar
707 68barfoo
708 69foo
709 70bar
710 71foobar
711 72barfoo
712 73foo
713 74bar
714 75foobar
715 76barfoo
716 77foo
717 78bar
718 79foobar
719 80barfoo
720 81foo
721 82bar
722 83foobar
723 84barfoo
724 85foo
725 86bar
726 87foobar
727 88barfoo
728 89foo
729 90bar
730 91foobar
731 92barfoo
732 93foo
733 94bar
734 95foobar
735 96barfoo
736 -----END RSA PRIVATE KEY-----`)
737 })
738
739 Convey("Allow unparsable sections", func() {
740 f, err := ini.LoadSources(ini.LoadOptions{
741 AllowPythonMultilineValues: true,
742 Insensitive: true,
743 UnparseableSections: []string{"core_lesson", "comments"},
744 }, []byte(`
703 require.NoError(t, err)
704 require.NotNil(t, f)
705
706 assert.Equal(t, `-----BEGIN RSA PRIVATE KEY-----
707 1foo
708 2bar
709 3foobar
710 4barfoo
711 5foo
712 6bar
713 7foobar
714 8barfoo
715 9foo
716 10bar
717 11foobar
718 12barfoo
719 13foo
720 14bar
721 15foobar
722 16barfoo
723 17foo
724 18bar
725 19foobar
726 20barfoo
727 21foo
728 22bar
729 23foobar
730 24barfoo
731 25foo
732 26bar
733 27foobar
734 28barfoo
735 29foo
736 30bar
737 31foobar
738 32barfoo
739 33foo
740 34bar
741 35foobar
742 36barfoo
743 37foo
744 38bar
745 39foobar
746 40barfoo
747 41foo
748 42bar
749 43foobar
750 44barfoo
751 45foo
752 46bar
753 47foobar
754 48barfoo
755 49foo
756 50bar
757 51foobar
758 52barfoo
759 53foo
760 54bar
761 55foobar
762 56barfoo
763 57foo
764 58bar
765 59foobar
766 60barfoo
767 61foo
768 62bar
769 63foobar
770 64barfoo
771 65foo
772 66bar
773 67foobar
774 68barfoo
775 69foo
776 70bar
777 71foobar
778 72barfoo
779 73foo
780 74bar
781 75foobar
782 76barfoo
783 77foo
784 78bar
785 79foobar
786 80barfoo
787 81foo
788 82bar
789 83foobar
790 84barfoo
791 85foo
792 86bar
793 87foobar
794 88barfoo
795 89foo
796 90bar
797 91foobar
798 92barfoo
799 93foo
800 94bar
801 95foobar
802 96barfoo
803 -----END RSA PRIVATE KEY-----`,
804 f.Section("long").Key("long_rsa_private_key").String(),
805 )
806 })
807
808 t.Run("allow unparsable sections", func(t *testing.T) {
809 f, err := LoadSources(LoadOptions{
810 AllowPythonMultilineValues: true,
811 Insensitive: true,
812 UnparseableSections: []string{"core_lesson", "comments"},
813 }, []byte(`
745814 Lesson_Location = 87
746815 Lesson_Status = C
747816 Score = 3
753822
754823 [COMMENTS]
755824 <1><L.Slide#2> This slide has the fuel listed in the wrong units <e.1>`))
756 So(err, ShouldBeNil)
757 So(f, ShouldNotBeNil)
758
759 So(f.Section("").Key("score").String(), ShouldEqual, "3")
760 So(f.Section("").Body(), ShouldBeEmpty)
761 So(f.Section("core_lesson").Body(), ShouldEqual, `my lesson state data – 1111111111111111111000000000000000001110000
762 111111111111111111100000000000111000000000 – end my lesson state data`)
763 So(f.Section("comments").Body(), ShouldEqual, `<1><L.Slide#2> This slide has the fuel listed in the wrong units <e.1>`)
764
765 Convey("Write out", func() {
766 var buf bytes.Buffer
767 _, err := f.WriteTo(&buf)
768 So(err, ShouldBeNil)
769 So(buf.String(), ShouldEqual, `lesson_location = 87
825 require.NoError(t, err)
826 require.NotNil(t, f)
827
828 assert.Equal(t, "3", f.Section("").Key("score").String())
829 assert.Empty(t, f.Section("").Body())
830 assert.Equal(t, `my lesson state data – 1111111111111111111000000000000000001110000
831 111111111111111111100000000000111000000000 – end my lesson state data`,
832 f.Section("core_lesson").Body(),
833 )
834 assert.Equal(t, `<1><L.Slide#2> This slide has the fuel listed in the wrong units <e.1>`, f.Section("comments").Body())
835
836 t.Run("write out", func(t *testing.T) {
837 var buf bytes.Buffer
838 _, err := f.WriteTo(&buf)
839 require.NoError(t, err)
840 assert.Equal(t, `lesson_location = 87
770841 lesson_status = C
771842 score = 3
772843 time = 00:02:30
777848
778849 [comments]
779850 <1><L.Slide#2> This slide has the fuel listed in the wrong units <e.1>
780 `)
781 })
782
783 Convey("Inverse case", func() {
784 _, err := ini.LoadSources(ini.LoadOptions{AllowPythonMultilineValues: true}, []byte(`
851 `,
852 buf.String(),
853 )
854 })
855
856 t.Run("inverse case", func(t *testing.T) {
857 _, err := LoadSources(LoadOptions{AllowPythonMultilineValues: true}, []byte(`
785858 [CORE_LESSON]
786859 my lesson state data – 1111111111111111111000000000000000001110000
787860 111111111111111111100000000000111000000000 – end my lesson state data`))
788 So(err, ShouldNotBeNil)
789 })
790 })
791
792 Convey("And false `SpaceBeforeInlineComment`", func() {
793 Convey("Can't parse INI files containing `#` or `;` in value", func() {
794 f, err := ini.LoadSources(
795 ini.LoadOptions{AllowPythonMultilineValues: false, SpaceBeforeInlineComment: false},
796 []byte(`
861 require.Error(t, err)
862 })
863 })
864
865 t.Run("and false `SpaceBeforeInlineComment`", func(t *testing.T) {
866 t.Run("cannot parse INI files containing `#` or `;` in value", func(t *testing.T) {
867 f, err := LoadSources(
868 LoadOptions{AllowPythonMultilineValues: false, SpaceBeforeInlineComment: false},
869 []byte(`
797870 [author]
798871 NAME = U#n#k#n#w#o#n
799872 GITHUB = U;n;k;n;w;o;n
800873 `))
801 So(err, ShouldBeNil)
802 So(f, ShouldNotBeNil)
803 sec := f.Section("author")
804 nameValue := sec.Key("NAME").String()
805 githubValue := sec.Key("GITHUB").String()
806 So(nameValue, ShouldEqual, "U")
807 So(githubValue, ShouldEqual, "U")
808 })
809 })
810
811 Convey("And true `SpaceBeforeInlineComment`", func() {
812 Convey("Can parse INI files containing `#` or `;` in value", func() {
813 f, err := ini.LoadSources(
814 ini.LoadOptions{AllowPythonMultilineValues: false, SpaceBeforeInlineComment: true},
815 []byte(`
874 require.NoError(t, err)
875 require.NotNil(t, f)
876 sec := f.Section("author")
877 nameValue := sec.Key("NAME").String()
878 githubValue := sec.Key("GITHUB").String()
879 assert.Equal(t, "U", nameValue)
880 assert.Equal(t, "U", githubValue)
881 })
882 })
883
884 t.Run("and true `SpaceBeforeInlineComment`", func(t *testing.T) {
885 t.Run("can parse INI files containing `#` or `;` in value", func(t *testing.T) {
886 f, err := LoadSources(
887 LoadOptions{AllowPythonMultilineValues: false, SpaceBeforeInlineComment: true},
888 []byte(`
816889 [author]
817890 NAME = U#n#k#n#w#o#n
818891 GITHUB = U;n;k;n;w;o;n
819892 `))
820 So(err, ShouldBeNil)
821 So(f, ShouldNotBeNil)
822 sec := f.Section("author")
823 nameValue := sec.Key("NAME").String()
824 githubValue := sec.Key("GITHUB").String()
825 So(nameValue, ShouldEqual, "U#n#k#n#w#o#n")
826 So(githubValue, ShouldEqual, "U;n;k;n;w;o;n")
827 })
828 })
829 })
830
831 Convey("with false `AllowPythonMultilineValues`", func() {
832 Convey("Ignore nonexistent files", func() {
833 f, err := ini.LoadSources(ini.LoadOptions{
893 require.NoError(t, err)
894 require.NotNil(t, f)
895 sec := f.Section("author")
896 nameValue := sec.Key("NAME").String()
897 githubValue := sec.Key("GITHUB").String()
898 assert.Equal(t, "U#n#k#n#w#o#n", nameValue)
899 assert.Equal(t, "U;n;k;n;w;o;n", githubValue)
900 })
901 })
902 })
903
904 t.Run("with false `AllowPythonMultilineValues`", func(t *testing.T) {
905 t.Run("ignore nonexistent files", func(t *testing.T) {
906 f, err := LoadSources(LoadOptions{
907 AllowPythonMultilineValues: false,
908 Loose: true,
909 }, notFoundConf, minimalConf)
910 require.NoError(t, err)
911 require.NotNil(t, f)
912
913 t.Run("inverse case", func(t *testing.T) {
914 _, err = LoadSources(LoadOptions{
834915 AllowPythonMultilineValues: false,
835 Loose: true,
836 }, notFoundConf, minimalConf)
837 So(err, ShouldBeNil)
838 So(f, ShouldNotBeNil)
839
840 Convey("Inverse case", func() {
841 _, err = ini.LoadSources(ini.LoadOptions{
842 AllowPythonMultilineValues: false,
843 }, notFoundConf)
844 So(err, ShouldNotBeNil)
845 })
846 })
847
848 Convey("Insensitive to section and key names", func() {
849 f, err := ini.LoadSources(ini.LoadOptions{
916 }, notFoundConf)
917 require.Error(t, err)
918 })
919 })
920
921 t.Run("insensitive to section and key names", func(t *testing.T) {
922 f, err := LoadSources(LoadOptions{
923 AllowPythonMultilineValues: false,
924 Insensitive: true,
925 }, minimalConf)
926 require.NoError(t, err)
927 require.NotNil(t, f)
928
929 assert.Equal(t, "u@gogs.io", f.Section("Author").Key("e-mail").String())
930
931 t.Run("write out", func(t *testing.T) {
932 var buf bytes.Buffer
933 _, err := f.WriteTo(&buf)
934 require.NoError(t, err)
935 assert.Equal(t, `[author]
936 e-mail = u@gogs.io
937
938 `,
939 buf.String(),
940 )
941 })
942
943 t.Run("inverse case", func(t *testing.T) {
944 f, err := LoadSources(LoadOptions{
850945 AllowPythonMultilineValues: false,
851 Insensitive: true,
852946 }, minimalConf)
853 So(err, ShouldBeNil)
854 So(f, ShouldNotBeNil)
855
856 So(f.Section("Author").Key("e-mail").String(), ShouldEqual, "u@gogs.io")
857
858 Convey("Write out", func() {
859 var buf bytes.Buffer
860 _, err := f.WriteTo(&buf)
861 So(err, ShouldBeNil)
862 So(buf.String(), ShouldEqual, `[author]
863 e-mail = u@gogs.io
864
865 `)
866 })
867
868 Convey("Inverse case", func() {
869 f, err := ini.LoadSources(ini.LoadOptions{
870 AllowPythonMultilineValues: false,
871 }, minimalConf)
872 So(err, ShouldBeNil)
873 So(f, ShouldNotBeNil)
874
875 So(f.Section("Author").Key("e-mail").String(), ShouldBeEmpty)
876 })
877 })
878
879 Convey("Ignore continuation lines", func() {
880 f, err := ini.LoadSources(ini.LoadOptions{
881 AllowPythonMultilineValues: false,
882 IgnoreContinuation: true,
883 }, []byte(`
947 require.NoError(t, err)
948 require.NotNil(t, f)
949
950 assert.Empty(t, f.Section("Author").Key("e-mail").String())
951 })
952 })
953
954 t.Run("ignore continuation lines", func(t *testing.T) {
955 f, err := LoadSources(LoadOptions{
956 AllowPythonMultilineValues: false,
957 IgnoreContinuation: true,
958 }, []byte(`
884959 key1=a\b\
885960 key2=c\d\
886961 key3=value`))
887 So(err, ShouldBeNil)
888 So(f, ShouldNotBeNil)
889
890 So(f.Section("").Key("key1").String(), ShouldEqual, `a\b\`)
891 So(f.Section("").Key("key2").String(), ShouldEqual, `c\d\`)
892 So(f.Section("").Key("key3").String(), ShouldEqual, "value")
893
894 Convey("Inverse case", func() {
895 f, err := ini.LoadSources(ini.LoadOptions{AllowPythonMultilineValues: false}, []byte(`
962 require.NoError(t, err)
963 require.NotNil(t, f)
964
965 assert.Equal(t, `a\b\`, f.Section("").Key("key1").String())
966 assert.Equal(t, `c\d\`, f.Section("").Key("key2").String())
967 assert.Equal(t, "value", f.Section("").Key("key3").String())
968
969 t.Run("inverse case", func(t *testing.T) {
970 f, err := LoadSources(LoadOptions{AllowPythonMultilineValues: false}, []byte(`
896971 key1=a\b\
897972 key2=c\d\`))
898 So(err, ShouldBeNil)
899 So(f, ShouldNotBeNil)
900
901 So(f.Section("").Key("key1").String(), ShouldEqual, `a\bkey2=c\d`)
902 })
903 })
904
905 Convey("Ignore inline comments", func() {
906 f, err := ini.LoadSources(ini.LoadOptions{
907 AllowPythonMultilineValues: false,
908 IgnoreInlineComment: true,
909 }, []byte(`
973 require.NoError(t, err)
974 require.NotNil(t, f)
975
976 assert.Equal(t, `a\bkey2=c\d`, f.Section("").Key("key1").String())
977 })
978 })
979
980 t.Run("ignore inline comments", func(t *testing.T) {
981 f, err := LoadSources(LoadOptions{
982 AllowPythonMultilineValues: false,
983 IgnoreInlineComment: true,
984 }, []byte(`
910985 key1=value ;comment
911986 key2=value2 #comment2
912987 key3=val#ue #comment3`))
913 So(err, ShouldBeNil)
914 So(f, ShouldNotBeNil)
915
916 So(f.Section("").Key("key1").String(), ShouldEqual, `value ;comment`)
917 So(f.Section("").Key("key2").String(), ShouldEqual, `value2 #comment2`)
918 So(f.Section("").Key("key3").String(), ShouldEqual, `val#ue #comment3`)
919
920 Convey("Inverse case", func() {
921 f, err := ini.LoadSources(ini.LoadOptions{AllowPythonMultilineValues: false}, []byte(`
988 require.NoError(t, err)
989 require.NotNil(t, f)
990
991 assert.Equal(t, `value ;comment`, f.Section("").Key("key1").String())
992 assert.Equal(t, `value2 #comment2`, f.Section("").Key("key2").String())
993 assert.Equal(t, `val#ue #comment3`, f.Section("").Key("key3").String())
994
995 t.Run("inverse case", func(t *testing.T) {
996 f, err := LoadSources(LoadOptions{AllowPythonMultilineValues: false}, []byte(`
922997 key1=value ;comment
923998 key2=value2 #comment2`))
924 So(err, ShouldBeNil)
925 So(f, ShouldNotBeNil)
926
927 So(f.Section("").Key("key1").String(), ShouldEqual, `value`)
928 So(f.Section("").Key("key1").Comment, ShouldEqual, `;comment`)
929 So(f.Section("").Key("key2").String(), ShouldEqual, `value2`)
930 So(f.Section("").Key("key2").Comment, ShouldEqual, `#comment2`)
931 })
932 })
933
934 Convey("Allow boolean type keys", func() {
935 f, err := ini.LoadSources(ini.LoadOptions{
936 AllowPythonMultilineValues: false,
937 AllowBooleanKeys: true,
938 }, []byte(`
999 require.NoError(t, err)
1000 require.NotNil(t, f)
1001
1002 assert.Equal(t, `value`, f.Section("").Key("key1").String())
1003 assert.Equal(t, `;comment`, f.Section("").Key("key1").Comment)
1004 assert.Equal(t, `value2`, f.Section("").Key("key2").String())
1005 assert.Equal(t, `#comment2`, f.Section("").Key("key2").Comment)
1006 })
1007 })
1008
1009 t.Run("allow boolean type keys", func(t *testing.T) {
1010 f, err := LoadSources(LoadOptions{
1011 AllowPythonMultilineValues: false,
1012 AllowBooleanKeys: true,
1013 }, []byte(`
9391014 key1=hello
9401015 #key2
9411016 key3`))
942 So(err, ShouldBeNil)
943 So(f, ShouldNotBeNil)
944
945 So(f.Section("").KeyStrings(), ShouldResemble, []string{"key1", "key3"})
946 So(f.Section("").Key("key3").MustBool(false), ShouldBeTrue)
947
948 Convey("Write out", func() {
949 var buf bytes.Buffer
950 _, err := f.WriteTo(&buf)
951 So(err, ShouldBeNil)
952 So(buf.String(), ShouldEqual, `key1 = hello
1017 require.NoError(t, err)
1018 require.NotNil(t, f)
1019
1020 assert.Equal(t, []string{"key1", "key3"}, f.Section("").KeyStrings())
1021 assert.True(t, f.Section("").Key("key3").MustBool(false))
1022
1023 t.Run("write out", func(t *testing.T) {
1024 var buf bytes.Buffer
1025 _, err := f.WriteTo(&buf)
1026 require.NoError(t, err)
1027 assert.Equal(t, `key1 = hello
9531028 # key2
9541029 key3
955 `)
956 })
957
958 Convey("Inverse case", func() {
959 _, err := ini.LoadSources(ini.LoadOptions{AllowPythonMultilineValues: false}, []byte(`
1030 `,
1031 buf.String(),
1032 )
1033 })
1034
1035 t.Run("inverse case", func(t *testing.T) {
1036 _, err := LoadSources(LoadOptions{AllowPythonMultilineValues: false}, []byte(`
9601037 key1=hello
9611038 #key2
9621039 key3`))
963 So(err, ShouldNotBeNil)
964 })
965 })
966
967 Convey("Allow shadow keys", func() {
968 f, err := ini.LoadSources(ini.LoadOptions{AllowPythonMultilineValues: false, AllowShadows: true}, []byte(`
1040 require.Error(t, err)
1041 })
1042 })
1043
1044 t.Run("allow shadow keys", func(t *testing.T) {
1045 f, err := LoadSources(LoadOptions{AllowPythonMultilineValues: false, AllowShadows: true}, []byte(`
9691046 [remote "origin"]
9701047 url = https://github.com/Antergone/test1.git
9711048 url = https://github.com/Antergone/test2.git
9721049 fetch = +refs/heads/*:refs/remotes/origin/*`))
973 So(err, ShouldBeNil)
974 So(f, ShouldNotBeNil)
975
976 So(f.Section(`remote "origin"`).Key("url").String(), ShouldEqual, "https://github.com/Antergone/test1.git")
977 So(f.Section(`remote "origin"`).Key("url").ValueWithShadows(), ShouldResemble, []string{
1050 require.NoError(t, err)
1051 require.NotNil(t, f)
1052
1053 assert.Equal(t, "https://github.com/Antergone/test1.git", f.Section(`remote "origin"`).Key("url").String())
1054 assert.Equal(
1055 t,
1056 []string{
9781057 "https://github.com/Antergone/test1.git",
9791058 "https://github.com/Antergone/test2.git",
980 })
981 So(f.Section(`remote "origin"`).Key("fetch").String(), ShouldEqual, "+refs/heads/*:refs/remotes/origin/*")
982
983 Convey("Write out", func() {
984 var buf bytes.Buffer
985 _, err := f.WriteTo(&buf)
986 So(err, ShouldBeNil)
987 So(buf.String(), ShouldEqual, `[remote "origin"]
1059 },
1060 f.Section(`remote "origin"`).Key("url").ValueWithShadows(),
1061 )
1062 assert.Equal(t, "+refs/heads/*:refs/remotes/origin/*", f.Section(`remote "origin"`).Key("fetch").String())
1063
1064 t.Run("write out", func(t *testing.T) {
1065 var buf bytes.Buffer
1066 _, err := f.WriteTo(&buf)
1067 require.NoError(t, err)
1068 assert.Equal(t, `[remote "origin"]
9881069 url = https://github.com/Antergone/test1.git
9891070 url = https://github.com/Antergone/test2.git
9901071 fetch = +refs/heads/*:refs/remotes/origin/*
9911072
992 `)
993 })
994
995 Convey("Inverse case", func() {
996 f, err := ini.LoadSources(ini.LoadOptions{AllowPythonMultilineValues: false}, []byte(`
1073 `,
1074 buf.String(),
1075 )
1076 })
1077
1078 t.Run("inverse case", func(t *testing.T) {
1079 f, err := LoadSources(LoadOptions{AllowPythonMultilineValues: false}, []byte(`
9971080 [remote "origin"]
9981081 url = https://github.com/Antergone/test1.git
9991082 url = https://github.com/Antergone/test2.git`))
1000 So(err, ShouldBeNil)
1001 So(f, ShouldNotBeNil)
1002
1003 So(f.Section(`remote "origin"`).Key("url").String(), ShouldEqual, "https://github.com/Antergone/test2.git")
1004 })
1005 })
1006
1007 Convey("Unescape double quotes inside value", func() {
1008 f, err := ini.LoadSources(ini.LoadOptions{
1009 AllowPythonMultilineValues: false,
1010 UnescapeValueDoubleQuotes: true,
1011 }, []byte(`
1083 require.NoError(t, err)
1084 require.NotNil(t, f)
1085
1086 assert.Equal(t, "https://github.com/Antergone/test2.git", f.Section(`remote "origin"`).Key("url").String())
1087 })
1088 })
1089
1090 t.Run("unescape double quotes inside value", func(t *testing.T) {
1091 f, err := LoadSources(LoadOptions{
1092 AllowPythonMultilineValues: false,
1093 UnescapeValueDoubleQuotes: true,
1094 }, []byte(`
10121095 create_repo="创建了仓库 <a href=\"%s\">%s</a>"`))
1013 So(err, ShouldBeNil)
1014 So(f, ShouldNotBeNil)
1015
1016 So(f.Section("").Key("create_repo").String(), ShouldEqual, `创建了仓库 <a href="%s">%s</a>`)
1017
1018 Convey("Inverse case", func() {
1019 f, err := ini.LoadSources(ini.LoadOptions{AllowPythonMultilineValues: false}, []byte(`
1096 require.NoError(t, err)
1097 require.NotNil(t, f)
1098
1099 assert.Equal(t, `创建了仓库 <a href="%s">%s</a>`, f.Section("").Key("create_repo").String())
1100
1101 t.Run("inverse case", func(t *testing.T) {
1102 f, err := LoadSources(LoadOptions{AllowPythonMultilineValues: false}, []byte(`
10201103 create_repo="创建了仓库 <a href=\"%s\">%s</a>"`))
1021 So(err, ShouldBeNil)
1022 So(f, ShouldNotBeNil)
1023
1024 So(f.Section("").Key("create_repo").String(), ShouldEqual, `"创建了仓库 <a href=\"%s\">%s</a>"`)
1025 })
1026 })
1027
1028 Convey("Unescape comment symbols inside value", func() {
1029 f, err := ini.LoadSources(ini.LoadOptions{
1030 AllowPythonMultilineValues: false,
1031 IgnoreInlineComment: true,
1032 UnescapeValueCommentSymbols: true,
1033 }, []byte(`
1104 require.NoError(t, err)
1105 require.NotNil(t, f)
1106
1107 assert.Equal(t, `"创建了仓库 <a href=\"%s\">%s</a>"`, f.Section("").Key("create_repo").String())
1108 })
1109 })
1110
1111 t.Run("unescape comment symbols inside value", func(t *testing.T) {
1112 f, err := LoadSources(LoadOptions{
1113 AllowPythonMultilineValues: false,
1114 IgnoreInlineComment: true,
1115 UnescapeValueCommentSymbols: true,
1116 }, []byte(`
10341117 key = test value <span style="color: %s\; background: %s">more text</span>
10351118 `))
1036 So(err, ShouldBeNil)
1037 So(f, ShouldNotBeNil)
1038
1039 So(f.Section("").Key("key").String(), ShouldEqual, `test value <span style="color: %s; background: %s">more text</span>`)
1040 })
1041
1042 Convey("Can't parse small python-compatible INI files", func() {
1043 f, err := ini.LoadSources(ini.LoadOptions{AllowPythonMultilineValues: false}, []byte(`
1119 require.NoError(t, err)
1120 require.NotNil(t, f)
1121
1122 assert.Equal(t, `test value <span style="color: %s; background: %s">more text</span>`, f.Section("").Key("key").String())
1123 })
1124
1125 t.Run("cannot parse small python-compatible INI files", func(t *testing.T) {
1126 f, err := LoadSources(LoadOptions{AllowPythonMultilineValues: false}, []byte(`
10441127 [long]
10451128 long_rsa_private_key = -----BEGIN RSA PRIVATE KEY-----
10461129 foo
10491132 barfoo
10501133 -----END RSA PRIVATE KEY-----
10511134 `))
1052 So(err, ShouldNotBeNil)
1053 So(f, ShouldBeNil)
1054 So(err.Error(), ShouldEqual, "key-value delimiter not found: foo\n")
1055 })
1056
1057 Convey("Can't parse big python-compatible INI files", func() {
1058 f, err := ini.LoadSources(ini.LoadOptions{AllowPythonMultilineValues: false}, []byte(`
1135 require.Error(t, err)
1136 assert.Nil(t, f)
1137 assert.Equal(t, "key-value delimiter not found: foo\n", err.Error())
1138 })
1139
1140 t.Run("cannot parse big python-compatible INI files", func(t *testing.T) {
1141 f, err := LoadSources(LoadOptions{AllowPythonMultilineValues: false}, []byte(`
10591142 [long]
10601143 long_rsa_private_key = -----BEGIN RSA PRIVATE KEY-----
10611144 1foo
11561239 96barfoo
11571240 -----END RSA PRIVATE KEY-----
11581241 `))
1159 So(err, ShouldNotBeNil)
1160 So(f, ShouldBeNil)
1161 So(err.Error(), ShouldEqual, "key-value delimiter not found: 1foo\n")
1162 })
1163
1164 Convey("Allow unparsable sections", func() {
1165 f, err := ini.LoadSources(ini.LoadOptions{
1166 AllowPythonMultilineValues: false,
1167 Insensitive: true,
1168 UnparseableSections: []string{"core_lesson", "comments"},
1169 }, []byte(`
1242 require.Error(t, err)
1243 assert.Nil(t, f)
1244 assert.Equal(t, "key-value delimiter not found: 1foo\n", err.Error())
1245 })
1246
1247 t.Run("allow unparsable sections", func(t *testing.T) {
1248 f, err := LoadSources(LoadOptions{
1249 AllowPythonMultilineValues: false,
1250 Insensitive: true,
1251 UnparseableSections: []string{"core_lesson", "comments"},
1252 }, []byte(`
11701253 Lesson_Location = 87
11711254 Lesson_Status = C
11721255 Score = 3
11781261
11791262 [COMMENTS]
11801263 <1><L.Slide#2> This slide has the fuel listed in the wrong units <e.1>`))
1181 So(err, ShouldBeNil)
1182 So(f, ShouldNotBeNil)
1183
1184 So(f.Section("").Key("score").String(), ShouldEqual, "3")
1185 So(f.Section("").Body(), ShouldBeEmpty)
1186 So(f.Section("core_lesson").Body(), ShouldEqual, `my lesson state data – 1111111111111111111000000000000000001110000
1187 111111111111111111100000000000111000000000 – end my lesson state data`)
1188 So(f.Section("comments").Body(), ShouldEqual, `<1><L.Slide#2> This slide has the fuel listed in the wrong units <e.1>`)
1189
1190 Convey("Write out", func() {
1191 var buf bytes.Buffer
1192 _, err := f.WriteTo(&buf)
1193 So(err, ShouldBeNil)
1194 So(buf.String(), ShouldEqual, `lesson_location = 87
1264 require.NoError(t, err)
1265 require.NotNil(t, f)
1266
1267 assert.Equal(t, "3", f.Section("").Key("score").String())
1268 assert.Empty(t, f.Section("").Body())
1269 assert.Equal(t, `my lesson state data – 1111111111111111111000000000000000001110000
1270 111111111111111111100000000000111000000000 – end my lesson state data`,
1271 f.Section("core_lesson").Body(),
1272 )
1273 assert.Equal(t, `<1><L.Slide#2> This slide has the fuel listed in the wrong units <e.1>`, f.Section("comments").Body())
1274
1275 t.Run("write out", func(t *testing.T) {
1276 var buf bytes.Buffer
1277 _, err := f.WriteTo(&buf)
1278 require.NoError(t, err)
1279 assert.Equal(t, `lesson_location = 87
11951280 lesson_status = C
11961281 score = 3
11971282 time = 00:02:30
12021287
12031288 [comments]
12041289 <1><L.Slide#2> This slide has the fuel listed in the wrong units <e.1>
1205 `)
1206 })
1207
1208 Convey("Inverse case", func() {
1209 _, err := ini.LoadSources(ini.LoadOptions{AllowPythonMultilineValues: false}, []byte(`
1290 `,
1291 buf.String(),
1292 )
1293 })
1294
1295 t.Run("inverse case", func(t *testing.T) {
1296 _, err := LoadSources(LoadOptions{AllowPythonMultilineValues: false}, []byte(`
12101297 [CORE_LESSON]
12111298 my lesson state data – 1111111111111111111000000000000000001110000
12121299 111111111111111111100000000000111000000000 – end my lesson state data`))
1213 So(err, ShouldNotBeNil)
1214 })
1215 })
1216
1217 Convey("And false `SpaceBeforeInlineComment`", func() {
1218 Convey("Can't parse INI files containing `#` or `;` in value", func() {
1219 f, err := ini.LoadSources(
1220 ini.LoadOptions{AllowPythonMultilineValues: true, SpaceBeforeInlineComment: false},
1221 []byte(`
1300 require.Error(t, err)
1301 })
1302 })
1303
1304 t.Run("and false `SpaceBeforeInlineComment`", func(t *testing.T) {
1305 t.Run("cannot parse INI files containing `#` or `;` in value", func(t *testing.T) {
1306 f, err := LoadSources(
1307 LoadOptions{AllowPythonMultilineValues: true, SpaceBeforeInlineComment: false},
1308 []byte(`
12221309 [author]
12231310 NAME = U#n#k#n#w#o#n
12241311 GITHUB = U;n;k;n;w;o;n
12251312 `))
1226 So(err, ShouldBeNil)
1227 So(f, ShouldNotBeNil)
1228 sec := f.Section("author")
1229 nameValue := sec.Key("NAME").String()
1230 githubValue := sec.Key("GITHUB").String()
1231 So(nameValue, ShouldEqual, "U")
1232 So(githubValue, ShouldEqual, "U")
1233 })
1234 })
1235
1236 Convey("And true `SpaceBeforeInlineComment`", func() {
1237 Convey("Can parse INI files containing `#` or `;` in value", func() {
1238 f, err := ini.LoadSources(
1239 ini.LoadOptions{AllowPythonMultilineValues: true, SpaceBeforeInlineComment: true},
1240 []byte(`
1313 require.NoError(t, err)
1314 require.NotNil(t, f)
1315 sec := f.Section("author")
1316 nameValue := sec.Key("NAME").String()
1317 githubValue := sec.Key("GITHUB").String()
1318 assert.Equal(t, "U", nameValue)
1319 assert.Equal(t, "U", githubValue)
1320 })
1321 })
1322
1323 t.Run("and true `SpaceBeforeInlineComment`", func(t *testing.T) {
1324 t.Run("can parse INI files containing `#` or `;` in value", func(t *testing.T) {
1325 f, err := LoadSources(
1326 LoadOptions{AllowPythonMultilineValues: true, SpaceBeforeInlineComment: true},
1327 []byte(`
12411328 [author]
12421329 NAME = U#n#k#n#w#o#n
12431330 GITHUB = U;n;k;n;w;o;n
12441331 `))
1245 So(err, ShouldBeNil)
1246 So(f, ShouldNotBeNil)
1247 sec := f.Section("author")
1248 nameValue := sec.Key("NAME").String()
1249 githubValue := sec.Key("GITHUB").String()
1250 So(nameValue, ShouldEqual, "U#n#k#n#w#o#n")
1251 So(githubValue, ShouldEqual, "U;n;k;n;w;o;n")
1252 })
1253 })
1332 require.NoError(t, err)
1333 require.NotNil(t, f)
1334 sec := f.Section("author")
1335 nameValue := sec.Key("NAME").String()
1336 githubValue := sec.Key("GITHUB").String()
1337 assert.Equal(t, "U#n#k#n#w#o#n", nameValue)
1338 assert.Equal(t, "U;n;k;n;w;o;n", githubValue)
1339 })
1340 })
1341 })
1342
1343 t.Run("with `ChildSectionDelimiter` ':'", func(t *testing.T) {
1344 t.Run("get all keys of parent sections", func(t *testing.T) {
1345 f := Empty(LoadOptions{ChildSectionDelimiter: ":"})
1346 require.NotNil(t, f)
1347
1348 k, err := f.Section("package").NewKey("NAME", "ini")
1349 require.NoError(t, err)
1350 assert.NotNil(t, k)
1351 k, err = f.Section("package").NewKey("VERSION", "v1")
1352 require.NoError(t, err)
1353 assert.NotNil(t, k)
1354 k, err = f.Section("package").NewKey("IMPORT_PATH", "gopkg.in/ini.v1")
1355 require.NoError(t, err)
1356 assert.NotNil(t, k)
1357
1358 keys := f.Section("package:sub:sub2").ParentKeys()
1359 names := []string{"NAME", "VERSION", "IMPORT_PATH"}
1360 assert.Equal(t, len(names), len(keys))
1361 for i, name := range names {
1362 assert.Equal(t, name, keys[i].Name())
1363 }
1364 })
1365
1366 t.Run("getting and setting values", func(t *testing.T) {
1367 f, err := LoadSources(LoadOptions{ChildSectionDelimiter: ":"}, fullConf)
1368 require.NoError(t, err)
1369 require.NotNil(t, f)
1370
1371 t.Run("get parent-keys that are available to the child section", func(t *testing.T) {
1372 parentKeys := f.Section("package:sub").ParentKeys()
1373 assert.NotNil(t, parentKeys)
1374 for _, k := range parentKeys {
1375 assert.Equal(t, "CLONE_URL", k.Name())
1376 }
1377 })
1378
1379 t.Run("get parent section value", func(t *testing.T) {
1380 assert.Equal(t, "https://gopkg.in/ini.v1", f.Section("package:sub").Key("CLONE_URL").String())
1381 assert.Equal(t, "https://gopkg.in/ini.v1", f.Section("package:fake:sub").Key("CLONE_URL").String())
1382 })
1383 })
1384
1385 t.Run("get child sections by parent name", func(t *testing.T) {
1386 f, err := LoadSources(LoadOptions{ChildSectionDelimiter: ":"}, []byte(`
1387 [node]
1388 [node:biz1]
1389 [node:biz2]
1390 [node.biz3]
1391 [node.bizN]
1392 `))
1393 require.NoError(t, err)
1394 require.NotNil(t, f)
1395
1396 children := f.ChildSections("node")
1397 names := []string{"node:biz1", "node:biz2"}
1398 assert.Equal(t, len(names), len(children))
1399 for i, name := range names {
1400 assert.Equal(t, name, children[i].Name())
1401 }
1402 })
1403 })
1404
1405 t.Run("ShortCircuit", func(t *testing.T) {
1406 t.Run("load the first available configuration, ignore other configuration", func(t *testing.T) {
1407 f, err := LoadSources(LoadOptions{ShortCircuit: true}, minimalConf, []byte(`key1 = value1`))
1408 require.NotNil(t, f)
1409 require.NoError(t, err)
1410 var buf bytes.Buffer
1411 _, err = f.WriteTo(&buf)
1412 require.NoError(t, err)
1413 assert.Equal(t, `[author]
1414 E-MAIL = u@gogs.io
1415
1416 `,
1417 buf.String(),
1418 )
1419 })
1420
1421 t.Run("return an error when fail to load", func(t *testing.T) {
1422 f, err := LoadSources(LoadOptions{ShortCircuit: true}, notFoundConf, minimalConf)
1423 assert.Nil(t, f)
1424 require.Error(t, err)
1425 })
1426
1427 t.Run("used with Loose to ignore errors that the file does not exist", func(t *testing.T) {
1428 f, err := LoadSources(LoadOptions{ShortCircuit: true, Loose: true}, notFoundConf, minimalConf)
1429 require.NotNil(t, f)
1430 require.NoError(t, err)
1431 var buf bytes.Buffer
1432 _, err = f.WriteTo(&buf)
1433 require.NoError(t, err)
1434 assert.Equal(t, `[author]
1435 E-MAIL = u@gogs.io
1436
1437 `,
1438 buf.String(),
1439 )
1440 })
1441
1442 t.Run("ensure all sources are loaded without ShortCircuit", func(t *testing.T) {
1443 f, err := LoadSources(LoadOptions{ShortCircuit: false}, minimalConf, []byte(`key1 = value1`))
1444 require.NotNil(t, f)
1445 require.NoError(t, err)
1446 var buf bytes.Buffer
1447 _, err = f.WriteTo(&buf)
1448 require.NoError(t, err)
1449 assert.Equal(t, `key1 = value1
1450
1451 [author]
1452 E-MAIL = u@gogs.io
1453
1454 `,
1455 buf.String(),
1456 )
12541457 })
12551458 })
12561459 }
12571460
12581461 func Test_KeyValueDelimiters(t *testing.T) {
1259 Convey("Custom key-value delimiters", t, func() {
1260 f, err := ini.LoadSources(ini.LoadOptions{
1462 t.Run("custom key-value delimiters", func(t *testing.T) {
1463 f, err := LoadSources(LoadOptions{
12611464 KeyValueDelimiters: "?!",
12621465 }, []byte(`
12631466 [section]
12641467 key1?value1
12651468 key2!value2
12661469 `))
1267 So(err, ShouldBeNil)
1268 So(f, ShouldNotBeNil)
1269
1270 So(f.Section("section").Key("key1").String(), ShouldEqual, "value1")
1271 So(f.Section("section").Key("key2").String(), ShouldEqual, "value2")
1470 require.NoError(t, err)
1471 require.NotNil(t, f)
1472
1473 assert.Equal(t, "value1", f.Section("section").Key("key1").String())
1474 assert.Equal(t, "value2", f.Section("section").Key("key2").String())
12721475 })
12731476 }
12741477
12751478 func Test_PreserveSurroundedQuote(t *testing.T) {
1276 Convey("Preserve surrounded quote test", t, func() {
1277 f, err := ini.LoadSources(ini.LoadOptions{
1479 t.Run("preserve surrounded quote test", func(t *testing.T) {
1480 f, err := LoadSources(LoadOptions{
12781481 PreserveSurroundedQuote: true,
12791482 }, []byte(`
12801483 [section]
12811484 key1 = "value1"
12821485 key2 = value2
12831486 `))
1284 So(err, ShouldBeNil)
1285 So(f, ShouldNotBeNil)
1286
1287 So(f.Section("section").Key("key1").String(), ShouldEqual, "\"value1\"")
1288 So(f.Section("section").Key("key2").String(), ShouldEqual, "value2")
1289 })
1290
1291 Convey("Preserve surrounded quote test inverse test", t, func() {
1292 f, err := ini.LoadSources(ini.LoadOptions{
1487 require.NoError(t, err)
1488 require.NotNil(t, f)
1489
1490 assert.Equal(t, "\"value1\"", f.Section("section").Key("key1").String())
1491 assert.Equal(t, "value2", f.Section("section").Key("key2").String())
1492 })
1493
1494 t.Run("preserve surrounded quote test inverse test", func(t *testing.T) {
1495 f, err := LoadSources(LoadOptions{
12931496 PreserveSurroundedQuote: false,
12941497 }, []byte(`
12951498 [section]
12961499 key1 = "value1"
12971500 key2 = value2
12981501 `))
1299 So(err, ShouldBeNil)
1300 So(f, ShouldNotBeNil)
1301
1302 So(f.Section("section").Key("key1").String(), ShouldEqual, "value1")
1303 So(f.Section("section").Key("key2").String(), ShouldEqual, "value2")
1502 require.NoError(t, err)
1503 require.NotNil(t, f)
1504
1505 assert.Equal(t, "value1", f.Section("section").Key("key1").String())
1506 assert.Equal(t, "value2", f.Section("section").Key("key2").String())
13041507 })
13051508 }
1509
1510 type testData struct {
1511 Value1 string `ini:"value1"`
1512 Value2 string `ini:"value2"`
1513 Value3 string `ini:"value3"`
1514 }
1515
1516 func TestPythonMultiline(t *testing.T) {
1517 if runtime.GOOS == "windows" {
1518 t.Skip("Skipping testing on Windows")
1519 }
1520
1521 path := filepath.Join("testdata", "multiline.ini")
1522 f, err := LoadSources(LoadOptions{
1523 AllowPythonMultilineValues: true,
1524 ReaderBufferSize: 64 * 1024,
1525 }, path)
1526 require.NoError(t, err)
1527 require.NotNil(t, f)
1528 assert.Len(t, f.Sections(), 1)
1529
1530 defaultSection := f.Section("")
1531 assert.NotNil(t, f.Section(""))
1532
1533 var testData testData
1534 err = defaultSection.MapTo(&testData)
1535 require.NoError(t, err)
1536 assert.Equal(t, "some text here\n\tsome more text here\n\t\n\tthere is an empty line above and below\n\t", testData.Value1)
1537 assert.Equal(t, "there is an empty line above\n that is not indented so it should not be part\n of the value", testData.Value2)
1538 assert.Equal(t, `.
1539
1540 Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Eu consequat ac felis donec et odio pellentesque diam volutpat. Mauris commodo quis imperdiet massa tincidunt nunc. Interdum velit euismod in pellentesque. Nisl condimentum id venenatis a condimentum vitae sapien pellentesque. Nascetur ridiculus mus mauris vitae. Posuere urna nec tincidunt praesent semper feugiat. Lorem donec massa sapien faucibus et molestie ac feugiat sed. Ipsum dolor sit amet consectetur adipiscing elit. Enim sed faucibus turpis in eu mi. A diam sollicitudin tempor id. Quam nulla porttitor massa id neque aliquam vestibulum morbi blandit.
1541
1542 Lectus sit amet est placerat in egestas. At risus viverra adipiscing at in tellus integer. Tristique senectus et netus et malesuada fames ac. In hac habitasse platea dictumst. Purus in mollis nunc sed. Pellentesque sit amet porttitor eget dolor morbi. Elit at imperdiet dui accumsan sit amet nulla. Cursus in hac habitasse platea dictumst. Bibendum arcu vitae elementum curabitur. Faucibus ornare suspendisse sed nisi lacus. In vitae turpis massa sed. Libero nunc consequat interdum varius sit amet. Molestie a iaculis at erat pellentesque.
1543
1544 Dui faucibus in ornare quam viverra orci sagittis eu. Purus in mollis nunc sed id semper. Sed arcu non odio euismod lacinia at. Quis commodo odio aenean sed adipiscing diam donec. Quisque id diam vel quam elementum pulvinar. Lorem ipsum dolor sit amet. Purus ut faucibus pulvinar elementum integer enim neque volutpat ac. Fermentum posuere urna nec tincidunt praesent semper feugiat nibh sed. Gravida rutrum quisque non tellus orci. Ipsum dolor sit amet consectetur adipiscing elit pellentesque habitant. Et sollicitudin ac orci phasellus egestas tellus rutrum tellus pellentesque. Eget gravida cum sociis natoque penatibus et magnis. Elementum eu facilisis sed odio morbi quis commodo. Mollis nunc sed id semper risus in hendrerit gravida rutrum. Lorem dolor sed viverra ipsum.
1545
1546 Pellentesque adipiscing commodo elit at imperdiet dui accumsan sit amet. Justo eget magna fermentum iaculis eu non diam. Condimentum mattis pellentesque id nibh tortor id aliquet lectus. Tellus molestie nunc non blandit massa enim. Mauris ultrices eros in cursus turpis. Purus viverra accumsan in nisl nisi scelerisque. Quis lectus nulla at volutpat. Purus ut faucibus pulvinar elementum integer enim. In pellentesque massa placerat duis ultricies lacus sed turpis. Elit sed vulputate mi sit amet mauris commodo. Tellus elementum sagittis vitae et. Duis tristique sollicitudin nibh sit amet commodo nulla facilisi nullam. Lectus vestibulum mattis ullamcorper velit sed ullamcorper morbi tincidunt ornare. Libero id faucibus nisl tincidunt eget nullam. Mattis aliquam faucibus purus in massa tempor. Fames ac turpis egestas sed tempus urna. Gravida in fermentum et sollicitudin ac orci phasellus egestas.
1547
1548 Blandit turpis cursus in hac habitasse. Sed id semper risus in. Amet porttitor eget dolor morbi non arcu. Rhoncus est pellentesque elit ullamcorper dignissim cras tincidunt. Ut morbi tincidunt augue interdum velit. Lorem mollis aliquam ut porttitor leo a. Nunc eget lorem dolor sed viverra. Scelerisque mauris pellentesque pulvinar pellentesque. Elit at imperdiet dui accumsan sit amet. Eget magna fermentum iaculis eu non diam phasellus vestibulum lorem. Laoreet non curabitur gravida arcu ac tortor dignissim. Tortor pretium viverra suspendisse potenti nullam ac tortor vitae purus. Lacus sed viverra tellus in hac habitasse platea dictumst vestibulum. Viverra adipiscing at in tellus. Duis at tellus at urna condimentum. Eget gravida cum sociis natoque penatibus et magnis dis parturient. Pharetra massa massa ultricies mi quis hendrerit.
1549
1550 Mauris pellentesque pulvinar pellentesque habitant morbi tristique. Maecenas volutpat blandit aliquam etiam. Sed turpis tincidunt id aliquet. Eget duis at tellus at urna condimentum. Pellentesque habitant morbi tristique senectus et. Amet aliquam id diam maecenas. Volutpat est velit egestas dui id. Vulputate eu scelerisque felis imperdiet proin fermentum leo vel orci. Massa sed elementum tempus egestas sed sed risus pretium. Quam quisque id diam vel quam elementum pulvinar etiam non. Sapien faucibus et molestie ac. Ipsum dolor sit amet consectetur adipiscing. Viverra orci sagittis eu volutpat. Leo urna molestie at elementum. Commodo viverra maecenas accumsan lacus. Non sodales neque sodales ut etiam sit amet. Habitant morbi tristique senectus et netus et malesuada fames. Habitant morbi tristique senectus et netus et malesuada. Blandit aliquam etiam erat velit scelerisque in. Varius duis at consectetur lorem donec massa sapien faucibus et.
1551
1552 Augue mauris augue neque gravida in. Odio ut sem nulla pharetra diam sit amet nisl suscipit. Nulla aliquet enim tortor at auctor urna nunc id. Morbi tristique senectus et netus et malesuada fames ac. Quam id leo in vitae turpis massa sed elementum tempus. Ipsum faucibus vitae aliquet nec ullamcorper sit amet risus nullam. Maecenas volutpat blandit aliquam etiam erat velit scelerisque in. Sagittis nisl rhoncus mattis rhoncus urna neque viverra justo. Massa tempor nec feugiat nisl pretium. Vulputate sapien nec sagittis aliquam malesuada bibendum arcu vitae elementum. Enim lobortis scelerisque fermentum dui faucibus in ornare. Faucibus ornare suspendisse sed nisi lacus. Morbi tristique senectus et netus et malesuada fames. Malesuada pellentesque elit eget gravida cum sociis natoque penatibus et. Dictum non consectetur a erat nam at. Leo urna molestie at elementum eu facilisis sed odio morbi. Quam id leo in vitae turpis massa. Neque egestas congue quisque egestas diam in arcu. Varius morbi enim nunc faucibus a pellentesque sit. Aliquet enim tortor at auctor urna.
1553
1554 Elit scelerisque mauris pellentesque pulvinar pellentesque habitant morbi tristique. Luctus accumsan tortor posuere ac. Eu ultrices vitae auctor eu augue ut lectus arcu bibendum. Pretium nibh ipsum consequat nisl vel pretium lectus. Aliquam etiam erat velit scelerisque in dictum. Sem et tortor consequat id porta nibh venenatis cras sed. A scelerisque purus semper eget duis at tellus at urna. At auctor urna nunc id. Ornare quam viverra orci sagittis eu volutpat odio. Nisl purus in mollis nunc sed id semper. Ornare suspendisse sed nisi lacus sed. Consectetur lorem donec massa sapien faucibus et. Ipsum dolor sit amet consectetur adipiscing elit ut. Porta nibh venenatis cras sed. Dignissim diam quis enim lobortis scelerisque. Quam nulla porttitor massa id. Tellus molestie nunc non blandit massa.
1555
1556 Malesuada fames ac turpis egestas. Suscipit tellus mauris a diam maecenas. Turpis in eu mi bibendum neque egestas. Venenatis tellus in metus vulputate eu scelerisque felis imperdiet. Quis imperdiet massa tincidunt nunc pulvinar sapien et. Urna duis convallis convallis tellus id. Velit egestas dui id ornare arcu odio. Consectetur purus ut faucibus pulvinar elementum integer enim neque. Aenean sed adipiscing diam donec adipiscing tristique. Tortor aliquam nulla facilisi cras fermentum odio eu. Diam in arcu cursus euismod quis viverra nibh cras.
1557
1558 Id ornare arcu odio ut sem. Arcu dictum varius duis at consectetur lorem donec massa sapien. Proin libero nunc consequat interdum varius sit. Ut eu sem integer vitae justo. Vitae elementum curabitur vitae nunc. Diam quam nulla porttitor massa. Lectus mauris ultrices eros in cursus turpis massa tincidunt dui. Natoque penatibus et magnis dis parturient montes. Pellentesque habitant morbi tristique senectus et netus et malesuada fames. Libero nunc consequat interdum varius sit. Rhoncus dolor purus non enim praesent. Pellentesque sit amet porttitor eget. Nibh tortor id aliquet lectus proin nibh. Fermentum iaculis eu non diam phasellus vestibulum lorem sed.
1559
1560 Eu feugiat pretium nibh ipsum consequat nisl vel pretium lectus. Habitant morbi tristique senectus et netus et malesuada fames ac. Urna condimentum mattis pellentesque id. Lorem sed risus ultricies tristique nulla aliquet enim tortor at. Ipsum dolor sit amet consectetur adipiscing elit. Convallis a cras semper auctor neque vitae tempus quam. A diam sollicitudin tempor id eu nisl nunc mi ipsum. Maecenas sed enim ut sem viverra aliquet eget. Massa enim nec dui nunc mattis enim. Nam aliquam sem et tortor consequat. Adipiscing commodo elit at imperdiet dui accumsan sit amet nulla. Nullam eget felis eget nunc lobortis. Mauris a diam maecenas sed enim ut sem viverra. Ornare massa eget egestas purus. In hac habitasse platea dictumst. Ut tortor pretium viverra suspendisse potenti nullam ac tortor. Nisl nunc mi ipsum faucibus. At varius vel pharetra vel. Mauris ultrices eros in cursus turpis massa tincidunt.`,
1561 testData.Value3,
1562 )
1563 }
1564
1565 func TestPythonMultiline_EOF(t *testing.T) {
1566 if runtime.GOOS == "windows" {
1567 t.Skip("Skipping testing on Windows")
1568 }
1569
1570 path := filepath.Join("testdata", "multiline_eof.ini")
1571 f, err := LoadSources(LoadOptions{
1572 AllowPythonMultilineValues: true,
1573 ReaderBufferSize: 64 * 1024,
1574 }, path)
1575 require.NoError(t, err)
1576 require.NotNil(t, f)
1577 assert.Len(t, f.Sections(), 1)
1578
1579 defaultSection := f.Section("")
1580 assert.NotNil(t, f.Section(""))
1581
1582 var testData testData
1583 err = defaultSection.MapTo(&testData)
1584 require.NoError(t, err)
1585 assert.Equal(t, "some text here\n\tsome more text here 2", testData.Value1)
1586 }
1587
1588 func Test_NestedValuesSpanningSections(t *testing.T) {
1589 t.Run("basic nested value", func(t *testing.T) {
1590 f, err := LoadSources(LoadOptions{
1591 AllowNestedValues: true,
1592 }, []byte(`
1593 [section]
1594 key1 = value1
1595 key2 =
1596 nested1 = nestedvalue1
1597 `))
1598 require.NoError(t, err)
1599 require.NotNil(t, f)
1600
1601 assert.Equal(t, "value1", f.Section("section").Key("key1").String())
1602 assert.Equal(t, "", f.Section("section").Key("key2").String())
1603 assert.Equal(t, []string{"nested1 = nestedvalue1"}, f.Section("section").Key("key2").NestedValues())
1604 })
1605
1606 t.Run("no nested values", func(t *testing.T) {
1607 f, err := LoadSources(LoadOptions{
1608 AllowNestedValues: true,
1609 }, []byte(`
1610 [section]
1611 key1 = value1
1612 key2 =
1613 `))
1614 require.NoError(t, err)
1615 require.NotNil(t, f)
1616
1617 assert.Equal(t, "value1", f.Section("section").Key("key1").String())
1618 assert.Equal(t, "", f.Section("section").Key("key2").String())
1619 })
1620
1621 t.Run("no nested values and following sections", func(t *testing.T) {
1622 f, err := LoadSources(LoadOptions{
1623 AllowNestedValues: true,
1624 }, []byte(`
1625 [section]
1626 key1 = value1
1627 key2 =
1628
1629 [section2]
1630 key3 = value3
1631 `))
1632 require.NoError(t, err)
1633 require.NotNil(t, f)
1634
1635 assert.Equal(t, "value1", f.Section("section").Key("key1").String())
1636 assert.Equal(t, "", f.Section("section").Key("key2").String())
1637 assert.Equal(t, "value3", f.Section("section2").Key("key3").String())
1638 })
1639
1640 t.Run("no nested values and following sections with indentation", func(t *testing.T) {
1641 f, err := LoadSources(LoadOptions{
1642 AllowNestedValues: true,
1643 }, []byte(`
1644 [section]
1645 key1 = value1
1646 key2 =
1647
1648 [section2]
1649 key3 = value3
1650 `))
1651 require.NoError(t, err)
1652 require.NotNil(t, f)
1653
1654 assert.Equal(t, "value1", f.Section("section").Key("key1").String())
1655 assert.Equal(t, "", f.Section("section").Key("key2").String())
1656 assert.Equal(t, "value3", f.Section("section2").Key("key3").String())
1657 })
1658 }
+106
-70
key.go less more
5353 return errors.New("cannot add shadow to auto-increment or boolean key")
5454 }
5555
56 // Deduplicate shadows based on their values.
57 if k.value == val {
58 return nil
59 }
60 for i := range k.shadows {
61 if k.shadows[i].value == val {
56 if !k.s.f.options.AllowDuplicateShadowValues {
57 // Deduplicate shadows based on their values.
58 if k.value == val {
6259 return nil
60 }
61 for i := range k.shadows {
62 if k.shadows[i].value == val {
63 return nil
64 }
6365 }
6466 }
6567
107109 return k.value
108110 }
109111
110 // ValueWithShadows returns raw values of key and its shadows if any.
112 // ValueWithShadows returns raw values of key and its shadows if any. Shadow
113 // keys with empty values are ignored from the returned list.
111114 func (k *Key) ValueWithShadows() []string {
112115 if len(k.shadows) == 0 {
116 if k.value == "" {
117 return []string{}
118 }
113119 return []string{k.value}
114120 }
115 vals := make([]string, len(k.shadows)+1)
116 vals[0] = k.value
117 for i := range k.shadows {
118 vals[i+1] = k.shadows[i].value
121
122 vals := make([]string, 0, len(k.shadows)+1)
123 if k.value != "" {
124 vals = append(vals, k.value)
125 }
126 for _, s := range k.shadows {
127 if s.value != "" {
128 vals = append(vals, s.value)
129 }
119130 }
120131 return vals
121132 }
685696 // parseBools transforms strings to bools.
686697 func (k *Key) parseBools(strs []string, addInvalid, returnOnInvalid bool) ([]bool, error) {
687698 vals := make([]bool, 0, len(strs))
688 for _, str := range strs {
699 parser := func(str string) (interface{}, error) {
689700 val, err := parseBool(str)
690 if err != nil && returnOnInvalid {
691 return nil, err
692 }
693 if err == nil || addInvalid {
694 vals = append(vals, val)
695 }
696 }
697 return vals, nil
701 return val, err
702 }
703 rawVals, err := k.doParse(strs, addInvalid, returnOnInvalid, parser)
704 if err == nil {
705 for _, val := range rawVals {
706 vals = append(vals, val.(bool))
707 }
708 }
709 return vals, err
698710 }
699711
700712 // parseFloat64s transforms strings to float64s.
701713 func (k *Key) parseFloat64s(strs []string, addInvalid, returnOnInvalid bool) ([]float64, error) {
702714 vals := make([]float64, 0, len(strs))
703 for _, str := range strs {
715 parser := func(str string) (interface{}, error) {
704716 val, err := strconv.ParseFloat(str, 64)
705 if err != nil && returnOnInvalid {
706 return nil, err
707 }
708 if err == nil || addInvalid {
709 vals = append(vals, val)
710 }
711 }
712 return vals, nil
717 return val, err
718 }
719 rawVals, err := k.doParse(strs, addInvalid, returnOnInvalid, parser)
720 if err == nil {
721 for _, val := range rawVals {
722 vals = append(vals, val.(float64))
723 }
724 }
725 return vals, err
713726 }
714727
715728 // parseInts transforms strings to ints.
716729 func (k *Key) parseInts(strs []string, addInvalid, returnOnInvalid bool) ([]int, error) {
717730 vals := make([]int, 0, len(strs))
718 for _, str := range strs {
719 valInt64, err := strconv.ParseInt(str, 0, 64)
720 val := int(valInt64)
721 if err != nil && returnOnInvalid {
722 return nil, err
723 }
724 if err == nil || addInvalid {
725 vals = append(vals, val)
726 }
727 }
728 return vals, nil
731 parser := func(str string) (interface{}, error) {
732 val, err := strconv.ParseInt(str, 0, 64)
733 return val, err
734 }
735 rawVals, err := k.doParse(strs, addInvalid, returnOnInvalid, parser)
736 if err == nil {
737 for _, val := range rawVals {
738 vals = append(vals, int(val.(int64)))
739 }
740 }
741 return vals, err
729742 }
730743
731744 // parseInt64s transforms strings to int64s.
732745 func (k *Key) parseInt64s(strs []string, addInvalid, returnOnInvalid bool) ([]int64, error) {
733746 vals := make([]int64, 0, len(strs))
734 for _, str := range strs {
747 parser := func(str string) (interface{}, error) {
735748 val, err := strconv.ParseInt(str, 0, 64)
736 if err != nil && returnOnInvalid {
737 return nil, err
738 }
739 if err == nil || addInvalid {
740 vals = append(vals, val)
741 }
742 }
743 return vals, nil
749 return val, err
750 }
751
752 rawVals, err := k.doParse(strs, addInvalid, returnOnInvalid, parser)
753 if err == nil {
754 for _, val := range rawVals {
755 vals = append(vals, val.(int64))
756 }
757 }
758 return vals, err
744759 }
745760
746761 // parseUints transforms strings to uints.
747762 func (k *Key) parseUints(strs []string, addInvalid, returnOnInvalid bool) ([]uint, error) {
748763 vals := make([]uint, 0, len(strs))
749 for _, str := range strs {
750 val, err := strconv.ParseUint(str, 0, 0)
751 if err != nil && returnOnInvalid {
752 return nil, err
753 }
754 if err == nil || addInvalid {
755 vals = append(vals, uint(val))
756 }
757 }
758 return vals, nil
764 parser := func(str string) (interface{}, error) {
765 val, err := strconv.ParseUint(str, 0, 64)
766 return val, err
767 }
768
769 rawVals, err := k.doParse(strs, addInvalid, returnOnInvalid, parser)
770 if err == nil {
771 for _, val := range rawVals {
772 vals = append(vals, uint(val.(uint64)))
773 }
774 }
775 return vals, err
759776 }
760777
761778 // parseUint64s transforms strings to uint64s.
762779 func (k *Key) parseUint64s(strs []string, addInvalid, returnOnInvalid bool) ([]uint64, error) {
763780 vals := make([]uint64, 0, len(strs))
764 for _, str := range strs {
781 parser := func(str string) (interface{}, error) {
765782 val, err := strconv.ParseUint(str, 0, 64)
766 if err != nil && returnOnInvalid {
767 return nil, err
768 }
769 if err == nil || addInvalid {
770 vals = append(vals, val)
771 }
772 }
773 return vals, nil
774 }
783 return val, err
784 }
785 rawVals, err := k.doParse(strs, addInvalid, returnOnInvalid, parser)
786 if err == nil {
787 for _, val := range rawVals {
788 vals = append(vals, val.(uint64))
789 }
790 }
791 return vals, err
792 }
793
794 type Parser func(str string) (interface{}, error)
775795
776796 // parseTimesFormat transforms strings to times in given format.
777797 func (k *Key) parseTimesFormat(format string, strs []string, addInvalid, returnOnInvalid bool) ([]time.Time, error) {
778798 vals := make([]time.Time, 0, len(strs))
799 parser := func(str string) (interface{}, error) {
800 val, err := time.Parse(format, str)
801 return val, err
802 }
803 rawVals, err := k.doParse(strs, addInvalid, returnOnInvalid, parser)
804 if err == nil {
805 for _, val := range rawVals {
806 vals = append(vals, val.(time.Time))
807 }
808 }
809 return vals, err
810 }
811
812 // doParse transforms strings to different types
813 func (k *Key) doParse(strs []string, addInvalid, returnOnInvalid bool, parser Parser) ([]interface{}, error) {
814 vals := make([]interface{}, 0, len(strs))
779815 for _, str := range strs {
780 val, err := time.Parse(format, str)
816 val, err := parser(str)
781817 if err != nil && returnOnInvalid {
782818 return nil, err
783819 }
1111 // License for the specific language governing permissions and limitations
1212 // under the License.
1313
14 package ini_test
14 package ini
1515
1616 import (
1717 "bytes"
1818 "fmt"
19 "runtime"
1920 "strings"
2021 "testing"
2122 "time"
2223
23 . "github.com/smartystreets/goconvey/convey"
24 "gopkg.in/ini.v1"
24 "github.com/stretchr/testify/assert"
25 "github.com/stretchr/testify/require"
2526 )
2627
2728 func TestKey_AddShadow(t *testing.T) {
28 Convey("Add shadow to a key", t, func() {
29 f, err := ini.ShadowLoad([]byte(`
29 t.Run("add shadow to a key", func(t *testing.T) {
30 f, err := ShadowLoad([]byte(`
3031 [notes]
3132 -: note1`))
32 So(err, ShouldBeNil)
33 So(f, ShouldNotBeNil)
33 require.NoError(t, err)
34 require.NotNil(t, f)
3435
3536 k, err := f.Section("").NewKey("NAME", "ini")
36 So(err, ShouldBeNil)
37 So(k, ShouldNotBeNil)
38
39 So(k.AddShadow("ini.v1"), ShouldBeNil)
40 So(k.ValueWithShadows(), ShouldResemble, []string{"ini", "ini.v1"})
41
42 Convey("Add shadow to boolean key", func() {
37 require.NoError(t, err)
38 require.NotNil(t, k)
39
40 assert.NoError(t, k.AddShadow("ini.v1"))
41 assert.Equal(t, []string{"ini", "ini.v1"}, k.ValueWithShadows())
42
43 t.Run("add shadow to boolean key", func(t *testing.T) {
4344 k, err := f.Section("").NewBooleanKey("published")
44 So(err, ShouldBeNil)
45 So(k, ShouldNotBeNil)
46 So(k.AddShadow("beta"), ShouldNotBeNil)
47 })
48
49 Convey("Add shadow to auto-increment key", func() {
50 So(f.Section("notes").Key("#1").AddShadow("beta"), ShouldNotBeNil)
51 })
52 })
53
54 Convey("Shadow is not allowed", t, func() {
55 f := ini.Empty()
56 So(f, ShouldNotBeNil)
45 require.NoError(t, err)
46 require.NotNil(t, k)
47 assert.Error(t, k.AddShadow("beta"))
48 })
49
50 t.Run("add shadow to auto-increment key", func(t *testing.T) {
51 assert.Error(t, f.Section("notes").Key("#1").AddShadow("beta"))
52 })
53
54 t.Run("deduplicate an existing value", func(t *testing.T) {
55 k := f.Section("").Key("NAME")
56 assert.NoError(t, k.AddShadow("ini"))
57 assert.Equal(t, []string{"ini", "ini.v1"}, k.ValueWithShadows())
58 })
59
60 t.Run("ignore empty shadow values", func(t *testing.T) {
61 k := f.Section("").Key("empty")
62 assert.NoError(t, k.AddShadow(""))
63 assert.NoError(t, k.AddShadow("ini"))
64 assert.Equal(t, []string{"ini"}, k.ValueWithShadows())
65 })
66 })
67
68 t.Run("allow duplicate shadowed values", func(t *testing.T) {
69 f := Empty(LoadOptions{
70 AllowShadows: true,
71 AllowDuplicateShadowValues: true,
72 })
73 require.NotNil(t, f)
5774
5875 k, err := f.Section("").NewKey("NAME", "ini")
59 So(err, ShouldBeNil)
60 So(k, ShouldNotBeNil)
61
62 So(k.AddShadow("ini.v1"), ShouldNotBeNil)
76 require.NoError(t, err)
77 require.NotNil(t, k)
78
79 assert.NoError(t, k.AddShadow("ini.v1"))
80 assert.NoError(t, k.AddShadow("ini"))
81 assert.NoError(t, k.AddShadow("ini"))
82 assert.Equal(t, []string{"ini", "ini.v1", "ini", "ini"}, k.ValueWithShadows())
83 })
84
85 t.Run("shadow is not allowed", func(t *testing.T) {
86 f := Empty()
87 require.NotNil(t, f)
88
89 k, err := f.Section("").NewKey("NAME", "ini")
90 require.NoError(t, err)
91 require.NotNil(t, k)
92
93 assert.Error(t, k.AddShadow("ini.v1"))
6394 })
6495 }
6596
6697 // Helpers for slice tests.
67 func float64sEqual(values []float64, expected ...float64) {
68 So(values, ShouldHaveLength, len(expected))
98 func float64sEqual(t *testing.T, values []float64, expected ...float64) {
99 t.Helper()
100
101 assert.Len(t, values, len(expected))
69102 for i, v := range expected {
70 So(values[i], ShouldEqual, v)
103 assert.Equal(t, v, values[i])
71104 }
72105 }
73106
74 func intsEqual(values []int, expected ...int) {
75 So(values, ShouldHaveLength, len(expected))
107 func intsEqual(t *testing.T, values []int, expected ...int) {
108 t.Helper()
109
110 assert.Len(t, values, len(expected))
76111 for i, v := range expected {
77 So(values[i], ShouldEqual, v)
112 assert.Equal(t, v, values[i])
78113 }
79114 }
80115
81 func int64sEqual(values []int64, expected ...int64) {
82 So(values, ShouldHaveLength, len(expected))
116 func int64sEqual(t *testing.T, values []int64, expected ...int64) {
117 t.Helper()
118
119 assert.Len(t, values, len(expected))
83120 for i, v := range expected {
84 So(values[i], ShouldEqual, v)
121 assert.Equal(t, v, values[i])
85122 }
86123 }
87124
88 func uintsEqual(values []uint, expected ...uint) {
89 So(values, ShouldHaveLength, len(expected))
125 func uintsEqual(t *testing.T, values []uint, expected ...uint) {
126 t.Helper()
127
128 assert.Len(t, values, len(expected))
90129 for i, v := range expected {
91 So(values[i], ShouldEqual, v)
130 assert.Equal(t, v, values[i])
92131 }
93132 }
94133
95 func uint64sEqual(values []uint64, expected ...uint64) {
96 So(values, ShouldHaveLength, len(expected))
134 func uint64sEqual(t *testing.T, values []uint64, expected ...uint64) {
135 t.Helper()
136
137 assert.Len(t, values, len(expected))
97138 for i, v := range expected {
98 So(values[i], ShouldEqual, v)
139 assert.Equal(t, v, values[i])
99140 }
100141 }
101142
102 func boolsEqual(values []bool, expected ...bool) {
103 So(values, ShouldHaveLength, len(expected))
143 func boolsEqual(t *testing.T, values []bool, expected ...bool) {
144 t.Helper()
145
146 assert.Len(t, values, len(expected))
104147 for i, v := range expected {
105 So(values[i], ShouldEqual, v)
148 assert.Equal(t, v, values[i])
106149 }
107150 }
108151
109 func timesEqual(values []time.Time, expected ...time.Time) {
110 So(values, ShouldHaveLength, len(expected))
152 func timesEqual(t *testing.T, values []time.Time, expected ...time.Time) {
153 t.Helper()
154
155 assert.Len(t, values, len(expected))
111156 for i, v := range expected {
112 So(values[i].String(), ShouldEqual, v.String())
157 assert.Equal(t, v.String(), values[i].String())
113158 }
114159 }
115160
116161 func TestKey_Helpers(t *testing.T) {
117 Convey("Getting and setting values", t, func() {
118 f, err := ini.Load(fullConf)
119 So(err, ShouldBeNil)
120 So(f, ShouldNotBeNil)
121
122 Convey("Get string representation", func() {
162 t.Run("getting and setting values", func(t *testing.T) {
163 f, err := Load(fullConf)
164 require.NoError(t, err)
165 require.NotNil(t, f)
166
167 t.Run("get string representation", func(t *testing.T) {
123168 sec := f.Section("")
124 So(sec, ShouldNotBeNil)
125 So(sec.Key("NAME").Value(), ShouldEqual, "ini")
126 So(sec.Key("NAME").String(), ShouldEqual, "ini")
127 So(sec.Key("NAME").Validate(func(in string) string {
169 require.NotNil(t, sec)
170 assert.Equal(t, "ini", sec.Key("NAME").Value())
171 assert.Equal(t, "ini", sec.Key("NAME").String())
172 assert.Equal(t, "ini", sec.Key("NAME").Validate(func(in string) string {
128173 return in
129 }), ShouldEqual, "ini")
130 So(sec.Key("NAME").Comment, ShouldEqual, "; Package name")
131 So(sec.Key("IMPORT_PATH").String(), ShouldEqual, "gopkg.in/ini.v1")
132
133 Convey("With ValueMapper", func() {
174 }))
175 assert.Equal(t, "; Package name", sec.Key("NAME").Comment)
176 assert.Equal(t, "gopkg.in/ini.v1", sec.Key("IMPORT_PATH").String())
177
178 t.Run("with ValueMapper", func(t *testing.T) {
134179 f.ValueMapper = func(in string) string {
135180 if in == "gopkg.in/%(NAME)s.%(VERSION)s" {
136181 return "github.com/go-ini/ini"
137182 }
138183 return in
139184 }
140 So(sec.Key("IMPORT_PATH").String(), ShouldEqual, "github.com/go-ini/ini")
185 assert.Equal(t, "github.com/go-ini/ini", sec.Key("IMPORT_PATH").String())
141186 })
142187 })
143188
144 Convey("Get values in non-default section", func() {
189 t.Run("get values in non-default section", func(t *testing.T) {
145190 sec := f.Section("author")
146 So(sec, ShouldNotBeNil)
147 So(sec.Key("NAME").String(), ShouldEqual, "Unknwon")
148 So(sec.Key("GITHUB").String(), ShouldEqual, "https://github.com/Unknwon")
191 require.NotNil(t, sec)
192 assert.Equal(t, "Unknwon", sec.Key("NAME").String())
193 assert.Equal(t, "https://github.com/Unknwon", sec.Key("GITHUB").String())
149194
150195 sec = f.Section("package")
151 So(sec, ShouldNotBeNil)
152 So(sec.Key("CLONE_URL").String(), ShouldEqual, "https://gopkg.in/ini.v1")
153 })
154
155 Convey("Get auto-increment key names", func() {
196 require.NotNil(t, sec)
197 assert.Equal(t, "https://gopkg.in/ini.v1", sec.Key("CLONE_URL").String())
198 })
199
200 t.Run("get auto-increment key names", func(t *testing.T) {
156201 keys := f.Section("features").Keys()
157202 for i, k := range keys {
158 So(k.Name(), ShouldEqual, fmt.Sprintf("#%d", i+1))
203 assert.Equal(t, fmt.Sprintf("#%d", i+1), k.Name())
159204 }
160205 })
161206
162 Convey("Get parent-keys that are available to the child section", func() {
207 t.Run("get parent-keys that are available to the child section", func(t *testing.T) {
163208 parentKeys := f.Section("package.sub").ParentKeys()
164209 for _, k := range parentKeys {
165 So(k.Name(), ShouldEqual, "CLONE_URL")
210 assert.Equal(t, "CLONE_URL", k.Name())
166211 }
167212 })
168213
169 Convey("Get overwrite value", func() {
170 So(f.Section("author").Key("E-MAIL").String(), ShouldEqual, "u@gogs.io")
171 })
172
173 Convey("Get sections", func() {
214 t.Run("get overwrite value", func(t *testing.T) {
215 assert.Equal(t, "u@gogs.io", f.Section("author").Key("E-MAIL").String())
216 })
217
218 t.Run("get sections", func(t *testing.T) {
174219 sections := f.Sections()
175 for i, name := range []string{ini.DefaultSection, "author", "package", "package.sub", "features", "types", "array", "note", "comments", "string escapes", "advance"} {
176 So(sections[i].Name(), ShouldEqual, name)
220 for i, name := range []string{DefaultSection, "author", "package", "package.sub", "features", "types", "array", "note", "comments", "string escapes", "advance"} {
221 assert.Equal(t, name, sections[i].Name())
177222 }
178223 })
179224
180 Convey("Get parent section value", func() {
181 So(f.Section("package.sub").Key("CLONE_URL").String(), ShouldEqual, "https://gopkg.in/ini.v1")
182 So(f.Section("package.fake.sub").Key("CLONE_URL").String(), ShouldEqual, "https://gopkg.in/ini.v1")
183 })
184
185 Convey("Get multiple line value", func() {
186 So(f.Section("author").Key("BIO").String(), ShouldEqual, "Gopher.\nCoding addict.\nGood man.\n")
187 })
188
189 Convey("Get values with type", func() {
225 t.Run("get parent section value", func(t *testing.T) {
226 assert.Equal(t, "https://gopkg.in/ini.v1", f.Section("package.sub").Key("CLONE_URL").String())
227 assert.Equal(t, "https://gopkg.in/ini.v1", f.Section("package.fake.sub").Key("CLONE_URL").String())
228 })
229
230 t.Run("get multiple line value", func(t *testing.T) {
231 if runtime.GOOS == "windows" {
232 t.Skip("Skipping testing on Windows")
233 }
234
235 assert.Equal(t, "Gopher.\nCoding addict.\nGood man.\n", f.Section("author").Key("BIO").String())
236 })
237
238 t.Run("get values with type", func(t *testing.T) {
190239 sec := f.Section("types")
191240 v1, err := sec.Key("BOOL").Bool()
192 So(err, ShouldBeNil)
193 So(v1, ShouldBeTrue)
241 require.NoError(t, err)
242 assert.True(t, v1)
194243
195244 v1, err = sec.Key("BOOL_FALSE").Bool()
196 So(err, ShouldBeNil)
197 So(v1, ShouldBeFalse)
245 require.NoError(t, err)
246 assert.False(t, v1)
198247
199248 v2, err := sec.Key("FLOAT64").Float64()
200 So(err, ShouldBeNil)
201 So(v2, ShouldEqual, 1.25)
249 require.NoError(t, err)
250 assert.Equal(t, 1.25, v2)
202251
203252 v3, err := sec.Key("INT").Int()
204 So(err, ShouldBeNil)
205 So(v3, ShouldEqual, 10)
253 require.NoError(t, err)
254 assert.Equal(t, 10, v3)
206255
207256 v4, err := sec.Key("INT").Int64()
208 So(err, ShouldBeNil)
209 So(v4, ShouldEqual, 10)
257 require.NoError(t, err)
258 assert.Equal(t, int64(10), v4)
210259
211260 v5, err := sec.Key("UINT").Uint()
212 So(err, ShouldBeNil)
213 So(v5, ShouldEqual, 3)
261 require.NoError(t, err)
262 assert.Equal(t, uint(3), v5)
214263
215264 v6, err := sec.Key("UINT").Uint64()
216 So(err, ShouldBeNil)
217 So(v6, ShouldEqual, 3)
218
219 t, err := time.Parse(time.RFC3339, "2015-01-01T20:17:05Z")
220 So(err, ShouldBeNil)
265 require.NoError(t, err)
266 assert.Equal(t, uint64(3), v6)
267
268 ti, err := time.Parse(time.RFC3339, "2015-01-01T20:17:05Z")
269 require.NoError(t, err)
221270 v7, err := sec.Key("TIME").Time()
222 So(err, ShouldBeNil)
223 So(v7.String(), ShouldEqual, t.String())
271 require.NoError(t, err)
272 assert.Equal(t, ti.String(), v7.String())
224273
225274 v8, err := sec.Key("HEX_NUMBER").Int()
226 So(err, ShouldBeNil)
227 So(v8, ShouldEqual, 0x3000)
228
229 Convey("Must get values with type", func() {
230 So(sec.Key("STRING").MustString("404"), ShouldEqual, "str")
231 So(sec.Key("BOOL").MustBool(), ShouldBeTrue)
232 So(sec.Key("FLOAT64").MustFloat64(), ShouldEqual, 1.25)
233 So(sec.Key("INT").MustInt(), ShouldEqual, 10)
234 So(sec.Key("INT").MustInt64(), ShouldEqual, 10)
235 So(sec.Key("UINT").MustUint(), ShouldEqual, 3)
236 So(sec.Key("UINT").MustUint64(), ShouldEqual, 3)
237 So(sec.Key("TIME").MustTime().String(), ShouldEqual, t.String())
238 So(sec.Key("HEX_NUMBER").MustInt(), ShouldEqual, 0x3000)
275 require.NoError(t, err)
276 assert.Equal(t, 0x3000, v8)
277
278 t.Run("must get values with type", func(t *testing.T) {
279 assert.Equal(t, "str", sec.Key("STRING").MustString("404"))
280 assert.True(t, sec.Key("BOOL").MustBool())
281 assert.Equal(t, float64(1.25), sec.Key("FLOAT64").MustFloat64())
282 assert.Equal(t, int(10), sec.Key("INT").MustInt())
283 assert.Equal(t, int64(10), sec.Key("INT").MustInt64())
284 assert.Equal(t, uint(3), sec.Key("UINT").MustUint())
285 assert.Equal(t, uint64(3), sec.Key("UINT").MustUint64())
286 assert.Equal(t, ti.String(), sec.Key("TIME").MustTime().String())
287 assert.Equal(t, 0x3000, sec.Key("HEX_NUMBER").MustInt())
239288
240289 dur, err := time.ParseDuration("2h45m")
241 So(err, ShouldBeNil)
242 So(sec.Key("DURATION").MustDuration().Seconds(), ShouldEqual, dur.Seconds())
243
244 Convey("Must get values with default value", func() {
245 So(sec.Key("STRING_404").MustString("404"), ShouldEqual, "404")
246 So(sec.Key("BOOL_404").MustBool(true), ShouldBeTrue)
247 So(sec.Key("FLOAT64_404").MustFloat64(2.5), ShouldEqual, 2.5)
248 So(sec.Key("INT_404").MustInt(15), ShouldEqual, 15)
249 So(sec.Key("INT64_404").MustInt64(15), ShouldEqual, 15)
250 So(sec.Key("UINT_404").MustUint(6), ShouldEqual, 6)
251 So(sec.Key("UINT64_404").MustUint64(6), ShouldEqual, 6)
252 So(sec.Key("HEX_NUMBER_404").MustInt(0x3001), ShouldEqual, 0x3001)
253
254 t, err := time.Parse(time.RFC3339, "2014-01-01T20:17:05Z")
255 So(err, ShouldBeNil)
256 So(sec.Key("TIME_404").MustTime(t).String(), ShouldEqual, t.String())
257
258 So(sec.Key("DURATION_404").MustDuration(dur).Seconds(), ShouldEqual, dur.Seconds())
259
260 Convey("Must should set default as key value", func() {
261 So(sec.Key("STRING_404").String(), ShouldEqual, "404")
262 So(sec.Key("BOOL_404").String(), ShouldEqual, "true")
263 So(sec.Key("FLOAT64_404").String(), ShouldEqual, "2.5")
264 So(sec.Key("INT_404").String(), ShouldEqual, "15")
265 So(sec.Key("INT64_404").String(), ShouldEqual, "15")
266 So(sec.Key("UINT_404").String(), ShouldEqual, "6")
267 So(sec.Key("UINT64_404").String(), ShouldEqual, "6")
268 So(sec.Key("TIME_404").String(), ShouldEqual, "2014-01-01T20:17:05Z")
269 So(sec.Key("DURATION_404").String(), ShouldEqual, "2h45m0s")
270 So(sec.Key("HEX_NUMBER_404").String(), ShouldEqual, "12289")
290 require.NoError(t, err)
291 assert.Equal(t, dur.Seconds(), sec.Key("DURATION").MustDuration().Seconds())
292
293 t.Run("must get values with default value", func(t *testing.T) {
294 assert.Equal(t, "404", sec.Key("STRING_404").MustString("404"))
295 assert.True(t, sec.Key("BOOL_404").MustBool(true))
296 assert.Equal(t, float64(2.5), sec.Key("FLOAT64_404").MustFloat64(2.5))
297 assert.Equal(t, int(15), sec.Key("INT_404").MustInt(15))
298 assert.Equal(t, int64(15), sec.Key("INT64_404").MustInt64(15))
299 assert.Equal(t, uint(6), sec.Key("UINT_404").MustUint(6))
300 assert.Equal(t, uint64(6), sec.Key("UINT64_404").MustUint64(6))
301 assert.Equal(t, 0x3001, sec.Key("HEX_NUMBER_404").MustInt(0x3001))
302
303 ti, err := time.Parse(time.RFC3339, "2014-01-01T20:17:05Z")
304 require.NoError(t, err)
305 assert.Equal(t, ti.String(), sec.Key("TIME_404").MustTime(ti).String())
306
307 assert.Equal(t, dur.Seconds(), sec.Key("DURATION_404").MustDuration(dur).Seconds())
308
309 t.Run("must should set default as key value", func(t *testing.T) {
310 assert.Equal(t, "404", sec.Key("STRING_404").String())
311 assert.Equal(t, "true", sec.Key("BOOL_404").String())
312 assert.Equal(t, "2.5", sec.Key("FLOAT64_404").String())
313 assert.Equal(t, "15", sec.Key("INT_404").String())
314 assert.Equal(t, "15", sec.Key("INT64_404").String())
315 assert.Equal(t, "6", sec.Key("UINT_404").String())
316 assert.Equal(t, "6", sec.Key("UINT64_404").String())
317 assert.Equal(t, "2014-01-01T20:17:05Z", sec.Key("TIME_404").String())
318 assert.Equal(t, "2h45m0s", sec.Key("DURATION_404").String())
319 assert.Equal(t, "12289", sec.Key("HEX_NUMBER_404").String())
271320 })
272321 })
273322 })
274323 })
275324
276 Convey("Get value with candidates", func() {
325 t.Run("get value with candidates", func(t *testing.T) {
277326 sec := f.Section("types")
278 So(sec.Key("STRING").In("", []string{"str", "arr", "types"}), ShouldEqual, "str")
279 So(sec.Key("FLOAT64").InFloat64(0, []float64{1.25, 2.5, 3.75}), ShouldEqual, 1.25)
280 So(sec.Key("INT").InInt(0, []int{10, 20, 30}), ShouldEqual, 10)
281 So(sec.Key("INT").InInt64(0, []int64{10, 20, 30}), ShouldEqual, 10)
282 So(sec.Key("UINT").InUint(0, []uint{3, 6, 9}), ShouldEqual, 3)
283 So(sec.Key("UINT").InUint64(0, []uint64{3, 6, 9}), ShouldEqual, 3)
327 assert.Equal(t, "str", sec.Key("STRING").In("", []string{"str", "arr", "types"}))
328 assert.Equal(t, float64(1.25), sec.Key("FLOAT64").InFloat64(0, []float64{1.25, 2.5, 3.75}))
329 assert.Equal(t, int(10), sec.Key("INT").InInt(0, []int{10, 20, 30}))
330 assert.Equal(t, int64(10), sec.Key("INT").InInt64(0, []int64{10, 20, 30}))
331 assert.Equal(t, uint(3), sec.Key("UINT").InUint(0, []uint{3, 6, 9}))
332 assert.Equal(t, uint64(3), sec.Key("UINT").InUint64(0, []uint64{3, 6, 9}))
284333
285334 zt, err := time.Parse(time.RFC3339, "0001-01-01T01:00:00Z")
286 So(err, ShouldBeNil)
287 t, err := time.Parse(time.RFC3339, "2015-01-01T20:17:05Z")
288 So(err, ShouldBeNil)
289 So(sec.Key("TIME").InTime(zt, []time.Time{t, time.Now(), time.Now().Add(1 * time.Second)}).String(), ShouldEqual, t.String())
290
291 Convey("Get value with candidates and default value", func() {
292 So(sec.Key("STRING_404").In("str", []string{"str", "arr", "types"}), ShouldEqual, "str")
293 So(sec.Key("FLOAT64_404").InFloat64(1.25, []float64{1.25, 2.5, 3.75}), ShouldEqual, 1.25)
294 So(sec.Key("INT_404").InInt(10, []int{10, 20, 30}), ShouldEqual, 10)
295 So(sec.Key("INT64_404").InInt64(10, []int64{10, 20, 30}), ShouldEqual, 10)
296 So(sec.Key("UINT_404").InUint(3, []uint{3, 6, 9}), ShouldEqual, 3)
297 So(sec.Key("UINT_404").InUint64(3, []uint64{3, 6, 9}), ShouldEqual, 3)
298 So(sec.Key("TIME_404").InTime(t, []time.Time{time.Now(), time.Now(), time.Now().Add(1 * time.Second)}).String(), ShouldEqual, t.String())
335 require.NoError(t, err)
336 ti, err := time.Parse(time.RFC3339, "2015-01-01T20:17:05Z")
337 require.NoError(t, err)
338 assert.Equal(t, ti.String(), sec.Key("TIME").InTime(zt, []time.Time{ti, time.Now(), time.Now().Add(1 * time.Second)}).String())
339
340 t.Run("get value with candidates and default value", func(t *testing.T) {
341 assert.Equal(t, "str", sec.Key("STRING_404_2").In("str", []string{"str", "arr", "types"}))
342 assert.Equal(t, float64(1.25), sec.Key("FLOAT64_404_2").InFloat64(1.25, []float64{1.25, 2.5, 3.75}))
343 assert.Equal(t, int(10), sec.Key("INT_404_2").InInt(10, []int{10, 20, 30}))
344 assert.Equal(t, int64(10), sec.Key("INT64_404_2").InInt64(10, []int64{10, 20, 30}))
345 assert.Equal(t, uint(3), sec.Key("UINT_404_2").InUint(3, []uint{3, 6, 9}))
346 assert.Equal(t, uint64(3), sec.Key("UINT_404_2").InUint64(3, []uint64{3, 6, 9}))
347 assert.Equal(t, ti.String(), sec.Key("TIME_404_2").InTime(ti, []time.Time{time.Now(), time.Now(), time.Now().Add(1 * time.Second)}).String())
299348 })
300349 })
301350
302 Convey("Get values in range", func() {
351 t.Run("get values in range", func(t *testing.T) {
303352 sec := f.Section("types")
304 So(sec.Key("FLOAT64").RangeFloat64(0, 1, 2), ShouldEqual, 1.25)
305 So(sec.Key("INT").RangeInt(0, 10, 20), ShouldEqual, 10)
306 So(sec.Key("INT").RangeInt64(0, 10, 20), ShouldEqual, 10)
353 assert.Equal(t, float64(1.25), sec.Key("FLOAT64").RangeFloat64(0, 1, 2))
354 assert.Equal(t, int(10), sec.Key("INT").RangeInt(0, 10, 20))
355 assert.Equal(t, int64(10), sec.Key("INT").RangeInt64(0, 10, 20))
307356
308357 minT, err := time.Parse(time.RFC3339, "0001-01-01T01:00:00Z")
309 So(err, ShouldBeNil)
358 require.NoError(t, err)
310359 midT, err := time.Parse(time.RFC3339, "2013-01-01T01:00:00Z")
311 So(err, ShouldBeNil)
360 require.NoError(t, err)
312361 maxT, err := time.Parse(time.RFC3339, "9999-01-01T01:00:00Z")
313 So(err, ShouldBeNil)
314 t, err := time.Parse(time.RFC3339, "2015-01-01T20:17:05Z")
315 So(err, ShouldBeNil)
316 So(sec.Key("TIME").RangeTime(t, minT, maxT).String(), ShouldEqual, t.String())
317
318 Convey("Get value in range with default value", func() {
319 So(sec.Key("FLOAT64").RangeFloat64(5, 0, 1), ShouldEqual, 5)
320 So(sec.Key("INT").RangeInt(7, 0, 5), ShouldEqual, 7)
321 So(sec.Key("INT").RangeInt64(7, 0, 5), ShouldEqual, 7)
322 So(sec.Key("TIME").RangeTime(t, minT, midT).String(), ShouldEqual, t.String())
362 require.NoError(t, err)
363 ti, err := time.Parse(time.RFC3339, "2015-01-01T20:17:05Z")
364 require.NoError(t, err)
365 assert.Equal(t, ti.String(), sec.Key("TIME").RangeTime(ti, minT, maxT).String())
366
367 t.Run("get value in range with default value", func(t *testing.T) {
368 assert.Equal(t, float64(5), sec.Key("FLOAT64").RangeFloat64(5, 0, 1))
369 assert.Equal(t, 7, sec.Key("INT").RangeInt(7, 0, 5))
370 assert.Equal(t, int64(7), sec.Key("INT").RangeInt64(7, 0, 5))
371 assert.Equal(t, ti.String(), sec.Key("TIME").RangeTime(ti, minT, midT).String())
323372 })
324373 })
325374
326 Convey("Get values into slice", func() {
375 t.Run("get values into slice", func(t *testing.T) {
327376 sec := f.Section("array")
328 So(strings.Join(sec.Key("STRINGS").Strings(","), ","), ShouldEqual, "en,zh,de")
329 So(len(sec.Key("STRINGS_404").Strings(",")), ShouldEqual, 0)
377 assert.Equal(t, "en,zh,de", strings.Join(sec.Key("STRINGS").Strings(","), ","))
378 assert.Equal(t, 0, len(sec.Key("STRINGS_404").Strings(",")))
330379
331380 vals1 := sec.Key("FLOAT64S").Float64s(",")
332 float64sEqual(vals1, 1.1, 2.2, 3.3)
381 float64sEqual(t, vals1, 1.1, 2.2, 3.3)
333382
334383 vals2 := sec.Key("INTS").Ints(",")
335 intsEqual(vals2, 1, 2, 3)
384 intsEqual(t, vals2, 1, 2, 3)
336385
337386 vals3 := sec.Key("INTS").Int64s(",")
338 int64sEqual(vals3, 1, 2, 3)
387 int64sEqual(t, vals3, 1, 2, 3)
339388
340389 vals4 := sec.Key("UINTS").Uints(",")
341 uintsEqual(vals4, 1, 2, 3)
390 uintsEqual(t, vals4, 1, 2, 3)
342391
343392 vals5 := sec.Key("UINTS").Uint64s(",")
344 uint64sEqual(vals5, 1, 2, 3)
393 uint64sEqual(t, vals5, 1, 2, 3)
345394
346395 vals6 := sec.Key("BOOLS").Bools(",")
347 boolsEqual(vals6, true, false, false)
348
349 t, err := time.Parse(time.RFC3339, "2015-01-01T20:17:05Z")
350 So(err, ShouldBeNil)
396 boolsEqual(t, vals6, true, false, false)
397
398 ti, err := time.Parse(time.RFC3339, "2015-01-01T20:17:05Z")
399 require.NoError(t, err)
351400 vals7 := sec.Key("TIMES").Times(",")
352 timesEqual(vals7, t, t, t)
353 })
354
355 Convey("Test string slice escapes", func() {
401 timesEqual(t, vals7, ti, ti, ti)
402 })
403
404 t.Run("test string slice escapes", func(t *testing.T) {
356405 sec := f.Section("string escapes")
357 So(sec.Key("key1").Strings(","), ShouldResemble, []string{"value1", "value2", "value3"})
358 So(sec.Key("key2").Strings(","), ShouldResemble, []string{"value1, value2"})
359 So(sec.Key("key3").Strings(","), ShouldResemble, []string{`val\ue1`, "value2"})
360 So(sec.Key("key4").Strings(","), ShouldResemble, []string{`value1\`, `value\\2`})
361 So(sec.Key("key5").Strings(",,"), ShouldResemble, []string{"value1,, value2"})
362 So(sec.Key("key6").Strings(" "), ShouldResemble, []string{"aaa", "bbb and space", "ccc"})
363 })
364
365 Convey("Get valid values into slice", func() {
406 assert.Equal(t, []string{"value1", "value2", "value3"}, sec.Key("key1").Strings(","))
407 assert.Equal(t, []string{"value1, value2"}, sec.Key("key2").Strings(","))
408 assert.Equal(t, []string{`val\ue1`, "value2"}, sec.Key("key3").Strings(","))
409 assert.Equal(t, []string{`value1\`, `value\\2`}, sec.Key("key4").Strings(","))
410 assert.Equal(t, []string{"value1,, value2"}, sec.Key("key5").Strings(",,"))
411 assert.Equal(t, []string{"aaa", "bbb and space", "ccc"}, sec.Key("key6").Strings(" "))
412 })
413
414 t.Run("get valid values into slice", func(t *testing.T) {
366415 sec := f.Section("array")
367416 vals1 := sec.Key("FLOAT64S").ValidFloat64s(",")
368 float64sEqual(vals1, 1.1, 2.2, 3.3)
417 float64sEqual(t, vals1, 1.1, 2.2, 3.3)
369418
370419 vals2 := sec.Key("INTS").ValidInts(",")
371 intsEqual(vals2, 1, 2, 3)
420 intsEqual(t, vals2, 1, 2, 3)
372421
373422 vals3 := sec.Key("INTS").ValidInt64s(",")
374 int64sEqual(vals3, 1, 2, 3)
423 int64sEqual(t, vals3, 1, 2, 3)
375424
376425 vals4 := sec.Key("UINTS").ValidUints(",")
377 uintsEqual(vals4, 1, 2, 3)
426 uintsEqual(t, vals4, 1, 2, 3)
378427
379428 vals5 := sec.Key("UINTS").ValidUint64s(",")
380 uint64sEqual(vals5, 1, 2, 3)
429 uint64sEqual(t, vals5, 1, 2, 3)
381430
382431 vals6 := sec.Key("BOOLS").ValidBools(",")
383 boolsEqual(vals6, true, false, false)
384
385 t, err := time.Parse(time.RFC3339, "2015-01-01T20:17:05Z")
386 So(err, ShouldBeNil)
432 boolsEqual(t, vals6, true, false, false)
433
434 ti, err := time.Parse(time.RFC3339, "2015-01-01T20:17:05Z")
435 require.NoError(t, err)
387436 vals7 := sec.Key("TIMES").ValidTimes(",")
388 timesEqual(vals7, t, t, t)
389 })
390
391 Convey("Get values one type into slice of another type", func() {
437 timesEqual(t, vals7, ti, ti, ti)
438 })
439
440 t.Run("get values one type into slice of another type", func(t *testing.T) {
392441 sec := f.Section("array")
393442 vals1 := sec.Key("STRINGS").ValidFloat64s(",")
394 So(vals1, ShouldBeEmpty)
443 assert.Empty(t, vals1)
395444
396445 vals2 := sec.Key("STRINGS").ValidInts(",")
397 So(vals2, ShouldBeEmpty)
446 assert.Empty(t, vals2)
398447
399448 vals3 := sec.Key("STRINGS").ValidInt64s(",")
400 So(vals3, ShouldBeEmpty)
449 assert.Empty(t, vals3)
401450
402451 vals4 := sec.Key("STRINGS").ValidUints(",")
403 So(vals4, ShouldBeEmpty)
452 assert.Empty(t, vals4)
404453
405454 vals5 := sec.Key("STRINGS").ValidUint64s(",")
406 So(vals5, ShouldBeEmpty)
455 assert.Empty(t, vals5)
407456
408457 vals6 := sec.Key("STRINGS").ValidBools(",")
409 So(vals6, ShouldBeEmpty)
458 assert.Empty(t, vals6)
410459
411460 vals7 := sec.Key("STRINGS").ValidTimes(",")
412 So(vals7, ShouldBeEmpty)
413 })
414
415 Convey("Get valid values into slice without errors", func() {
461 assert.Empty(t, vals7)
462 })
463
464 t.Run("get valid values into slice without errors", func(t *testing.T) {
416465 sec := f.Section("array")
417466 vals1, err := sec.Key("FLOAT64S").StrictFloat64s(",")
418 So(err, ShouldBeNil)
419 float64sEqual(vals1, 1.1, 2.2, 3.3)
467 require.NoError(t, err)
468 float64sEqual(t, vals1, 1.1, 2.2, 3.3)
420469
421470 vals2, err := sec.Key("INTS").StrictInts(",")
422 So(err, ShouldBeNil)
423 intsEqual(vals2, 1, 2, 3)
471 require.NoError(t, err)
472 intsEqual(t, vals2, 1, 2, 3)
424473
425474 vals3, err := sec.Key("INTS").StrictInt64s(",")
426 So(err, ShouldBeNil)
427 int64sEqual(vals3, 1, 2, 3)
475 require.NoError(t, err)
476 int64sEqual(t, vals3, 1, 2, 3)
428477
429478 vals4, err := sec.Key("UINTS").StrictUints(",")
430 So(err, ShouldBeNil)
431 uintsEqual(vals4, 1, 2, 3)
479 require.NoError(t, err)
480 uintsEqual(t, vals4, 1, 2, 3)
432481
433482 vals5, err := sec.Key("UINTS").StrictUint64s(",")
434 So(err, ShouldBeNil)
435 uint64sEqual(vals5, 1, 2, 3)
483 require.NoError(t, err)
484 uint64sEqual(t, vals5, 1, 2, 3)
436485
437486 vals6, err := sec.Key("BOOLS").StrictBools(",")
438 So(err, ShouldBeNil)
439 boolsEqual(vals6, true, false, false)
440
441 t, err := time.Parse(time.RFC3339, "2015-01-01T20:17:05Z")
442 So(err, ShouldBeNil)
487 require.NoError(t, err)
488 boolsEqual(t, vals6, true, false, false)
489
490 ti, err := time.Parse(time.RFC3339, "2015-01-01T20:17:05Z")
491 require.NoError(t, err)
443492 vals7, err := sec.Key("TIMES").StrictTimes(",")
444 So(err, ShouldBeNil)
445 timesEqual(vals7, t, t, t)
446 })
447
448 Convey("Get invalid values into slice", func() {
493 require.NoError(t, err)
494 timesEqual(t, vals7, ti, ti, ti)
495 })
496
497 t.Run("get invalid values into slice", func(t *testing.T) {
449498 sec := f.Section("array")
450499 vals1, err := sec.Key("STRINGS").StrictFloat64s(",")
451 So(vals1, ShouldBeEmpty)
452 So(err, ShouldNotBeNil)
500 assert.Empty(t, vals1)
501 assert.Error(t, err)
453502
454503 vals2, err := sec.Key("STRINGS").StrictInts(",")
455 So(vals2, ShouldBeEmpty)
456 So(err, ShouldNotBeNil)
504 assert.Empty(t, vals2)
505 assert.Error(t, err)
457506
458507 vals3, err := sec.Key("STRINGS").StrictInt64s(",")
459 So(vals3, ShouldBeEmpty)
460 So(err, ShouldNotBeNil)
508 assert.Empty(t, vals3)
509 assert.Error(t, err)
461510
462511 vals4, err := sec.Key("STRINGS").StrictUints(",")
463 So(vals4, ShouldBeEmpty)
464 So(err, ShouldNotBeNil)
512 assert.Empty(t, vals4)
513 assert.Error(t, err)
465514
466515 vals5, err := sec.Key("STRINGS").StrictUint64s(",")
467 So(vals5, ShouldBeEmpty)
468 So(err, ShouldNotBeNil)
516 assert.Empty(t, vals5)
517 assert.Error(t, err)
469518
470519 vals6, err := sec.Key("STRINGS").StrictBools(",")
471 So(vals6, ShouldBeEmpty)
472 So(err, ShouldNotBeNil)
520 assert.Empty(t, vals6)
521 assert.Error(t, err)
473522
474523 vals7, err := sec.Key("STRINGS").StrictTimes(",")
475 So(vals7, ShouldBeEmpty)
476 So(err, ShouldNotBeNil)
477 })
524 assert.Empty(t, vals7)
525 assert.Error(t, err)
526 })
527 })
528 }
529
530 func TestKey_ValueWithShadows(t *testing.T) {
531 t.Run("", func(t *testing.T) {
532 f, err := ShadowLoad([]byte(`
533 keyName = value1
534 keyName = value2
535 `))
536 require.NoError(t, err)
537 require.NotNil(t, f)
538
539 k := f.Section("").Key("FakeKey")
540 require.NotNil(t, k)
541 assert.Equal(t, []string{}, k.ValueWithShadows())
542
543 k = f.Section("").Key("keyName")
544 require.NotNil(t, k)
545 assert.Equal(t, []string{"value1", "value2"}, k.ValueWithShadows())
478546 })
479547 }
480548
481549 func TestKey_StringsWithShadows(t *testing.T) {
482 Convey("Get strings of shadows of a key", t, func() {
483 f, err := ini.ShadowLoad([]byte(""))
484 So(err, ShouldBeNil)
485 So(f, ShouldNotBeNil)
550 t.Run("get strings of shadows of a key", func(t *testing.T) {
551 f, err := ShadowLoad([]byte(""))
552 require.NoError(t, err)
553 require.NotNil(t, f)
486554
487555 k, err := f.Section("").NewKey("NUMS", "1,2")
488 So(err, ShouldBeNil)
489 So(k, ShouldNotBeNil)
556 require.NoError(t, err)
557 require.NotNil(t, k)
490558 k, err = f.Section("").NewKey("NUMS", "4,5,6")
491 So(err, ShouldBeNil)
492 So(k, ShouldNotBeNil)
493
494 So(k.StringsWithShadows(","), ShouldResemble, []string{"1", "2", "4", "5", "6"})
559 require.NoError(t, err)
560 require.NotNil(t, k)
561
562 assert.Equal(t, []string{"1", "2", "4", "5", "6"}, k.StringsWithShadows(","))
495563 })
496564 }
497565
498566 func TestKey_SetValue(t *testing.T) {
499 Convey("Set value of key", t, func() {
500 f := ini.Empty()
501 So(f, ShouldNotBeNil)
567 t.Run("set value of key", func(t *testing.T) {
568 f := Empty()
569 require.NotNil(t, f)
502570
503571 k, err := f.Section("").NewKey("NAME", "ini")
504 So(err, ShouldBeNil)
505 So(k, ShouldNotBeNil)
506 So(k.Value(), ShouldEqual, "ini")
572 require.NoError(t, err)
573 require.NotNil(t, k)
574 assert.Equal(t, "ini", k.Value())
507575
508576 k.SetValue("ini.v1")
509 So(k.Value(), ShouldEqual, "ini.v1")
577 assert.Equal(t, "ini.v1", k.Value())
510578 })
511579 }
512580
513581 func TestKey_NestedValues(t *testing.T) {
514 Convey("Read and write nested values", t, func() {
515 f, err := ini.LoadSources(ini.LoadOptions{
582 t.Run("read and write nested values", func(t *testing.T) {
583 f, err := LoadSources(LoadOptions{
516584 AllowNestedValues: true,
517585 }, []byte(`
518586 aws_access_key_id = foo
521589 s3 =
522590 max_concurrent_requests=10
523591 max_queue_size=1000`))
524 So(err, ShouldBeNil)
525 So(f, ShouldNotBeNil)
526
527 So(f.Section("").Key("s3").NestedValues(), ShouldResemble, []string{"max_concurrent_requests=10", "max_queue_size=1000"})
592 require.NoError(t, err)
593 require.NotNil(t, f)
594
595 assert.Equal(t, []string{"max_concurrent_requests=10", "max_queue_size=1000"}, f.Section("").Key("s3").NestedValues())
528596
529597 var buf bytes.Buffer
530598 _, err = f.WriteTo(&buf)
531 So(err, ShouldBeNil)
532 So(buf.String(), ShouldEqual, `aws_access_key_id = foo
599 require.NoError(t, err)
600 assert.Equal(t, `aws_access_key_id = foo
533601 aws_secret_access_key = bar
534602 region = us-west-2
535603 s3 =
536604 max_concurrent_requests=10
537605 max_queue_size=1000
538606
539 `)
607 `,
608 buf.String(),
609 )
540610 })
541611 }
542612
543613 func TestRecursiveValues(t *testing.T) {
544 Convey("Recursive values should not reflect on same key", t, func() {
545 f, err := ini.Load([]byte(`
614 t.Run("recursive values should not reflect on same key", func(t *testing.T) {
615 f, err := Load([]byte(`
546616 NAME = ini
547617 expires = yes
548618 [package]
549619 NAME = %(NAME)s
550620 expires = %(expires)s`))
551 So(err, ShouldBeNil)
552 So(f, ShouldNotBeNil)
553
554 So(f.Section("package").Key("NAME").String(), ShouldEqual, "ini")
555 So(f.Section("package").Key("expires").String(), ShouldEqual, "yes")
556 })
557
558 Convey("Recursive value with no target found", t, func() {
559 f, err := ini.Load([]byte(`
621 require.NoError(t, err)
622 require.NotNil(t, f)
623
624 assert.Equal(t, "ini", f.Section("package").Key("NAME").String())
625 assert.Equal(t, "yes", f.Section("package").Key("expires").String())
626 })
627
628 t.Run("recursive value with no target found", func(t *testing.T) {
629 f, err := Load([]byte(`
560630 [foo]
561631 bar = %(missing)s
562632 `))
563 So(err, ShouldBeNil)
564 So(f, ShouldNotBeNil)
565
566 So(f.Section("foo").Key("bar").String(), ShouldEqual, "%(missing)s")
567 })
568 }
633 require.NoError(t, err)
634 require.NotNil(t, f)
635
636 assert.Equal(t, "%(missing)s", f.Section("foo").Key("bar").String())
637 })
638 }
8383 case mask[0] == 254 && mask[1] == 255:
8484 fallthrough
8585 case mask[0] == 255 && mask[1] == 254:
86 p.buf.Read(mask)
86 _, err = p.buf.Read(mask)
87 if err != nil {
88 return err
89 }
8790 case mask[0] == 239 && mask[1] == 187:
8891 mask, err := p.buf.Peek(3)
8992 if err != nil && err != io.EOF {
9295 return nil
9396 }
9497 if mask[2] == 191 {
95 p.buf.Read(mask)
98 _, err = p.buf.Read(mask)
99 if err != nil {
100 return err
101 }
96102 }
97103 }
98104 return nil
124130 // Check if key name surrounded by quotes.
125131 var keyQuote string
126132 if line[0] == '"' {
127 if len(line) > 6 && string(line[0:3]) == `"""` {
133 if len(line) > 6 && line[0:3] == `"""` {
128134 keyQuote = `"""`
129135 } else {
130136 keyQuote = `"`
134140 }
135141
136142 // Get out key name
137 endIdx := -1
143 var endIdx int
138144 if len(keyQuote) > 0 {
139145 startIdx := len(keyQuote)
140146 // FIXME: fail case -> """"""name"""=value
225231 }
226232
227233 var valQuote string
228 if len(line) > 3 && string(line[0:3]) == `"""` {
234 if len(line) > 3 && line[0:3] == `"""` {
229235 valQuote = `"""`
230236 } else if line[0] == '`' {
231237 valQuote = "`"
282288 hasSurroundedQuote(line, '"')) && !p.options.PreserveSurroundedQuote {
283289 line = line[1 : len(line)-1]
284290 } else if len(valQuote) == 0 && p.options.UnescapeValueCommentSymbols {
285 if strings.Contains(line, `\;`) {
286 line = strings.Replace(line, `\;`, ";", -1)
287 }
288 if strings.Contains(line, `\#`) {
289 line = strings.Replace(line, `\#`, "#", -1)
290 }
291 line = strings.ReplaceAll(line, `\;`, ";")
292 line = strings.ReplaceAll(line, `\#`, "#")
291293 } else if p.options.AllowPythonMultilineValues && lastChar == '\n' {
292294 return p.readPythonMultilines(line, bufferSize)
293295 }
299301 parserBufferPeekResult, _ := p.buf.Peek(bufferSize)
300302 peekBuffer := bytes.NewBuffer(parserBufferPeekResult)
301303
302 indentSize := 0
303304 for {
304305 peekData, peekErr := peekBuffer.ReadBytes('\n')
305 if peekErr != nil {
306 if peekErr == io.EOF {
307 p.debug("readPythonMultilines: io.EOF, peekData: %q, line: %q", string(peekData), line)
308 return line, nil
309 }
310
306 if peekErr != nil && peekErr != io.EOF {
311307 p.debug("readPythonMultilines: failed to peek with error: %v", peekErr)
312308 return "", peekErr
313309 }
326322 return line, nil
327323 }
328324
329 // Determine indent size and line prefix.
330 currentIndentSize := len(peekMatches[1])
331 if indentSize < 1 {
332 indentSize = currentIndentSize
333 p.debug("readPythonMultilines: indent size is %d", indentSize)
334 }
335
336 // Make sure each line is indented at least as far as first line.
337 if currentIndentSize < indentSize {
338 p.debug("readPythonMultilines: end of value, current indent: %d, expected indent: %d, line: %q", currentIndentSize, indentSize, line)
339 return line, nil
340 }
341
342325 // Advance the parser reader (buffer) in-sync with the peek buffer.
343326 _, err := p.buf.Discard(len(peekData))
344327 if err != nil {
346329 return "", err
347330 }
348331
349 // Handle indented empty line.
350 line += "\n" + peekMatches[1][indentSize:] + peekMatches[2]
332 line += "\n" + peekMatches[0]
351333 }
352334 }
353335
370352
371353 // Ignore error because default section name is never empty string.
372354 name := DefaultSection
373 if f.options.Insensitive {
355 if f.options.Insensitive || f.options.InsensitiveSections {
374356 name = strings.ToLower(DefaultSection)
375357 }
376358 section, _ := f.NewSection(name)
412394 if f.options.AllowNestedValues &&
413395 isLastValueEmpty && len(line) > 0 {
414396 if line[0] == ' ' || line[0] == '\t' {
415 lastRegularKey.addNestedValue(string(bytes.TrimSpace(line)))
397 err = lastRegularKey.addNestedValue(string(bytes.TrimSpace(line)))
398 if err != nil {
399 return err
400 }
416401 continue
417402 }
418403 }
455440 // Reset auto-counter and comments
456441 p.comment.Reset()
457442 p.count = 1
443 // Nested values can't span sections
444 isLastValueEmpty = false
458445
459446 inUnparseableSection = false
460447 for i := range f.options.UnparseableSections {
461448 if f.options.UnparseableSections[i] == name ||
462 (f.options.Insensitive && strings.ToLower(f.options.UnparseableSections[i]) == strings.ToLower(name)) {
449 ((f.options.Insensitive || f.options.InsensitiveSections) && strings.EqualFold(f.options.UnparseableSections[i], name)) {
463450 inUnparseableSection = true
464451 continue
465452 }
1111 // License for the specific language governing permissions and limitations
1212 // under the License.
1313
14 package ini_test
14 package ini
1515
1616 import (
1717 "testing"
1818
19 . "github.com/smartystreets/goconvey/convey"
20 "gopkg.in/ini.v1"
19 "github.com/stretchr/testify/assert"
20 "github.com/stretchr/testify/require"
2121 )
2222
2323 func TestBOM(t *testing.T) {
24 Convey("Test handling BOM", t, func() {
25 Convey("UTF-8-BOM", func() {
26 f, err := ini.Load("testdata/UTF-8-BOM.ini")
27 So(err, ShouldBeNil)
28 So(f, ShouldNotBeNil)
24 t.Run("test handling BOM", func(t *testing.T) {
25 t.Run("UTF-8-BOM", func(t *testing.T) {
26 f, err := Load("testdata/UTF-8-BOM.ini")
27 require.NoError(t, err)
28 require.NotNil(t, f)
2929
30 So(f.Section("author").Key("E-MAIL").String(), ShouldEqual, "example@email.com")
30 assert.Equal(t, "example@email.com", f.Section("author").Key("E-MAIL").String())
3131 })
3232
33 Convey("UTF-16-LE-BOM", func() {
34 f, err := ini.Load("testdata/UTF-16-LE-BOM.ini")
35 So(err, ShouldBeNil)
36 So(f, ShouldNotBeNil)
33 t.Run("UTF-16-LE-BOM", func(t *testing.T) {
34 f, err := Load("testdata/UTF-16-LE-BOM.ini")
35 require.NoError(t, err)
36 require.NotNil(t, f)
3737 })
3838
39 Convey("UTF-16-BE-BOM", func() {
39 t.Run("UTF-16-BE-BOM", func(t *testing.T) {
4040 })
4141 })
4242 }
4343
4444 func TestBadLoad(t *testing.T) {
45 Convey("Load with bad data", t, func() {
46 Convey("Bad section name", func() {
47 _, err := ini.Load([]byte("[]"))
48 So(err, ShouldNotBeNil)
45 t.Run("load with bad data", func(t *testing.T) {
46 t.Run("bad section name", func(t *testing.T) {
47 _, err := Load([]byte("[]"))
48 require.Error(t, err)
4949
50 _, err = ini.Load([]byte("["))
51 So(err, ShouldNotBeNil)
50 _, err = Load([]byte("["))
51 require.Error(t, err)
5252 })
5353
54 Convey("Bad keys", func() {
55 _, err := ini.Load([]byte(`"""name`))
56 So(err, ShouldNotBeNil)
54 t.Run("bad keys", func(t *testing.T) {
55 _, err := Load([]byte(`"""name`))
56 require.Error(t, err)
5757
58 _, err = ini.Load([]byte(`"""name"""`))
59 So(err, ShouldNotBeNil)
58 _, err = Load([]byte(`"""name"""`))
59 require.Error(t, err)
6060
61 _, err = ini.Load([]byte(`""=1`))
62 So(err, ShouldNotBeNil)
61 _, err = Load([]byte(`""=1`))
62 require.Error(t, err)
6363
64 _, err = ini.Load([]byte(`=`))
65 So(err, ShouldNotBeNil)
64 _, err = Load([]byte(`=`))
65 require.Error(t, err)
6666
67 _, err = ini.Load([]byte(`name`))
68 So(err, ShouldNotBeNil)
67 _, err = Load([]byte(`name`))
68 require.Error(t, err)
6969 })
7070
71 Convey("Bad values", func() {
72 _, err := ini.Load([]byte(`name="""Unknwon`))
73 So(err, ShouldNotBeNil)
71 t.Run("bad values", func(t *testing.T) {
72 _, err := Load([]byte(`name="""Unknwon`))
73 require.Error(t, err)
7474 })
7575 })
7676 }
6565 func (s *Section) NewKey(name, val string) (*Key, error) {
6666 if len(name) == 0 {
6767 return nil, errors.New("error creating new key: empty key name")
68 } else if s.f.options.Insensitive {
68 } else if s.f.options.Insensitive || s.f.options.InsensitiveKeys {
6969 name = strings.ToLower(name)
7070 }
7171
108108 if s.f.BlockMode {
109109 s.f.lock.RLock()
110110 }
111 if s.f.options.Insensitive {
111 if s.f.options.Insensitive || s.f.options.InsensitiveKeys {
112112 name = strings.ToLower(name)
113113 }
114114 key := s.keys[name]
120120 // Check if it is a child-section.
121121 sname := s.name
122122 for {
123 if i := strings.LastIndex(sname, "."); i > -1 {
123 if i := strings.LastIndex(sname, s.f.options.ChildSectionDelimiter); i > -1 {
124124 sname = sname[:i]
125125 sec, err := s.f.GetSection(sname)
126126 if err != nil {
187187 var parentKeys []*Key
188188 sname := s.name
189189 for {
190 if i := strings.LastIndex(sname, "."); i > -1 {
190 if i := strings.LastIndex(sname, s.f.options.ChildSectionDelimiter); i > -1 {
191191 sname = sname[:i]
192192 sec, err := s.f.GetSection(sname)
193193 if err != nil {
216216 defer s.f.lock.RUnlock()
217217 }
218218
219 hash := map[string]string{}
219 hash := make(map[string]string, len(s.keysHash))
220220 for key, value := range s.keysHash {
221221 hash[key] = value
222222 }
244244 // For example, "[parent.child1]" and "[parent.child12]" are child sections
245245 // of section "[parent]".
246246 func (s *Section) ChildSections() []*Section {
247 prefix := s.name + "."
247 prefix := s.name + s.f.options.ChildSectionDelimiter
248248 children := make([]*Section, 0, 3)
249249 for _, name := range s.f.sectionList {
250250 if strings.HasPrefix(name, prefix) {
1111 // License for the specific language governing permissions and limitations
1212 // under the License.
1313
14 package ini_test
14 package ini
1515
1616 import (
1717 "testing"
1818
19 . "github.com/smartystreets/goconvey/convey"
20 "gopkg.in/ini.v1"
19 "github.com/stretchr/testify/assert"
20 "github.com/stretchr/testify/require"
2121 )
2222
2323 func TestSection_SetBody(t *testing.T) {
24 Convey("Set body of raw section", t, func() {
25 f := ini.Empty()
26 So(f, ShouldNotBeNil)
24 t.Run("set body of raw section", func(t *testing.T) {
25 f := Empty()
26 require.NotNil(t, f)
2727
2828 sec, err := f.NewRawSection("comments", `1111111111111111111000000000000000001110000
2929 111111111111111111100000000000111000000000`)
30 So(err, ShouldBeNil)
31 So(sec, ShouldNotBeNil)
32 So(sec.Body(), ShouldEqual, `1111111111111111111000000000000000001110000
33 111111111111111111100000000000111000000000`)
30 require.NoError(t, err)
31 require.NotNil(t, sec)
32 assert.Equal(t, `1111111111111111111000000000000000001110000
33 111111111111111111100000000000111000000000`, sec.Body())
3434
3535 sec.SetBody("1111111111111111111000000000000000001110000")
36 So(sec.Body(), ShouldEqual, `1111111111111111111000000000000000001110000`)
37
38 Convey("Set for non-raw section", func() {
36 assert.Equal(t, `1111111111111111111000000000000000001110000`, sec.Body())
37
38 t.Run("set for non-raw section", func(t *testing.T) {
3939 sec, err := f.NewSection("author")
40 So(err, ShouldBeNil)
41 So(sec, ShouldNotBeNil)
42 So(sec.Body(), ShouldBeEmpty)
40 require.NoError(t, err)
41 require.NotNil(t, sec)
42 assert.Empty(t, sec.Body())
4343
4444 sec.SetBody("1111111111111111111000000000000000001110000")
45 So(sec.Body(), ShouldBeEmpty)
45 assert.Empty(t, sec.Body())
4646 })
4747 })
4848 }
4949
5050 func TestSection_NewKey(t *testing.T) {
51 Convey("Create a new key", t, func() {
52 f := ini.Empty()
53 So(f, ShouldNotBeNil)
54
55 k, err := f.Section("").NewKey("NAME", "ini")
56 So(err, ShouldBeNil)
57 So(k, ShouldNotBeNil)
58 So(k.Name(), ShouldEqual, "NAME")
59 So(k.Value(), ShouldEqual, "ini")
60
61 Convey("With duplicated name", func() {
51 t.Run("create a new key", func(t *testing.T) {
52 f := Empty()
53 require.NotNil(t, f)
54
55 k, err := f.Section("").NewKey("NAME", "ini")
56 require.NoError(t, err)
57 require.NotNil(t, k)
58 assert.Equal(t, "NAME", k.Name())
59 assert.Equal(t, "ini", k.Value())
60
61 t.Run("with duplicated name", func(t *testing.T) {
6262 k, err := f.Section("").NewKey("NAME", "ini.v1")
63 So(err, ShouldBeNil)
64 So(k, ShouldNotBeNil)
63 require.NoError(t, err)
64 require.NotNil(t, k)
6565
6666 // Overwrite previous existed key
67 So(k.Value(), ShouldEqual, "ini.v1")
68 })
69
70 Convey("With empty string", func() {
67 assert.Equal(t, "ini.v1", k.Value())
68 })
69
70 t.Run("with empty string", func(t *testing.T) {
7171 _, err := f.Section("").NewKey("", "")
72 So(err, ShouldNotBeNil)
73 })
74 })
75
76 Convey("Create keys with same name and allow shadow", t, func() {
77 f, err := ini.ShadowLoad([]byte(""))
78 So(err, ShouldBeNil)
79 So(f, ShouldNotBeNil)
80
81 k, err := f.Section("").NewKey("NAME", "ini")
82 So(err, ShouldBeNil)
83 So(k, ShouldNotBeNil)
72 require.Error(t, err)
73 })
74 })
75
76 t.Run("create keys with same name and allow shadow", func(t *testing.T) {
77 f, err := ShadowLoad([]byte(""))
78 require.NoError(t, err)
79 require.NotNil(t, f)
80
81 k, err := f.Section("").NewKey("NAME", "ini")
82 require.NoError(t, err)
83 require.NotNil(t, k)
8484 k, err = f.Section("").NewKey("NAME", "ini.v1")
85 So(err, ShouldBeNil)
86 So(k, ShouldNotBeNil)
87
88 So(k.ValueWithShadows(), ShouldResemble, []string{"ini", "ini.v1"})
85 require.NoError(t, err)
86 require.NotNil(t, k)
87
88 assert.Equal(t, []string{"ini", "ini.v1"}, k.ValueWithShadows())
8989 })
9090 }
9191
9292 func TestSection_NewBooleanKey(t *testing.T) {
93 Convey("Create a new boolean key", t, func() {
94 f := ini.Empty()
95 So(f, ShouldNotBeNil)
93 t.Run("create a new boolean key", func(t *testing.T) {
94 f := Empty()
95 require.NotNil(t, f)
9696
9797 k, err := f.Section("").NewBooleanKey("start-ssh-server")
98 So(err, ShouldBeNil)
99 So(k, ShouldNotBeNil)
100 So(k.Name(), ShouldEqual, "start-ssh-server")
101 So(k.Value(), ShouldEqual, "true")
102
103 Convey("With empty string", func() {
98 require.NoError(t, err)
99 require.NotNil(t, k)
100 assert.Equal(t, "start-ssh-server", k.Name())
101 assert.Equal(t, "true", k.Value())
102
103 t.Run("with empty string", func(t *testing.T) {
104104 _, err := f.Section("").NewBooleanKey("")
105 So(err, ShouldNotBeNil)
105 require.Error(t, err)
106106 })
107107 })
108108 }
109109
110110 func TestSection_GetKey(t *testing.T) {
111 Convey("Get a key", t, func() {
112 f := ini.Empty()
113 So(f, ShouldNotBeNil)
114
115 k, err := f.Section("").NewKey("NAME", "ini")
116 So(err, ShouldBeNil)
117 So(k, ShouldNotBeNil)
111 t.Run("get a key", func(t *testing.T) {
112 f := Empty()
113 require.NotNil(t, f)
114
115 k, err := f.Section("").NewKey("NAME", "ini")
116 require.NoError(t, err)
117 require.NotNil(t, k)
118118
119119 k, err = f.Section("").GetKey("NAME")
120 So(err, ShouldBeNil)
121 So(k, ShouldNotBeNil)
122 So(k.Name(), ShouldEqual, "NAME")
123 So(k.Value(), ShouldEqual, "ini")
124
125 Convey("Key not exists", func() {
120 require.NoError(t, err)
121 require.NotNil(t, k)
122 assert.Equal(t, "NAME", k.Name())
123 assert.Equal(t, "ini", k.Value())
124
125 t.Run("key not exists", func(t *testing.T) {
126126 _, err := f.Section("").GetKey("404")
127 So(err, ShouldNotBeNil)
128 })
129
130 Convey("Key exists in parent section", func() {
127 require.Error(t, err)
128 })
129
130 t.Run("key exists in parent section", func(t *testing.T) {
131131 k, err := f.Section("parent").NewKey("AGE", "18")
132 So(err, ShouldBeNil)
133 So(k, ShouldNotBeNil)
132 require.NoError(t, err)
133 require.NotNil(t, k)
134134
135135 k, err = f.Section("parent.child.son").GetKey("AGE")
136 So(err, ShouldBeNil)
137 So(k, ShouldNotBeNil)
138 So(k.Value(), ShouldEqual, "18")
136 require.NoError(t, err)
137 require.NotNil(t, k)
138 assert.Equal(t, "18", k.Value())
139139 })
140140 })
141141 }
142142
143143 func TestSection_HasKey(t *testing.T) {
144 Convey("Check if a key exists", t, func() {
145 f := ini.Empty()
146 So(f, ShouldNotBeNil)
147
148 k, err := f.Section("").NewKey("NAME", "ini")
149 So(err, ShouldBeNil)
150 So(k, ShouldNotBeNil)
151
152 So(f.Section("").HasKey("NAME"), ShouldBeTrue)
153 So(f.Section("").HasKey("NAME"), ShouldBeTrue)
154 So(f.Section("").HasKey("404"), ShouldBeFalse)
155 So(f.Section("").HasKey("404"), ShouldBeFalse)
144 t.Run("check if a key exists", func(t *testing.T) {
145 f := Empty()
146 require.NotNil(t, f)
147
148 k, err := f.Section("").NewKey("NAME", "ini")
149 require.NoError(t, err)
150 require.NotNil(t, k)
151
152 assert.True(t, f.Section("").HasKey("NAME"))
153 assert.True(t, f.Section("").HasKey("NAME"))
154 assert.False(t, f.Section("").HasKey("404"))
155 assert.False(t, f.Section("").HasKey("404"))
156156 })
157157 }
158158
159159 func TestSection_HasValue(t *testing.T) {
160 Convey("Check if contains a value in any key", t, func() {
161 f := ini.Empty()
162 So(f, ShouldNotBeNil)
163
164 k, err := f.Section("").NewKey("NAME", "ini")
165 So(err, ShouldBeNil)
166 So(k, ShouldNotBeNil)
167
168 So(f.Section("").HasValue("ini"), ShouldBeTrue)
169 So(f.Section("").HasValue("404"), ShouldBeFalse)
160 t.Run("check if contains a value in any key", func(t *testing.T) {
161 f := Empty()
162 require.NotNil(t, f)
163
164 k, err := f.Section("").NewKey("NAME", "ini")
165 require.NoError(t, err)
166 require.NotNil(t, k)
167
168 assert.True(t, f.Section("").HasValue("ini"))
169 assert.False(t, f.Section("").HasValue("404"))
170170 })
171171 }
172172
173173 func TestSection_Key(t *testing.T) {
174 Convey("Get a key", t, func() {
175 f := ini.Empty()
176 So(f, ShouldNotBeNil)
177
178 k, err := f.Section("").NewKey("NAME", "ini")
179 So(err, ShouldBeNil)
180 So(k, ShouldNotBeNil)
174 t.Run("get a key", func(t *testing.T) {
175 f := Empty()
176 require.NotNil(t, f)
177
178 k, err := f.Section("").NewKey("NAME", "ini")
179 require.NoError(t, err)
180 require.NotNil(t, k)
181181
182182 k = f.Section("").Key("NAME")
183 So(k, ShouldNotBeNil)
184 So(k.Name(), ShouldEqual, "NAME")
185 So(k.Value(), ShouldEqual, "ini")
186
187 Convey("Key not exists", func() {
183 require.NotNil(t, k)
184 assert.Equal(t, "NAME", k.Name())
185 assert.Equal(t, "ini", k.Value())
186
187 t.Run("key not exists", func(t *testing.T) {
188188 k := f.Section("").Key("404")
189 So(k, ShouldNotBeNil)
190 So(k.Name(), ShouldEqual, "404")
191 })
192
193 Convey("Key exists in parent section", func() {
189 require.NotNil(t, k)
190 assert.Equal(t, "404", k.Name())
191 })
192
193 t.Run("key exists in parent section", func(t *testing.T) {
194194 k, err := f.Section("parent").NewKey("AGE", "18")
195 So(err, ShouldBeNil)
196 So(k, ShouldNotBeNil)
195 require.NoError(t, err)
196 require.NotNil(t, k)
197197
198198 k = f.Section("parent.child.son").Key("AGE")
199 So(k, ShouldNotBeNil)
200 So(k.Value(), ShouldEqual, "18")
199 require.NotNil(t, k)
200 assert.Equal(t, "18", k.Value())
201201 })
202202 })
203203 }
204204
205205 func TestSection_Keys(t *testing.T) {
206 Convey("Get all keys in a section", t, func() {
207 f := ini.Empty()
208 So(f, ShouldNotBeNil)
209
210 k, err := f.Section("").NewKey("NAME", "ini")
211 So(err, ShouldBeNil)
212 So(k, ShouldNotBeNil)
206 t.Run("get all keys in a section", func(t *testing.T) {
207 f := Empty()
208 require.NotNil(t, f)
209
210 k, err := f.Section("").NewKey("NAME", "ini")
211 require.NoError(t, err)
212 require.NotNil(t, k)
213213 k, err = f.Section("").NewKey("VERSION", "v1")
214 So(err, ShouldBeNil)
215 So(k, ShouldNotBeNil)
214 require.NoError(t, err)
215 require.NotNil(t, k)
216216 k, err = f.Section("").NewKey("IMPORT_PATH", "gopkg.in/ini.v1")
217 So(err, ShouldBeNil)
218 So(k, ShouldNotBeNil)
217 require.NoError(t, err)
218 require.NotNil(t, k)
219219
220220 keys := f.Section("").Keys()
221221 names := []string{"NAME", "VERSION", "IMPORT_PATH"}
222 So(len(keys), ShouldEqual, len(names))
222 assert.Equal(t, len(names), len(keys))
223223 for i, name := range names {
224 So(keys[i].Name(), ShouldEqual, name)
224 assert.Equal(t, name, keys[i].Name())
225225 }
226226 })
227227 }
228228
229229 func TestSection_ParentKeys(t *testing.T) {
230 Convey("Get all keys of parent sections", t, func() {
231 f := ini.Empty()
232 So(f, ShouldNotBeNil)
230 t.Run("get all keys of parent sections", func(t *testing.T) {
231 f := Empty()
232 require.NotNil(t, f)
233233
234234 k, err := f.Section("package").NewKey("NAME", "ini")
235 So(err, ShouldBeNil)
236 So(k, ShouldNotBeNil)
235 require.NoError(t, err)
236 require.NotNil(t, k)
237237 k, err = f.Section("package").NewKey("VERSION", "v1")
238 So(err, ShouldBeNil)
239 So(k, ShouldNotBeNil)
238 require.NoError(t, err)
239 require.NotNil(t, k)
240240 k, err = f.Section("package").NewKey("IMPORT_PATH", "gopkg.in/ini.v1")
241 So(err, ShouldBeNil)
242 So(k, ShouldNotBeNil)
241 require.NoError(t, err)
242 require.NotNil(t, k)
243243
244244 keys := f.Section("package.sub.sub2").ParentKeys()
245245 names := []string{"NAME", "VERSION", "IMPORT_PATH"}
246 So(len(keys), ShouldEqual, len(names))
246 assert.Equal(t, len(names), len(keys))
247247 for i, name := range names {
248 So(keys[i].Name(), ShouldEqual, name)
248 assert.Equal(t, name, keys[i].Name())
249249 }
250250 })
251251 }
252252
253253 func TestSection_KeyStrings(t *testing.T) {
254 Convey("Get all key names in a section", t, func() {
255 f := ini.Empty()
256 So(f, ShouldNotBeNil)
257
258 k, err := f.Section("").NewKey("NAME", "ini")
259 So(err, ShouldBeNil)
260 So(k, ShouldNotBeNil)
254 t.Run("get all key names in a section", func(t *testing.T) {
255 f := Empty()
256 require.NotNil(t, f)
257
258 k, err := f.Section("").NewKey("NAME", "ini")
259 require.NoError(t, err)
260 require.NotNil(t, k)
261261 k, err = f.Section("").NewKey("VERSION", "v1")
262 So(err, ShouldBeNil)
263 So(k, ShouldNotBeNil)
262 require.NoError(t, err)
263 require.NotNil(t, k)
264264 k, err = f.Section("").NewKey("IMPORT_PATH", "gopkg.in/ini.v1")
265 So(err, ShouldBeNil)
266 So(k, ShouldNotBeNil)
267
268 So(f.Section("").KeyStrings(), ShouldResemble, []string{"NAME", "VERSION", "IMPORT_PATH"})
265 require.NoError(t, err)
266 require.NotNil(t, k)
267
268 assert.Equal(t, []string{"NAME", "VERSION", "IMPORT_PATH"}, f.Section("").KeyStrings())
269269 })
270270 }
271271
272272 func TestSection_KeyHash(t *testing.T) {
273 Convey("Get clone of key hash", t, func() {
274 f, err := ini.Load([]byte(`
273 t.Run("get clone of key hash", func(t *testing.T) {
274 f, err := Load([]byte(`
275275 key = one
276276 [log]
277277 name = app
282282 name = app2
283283 file = b.log
284284 `))
285 So(err, ShouldBeNil)
286 So(f, ShouldNotBeNil)
287
288 So(f.Section("").Key("key").String(), ShouldEqual, "two")
285 require.NoError(t, err)
286 require.NotNil(t, f)
287
288 assert.Equal(t, "two", f.Section("").Key("key").String())
289289
290290 hash := f.Section("log").KeysHash()
291291 relation := map[string]string{
293293 "file": "b.log",
294294 }
295295 for k, v := range hash {
296 So(v, ShouldEqual, relation[k])
296 assert.Equal(t, relation[k], v)
297297 }
298298 })
299299 }
300300
301301 func TestSection_DeleteKey(t *testing.T) {
302 Convey("Delete a key", t, func() {
303 f := ini.Empty()
304 So(f, ShouldNotBeNil)
305
306 k, err := f.Section("").NewKey("NAME", "ini")
307 So(err, ShouldBeNil)
308 So(k, ShouldNotBeNil)
309
310 So(f.Section("").HasKey("NAME"), ShouldBeTrue)
302 t.Run("delete a key", func(t *testing.T) {
303 f := Empty()
304 require.NotNil(t, f)
305
306 k, err := f.Section("").NewKey("NAME", "ini")
307 require.NoError(t, err)
308 require.NotNil(t, k)
309
310 assert.True(t, f.Section("").HasKey("NAME"))
311311 f.Section("").DeleteKey("NAME")
312 So(f.Section("").HasKey("NAME"), ShouldBeFalse)
313 })
314 }
312 assert.False(t, f.Section("").HasKey("NAME"))
313 })
314 }
262262 return nil
263263 }
264264
265 func parseTagOptions(tag string) (rawName string, omitEmpty bool, allowShadow bool, allowNonUnique bool) {
266 opts := strings.SplitN(tag, ",", 4)
265 func parseTagOptions(tag string) (rawName string, omitEmpty bool, allowShadow bool, allowNonUnique bool, extends bool) {
266 opts := strings.SplitN(tag, ",", 5)
267267 rawName = opts[0]
268 if len(opts) > 1 {
269 omitEmpty = opts[1] == "omitempty"
270 }
271 if len(opts) > 2 {
272 allowShadow = opts[2] == "allowshadow"
273 }
274 if len(opts) > 3 {
275 allowNonUnique = opts[3] == "nonunique"
276 }
277 return rawName, omitEmpty, allowShadow, allowNonUnique
278 }
279
280 func (s *Section) mapToField(val reflect.Value, isStrict bool) error {
268 for _, opt := range opts[1:] {
269 omitEmpty = omitEmpty || (opt == "omitempty")
270 allowShadow = allowShadow || (opt == "allowshadow")
271 allowNonUnique = allowNonUnique || (opt == "nonunique")
272 extends = extends || (opt == "extends")
273 }
274 return rawName, omitEmpty, allowShadow, allowNonUnique, extends
275 }
276
277 // mapToField maps the given value to the matching field of the given section.
278 // The sectionIndex is the index (if non unique sections are enabled) to which the value should be added.
279 func (s *Section) mapToField(val reflect.Value, isStrict bool, sectionIndex int, sectionName string) error {
281280 if val.Kind() == reflect.Ptr {
282281 val = val.Elem()
283282 }
292291 continue
293292 }
294293
295 rawName, _, allowShadow, allowNonUnique := parseTagOptions(tag)
294 rawName, _, allowShadow, allowNonUnique, extends := parseTagOptions(tag)
296295 fieldName := s.parseFieldName(tpField.Name, rawName)
297296 if len(fieldName) == 0 || !field.CanSet() {
298297 continue
300299
301300 isStruct := tpField.Type.Kind() == reflect.Struct
302301 isStructPtr := tpField.Type.Kind() == reflect.Ptr && tpField.Type.Elem().Kind() == reflect.Struct
303 isAnonymous := tpField.Type.Kind() == reflect.Ptr && tpField.Anonymous
304 if isAnonymous {
302 isAnonymousPtr := tpField.Type.Kind() == reflect.Ptr && tpField.Anonymous
303 if isAnonymousPtr {
305304 field.Set(reflect.New(tpField.Type.Elem()))
306305 }
307306
308 if isAnonymous || isStruct || isStructPtr {
309 if sec, err := s.f.GetSection(fieldName); err == nil {
307 if extends && (isAnonymousPtr || (isStruct && tpField.Anonymous)) {
308 if isStructPtr && field.IsNil() {
309 field.Set(reflect.New(tpField.Type.Elem()))
310 }
311 fieldSection := s
312 if rawName != "" {
313 sectionName = s.name + s.f.options.ChildSectionDelimiter + rawName
314 if secs, err := s.f.SectionsByName(sectionName); err == nil && sectionIndex < len(secs) {
315 fieldSection = secs[sectionIndex]
316 }
317 }
318 if err := fieldSection.mapToField(field, isStrict, sectionIndex, sectionName); err != nil {
319 return fmt.Errorf("map to field %q: %v", fieldName, err)
320 }
321 } else if isAnonymousPtr || isStruct || isStructPtr {
322 if secs, err := s.f.SectionsByName(fieldName); err == nil {
323 if len(secs) <= sectionIndex {
324 return fmt.Errorf("there are not enough sections (%d <= %d) for the field %q", len(secs), sectionIndex, fieldName)
325 }
310326 // Only set the field to non-nil struct value if we have a section for it.
311327 // Otherwise, we end up with a non-nil struct ptr even though there is no data.
312328 if isStructPtr && field.IsNil() {
313329 field.Set(reflect.New(tpField.Type.Elem()))
314330 }
315 if err = sec.mapToField(field, isStrict); err != nil {
331 if err = secs[sectionIndex].mapToField(field, isStrict, sectionIndex, fieldName); err != nil {
316332 return fmt.Errorf("map to field %q: %v", fieldName, err)
317333 }
318334 continue
349365 }
350366
351367 typ := val.Type().Elem()
352 for _, sec := range secs {
368 for i, sec := range secs {
353369 elem := reflect.New(typ)
354 if err = sec.mapToField(elem, isStrict); err != nil {
370 if err = sec.mapToField(elem, isStrict, i, sec.name); err != nil {
355371 return reflect.Value{}, fmt.Errorf("map to field from section %q: %v", secName, err)
356372 }
357373
381397 return nil
382398 }
383399
384 return s.mapToField(val, isStrict)
400 return s.mapToField(val, isStrict, 0, s.name)
385401 }
386402
387403 // MapTo maps section to given struct.
473489 _ = keyWithShadows.AddShadow(val)
474490 }
475491 }
476 key = keyWithShadows
492 *key = *keyWithShadows
477493 return nil
478494 }
479495
563579 typ := val.Type()
564580
565581 for i := 0; i < typ.NumField(); i++ {
582 if !val.Field(i).CanInterface() {
583 continue
584 }
585
566586 field := val.Field(i)
567587 tpField := typ.Field(i)
568588
571591 continue
572592 }
573593
574 rawName, omitEmpty, allowShadow, allowNonUnique := parseTagOptions(tag)
594 rawName, omitEmpty, allowShadow, allowNonUnique, extends := parseTagOptions(tag)
575595 if omitEmpty && isEmptyValue(field) {
576596 continue
577597 }
585605 continue
586606 }
587607
588 if (tpField.Type.Kind() == reflect.Ptr && tpField.Anonymous) ||
608 if extends && tpField.Anonymous && (tpField.Type.Kind() == reflect.Ptr || tpField.Type.Kind() == reflect.Struct) {
609 if err := s.reflectFrom(field); err != nil {
610 return fmt.Errorf("reflect from field %q: %v", fieldName, err)
611 }
612 continue
613 }
614
615 if (tpField.Type.Kind() == reflect.Ptr && tpField.Type.Elem().Kind() == reflect.Struct) ||
589616 (tpField.Type.Kind() == reflect.Struct && tpField.Type.Name() != "Time") {
590617 // Note: The only error here is section doesn't exist.
591618 sec, err := s.f.GetSection(fieldName)
694721 }
695722
696723 if typ.Kind() == reflect.Ptr {
697 typ = typ.Elem()
698724 val = val.Elem()
699725 } else {
700726 return errors.New("not a pointer to a struct")
1111 // License for the specific language governing permissions and limitations
1212 // under the License.
1313
14 package ini_test
14 package ini
1515
1616 import (
1717 "bytes"
2020 "testing"
2121 "time"
2222
23 . "github.com/smartystreets/goconvey/convey"
24
25 "gopkg.in/ini.v1"
23 "github.com/stretchr/testify/assert"
24 "github.com/stretchr/testify/require"
2625 )
2726
2827 type testNested struct {
5756 Unused int `ini:"-"`
5857 Unsigned uint
5958 Omitted bool `ini:"omitthis,omitempty"`
60 Shadows []string `ini:",,allowshadow"`
61 ShadowInts []int `ini:"Shadows,,allowshadow"`
59 Shadows []string `ini:",allowshadow"`
60 ShadowInts []int `ini:"Shadows,allowshadow"`
6261 BoolPtr *bool
6362 BoolPtrNil *bool
6463 FloatPtr *float64
8988
9089 type testNonUniqueSectionsStruct struct {
9190 Interface testInterface
92 Peer []testPeer `ini:",,,nonunique"`
91 Peer []testPeer `ini:",nonunique"`
92 }
93
94 type BaseStruct struct {
95 Base bool
96 }
97
98 type testExtend struct {
99 BaseStruct `ini:",extends"`
100 Extend bool
93101 }
94102
95103 const confDataStruct = `
140148 [foo.bar]
141149 Here = there
142150 When = then
151
152 [extended]
153 Base = true
154 Extend = true
143155 `
144156
145157 const confNonUniqueSectionDataStruct = `[Interface]
201213 `
202214
203215 func Test_MapToStruct(t *testing.T) {
204 Convey("Map to struct", t, func() {
205 Convey("Map file to struct", func() {
216 t.Run("map to struct", func(t *testing.T) {
217 t.Run("map file to struct", func(t *testing.T) {
206218 ts := new(testStruct)
207 So(ini.MapTo(ts, []byte(confDataStruct)), ShouldBeNil)
208
209 So(ts.Name, ShouldEqual, "Unknwon")
210 So(ts.Age, ShouldEqual, 21)
211 So(ts.Male, ShouldBeTrue)
212 So(ts.Money, ShouldEqual, 1.25)
213 So(ts.Unsigned, ShouldEqual, 3)
214
215 t, err := time.Parse(time.RFC3339, "1993-10-07T20:17:05Z")
216 So(err, ShouldBeNil)
217 So(ts.Born.String(), ShouldEqual, t.String())
219 assert.NoError(t, MapTo(ts, []byte(confDataStruct)))
220
221 assert.Equal(t, "Unknwon", ts.Name)
222 assert.Equal(t, 21, ts.Age)
223 assert.True(t, ts.Male)
224 assert.Equal(t, 1.25, ts.Money)
225 assert.Equal(t, uint(3), ts.Unsigned)
226
227 ti, err := time.Parse(time.RFC3339, "1993-10-07T20:17:05Z")
228 require.NoError(t, err)
229 assert.Equal(t, ti.String(), ts.Born.String())
218230
219231 dur, err := time.ParseDuration("2h45m")
220 So(err, ShouldBeNil)
221 So(ts.Time.Seconds(), ShouldEqual, dur.Seconds())
222
223 So(ts.OldVersionTime*time.Second, ShouldEqual, 30*time.Second)
224
225 So(strings.Join(ts.Others.Cities, ","), ShouldEqual, "HangZhou,Boston")
226 So(ts.Others.Visits[0].String(), ShouldEqual, t.String())
227 So(fmt.Sprint(ts.Others.Years), ShouldEqual, "[1993 1994]")
228 So(fmt.Sprint(ts.Others.Numbers), ShouldEqual, "[10010 10086]")
229 So(fmt.Sprint(ts.Others.Ages), ShouldEqual, "[18 19]")
230 So(fmt.Sprint(ts.Others.Populations), ShouldEqual, "[12345678 98765432]")
231 So(fmt.Sprint(ts.Others.Coordinates), ShouldEqual, "[192.168 10.11]")
232 So(fmt.Sprint(ts.Others.Flags), ShouldEqual, "[true false]")
233 So(ts.Others.Note, ShouldEqual, "Hello world!")
234 So(ts.TestEmbeded.GPA, ShouldEqual, 2.8)
235
236 So(strings.Join(ts.OthersPtr.Cities, ","), ShouldEqual, "HangZhou,Boston")
237 So(ts.OthersPtr.Visits[0].String(), ShouldEqual, t.String())
238 So(fmt.Sprint(ts.OthersPtr.Years), ShouldEqual, "[1993 1994]")
239 So(fmt.Sprint(ts.OthersPtr.Numbers), ShouldEqual, "[10010 10086]")
240 So(fmt.Sprint(ts.OthersPtr.Ages), ShouldEqual, "[18 19]")
241 So(fmt.Sprint(ts.OthersPtr.Populations), ShouldEqual, "[12345678 98765432]")
242 So(fmt.Sprint(ts.OthersPtr.Coordinates), ShouldEqual, "[192.168 10.11]")
243 So(fmt.Sprint(ts.OthersPtr.Flags), ShouldEqual, "[true false]")
244 So(ts.OthersPtr.Note, ShouldEqual, "Hello world!")
245
246 So(ts.NilPtr, ShouldBeNil)
247
248 So(*ts.BoolPtr, ShouldEqual, false)
249 So(ts.BoolPtrNil, ShouldEqual, nil)
250 So(*ts.FloatPtr, ShouldEqual, 0)
251 So(ts.FloatPtrNil, ShouldEqual, nil)
252 So(*ts.IntPtr, ShouldEqual, 0)
253 So(ts.IntPtrNil, ShouldEqual, nil)
254 So(*ts.UintPtr, ShouldEqual, 0)
255 So(ts.UintPtrNil, ShouldEqual, nil)
256 So(*ts.StringPtr, ShouldEqual, "")
257 So(ts.StringPtrNil, ShouldEqual, nil)
258 So(*ts.TimePtr, ShouldNotEqual, nil)
259 So(ts.TimePtrNil, ShouldEqual, nil)
260 So(*ts.DurationPtr, ShouldEqual, 0)
261 So(ts.DurationPtrNil, ShouldEqual, nil)
262
263 })
264
265 Convey("Map section to struct", func() {
232 require.NoError(t, err)
233 assert.Equal(t, dur.Seconds(), ts.Time.Seconds())
234
235 assert.Equal(t, 30*time.Second, ts.OldVersionTime*time.Second)
236
237 assert.Equal(t, "HangZhou,Boston", strings.Join(ts.Others.Cities, ","))
238 assert.Equal(t, ti.String(), ts.Others.Visits[0].String())
239 assert.Equal(t, "[1993 1994]", fmt.Sprint(ts.Others.Years))
240 assert.Equal(t, "[10010 10086]", fmt.Sprint(ts.Others.Numbers))
241 assert.Equal(t, "[18 19]", fmt.Sprint(ts.Others.Ages))
242 assert.Equal(t, "[12345678 98765432]", fmt.Sprint(ts.Others.Populations))
243 assert.Equal(t, "[192.168 10.11]", fmt.Sprint(ts.Others.Coordinates))
244 assert.Equal(t, "[true false]", fmt.Sprint(ts.Others.Flags))
245 assert.Equal(t, "Hello world!", ts.Others.Note)
246 assert.Equal(t, 2.8, ts.TestEmbeded.GPA)
247
248 assert.Equal(t, "HangZhou,Boston", strings.Join(ts.OthersPtr.Cities, ","))
249 assert.Equal(t, ti.String(), ts.OthersPtr.Visits[0].String())
250 assert.Equal(t, "[1993 1994]", fmt.Sprint(ts.OthersPtr.Years))
251 assert.Equal(t, "[10010 10086]", fmt.Sprint(ts.OthersPtr.Numbers))
252 assert.Equal(t, "[18 19]", fmt.Sprint(ts.OthersPtr.Ages))
253 assert.Equal(t, "[12345678 98765432]", fmt.Sprint(ts.OthersPtr.Populations))
254 assert.Equal(t, "[192.168 10.11]", fmt.Sprint(ts.OthersPtr.Coordinates))
255 assert.Equal(t, "[true false]", fmt.Sprint(ts.OthersPtr.Flags))
256 assert.Equal(t, "Hello world!", ts.OthersPtr.Note)
257
258 assert.Nil(t, ts.NilPtr)
259
260 assert.Equal(t, false, *ts.BoolPtr)
261 assert.Nil(t, ts.BoolPtrNil)
262 assert.Equal(t, float64(0), *ts.FloatPtr)
263 assert.Nil(t, ts.FloatPtrNil)
264 assert.Equal(t, 0, *ts.IntPtr)
265 assert.Nil(t, ts.IntPtrNil)
266 assert.Equal(t, uint(0), *ts.UintPtr)
267 assert.Nil(t, ts.UintPtrNil)
268 assert.Equal(t, "", *ts.StringPtr)
269 assert.Nil(t, ts.StringPtrNil)
270 assert.NotNil(t, *ts.TimePtr)
271 assert.Nil(t, ts.TimePtrNil)
272 assert.Equal(t, time.Duration(0), *ts.DurationPtr)
273 assert.Nil(t, ts.DurationPtrNil)
274 })
275
276 t.Run("map section to struct", func(t *testing.T) {
266277 foobar := new(fooBar)
267 f, err := ini.Load([]byte(confDataStruct))
268 So(err, ShouldBeNil)
269
270 So(f.Section("foo.bar").MapTo(foobar), ShouldBeNil)
271 So(foobar.Here, ShouldEqual, "there")
272 So(foobar.When, ShouldEqual, "then")
273 })
274
275 Convey("Map to non-pointer struct", func() {
276 f, err := ini.Load([]byte(confDataStruct))
277 So(err, ShouldBeNil)
278 So(f, ShouldNotBeNil)
279
280 So(f.MapTo(testStruct{}), ShouldNotBeNil)
281 })
282
283 Convey("Map to unsupported type", func() {
284 f, err := ini.Load([]byte(confDataStruct))
285 So(err, ShouldBeNil)
286 So(f, ShouldNotBeNil)
278 f, err := Load([]byte(confDataStruct))
279 require.NoError(t, err)
280
281 assert.NoError(t, f.Section("foo.bar").MapTo(foobar))
282 assert.Equal(t, "there", foobar.Here)
283 assert.Equal(t, "then", foobar.When)
284 })
285
286 t.Run("map to non-pointer struct", func(t *testing.T) {
287 f, err := Load([]byte(confDataStruct))
288 require.NoError(t, err)
289 require.NotNil(t, f)
290
291 assert.Error(t, f.MapTo(testStruct{}))
292 })
293
294 t.Run("map to unsupported type", func(t *testing.T) {
295 f, err := Load([]byte(confDataStruct))
296 require.NoError(t, err)
297 require.NotNil(t, f)
287298
288299 f.NameMapper = func(raw string) string {
289300 if raw == "Byte" {
291302 }
292303 return raw
293304 }
294 So(f.MapTo(&unsupport{}), ShouldNotBeNil)
295 So(f.MapTo(&unsupport2{}), ShouldNotBeNil)
296 So(f.MapTo(&unsupport4{}), ShouldNotBeNil)
297 })
298
299 Convey("Map to omitempty field", func() {
305 assert.Error(t, f.MapTo(&unsupport{}))
306 assert.Error(t, f.MapTo(&unsupport2{}))
307 assert.Error(t, f.MapTo(&unsupport4{}))
308 })
309
310 t.Run("map to omitempty field", func(t *testing.T) {
300311 ts := new(testStruct)
301 So(ini.MapTo(ts, []byte(confDataStruct)), ShouldBeNil)
302
303 So(ts.Omitted, ShouldEqual, true)
304 })
305
306 Convey("Map with shadows", func() {
307 f, err := ini.LoadSources(ini.LoadOptions{AllowShadows: true}, []byte(confDataStruct))
308 So(err, ShouldBeNil)
312 assert.NoError(t, MapTo(ts, []byte(confDataStruct)))
313
314 assert.Equal(t, true, ts.Omitted)
315 })
316
317 t.Run("map with shadows", func(t *testing.T) {
318 f, err := LoadSources(LoadOptions{AllowShadows: true}, []byte(confDataStruct))
319 require.NoError(t, err)
309320 ts := new(testStruct)
310 So(f.MapTo(ts), ShouldBeNil)
311
312 So(strings.Join(ts.Shadows, " "), ShouldEqual, "1 2 3 4")
313 So(fmt.Sprintf("%v", ts.ShadowInts), ShouldEqual, "[1 2 3 4]")
314 })
315
316 Convey("Map from invalid data source", func() {
317 So(ini.MapTo(&testStruct{}, "hi"), ShouldNotBeNil)
318 })
319
320 Convey("Map to wrong types and gain default values", func() {
321 f, err := ini.Load([]byte(invalidDataConfStruct))
322 So(err, ShouldBeNil)
323
324 t, err := time.Parse(time.RFC3339, "1993-10-07T20:17:05Z")
325 So(err, ShouldBeNil)
326 dv := &defaultValue{"Joe", 10, true, nil, 1.25, t, []string{"HangZhou", "Boston"}}
327 So(f.MapTo(dv), ShouldBeNil)
328 So(dv.Name, ShouldEqual, "Joe")
329 So(dv.Age, ShouldEqual, 10)
330 So(dv.Male, ShouldBeTrue)
331 So(dv.Money, ShouldEqual, 1.25)
332 So(dv.Born.String(), ShouldEqual, t.String())
333 So(strings.Join(dv.Cities, ","), ShouldEqual, "HangZhou,Boston")
321 assert.NoError(t, f.MapTo(ts))
322
323 assert.Equal(t, "1 2 3 4", strings.Join(ts.Shadows, " "))
324 assert.Equal(t, "[1 2 3 4]", fmt.Sprintf("%v", ts.ShadowInts))
325 })
326
327 t.Run("map from invalid data source", func(t *testing.T) {
328 assert.Error(t, MapTo(&testStruct{}, "hi"))
329 })
330
331 t.Run("map to wrong types and gain default values", func(t *testing.T) {
332 f, err := Load([]byte(invalidDataConfStruct))
333 require.NoError(t, err)
334
335 ti, err := time.Parse(time.RFC3339, "1993-10-07T20:17:05Z")
336 require.NoError(t, err)
337 dv := &defaultValue{"Joe", 10, true, nil, 1.25, ti, []string{"HangZhou", "Boston"}}
338 assert.NoError(t, f.MapTo(dv))
339 assert.Equal(t, "Joe", dv.Name)
340 assert.Equal(t, 10, dv.Age)
341 assert.True(t, dv.Male)
342 assert.Equal(t, 1.25, dv.Money)
343 assert.Equal(t, ti.String(), dv.Born.String())
344 assert.Equal(t, "HangZhou,Boston", strings.Join(dv.Cities, ","))
345 })
346
347 t.Run("map to extended base", func(t *testing.T) {
348 f, err := Load([]byte(confDataStruct))
349 require.NoError(t, err)
350 require.NotNil(t, f)
351 te := testExtend{}
352 assert.NoError(t, f.Section("extended").MapTo(&te))
353 assert.True(t, te.Base)
354 assert.True(t, te.Extend)
334355 })
335356 })
336357
337 Convey("Map to struct in strict mode", t, func() {
338 f, err := ini.Load([]byte(`
358 t.Run("map to struct in strict mode", func(t *testing.T) {
359 f, err := Load([]byte(`
339360 name=bruce
340361 age=a30`))
341 So(err, ShouldBeNil)
362 require.NoError(t, err)
342363
343364 type Strict struct {
344365 Name string `ini:"name"`
346367 }
347368 s := new(Strict)
348369
349 So(f.Section("").StrictMapTo(s), ShouldNotBeNil)
370 assert.Error(t, f.Section("").StrictMapTo(s))
350371 })
351372
352 Convey("Map slice in strict mode", t, func() {
353 f, err := ini.Load([]byte(`
373 t.Run("map slice in strict mode", func(t *testing.T) {
374 f, err := Load([]byte(`
354375 names=alice, bruce`))
355 So(err, ShouldBeNil)
376 require.NoError(t, err)
356377
357378 type Strict struct {
358379 Names []string `ini:"names"`
359380 }
360381 s := new(Strict)
361382
362 So(f.Section("").StrictMapTo(s), ShouldBeNil)
363 So(fmt.Sprint(s.Names), ShouldEqual, "[alice bruce]")
383 assert.NoError(t, f.Section("").StrictMapTo(s))
384 assert.Equal(t, "[alice bruce]", fmt.Sprint(s.Names))
364385 })
365386 }
366387
367388 func Test_MapToStructNonUniqueSections(t *testing.T) {
368 Convey("Map to struct non unique", t, func() {
369 Convey("Map file to struct non unique", func() {
370 f, err := ini.LoadSources(ini.LoadOptions{AllowNonUniqueSections: true}, []byte(confNonUniqueSectionDataStruct))
371 So(err, ShouldBeNil)
389 t.Run("map to struct non unique", func(t *testing.T) {
390 t.Run("map file to struct non unique", func(t *testing.T) {
391 f, err := LoadSources(LoadOptions{AllowNonUniqueSections: true}, []byte(confNonUniqueSectionDataStruct))
392 require.NoError(t, err)
372393 ts := new(testNonUniqueSectionsStruct)
373394
374 So(f.MapTo(ts), ShouldBeNil)
375
376 So(ts.Interface.Address, ShouldEqual, "10.2.0.1/24")
377 So(ts.Interface.ListenPort, ShouldEqual, 34777)
378 So(ts.Interface.PrivateKey, ShouldEqual, "privServerKey")
379
380 So(ts.Peer[0].PublicKey, ShouldEqual, "pubClientKey")
381 So(ts.Peer[0].PresharedKey, ShouldEqual, "psKey")
382 So(ts.Peer[0].AllowedIPs[0], ShouldEqual, "10.2.0.2/32")
383 So(ts.Peer[0].AllowedIPs[1], ShouldEqual, "fd00:2::2/128")
384
385 So(ts.Peer[1].PublicKey, ShouldEqual, "pubClientKey2")
386 So(ts.Peer[1].PresharedKey, ShouldEqual, "psKey2")
387 So(ts.Peer[1].AllowedIPs[0], ShouldEqual, "10.2.0.3/32")
388 So(ts.Peer[1].AllowedIPs[1], ShouldEqual, "fd00:2::3/128")
389 })
390
391 Convey("Map non unique section to struct", func() {
395 assert.NoError(t, f.MapTo(ts))
396
397 assert.Equal(t, "10.2.0.1/24", ts.Interface.Address)
398 assert.Equal(t, 34777, ts.Interface.ListenPort)
399 assert.Equal(t, "privServerKey", ts.Interface.PrivateKey)
400
401 assert.Equal(t, "pubClientKey", ts.Peer[0].PublicKey)
402 assert.Equal(t, "psKey", ts.Peer[0].PresharedKey)
403 assert.Equal(t, "10.2.0.2/32", ts.Peer[0].AllowedIPs[0])
404 assert.Equal(t, "fd00:2::2/128", ts.Peer[0].AllowedIPs[1])
405
406 assert.Equal(t, "pubClientKey2", ts.Peer[1].PublicKey)
407 assert.Equal(t, "psKey2", ts.Peer[1].PresharedKey)
408 assert.Equal(t, "10.2.0.3/32", ts.Peer[1].AllowedIPs[0])
409 assert.Equal(t, "fd00:2::3/128", ts.Peer[1].AllowedIPs[1])
410 })
411
412 t.Run("map non unique section to struct", func(t *testing.T) {
392413 newPeer := new(testPeer)
393414 newPeerSlice := make([]testPeer, 0)
394415
395 f, err := ini.LoadSources(ini.LoadOptions{AllowNonUniqueSections: true}, []byte(confNonUniqueSectionDataStruct))
396 So(err, ShouldBeNil)
416 f, err := LoadSources(LoadOptions{AllowNonUniqueSections: true}, []byte(confNonUniqueSectionDataStruct))
417 require.NoError(t, err)
397418
398419 // try only first one
399 So(f.Section("Peer").MapTo(newPeer), ShouldBeNil)
400 So(newPeer.PublicKey, ShouldEqual, "pubClientKey")
401 So(newPeer.PresharedKey, ShouldEqual, "psKey")
402 So(newPeer.AllowedIPs[0], ShouldEqual, "10.2.0.2/32")
403 So(newPeer.AllowedIPs[1], ShouldEqual, "fd00:2::2/128")
420 assert.NoError(t, f.Section("Peer").MapTo(newPeer))
421 assert.Equal(t, "pubClientKey", newPeer.PublicKey)
422 assert.Equal(t, "psKey", newPeer.PresharedKey)
423 assert.Equal(t, "10.2.0.2/32", newPeer.AllowedIPs[0])
424 assert.Equal(t, "fd00:2::2/128", newPeer.AllowedIPs[1])
404425
405426 // try all
406 So(f.Section("Peer").MapTo(&newPeerSlice), ShouldBeNil)
407 So(newPeerSlice[0].PublicKey, ShouldEqual, "pubClientKey")
408 So(newPeerSlice[0].PresharedKey, ShouldEqual, "psKey")
409 So(newPeerSlice[0].AllowedIPs[0], ShouldEqual, "10.2.0.2/32")
410 So(newPeerSlice[0].AllowedIPs[1], ShouldEqual, "fd00:2::2/128")
411
412 So(newPeerSlice[1].PublicKey, ShouldEqual, "pubClientKey2")
413 So(newPeerSlice[1].PresharedKey, ShouldEqual, "psKey2")
414 So(newPeerSlice[1].AllowedIPs[0], ShouldEqual, "10.2.0.3/32")
415 So(newPeerSlice[1].AllowedIPs[1], ShouldEqual, "fd00:2::3/128")
427 assert.NoError(t, f.Section("Peer").MapTo(&newPeerSlice))
428 assert.Equal(t, "pubClientKey", newPeerSlice[0].PublicKey)
429 assert.Equal(t, "psKey", newPeerSlice[0].PresharedKey)
430 assert.Equal(t, "10.2.0.2/32", newPeerSlice[0].AllowedIPs[0])
431 assert.Equal(t, "fd00:2::2/128", newPeerSlice[0].AllowedIPs[1])
432
433 assert.Equal(t, "pubClientKey2", newPeerSlice[1].PublicKey)
434 assert.Equal(t, "psKey2", newPeerSlice[1].PresharedKey)
435 assert.Equal(t, "10.2.0.3/32", newPeerSlice[1].AllowedIPs[0])
436 assert.Equal(t, "fd00:2::3/128", newPeerSlice[1].AllowedIPs[1])
437 })
438
439 t.Run("map non unique sections with subsections to struct", func(t *testing.T) {
440 iniFile, err := LoadSources(LoadOptions{AllowNonUniqueSections: true}, strings.NewReader(`
441 [Section]
442 FieldInSubSection = 1
443 FieldInSubSection2 = 2
444 FieldInSection = 3
445
446 [Section]
447 FieldInSubSection = 4
448 FieldInSubSection2 = 5
449 FieldInSection = 6
450 `))
451 require.NoError(t, err)
452
453 type SubSection struct {
454 FieldInSubSection string `ini:"FieldInSubSection"`
455 }
456 type SubSection2 struct {
457 FieldInSubSection2 string `ini:"FieldInSubSection2"`
458 }
459
460 type Section struct {
461 SubSection `ini:"Section"`
462 SubSection2 `ini:"Section"`
463 FieldInSection string `ini:"FieldInSection"`
464 }
465
466 type File struct {
467 Sections []Section `ini:"Section,nonunique"`
468 }
469
470 f := new(File)
471 err = iniFile.MapTo(f)
472 require.NoError(t, err)
473
474 assert.Equal(t, "1", f.Sections[0].FieldInSubSection)
475 assert.Equal(t, "2", f.Sections[0].FieldInSubSection2)
476 assert.Equal(t, "3", f.Sections[0].FieldInSection)
477
478 assert.Equal(t, "4", f.Sections[1].FieldInSubSection)
479 assert.Equal(t, "5", f.Sections[1].FieldInSubSection2)
480 assert.Equal(t, "6", f.Sections[1].FieldInSection)
416481 })
417482 })
418483 }
419484
420485 func Test_ReflectFromStruct(t *testing.T) {
421 Convey("Reflect from struct", t, func() {
486 t.Run("reflect from struct", func(t *testing.T) {
422487 type Embeded struct {
423488 Dates []time.Time `delim:"|" comment:"Time data"`
424489 Places []string
439504 GPA float64
440505 Date time.Time
441506 NeverMind string `ini:"-"`
507 ignored string
442508 *Embeded `ini:"infos" comment:"Embeded section"`
443509 }
444510
445 t, err := time.Parse(time.RFC3339, "1993-10-07T20:17:05Z")
446 So(err, ShouldBeNil)
447 a := &Author{"Unknwon", true, nil, 21, 100, 2.8, t, "",
511 ti, err := time.Parse(time.RFC3339, "1993-10-07T20:17:05Z")
512 require.NoError(t, err)
513 a := &Author{"Unknwon", true, nil, 21, 100, 2.8, ti, "", "ignored",
448514 &Embeded{
449 []time.Time{t, t},
515 []time.Time{ti, ti},
450516 []string{"HangZhou", "Boston"},
451517 []int{1993, 1994},
452518 []int64{10010, 10086},
456522 []bool{true, false},
457523 []int{},
458524 }}
459 cfg := ini.Empty()
460 So(ini.ReflectFrom(cfg, a), ShouldBeNil)
525 cfg := Empty()
526 assert.NoError(t, ReflectFrom(cfg, a))
461527
462528 var buf bytes.Buffer
463529 _, err = cfg.WriteTo(&buf)
464 So(err, ShouldBeNil)
465 So(buf.String(), ShouldEqual, `NAME = Unknwon
530 require.NoError(t, err)
531 assert.Equal(t, `NAME = Unknwon
466532 Male = true
467533 Optional =
468534 ; Author's age
484550 Flags = true,false
485551 None =
486552
487 `)
488
489 Convey("Reflect from non-point struct", func() {
490 So(ini.ReflectFrom(cfg, Author{}), ShouldNotBeNil)
491 })
492
493 Convey("Reflect from struct with omitempty", func() {
494 cfg := ini.Empty()
553 `,
554 buf.String(),
555 )
556
557 t.Run("reflect from non-point struct", func(t *testing.T) {
558 assert.Error(t, ReflectFrom(cfg, Author{}))
559 })
560
561 t.Run("reflect from struct with omitempty", func(t *testing.T) {
562 cfg := Empty()
495563 type SpecialStruct struct {
496564 FirstName string `ini:"first_name"`
497 LastName string `ini:"last_name"`
565 LastName string `ini:"last_name,omitempty"`
498566 JustOmitMe string `ini:"omitempty"`
499567 LastLogin time.Time `ini:"last_login,omitempty"`
500568 LastLogin2 time.Time `ini:",omitempty"`
501569 NotEmpty int `ini:"omitempty"`
502 }
503
504 So(ini.ReflectFrom(cfg, &SpecialStruct{FirstName: "John", LastName: "Doe", NotEmpty: 9}), ShouldBeNil)
570 Number int64 `ini:",omitempty"`
571 Ages uint `ini:",omitempty"`
572 Population uint64 `ini:",omitempty"`
573 Coordinate float64 `ini:",omitempty"`
574 Flag bool `ini:",omitempty"`
575 Note *string `ini:",omitempty"`
576 }
577 special := &SpecialStruct{
578 FirstName: "John",
579 LastName: "Doe",
580 NotEmpty: 9,
581 }
582
583 assert.NoError(t, ReflectFrom(cfg, special))
505584
506585 var buf bytes.Buffer
507586 _, err = cfg.WriteTo(&buf)
508 So(buf.String(), ShouldEqual, `first_name = John
587 assert.Equal(t, `first_name = John
509588 last_name = Doe
510589 omitempty = 9
511590
512 `)
591 `,
592 buf.String(),
593 )
594 })
595
596 t.Run("reflect from struct with non-anonymous structure pointer", func(t *testing.T) {
597 cfg := Empty()
598 type Rpc struct {
599 Enable bool `ini:"enable"`
600 Type string `ini:"type"`
601 Address string `ini:"addr"`
602 Name string `ini:"name"`
603 }
604 type Cfg struct {
605 Rpc *Rpc `ini:"rpc"`
606 }
607
608 config := &Cfg{
609 Rpc: &Rpc{
610 Enable: true,
611 Type: "type",
612 Address: "address",
613 Name: "name",
614 },
615 }
616 assert.NoError(t, cfg.ReflectFrom(config))
617
618 var buf bytes.Buffer
619 _, err = cfg.WriteTo(&buf)
620 assert.Equal(t, `[rpc]
621 enable = true
622 type = type
623 addr = address
624 name = name
625
626 `,
627 buf.String(),
628 )
513629 })
514630 })
515631 }
516632
517633 func Test_ReflectFromStructNonUniqueSections(t *testing.T) {
518 Convey("Reflect from struct with non unique sections", t, func() {
634 t.Run("reflect from struct with non unique sections", func(t *testing.T) {
519635 nonUnique := &testNonUniqueSectionsStruct{
520636 Interface: testInterface{
521637 Address: "10.2.0.1/24",
536652 },
537653 }
538654
539 cfg := ini.Empty(ini.LoadOptions{
655 cfg := Empty(LoadOptions{
540656 AllowNonUniqueSections: true,
541657 })
542658
543 So(ini.ReflectFrom(cfg, nonUnique), ShouldBeNil)
659 assert.NoError(t, ReflectFrom(cfg, nonUnique))
544660
545661 var buf bytes.Buffer
546662 _, err := cfg.WriteTo(&buf)
547 So(err, ShouldBeNil)
548 So(buf.String(), ShouldEqual, confNonUniqueSectionDataStruct)
663 require.NoError(t, err)
664 assert.Equal(t, confNonUniqueSectionDataStruct, buf.String())
549665
550666 // note: using ReflectFrom from should overwrite the existing sections
551667 err = cfg.Section("Peer").ReflectFrom([]*testPeer{
561677 },
562678 })
563679
564 So(err, ShouldBeNil)
680 require.NoError(t, err)
565681
566682 buf = bytes.Buffer{}
567683 _, err = cfg.WriteTo(&buf)
568 So(err, ShouldBeNil)
569 So(buf.String(), ShouldEqual, `[Interface]
684 require.NoError(t, err)
685 assert.Equal(t, `[Interface]
570686 Address = 10.2.0.1/24
571687 ListenPort = 34777
572688 PrivateKey = privServerKey
581697 PresharedKey = psKey4
582698 AllowedIPs = 10.2.0.5/32,fd00:2::5/128
583699
584 `)
700 `,
701 buf.String(),
702 )
585703
586704 // note: using ReflectFrom from should overwrite the existing sections
587705 err = cfg.Section("Peer").ReflectFrom(&testPeer{
590708 AllowedIPs: []string{"10.2.0.6/32,fd00:2::6/128"},
591709 })
592710
593 So(err, ShouldBeNil)
711 require.NoError(t, err)
594712
595713 buf = bytes.Buffer{}
596714 _, err = cfg.WriteTo(&buf)
597 So(err, ShouldBeNil)
598 So(buf.String(), ShouldEqual, `[Interface]
715 require.NoError(t, err)
716 assert.Equal(t, `[Interface]
599717 Address = 10.2.0.1/24
600718 ListenPort = 34777
601719 PrivateKey = privServerKey
605723 PresharedKey = psKey5
606724 AllowedIPs = 10.2.0.6/32,fd00:2::6/128
607725
608 `)
726 `,
727 buf.String(),
728 )
609729 })
610730 }
611731
612732 // Inspired by https://github.com/go-ini/ini/issues/196
613733 func TestMapToAndReflectFromStructWithShadows(t *testing.T) {
614 Convey("Map to struct and then reflect with shadows should generate original config content", t, func() {
734 t.Run("map to struct and then reflect with shadows should generate original config content", func(t *testing.T) {
615735 type include struct {
616736 Paths []string `ini:"path,omitempty,allowshadow"`
617737 }
618738
619 cfg, err := ini.LoadSources(ini.LoadOptions{
739 cfg, err := LoadSources(LoadOptions{
620740 AllowShadows: true,
621741 }, []byte(`
622742 [include]
623743 path = /tmp/gpm-profiles/test5.profile
624744 path = /tmp/gpm-profiles/test1.profile`))
625 So(err, ShouldBeNil)
745 require.NoError(t, err)
626746
627747 sec := cfg.Section("include")
628748 inc := new(include)
629749 err = sec.MapTo(inc)
630 So(err, ShouldBeNil)
750 require.NoError(t, err)
631751
632752 err = sec.ReflectFrom(inc)
633 So(err, ShouldBeNil)
753 require.NoError(t, err)
634754
635755 var buf bytes.Buffer
636756 _, err = cfg.WriteTo(&buf)
637 So(err, ShouldBeNil)
638 So(buf.String(), ShouldEqual, `[include]
757 require.NoError(t, err)
758 assert.Equal(t, `[include]
639759 path = /tmp/gpm-profiles/test5.profile
640760 path = /tmp/gpm-profiles/test1.profile
641761
642 `)
762 `,
763 buf.String(),
764 )
765
766 t.Run("reflect from struct with shadows", func(t *testing.T) {
767 cfg := Empty(LoadOptions{
768 AllowShadows: true,
769 })
770 type ShadowStruct struct {
771 StringArray []string `ini:"sa,allowshadow"`
772 EmptyStringArrat []string `ini:"empty,omitempty,allowshadow"`
773 Allowshadow []string `ini:"allowshadow,allowshadow"`
774 Dates []time.Time `ini:",allowshadow"`
775 Places []string `ini:",allowshadow"`
776 Years []int `ini:",allowshadow"`
777 Numbers []int64 `ini:",allowshadow"`
778 Ages []uint `ini:",allowshadow"`
779 Populations []uint64 `ini:",allowshadow"`
780 Coordinates []float64 `ini:",allowshadow"`
781 Flags []bool `ini:",allowshadow"`
782 None []int `ini:",allowshadow"`
783 }
784
785 shadow := &ShadowStruct{
786 StringArray: []string{"s1", "s2"},
787 Allowshadow: []string{"s3", "s4"},
788 Dates: []time.Time{time.Date(2020, 9, 12, 00, 00, 00, 651387237, time.UTC),
789 time.Date(2020, 9, 12, 00, 00, 00, 651387237, time.UTC)},
790 Places: []string{"HangZhou", "Boston"},
791 Years: []int{1993, 1994},
792 Numbers: []int64{10010, 10086},
793 Ages: []uint{18, 19},
794 Populations: []uint64{12345678, 98765432},
795 Coordinates: []float64{192.168, 10.11},
796 Flags: []bool{true, false},
797 None: []int{},
798 }
799
800 assert.NoError(t, ReflectFrom(cfg, shadow))
801
802 var buf bytes.Buffer
803 _, err := cfg.WriteTo(&buf)
804 require.NoError(t, err)
805 assert.Equal(t, `sa = s1
806 sa = s2
807 allowshadow = s3
808 allowshadow = s4
809 Dates = 2020-09-12T00:00:00Z
810 Places = HangZhou
811 Places = Boston
812 Years = 1993
813 Years = 1994
814 Numbers = 10010
815 Numbers = 10086
816 Ages = 18
817 Ages = 19
818 Populations = 12345678
819 Populations = 98765432
820 Coordinates = 192.168
821 Coordinates = 10.11
822 Flags = true
823 Flags = false
824 None =
825
826 `,
827 buf.String(),
828 )
829 })
643830 })
644831 }
645832
648835 }
649836
650837 func Test_NameGetter(t *testing.T) {
651 Convey("Test name mappers", t, func() {
652 So(ini.MapToWithMapper(&testMapper{}, ini.TitleUnderscore, []byte("packag_name=ini")), ShouldBeNil)
653
654 cfg, err := ini.Load([]byte("PACKAGE_NAME=ini"))
655 So(err, ShouldBeNil)
656 So(cfg, ShouldNotBeNil)
657
658 cfg.NameMapper = ini.SnackCase
838 t.Run("test name mappers", func(t *testing.T) {
839 assert.NoError(t, MapToWithMapper(&testMapper{}, TitleUnderscore, []byte("packag_name=ini")))
840
841 cfg, err := Load([]byte("PACKAGE_NAME=ini"))
842 require.NoError(t, err)
843 require.NotNil(t, cfg)
844
845 cfg.NameMapper = SnackCase
659846 tg := new(testMapper)
660 So(cfg.MapTo(tg), ShouldBeNil)
661 So(tg.PackageName, ShouldEqual, "ini")
847 assert.NoError(t, cfg.MapTo(tg))
848 assert.Equal(t, "ini", tg.PackageName)
662849 })
663850 }
664851
667854 }
668855
669856 func Test_Duration(t *testing.T) {
670 Convey("Duration less than 16m50s", t, func() {
857 t.Run("duration less than 16m50s", func(t *testing.T) {
671858 ds := new(testDurationStruct)
672 So(ini.MapTo(ds, []byte("Duration=16m49s")), ShouldBeNil)
859 assert.NoError(t, MapTo(ds, []byte("Duration=16m49s")))
673860
674861 dur, err := time.ParseDuration("16m49s")
675 So(err, ShouldBeNil)
676 So(ds.Duration.Seconds(), ShouldEqual, dur.Seconds())
862 require.NoError(t, err)
863 assert.Equal(t, dur.Seconds(), ds.Duration.Seconds())
677864 })
678865 }
679866
684871
685872 type Employers []*Employer
686873
687 func (es Employers) ReflectINIStruct(f *ini.File) error {
874 func (es Employers) ReflectINIStruct(f *File) error {
688875 for _, e := range es {
689876 f.Section(e.Name).Key("Title").SetValue(e.Title)
690877 }
693880
694881 // Inspired by https://github.com/go-ini/ini/issues/199
695882 func Test_StructReflector(t *testing.T) {
696 Convey("Reflect with StructReflector interface", t, func() {
883 t.Run("reflect with StructReflector interface", func(t *testing.T) {
697884 p := &struct {
698885 FirstName string
699886 Employer Employers
711898 },
712899 }
713900
714 f := ini.Empty()
715 So(f.ReflectFrom(p), ShouldBeNil)
901 f := Empty()
902 assert.NoError(t, f.ReflectFrom(p))
716903
717904 var buf bytes.Buffer
718905 _, err := f.WriteTo(&buf)
719 So(err, ShouldBeNil)
720
721 So(buf.String(), ShouldEqual, `FirstName = Andrew
906 require.NoError(t, err)
907
908 assert.Equal(t, `FirstName = Andrew
722909
723910 [Employer "VMware"]
724911 Title = Staff II Engineer
726913 [Employer "EMC"]
727914 Title = Consultant Engineer
728915
729 `)
916 `,
917 buf.String(),
918 )
730919 })
731920 }
8383 ADDRESS = """404 road,
8484 NotFound, State, 50000"""
8585 two_lines = how about continuation lines?
86 lots_of_lines = 1 2 3 4
86 lots_of_lines = "1 2 3 4 "
8787
44
55
66 value2 = there is an empty line above
7 that is not indented so it should not be part
8 of the value
7 that is not indented so it should not be part
8 of the value
99
1010 value3 = .
1111
0 value1 = some text here
1 some more text here 2