New upstream release.
Debian Janitor
2 years ago
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 | --- | |
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 | --- | |
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 | |
1 | 1 | |
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 |
0 | 0 | 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 | ||
2 | 10 | jobs: |
3 | build: | |
11 | lsif-go: | |
12 | if: github.repository == 'go-ini/ini' | |
4 | 13 | runs-on: ubuntu-latest |
5 | 14 | steps: |
6 | - uses: actions/checkout@v1 | |
15 | - uses: actions/checkout@v2 | |
7 | 16 | - name: Generate LSIF data |
8 | 17 | 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 | |
9 | 21 | 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 | |
13 | 24 | continue-on-error: true |
25 | uses: docker://sourcegraph/src-cli:latest | |
14 | 26 | 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 }} |
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 | 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 |
0 | 0 | # INI |
1 | 1 | |
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) | |
3 | 6 | |
4 | 7 | ![](https://avatars0.githubusercontent.com/u/10216035?v=3&s=200) |
5 | 8 | |
20 | 23 | |
21 | 24 | ## Installation |
22 | 25 | |
23 | The minimum requirement of Go is **1.6**. | |
26 | The minimum requirement of Go is **1.12**. | |
24 | 27 | |
25 | 28 | ```sh |
26 | 29 | $ go get gopkg.in/ini.v1 |
32 | 35 | |
33 | 36 | - [Getting Started](https://ini.unknwon.io/docs/intro/getting_started) |
34 | 37 | - [API Documentation](https://gowalker.org/gopkg.in/ini.v1) |
38 | - 中国大陆镜像:https://ini.unknwon.cn | |
35 | 39 | |
36 | 40 | ## License |
37 | 41 |
11 | 11 | // License for the specific language governing permissions and limitations |
12 | 12 | // under the License. |
13 | 13 | |
14 | package ini_test | |
14 | package ini | |
15 | 15 | |
16 | 16 | import ( |
17 | 17 | "testing" |
18 | ||
19 | "gopkg.in/ini.v1" | |
20 | 18 | ) |
21 | 19 | |
22 | func newTestFile(block bool) *ini.File { | |
23 | c, _ := ini.Load([]byte(confData)) | |
20 | func newTestFile(block bool) *File { | |
21 | c, _ := Load([]byte(confData)) | |
24 | 22 | c.BlockMode = block |
25 | 23 | return c |
26 | 24 | } |
0 | coverage: | |
1 | range: "60...95" | |
2 | status: | |
3 | project: | |
4 | default: | |
5 | threshold: 1% | |
6 | ||
7 | comment: | |
8 | layout: 'diff' |
65 | 65 | return sourceFile{s}, nil |
66 | 66 | case []byte: |
67 | 67 | return &sourceData{s}, nil |
68 | case io.ReadCloser: | |
69 | return &sourceReadCloser{s}, nil | |
68 | 70 | case io.Reader: |
69 | 71 | return &sourceReadCloser{ioutil.NopCloser(s)}, nil |
70 | case io.ReadCloser: | |
71 | return &sourceReadCloser{s}, nil | |
72 | 72 | default: |
73 | 73 | return nil, fmt.Errorf("error parsing data source: unknown type %q", s) |
74 | 74 | } |
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 | ||
0 | 6 | golang-github-go-ini-ini (1.55.0-1) unstable; urgency=medium |
1 | 7 | |
2 | 8 | * Team Upload. |
54 | 54 | if len(opts.KeyValueDelimiterOnWrite) == 0 { |
55 | 55 | opts.KeyValueDelimiterOnWrite = "=" |
56 | 56 | } |
57 | if len(opts.ChildSectionDelimiter) == 0 { | |
58 | opts.ChildSectionDelimiter = "." | |
59 | } | |
57 | 60 | |
58 | 61 | return &File{ |
59 | 62 | BlockMode: true, |
81 | 84 | return nil, errors.New("empty section name") |
82 | 85 | } |
83 | 86 | |
84 | if f.options.Insensitive && name != DefaultSection { | |
87 | if (f.options.Insensitive || f.options.InsensitiveSections) && name != DefaultSection { | |
85 | 88 | name = strings.ToLower(name) |
86 | 89 | } |
87 | 90 | |
138 | 141 | return secs[0], err |
139 | 142 | } |
140 | 143 | |
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 | ||
141 | 150 | // SectionsByName returns all sections with given name. |
142 | 151 | func (f *File) SectionsByName(name string) ([]*Section, error) { |
143 | 152 | if len(name) == 0 { |
144 | 153 | name = DefaultSection |
145 | 154 | } |
146 | if f.options.Insensitive { | |
155 | if f.options.Insensitive || f.options.InsensitiveSections { | |
147 | 156 | name = strings.ToLower(name) |
148 | 157 | } |
149 | 158 | |
164 | 173 | func (f *File) Section(name string) *Section { |
165 | 174 | sec, err := f.GetSection(name) |
166 | 175 | 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 | } | |
169 | 179 | sec, _ = f.NewSection(name) |
170 | 180 | return sec |
171 | 181 | } |
235 | 245 | if len(name) == 0 { |
236 | 246 | name = DefaultSection |
237 | 247 | } |
238 | if f.options.Insensitive { | |
248 | if f.options.Insensitive || f.options.InsensitiveSections { | |
239 | 249 | name = strings.ToLower(name) |
240 | 250 | } |
241 | 251 | |
297 | 307 | continue |
298 | 308 | } |
299 | 309 | return err |
310 | } | |
311 | if f.options.ShortCircuit { | |
312 | return nil | |
300 | 313 | } |
301 | 314 | } |
302 | 315 | return nil |
346 | 359 | } |
347 | 360 | } |
348 | 361 | |
349 | if i > 0 || DefaultHeader { | |
362 | if i > 0 || DefaultHeader || (i == 0 && strings.ToUpper(sec.name) != DefaultSection) { | |
350 | 363 | if _, err := buf.WriteString("[" + sname + "]" + LineBreak); err != nil { |
351 | 364 | return nil, err |
352 | 365 | } |
428 | 441 | kname = `"""` + kname + `"""` |
429 | 442 | } |
430 | 443 | |
431 | for _, val := range key.ValueWithShadows() { | |
444 | writeKeyValue := func(val string) (bool, error) { | |
432 | 445 | if _, err := buf.WriteString(kname); err != nil { |
433 | return nil, err | |
446 | return false, err | |
434 | 447 | } |
435 | 448 | |
436 | 449 | if key.isBooleanType { |
437 | 450 | if kname != sec.keyList[len(sec.keyList)-1] { |
438 | 451 | buf.WriteString(LineBreak) |
439 | 452 | } |
440 | continue KeyList | |
453 | return true, nil | |
441 | 454 | } |
442 | 455 | |
443 | 456 | // Write out alignment spaces before "=" sign |
450 | 463 | val = `"""` + val + `"""` |
451 | 464 | } else if !f.options.IgnoreInlineComment && strings.ContainsAny(val, "#;") { |
452 | 465 | val = "`" + val + "`" |
466 | } else if len(strings.TrimSpace(val)) != len(val) { | |
467 | val = `"` + val + `"` | |
453 | 468 | } |
454 | 469 | 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 { | |
455 | 478 | 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 | |
456 | 488 | } |
457 | 489 | } |
458 | 490 | |
493 | 525 | // SaveToIndent writes content to file system with given value indention. |
494 | 526 | func (f *File) SaveToIndent(filename, indent string) error { |
495 | 527 | // 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. | |
497 | 529 | buf, err := f.writeToBuffer(indent) |
498 | 530 | if err != nil { |
499 | 531 | return err |
11 | 11 | // License for the specific language governing permissions and limitations |
12 | 12 | // under the License. |
13 | 13 | |
14 | package ini_test | |
14 | package ini | |
15 | 15 | |
16 | 16 | import ( |
17 | 17 | "bytes" |
18 | 18 | "io/ioutil" |
19 | "runtime" | |
20 | "sort" | |
19 | 21 | "testing" |
20 | 22 | |
21 | . "github.com/smartystreets/goconvey/convey" | |
22 | "gopkg.in/ini.v1" | |
23 | "github.com/stretchr/testify/assert" | |
24 | "github.com/stretchr/testify/require" | |
23 | 25 | ) |
24 | 26 | |
25 | 27 | 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) | |
36 | 36 | } |
37 | 37 | |
38 | 38 | 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) { | |
43 | 50 | 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) | |
63 | 61 | }) |
64 | 62 | } |
65 | 63 | |
66 | 64 | 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{ | |
69 | 67 | AllowNonUniqueSections: true, |
70 | 68 | }, []byte(`[Interface] |
71 | 69 | Address = 192.168.2.1 |
79 | 77 | [Peer] |
80 | 78 | PublicKey = <client2's publickey> |
81 | 79 | AllowedIPs = 192.168.2.3/32`)) |
82 | So(err, ShouldBeNil) | |
83 | So(f, ShouldNotBeNil) | |
80 | require.NoError(t, err) | |
81 | require.NotNil(t, f) | |
84 | 82 | |
85 | 83 | 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") | |
91 | 89 | |
92 | 90 | var buf bytes.Buffer |
93 | 91 | _, err = f.WriteTo(&buf) |
94 | So(err, ShouldBeNil) | |
92 | require.NoError(t, err) | |
95 | 93 | str := buf.String() |
96 | So(str, ShouldEqual, `[Interface] | |
94 | assert.Equal(t, `[Interface] | |
97 | 95 | Address = 192.168.2.1 |
98 | 96 | PrivateKey = <server's privatekey> |
99 | 97 | ListenPort = 51820 |
110 | 108 | PublicKey = <client3's publickey> |
111 | 109 | AllowedIPs = 192.168.2.4/32 |
112 | 110 | |
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{ | |
118 | 116 | AllowNonUniqueSections: true, |
119 | 117 | }, []byte(`[Interface] |
120 | 118 | Address = 192.168.2.1 |
134 | 132 | AllowedIPs = 192.168.2.4/32 |
135 | 133 | |
136 | 134 | `)) |
137 | So(err, ShouldBeNil) | |
138 | So(f, ShouldNotBeNil) | |
135 | require.NoError(t, err) | |
136 | require.NotNil(t, f) | |
139 | 137 | |
140 | 138 | err = f.DeleteSectionWithIndex("Peer", 1) |
141 | So(err, ShouldBeNil) | |
139 | require.NoError(t, err) | |
142 | 140 | |
143 | 141 | var buf bytes.Buffer |
144 | 142 | _, err = f.WriteTo(&buf) |
145 | So(err, ShouldBeNil) | |
143 | require.NoError(t, err) | |
146 | 144 | str := buf.String() |
147 | So(str, ShouldEqual, `[Interface] | |
145 | assert.Equal(t, `[Interface] | |
148 | 146 | Address = 192.168.2.1 |
149 | 147 | PrivateKey = <server's privatekey> |
150 | 148 | ListenPort = 51820 |
157 | 155 | PublicKey = <client3's publickey> |
158 | 156 | AllowedIPs = 192.168.2.4/32 |
159 | 157 | |
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{ | |
165 | 163 | AllowNonUniqueSections: true, |
166 | 164 | }) |
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()) | |
171 | 169 | f.DeleteSection("Peer") |
172 | So(f.SectionStrings(), ShouldResemble, []string{ini.DefaultSection, "Interface"}) | |
170 | assert.Equal(t, []string{DefaultSection, "Interface"}, f.SectionStrings()) | |
173 | 171 | }) |
174 | 172 | } |
175 | 173 | |
176 | 174 | 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 | |
182 | 179 | 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()) | |
199 | 267 | }) |
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(` | |
266 | 272 | [default] |
267 | 273 | NAME = ini |
268 | 274 | 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 | ||
275 | 307 | } |
276 | 308 | |
277 | 309 | 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 | } | |
290 | 320 | } |
291 | 321 | |
292 | 322 | 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(` | |
295 | 324 | [node] |
296 | 325 | [node.biz1] |
297 | 326 | [node.biz2] |
298 | 327 | [node.biz3] |
299 | 328 | [node.bizN] |
300 | 329 | `)) |
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 | } | |
311 | 339 | } |
312 | 340 | |
313 | 341 | 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()) | |
321 | 347 | } |
322 | 348 | |
323 | 349 | 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") | |
329 | 355 | f.DeleteSection("features") |
330 | 356 | 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()) | |
332 | 387 | }) |
333 | 388 | } |
334 | 389 | |
335 | 390 | 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(` | |
341 | 395 | [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)) | |
348 | 401 | }) |
349 | 402 | } |
350 | 403 | |
351 | 404 | 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) | |
356 | 413 | |
357 | 414 | f.Section("author").Comment = `Information about package author |
358 | 415 | # Bio can be written in multiple lines.` |
359 | 416 | 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") | |
362 | 419 | |
363 | 420 | var buf bytes.Buffer |
364 | 421 | _, err = f.WriteTo(&buf) |
365 | So(err, ShouldBeNil) | |
422 | require.NoError(t, err) | |
366 | 423 | |
367 | 424 | golden := "testdata/TestFile_WriteTo.golden" |
368 | 425 | if *update { |
369 | ioutil.WriteFile(golden, buf.Bytes(), 0644) | |
426 | require.NoError(t, ioutil.WriteFile(golden, buf.Bytes(), 0644)) | |
370 | 427 | } |
371 | 428 | |
372 | 429 | 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(` | |
379 | 436 | # |
380 | 437 | # general.domain |
381 | 438 | # |
382 | 439 | # Domain name of XX system. |
383 | 440 | domain = mydomain.com |
384 | 441 | `)) |
385 | So(err, ShouldBeNil) | |
442 | require.NoError(t, err) | |
386 | 443 | |
387 | 444 | f.Section("").Key("test").Comment = "Multiline\nComment" |
388 | 445 | |
389 | 446 | var buf bytes.Buffer |
390 | 447 | _, err = f.WriteTo(&buf) |
391 | So(err, ShouldBeNil) | |
392 | ||
393 | So(buf.String(), ShouldEqual, `# | |
448 | require.NoError(t, err) | |
449 | ||
450 | assert.Equal(t, `# | |
394 | 451 | # general.domain |
395 | 452 | # |
396 | 453 | # Domain name of XX system. |
399 | 456 | ; Comment |
400 | 457 | test = |
401 | 458 | |
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()) | |
404 | 480 | }) |
405 | 481 | } |
406 | 482 | |
407 | 483 | 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")) | |
416 | 490 | } |
417 | 491 | |
418 | 492 | 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] | |
423 | 496 | Cities = HangZhou|Boston |
424 | 497 | Visits = 1993-10-07T20:17:05Z, 1993-10-07T20:17:05Z |
425 | 498 | Years = 1993,1994 |
429 | 502 | Coordinates = 192.168,10.11 |
430 | 503 | Flags = true,false |
431 | 504 | 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] | |
437 | 510 | Cities -> HangZhou|Boston |
438 | 511 | Visits -> 1993-10-07T20:17:05Z, 1993-10-07T20:17:05Z |
439 | 512 | Years -> 1993,1994 |
445 | 518 | Note -> Hello world! |
446 | 519 | |
447 | 520 | `) |
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()) | |
453 | 525 | } |
454 | 526 | |
455 | 527 | // Inspired by https://github.com/go-ini/ini/issues/207 |
456 | 528 | func TestReloadAfterShadowLoad(t *testing.T) { |
457 | Convey("Reload file after ShadowLoad", t, func() { | |
458 | f, err := ini.ShadowLoad([]byte(` | |
529 | f, err := ShadowLoad([]byte(` | |
459 | 530 | [slice] |
460 | 531 | v = 1 |
461 | 532 | v = 2 |
462 | 533 | v = 3 |
463 | 534 | `)) |
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 | } |
16 | 16 | import ( |
17 | 17 | "testing" |
18 | 18 | |
19 | . "github.com/smartystreets/goconvey/convey" | |
19 | "github.com/stretchr/testify/assert" | |
20 | 20 | ) |
21 | 21 | |
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)) | |
28 | 26 | } |
0 | // +build go1.6 | |
1 | ||
2 | 0 | // Copyright 2014 Unknwon |
3 | 1 | // |
4 | 2 | // Licensed under the Apache License, Version 2.0 (the "License"): you may |
17 | 15 | package ini |
18 | 16 | |
19 | 17 | import ( |
18 | "os" | |
20 | 19 | "regexp" |
21 | 20 | "runtime" |
21 | "strings" | |
22 | 22 | ) |
23 | 23 | |
24 | 24 | const ( |
54 | 54 | DefaultFormatRight = "" |
55 | 55 | ) |
56 | 56 | |
57 | var inTest = len(os.Args) > 0 && strings.HasSuffix(strings.TrimSuffix(os.Args[0], ".exe"), ".test") | |
58 | ||
57 | 59 | func init() { |
58 | if runtime.GOOS == "windows" { | |
60 | if runtime.GOOS == "windows" && !inTest { | |
59 | 61 | LineBreak = "\r\n" |
60 | 62 | } |
61 | 63 | } |
66 | 68 | Loose bool |
67 | 69 | // Insensitive indicates whether the parser forces all section and key names to lowercase. |
68 | 70 | 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 | |
69 | 75 | // IgnoreContinuation indicates whether to ignore continuation lines while parsing. |
70 | 76 | IgnoreContinuation bool |
71 | 77 | // IgnoreInlineComment indicates whether to ignore comments at the end of value and treat it as part of value. |
72 | 78 | IgnoreInlineComment bool |
73 | 79 | // SkipUnrecognizableLines indicates whether to skip unrecognizable lines that do not conform to key/value pairs. |
74 | 80 | SkipUnrecognizableLines bool |
81 | // ShortCircuit indicates whether to ignore other configuration sources after loaded the first available configuration source. | |
82 | ShortCircuit bool | |
75 | 83 | // AllowBooleanKeys indicates whether to allow boolean type keys or treat as value is missing. |
76 | 84 | // This type of keys are mostly used in my.cnf. |
77 | 85 | AllowBooleanKeys bool |
102 | 110 | UnparseableSections []string |
103 | 111 | // KeyValueDelimiters is the sequence of delimiters that are used to separate key and value. By default, it is "=:". |
104 | 112 | 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 "=". | |
106 | 114 | KeyValueDelimiterOnWrite string |
115 | // ChildSectionDelimiter is the delimiter that is used to separate child sections. By default, it is ".". | |
116 | ChildSectionDelimiter string | |
107 | 117 | // PreserveSurroundedQuote indicates whether to preserve surrounded quote (single and double quotes). |
108 | 118 | PreserveSurroundedQuote bool |
109 | 119 | // DebugFunc is called to collect debug information (currently only useful to debug parsing Python-style multiline values). |
112 | 122 | ReaderBufferSize int |
113 | 123 | // AllowNonUniqueSections indicates whether to allow sections with the same name multiple times. |
114 | 124 | AllowNonUniqueSections bool |
125 | // AllowDuplicateShadowValues indicates whether values for shadowed keys should be deduplicated. | |
126 | AllowDuplicateShadowValues bool | |
115 | 127 | } |
116 | 128 | |
117 | 129 | // DebugFunc is the type of function called to log parse events. |
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 | } |
11 | 11 | // License for the specific language governing permissions and limitations |
12 | 12 | // under the License. |
13 | 13 | |
14 | package ini_test | |
14 | package ini | |
15 | 15 | |
16 | 16 | import ( |
17 | 17 | "bytes" |
18 | 18 | "flag" |
19 | 19 | "io/ioutil" |
20 | "path/filepath" | |
21 | "runtime" | |
20 | 22 | "testing" |
21 | 23 | |
22 | . "github.com/smartystreets/goconvey/convey" | |
23 | "gopkg.in/ini.v1" | |
24 | "github.com/stretchr/testify/assert" | |
25 | "github.com/stretchr/testify/require" | |
24 | 26 | ) |
25 | 27 | |
26 | 28 | const ( |
50 | 52 | var update = flag.Bool("update", false, "Update .golden files") |
51 | 53 | |
52 | 54 | 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( | |
55 | 57 | "testdata/minimal.ini", |
56 | 58 | []byte("NAME = ini\nIMPORT_PATH = gopkg.in/%(NAME)s.%(VERSION)s"), |
57 | 59 | bytes.NewReader([]byte(`VERSION = v1`)), |
58 | 60 | ioutil.NopCloser(bytes.NewReader([]byte("[author]\nNAME = Unknwon"))), |
59 | 61 | ) |
60 | So(err, ShouldBeNil) | |
61 | So(f, ShouldNotBeNil) | |
62 | require.NoError(t, err) | |
63 | require.NotNil(t, f) | |
62 | 64 | |
63 | 65 | // Validate values make sure all sources are loaded correctly |
64 | 66 | 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()) | |
68 | 70 | |
69 | 71 | 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(` | |
88 | 90 | [author] |
89 | 91 | NAME = U#n#k#n#w#o#n |
90 | 92 | GITHUB = U;n;k;n;w;o;n |
91 | 93 | `)) |
92 | So(err, ShouldBeNil) | |
93 | So(f, ShouldNotBeNil) | |
94 | require.NoError(t, err) | |
95 | require.NotNil(t, f) | |
94 | 96 | |
95 | 97 | sec := f.Section("author") |
96 | 98 | nameValue := sec.Key("NAME").String() |
97 | 99 | 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(` | |
104 | 106 | [long] |
105 | 107 | long_rsa_private_key = -----BEGIN RSA PRIVATE KEY----- |
106 | 108 | foo |
109 | 111 | barfoo |
110 | 112 | -----END RSA PRIVATE KEY----- |
111 | 113 | `)) |
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(` | |
119 | 121 | [long] |
120 | 122 | long_rsa_private_key = -----BEGIN RSA PRIVATE KEY----- |
121 | 123 | 1foo |
216 | 218 | 96barfoo |
217 | 219 | -----END RSA PRIVATE KEY----- |
218 | 220 | `)) |
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()) | |
222 | 224 | }) |
223 | 225 | } |
224 | 226 | |
225 | 227 | 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) | |
235 | 235 | }) |
236 | 236 | } |
237 | 237 | |
238 | 238 | 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) { | |
247 | 247 | var buf bytes.Buffer |
248 | 248 | _, err := f.WriteTo(&buf) |
249 | So(err, ShouldBeNil) | |
250 | So(buf.String(), ShouldEqual, `[author] | |
249 | require.NoError(t, err) | |
250 | assert.Equal(t, `[author] | |
251 | 251 | e-mail = u@gogs.io |
252 | 252 | |
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()) | |
262 | 264 | }) |
263 | 265 | }) |
264 | 266 | |
265 | 267 | // 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(` | |
268 | 270 | user = unknwon |
269 | 271 | [profile] |
270 | 272 | email = unknwon@local |
271 | 273 | `)) |
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()) | |
276 | 278 | }) |
277 | 279 | } |
278 | 280 | |
279 | 281 | 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] | |
305 | 306 | e-mail = u@gogs.io |
306 | 307 | |
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(` | |
324 | 383 | key1=a\b\ |
325 | 384 | key2=c\d\ |
326 | 385 | 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(` | |
336 | 395 | key1=a\b\ |
337 | 396 | 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(` | |
350 | 409 | key1=value ;comment |
351 | 410 | key2=value2 #comment2 |
352 | 411 | 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(` | |
362 | 421 | key1=value ;comment |
363 | 422 | 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(` | |
378 | 437 | GenerationDepth: 13 |
379 | 438 | |
380 | 439 | BiomeRarityScale: 100 |
386 | 445 | BiomeGroup(NormalBiomes, 3, 99, RoofedForestEnchanted, ForestSakura, FloatingJungle |
387 | 446 | BiomeGroup(IceBiomes, 4, 85, Ice Plains) |
388 | 447 | `)) |
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(` | |
402 | 461 | key1=hello |
403 | 462 | #key2 |
404 | 463 | 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 | |
416 | 475 | # key2 |
417 | 476 | 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(` | |
423 | 484 | key1=hello |
424 | 485 | #key2 |
425 | 486 | 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(` | |
432 | 493 | [remote "origin"] |
433 | 494 | url = https://github.com/Antergone/test1.git |
434 | 495 | url = https://github.com/Antergone/test2.git |
435 | 496 | 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{ | |
441 | 504 | "https://github.com/Antergone/test1.git", |
442 | 505 | "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"] | |
451 | 516 | url = https://github.com/Antergone/test1.git |
452 | 517 | url = https://github.com/Antergone/test2.git |
453 | 518 | fetch = +refs/heads/*:refs/remotes/origin/* |
454 | 519 | |
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(` | |
460 | 527 | [remote "origin"] |
461 | 528 | url = https://github.com/Antergone/test1.git |
462 | 529 | 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(` | |
475 | 542 | 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(` | |
483 | 550 | 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(` | |
497 | 564 | key = test value <span style="color: %s\; background: %s">more text</span> |
498 | 565 | `)) |
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(` | |
511 | 578 | [long] |
512 | 579 | long_rsa_private_key = -----BEGIN RSA PRIVATE KEY----- |
513 | 580 | foo |
520 | 587 | second |
521 | 588 | third |
522 | 589 | `)) |
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(` | |
536 | 603 | [long] |
537 | 604 | long_rsa_private_key = -----BEGIN RSA PRIVATE KEY----- |
538 | 605 | 1foo |
633 | 700 | 96barfoo |
634 | 701 | -----END RSA PRIVATE KEY----- |
635 | 702 | `)) |
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(` | |
745 | 814 | Lesson_Location = 87 |
746 | 815 | Lesson_Status = C |
747 | 816 | Score = 3 |
753 | 822 | |
754 | 823 | [COMMENTS] |
755 | 824 | <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 | |
770 | 841 | lesson_status = C |
771 | 842 | score = 3 |
772 | 843 | time = 00:02:30 |
777 | 848 | |
778 | 849 | [comments] |
779 | 850 | <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(` | |
785 | 858 | [CORE_LESSON] |
786 | 859 | my lesson state data – 1111111111111111111000000000000000001110000 |
787 | 860 | 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(` | |
797 | 870 | [author] |
798 | 871 | NAME = U#n#k#n#w#o#n |
799 | 872 | GITHUB = U;n;k;n;w;o;n |
800 | 873 | `)) |
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(` | |
816 | 889 | [author] |
817 | 890 | NAME = U#n#k#n#w#o#n |
818 | 891 | GITHUB = U;n;k;n;w;o;n |
819 | 892 | `)) |
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{ | |
834 | 915 | 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{ | |
850 | 945 | AllowPythonMultilineValues: false, |
851 | Insensitive: true, | |
852 | 946 | }, 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(` | |
884 | 959 | key1=a\b\ |
885 | 960 | key2=c\d\ |
886 | 961 | 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(` | |
896 | 971 | key1=a\b\ |
897 | 972 | 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(` | |
910 | 985 | key1=value ;comment |
911 | 986 | key2=value2 #comment2 |
912 | 987 | 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(` | |
922 | 997 | key1=value ;comment |
923 | 998 | 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(` | |
939 | 1014 | key1=hello |
940 | 1015 | #key2 |
941 | 1016 | 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 | |
953 | 1028 | # key2 |
954 | 1029 | 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(` | |
960 | 1037 | key1=hello |
961 | 1038 | #key2 |
962 | 1039 | 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(` | |
969 | 1046 | [remote "origin"] |
970 | 1047 | url = https://github.com/Antergone/test1.git |
971 | 1048 | url = https://github.com/Antergone/test2.git |
972 | 1049 | 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{ | |
978 | 1057 | "https://github.com/Antergone/test1.git", |
979 | 1058 | "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"] | |
988 | 1069 | url = https://github.com/Antergone/test1.git |
989 | 1070 | url = https://github.com/Antergone/test2.git |
990 | 1071 | fetch = +refs/heads/*:refs/remotes/origin/* |
991 | 1072 | |
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(` | |
997 | 1080 | [remote "origin"] |
998 | 1081 | url = https://github.com/Antergone/test1.git |
999 | 1082 | 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(` | |
1012 | 1095 | 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(` | |
1020 | 1103 | 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(` | |
1034 | 1117 | key = test value <span style="color: %s\; background: %s">more text</span> |
1035 | 1118 | `)) |
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(` | |
1044 | 1127 | [long] |
1045 | 1128 | long_rsa_private_key = -----BEGIN RSA PRIVATE KEY----- |
1046 | 1129 | foo |
1049 | 1132 | barfoo |
1050 | 1133 | -----END RSA PRIVATE KEY----- |
1051 | 1134 | `)) |
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(` | |
1059 | 1142 | [long] |
1060 | 1143 | long_rsa_private_key = -----BEGIN RSA PRIVATE KEY----- |
1061 | 1144 | 1foo |
1156 | 1239 | 96barfoo |
1157 | 1240 | -----END RSA PRIVATE KEY----- |
1158 | 1241 | `)) |
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(` | |
1170 | 1253 | Lesson_Location = 87 |
1171 | 1254 | Lesson_Status = C |
1172 | 1255 | Score = 3 |
1178 | 1261 | |
1179 | 1262 | [COMMENTS] |
1180 | 1263 | <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 | |
1195 | 1280 | lesson_status = C |
1196 | 1281 | score = 3 |
1197 | 1282 | time = 00:02:30 |
1202 | 1287 | |
1203 | 1288 | [comments] |
1204 | 1289 | <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(` | |
1210 | 1297 | [CORE_LESSON] |
1211 | 1298 | my lesson state data – 1111111111111111111000000000000000001110000 |
1212 | 1299 | 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(` | |
1222 | 1309 | [author] |
1223 | 1310 | NAME = U#n#k#n#w#o#n |
1224 | 1311 | GITHUB = U;n;k;n;w;o;n |
1225 | 1312 | `)) |
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(` | |
1241 | 1328 | [author] |
1242 | 1329 | NAME = U#n#k#n#w#o#n |
1243 | 1330 | GITHUB = U;n;k;n;w;o;n |
1244 | 1331 | `)) |
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 | ) | |
1254 | 1457 | }) |
1255 | 1458 | }) |
1256 | 1459 | } |
1257 | 1460 | |
1258 | 1461 | 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{ | |
1261 | 1464 | KeyValueDelimiters: "?!", |
1262 | 1465 | }, []byte(` |
1263 | 1466 | [section] |
1264 | 1467 | key1?value1 |
1265 | 1468 | key2!value2 |
1266 | 1469 | `)) |
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()) | |
1272 | 1475 | }) |
1273 | 1476 | } |
1274 | 1477 | |
1275 | 1478 | 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{ | |
1278 | 1481 | PreserveSurroundedQuote: true, |
1279 | 1482 | }, []byte(` |
1280 | 1483 | [section] |
1281 | 1484 | key1 = "value1" |
1282 | 1485 | key2 = value2 |
1283 | 1486 | `)) |
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{ | |
1293 | 1496 | PreserveSurroundedQuote: false, |
1294 | 1497 | }, []byte(` |
1295 | 1498 | [section] |
1296 | 1499 | key1 = "value1" |
1297 | 1500 | key2 = value2 |
1298 | 1501 | `)) |
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()) | |
1304 | 1507 | }) |
1305 | 1508 | } |
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 | } |
53 | 53 | return errors.New("cannot add shadow to auto-increment or boolean key") |
54 | 54 | } |
55 | 55 | |
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 { | |
62 | 59 | return nil |
60 | } | |
61 | for i := range k.shadows { | |
62 | if k.shadows[i].value == val { | |
63 | return nil | |
64 | } | |
63 | 65 | } |
64 | 66 | } |
65 | 67 | |
107 | 109 | return k.value |
108 | 110 | } |
109 | 111 | |
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. | |
111 | 114 | func (k *Key) ValueWithShadows() []string { |
112 | 115 | if len(k.shadows) == 0 { |
116 | if k.value == "" { | |
117 | return []string{} | |
118 | } | |
113 | 119 | return []string{k.value} |
114 | 120 | } |
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 | } | |
119 | 130 | } |
120 | 131 | return vals |
121 | 132 | } |
685 | 696 | // parseBools transforms strings to bools. |
686 | 697 | func (k *Key) parseBools(strs []string, addInvalid, returnOnInvalid bool) ([]bool, error) { |
687 | 698 | vals := make([]bool, 0, len(strs)) |
688 | for _, str := range strs { | |
699 | parser := func(str string) (interface{}, error) { | |
689 | 700 | 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 | |
698 | 710 | } |
699 | 711 | |
700 | 712 | // parseFloat64s transforms strings to float64s. |
701 | 713 | func (k *Key) parseFloat64s(strs []string, addInvalid, returnOnInvalid bool) ([]float64, error) { |
702 | 714 | vals := make([]float64, 0, len(strs)) |
703 | for _, str := range strs { | |
715 | parser := func(str string) (interface{}, error) { | |
704 | 716 | 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 | |
713 | 726 | } |
714 | 727 | |
715 | 728 | // parseInts transforms strings to ints. |
716 | 729 | func (k *Key) parseInts(strs []string, addInvalid, returnOnInvalid bool) ([]int, error) { |
717 | 730 | 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 | |
729 | 742 | } |
730 | 743 | |
731 | 744 | // parseInt64s transforms strings to int64s. |
732 | 745 | func (k *Key) parseInt64s(strs []string, addInvalid, returnOnInvalid bool) ([]int64, error) { |
733 | 746 | vals := make([]int64, 0, len(strs)) |
734 | for _, str := range strs { | |
747 | parser := func(str string) (interface{}, error) { | |
735 | 748 | 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 | |
744 | 759 | } |
745 | 760 | |
746 | 761 | // parseUints transforms strings to uints. |
747 | 762 | func (k *Key) parseUints(strs []string, addInvalid, returnOnInvalid bool) ([]uint, error) { |
748 | 763 | 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 | |
759 | 776 | } |
760 | 777 | |
761 | 778 | // parseUint64s transforms strings to uint64s. |
762 | 779 | func (k *Key) parseUint64s(strs []string, addInvalid, returnOnInvalid bool) ([]uint64, error) { |
763 | 780 | vals := make([]uint64, 0, len(strs)) |
764 | for _, str := range strs { | |
781 | parser := func(str string) (interface{}, error) { | |
765 | 782 | 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) | |
775 | 795 | |
776 | 796 | // parseTimesFormat transforms strings to times in given format. |
777 | 797 | func (k *Key) parseTimesFormat(format string, strs []string, addInvalid, returnOnInvalid bool) ([]time.Time, error) { |
778 | 798 | 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)) | |
779 | 815 | for _, str := range strs { |
780 | val, err := time.Parse(format, str) | |
816 | val, err := parser(str) | |
781 | 817 | if err != nil && returnOnInvalid { |
782 | 818 | return nil, err |
783 | 819 | } |
11 | 11 | // License for the specific language governing permissions and limitations |
12 | 12 | // under the License. |
13 | 13 | |
14 | package ini_test | |
14 | package ini | |
15 | 15 | |
16 | 16 | import ( |
17 | 17 | "bytes" |
18 | 18 | "fmt" |
19 | "runtime" | |
19 | 20 | "strings" |
20 | 21 | "testing" |
21 | 22 | "time" |
22 | 23 | |
23 | . "github.com/smartystreets/goconvey/convey" | |
24 | "gopkg.in/ini.v1" | |
24 | "github.com/stretchr/testify/assert" | |
25 | "github.com/stretchr/testify/require" | |
25 | 26 | ) |
26 | 27 | |
27 | 28 | 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(` | |
30 | 31 | [notes] |
31 | 32 | -: note1`)) |
32 | So(err, ShouldBeNil) | |
33 | So(f, ShouldNotBeNil) | |
33 | require.NoError(t, err) | |
34 | require.NotNil(t, f) | |
34 | 35 | |
35 | 36 | 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) { | |
43 | 44 | 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) | |
57 | 74 | |
58 | 75 | 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")) | |
63 | 94 | }) |
64 | 95 | } |
65 | 96 | |
66 | 97 | // 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)) | |
69 | 102 | for i, v := range expected { |
70 | So(values[i], ShouldEqual, v) | |
103 | assert.Equal(t, v, values[i]) | |
71 | 104 | } |
72 | 105 | } |
73 | 106 | |
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)) | |
76 | 111 | for i, v := range expected { |
77 | So(values[i], ShouldEqual, v) | |
112 | assert.Equal(t, v, values[i]) | |
78 | 113 | } |
79 | 114 | } |
80 | 115 | |
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)) | |
83 | 120 | for i, v := range expected { |
84 | So(values[i], ShouldEqual, v) | |
121 | assert.Equal(t, v, values[i]) | |
85 | 122 | } |
86 | 123 | } |
87 | 124 | |
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)) | |
90 | 129 | for i, v := range expected { |
91 | So(values[i], ShouldEqual, v) | |
130 | assert.Equal(t, v, values[i]) | |
92 | 131 | } |
93 | 132 | } |
94 | 133 | |
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)) | |
97 | 138 | for i, v := range expected { |
98 | So(values[i], ShouldEqual, v) | |
139 | assert.Equal(t, v, values[i]) | |
99 | 140 | } |
100 | 141 | } |
101 | 142 | |
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)) | |
104 | 147 | for i, v := range expected { |
105 | So(values[i], ShouldEqual, v) | |
148 | assert.Equal(t, v, values[i]) | |
106 | 149 | } |
107 | 150 | } |
108 | 151 | |
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)) | |
111 | 156 | for i, v := range expected { |
112 | So(values[i].String(), ShouldEqual, v.String()) | |
157 | assert.Equal(t, v.String(), values[i].String()) | |
113 | 158 | } |
114 | 159 | } |
115 | 160 | |
116 | 161 | 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) { | |
123 | 168 | 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 { | |
128 | 173 | 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) { | |
134 | 179 | f.ValueMapper = func(in string) string { |
135 | 180 | if in == "gopkg.in/%(NAME)s.%(VERSION)s" { |
136 | 181 | return "github.com/go-ini/ini" |
137 | 182 | } |
138 | 183 | return in |
139 | 184 | } |
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()) | |
141 | 186 | }) |
142 | 187 | }) |
143 | 188 | |
144 | Convey("Get values in non-default section", func() { | |
189 | t.Run("get values in non-default section", func(t *testing.T) { | |
145 | 190 | 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()) | |
149 | 194 | |
150 | 195 | 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) { | |
156 | 201 | keys := f.Section("features").Keys() |
157 | 202 | 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()) | |
159 | 204 | } |
160 | 205 | }) |
161 | 206 | |
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) { | |
163 | 208 | parentKeys := f.Section("package.sub").ParentKeys() |
164 | 209 | for _, k := range parentKeys { |
165 | So(k.Name(), ShouldEqual, "CLONE_URL") | |
210 | assert.Equal(t, "CLONE_URL", k.Name()) | |
166 | 211 | } |
167 | 212 | }) |
168 | 213 | |
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) { | |
174 | 219 | 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()) | |
177 | 222 | } |
178 | 223 | }) |
179 | 224 | |
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) { | |
190 | 239 | sec := f.Section("types") |
191 | 240 | v1, err := sec.Key("BOOL").Bool() |
192 | So(err, ShouldBeNil) | |
193 | So(v1, ShouldBeTrue) | |
241 | require.NoError(t, err) | |
242 | assert.True(t, v1) | |
194 | 243 | |
195 | 244 | 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) | |
198 | 247 | |
199 | 248 | 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) | |
202 | 251 | |
203 | 252 | 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) | |
206 | 255 | |
207 | 256 | 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) | |
210 | 259 | |
211 | 260 | 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) | |
214 | 263 | |
215 | 264 | 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) | |
221 | 270 | 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()) | |
224 | 273 | |
225 | 274 | 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()) | |
239 | 288 | |
240 | 289 | 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()) | |
271 | 320 | }) |
272 | 321 | }) |
273 | 322 | }) |
274 | 323 | }) |
275 | 324 | |
276 | Convey("Get value with candidates", func() { | |
325 | t.Run("get value with candidates", func(t *testing.T) { | |
277 | 326 | 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})) | |
284 | 333 | |
285 | 334 | 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()) | |
299 | 348 | }) |
300 | 349 | }) |
301 | 350 | |
302 | Convey("Get values in range", func() { | |
351 | t.Run("get values in range", func(t *testing.T) { | |
303 | 352 | 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)) | |
307 | 356 | |
308 | 357 | minT, err := time.Parse(time.RFC3339, "0001-01-01T01:00:00Z") |
309 | So(err, ShouldBeNil) | |
358 | require.NoError(t, err) | |
310 | 359 | midT, err := time.Parse(time.RFC3339, "2013-01-01T01:00:00Z") |
311 | So(err, ShouldBeNil) | |
360 | require.NoError(t, err) | |
312 | 361 | 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()) | |
323 | 372 | }) |
324 | 373 | }) |
325 | 374 | |
326 | Convey("Get values into slice", func() { | |
375 | t.Run("get values into slice", func(t *testing.T) { | |
327 | 376 | 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(","))) | |
330 | 379 | |
331 | 380 | vals1 := sec.Key("FLOAT64S").Float64s(",") |
332 | float64sEqual(vals1, 1.1, 2.2, 3.3) | |
381 | float64sEqual(t, vals1, 1.1, 2.2, 3.3) | |
333 | 382 | |
334 | 383 | vals2 := sec.Key("INTS").Ints(",") |
335 | intsEqual(vals2, 1, 2, 3) | |
384 | intsEqual(t, vals2, 1, 2, 3) | |
336 | 385 | |
337 | 386 | vals3 := sec.Key("INTS").Int64s(",") |
338 | int64sEqual(vals3, 1, 2, 3) | |
387 | int64sEqual(t, vals3, 1, 2, 3) | |
339 | 388 | |
340 | 389 | vals4 := sec.Key("UINTS").Uints(",") |
341 | uintsEqual(vals4, 1, 2, 3) | |
390 | uintsEqual(t, vals4, 1, 2, 3) | |
342 | 391 | |
343 | 392 | vals5 := sec.Key("UINTS").Uint64s(",") |
344 | uint64sEqual(vals5, 1, 2, 3) | |
393 | uint64sEqual(t, vals5, 1, 2, 3) | |
345 | 394 | |
346 | 395 | 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) | |
351 | 400 | 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) { | |
356 | 405 | 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) { | |
366 | 415 | sec := f.Section("array") |
367 | 416 | vals1 := sec.Key("FLOAT64S").ValidFloat64s(",") |
368 | float64sEqual(vals1, 1.1, 2.2, 3.3) | |
417 | float64sEqual(t, vals1, 1.1, 2.2, 3.3) | |
369 | 418 | |
370 | 419 | vals2 := sec.Key("INTS").ValidInts(",") |
371 | intsEqual(vals2, 1, 2, 3) | |
420 | intsEqual(t, vals2, 1, 2, 3) | |
372 | 421 | |
373 | 422 | vals3 := sec.Key("INTS").ValidInt64s(",") |
374 | int64sEqual(vals3, 1, 2, 3) | |
423 | int64sEqual(t, vals3, 1, 2, 3) | |
375 | 424 | |
376 | 425 | vals4 := sec.Key("UINTS").ValidUints(",") |
377 | uintsEqual(vals4, 1, 2, 3) | |
426 | uintsEqual(t, vals4, 1, 2, 3) | |
378 | 427 | |
379 | 428 | vals5 := sec.Key("UINTS").ValidUint64s(",") |
380 | uint64sEqual(vals5, 1, 2, 3) | |
429 | uint64sEqual(t, vals5, 1, 2, 3) | |
381 | 430 | |
382 | 431 | 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) | |
387 | 436 | 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) { | |
392 | 441 | sec := f.Section("array") |
393 | 442 | vals1 := sec.Key("STRINGS").ValidFloat64s(",") |
394 | So(vals1, ShouldBeEmpty) | |
443 | assert.Empty(t, vals1) | |
395 | 444 | |
396 | 445 | vals2 := sec.Key("STRINGS").ValidInts(",") |
397 | So(vals2, ShouldBeEmpty) | |
446 | assert.Empty(t, vals2) | |
398 | 447 | |
399 | 448 | vals3 := sec.Key("STRINGS").ValidInt64s(",") |
400 | So(vals3, ShouldBeEmpty) | |
449 | assert.Empty(t, vals3) | |
401 | 450 | |
402 | 451 | vals4 := sec.Key("STRINGS").ValidUints(",") |
403 | So(vals4, ShouldBeEmpty) | |
452 | assert.Empty(t, vals4) | |
404 | 453 | |
405 | 454 | vals5 := sec.Key("STRINGS").ValidUint64s(",") |
406 | So(vals5, ShouldBeEmpty) | |
455 | assert.Empty(t, vals5) | |
407 | 456 | |
408 | 457 | vals6 := sec.Key("STRINGS").ValidBools(",") |
409 | So(vals6, ShouldBeEmpty) | |
458 | assert.Empty(t, vals6) | |
410 | 459 | |
411 | 460 | 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) { | |
416 | 465 | sec := f.Section("array") |
417 | 466 | 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) | |
420 | 469 | |
421 | 470 | 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) | |
424 | 473 | |
425 | 474 | 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) | |
428 | 477 | |
429 | 478 | 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) | |
432 | 481 | |
433 | 482 | 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) | |
436 | 485 | |
437 | 486 | 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) | |
443 | 492 | 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) { | |
449 | 498 | sec := f.Section("array") |
450 | 499 | vals1, err := sec.Key("STRINGS").StrictFloat64s(",") |
451 | So(vals1, ShouldBeEmpty) | |
452 | So(err, ShouldNotBeNil) | |
500 | assert.Empty(t, vals1) | |
501 | assert.Error(t, err) | |
453 | 502 | |
454 | 503 | vals2, err := sec.Key("STRINGS").StrictInts(",") |
455 | So(vals2, ShouldBeEmpty) | |
456 | So(err, ShouldNotBeNil) | |
504 | assert.Empty(t, vals2) | |
505 | assert.Error(t, err) | |
457 | 506 | |
458 | 507 | vals3, err := sec.Key("STRINGS").StrictInt64s(",") |
459 | So(vals3, ShouldBeEmpty) | |
460 | So(err, ShouldNotBeNil) | |
508 | assert.Empty(t, vals3) | |
509 | assert.Error(t, err) | |
461 | 510 | |
462 | 511 | vals4, err := sec.Key("STRINGS").StrictUints(",") |
463 | So(vals4, ShouldBeEmpty) | |
464 | So(err, ShouldNotBeNil) | |
512 | assert.Empty(t, vals4) | |
513 | assert.Error(t, err) | |
465 | 514 | |
466 | 515 | vals5, err := sec.Key("STRINGS").StrictUint64s(",") |
467 | So(vals5, ShouldBeEmpty) | |
468 | So(err, ShouldNotBeNil) | |
516 | assert.Empty(t, vals5) | |
517 | assert.Error(t, err) | |
469 | 518 | |
470 | 519 | vals6, err := sec.Key("STRINGS").StrictBools(",") |
471 | So(vals6, ShouldBeEmpty) | |
472 | So(err, ShouldNotBeNil) | |
520 | assert.Empty(t, vals6) | |
521 | assert.Error(t, err) | |
473 | 522 | |
474 | 523 | 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()) | |
478 | 546 | }) |
479 | 547 | } |
480 | 548 | |
481 | 549 | 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) | |
486 | 554 | |
487 | 555 | 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) | |
490 | 558 | 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(",")) | |
495 | 563 | }) |
496 | 564 | } |
497 | 565 | |
498 | 566 | 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) | |
502 | 570 | |
503 | 571 | 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()) | |
507 | 575 | |
508 | 576 | k.SetValue("ini.v1") |
509 | So(k.Value(), ShouldEqual, "ini.v1") | |
577 | assert.Equal(t, "ini.v1", k.Value()) | |
510 | 578 | }) |
511 | 579 | } |
512 | 580 | |
513 | 581 | 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{ | |
516 | 584 | AllowNestedValues: true, |
517 | 585 | }, []byte(` |
518 | 586 | aws_access_key_id = foo |
521 | 589 | s3 = |
522 | 590 | max_concurrent_requests=10 |
523 | 591 | 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()) | |
528 | 596 | |
529 | 597 | var buf bytes.Buffer |
530 | 598 | _, 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 | |
533 | 601 | aws_secret_access_key = bar |
534 | 602 | region = us-west-2 |
535 | 603 | s3 = |
536 | 604 | max_concurrent_requests=10 |
537 | 605 | max_queue_size=1000 |
538 | 606 | |
539 | `) | |
607 | `, | |
608 | buf.String(), | |
609 | ) | |
540 | 610 | }) |
541 | 611 | } |
542 | 612 | |
543 | 613 | 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(` | |
546 | 616 | NAME = ini |
547 | 617 | expires = yes |
548 | 618 | [package] |
549 | 619 | NAME = %(NAME)s |
550 | 620 | 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(` | |
560 | 630 | [foo] |
561 | 631 | bar = %(missing)s |
562 | 632 | `)) |
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 | } |
83 | 83 | case mask[0] == 254 && mask[1] == 255: |
84 | 84 | fallthrough |
85 | 85 | 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 | } | |
87 | 90 | case mask[0] == 239 && mask[1] == 187: |
88 | 91 | mask, err := p.buf.Peek(3) |
89 | 92 | if err != nil && err != io.EOF { |
92 | 95 | return nil |
93 | 96 | } |
94 | 97 | if mask[2] == 191 { |
95 | p.buf.Read(mask) | |
98 | _, err = p.buf.Read(mask) | |
99 | if err != nil { | |
100 | return err | |
101 | } | |
96 | 102 | } |
97 | 103 | } |
98 | 104 | return nil |
124 | 130 | // Check if key name surrounded by quotes. |
125 | 131 | var keyQuote string |
126 | 132 | if line[0] == '"' { |
127 | if len(line) > 6 && string(line[0:3]) == `"""` { | |
133 | if len(line) > 6 && line[0:3] == `"""` { | |
128 | 134 | keyQuote = `"""` |
129 | 135 | } else { |
130 | 136 | keyQuote = `"` |
134 | 140 | } |
135 | 141 | |
136 | 142 | // Get out key name |
137 | endIdx := -1 | |
143 | var endIdx int | |
138 | 144 | if len(keyQuote) > 0 { |
139 | 145 | startIdx := len(keyQuote) |
140 | 146 | // FIXME: fail case -> """"""name"""=value |
225 | 231 | } |
226 | 232 | |
227 | 233 | var valQuote string |
228 | if len(line) > 3 && string(line[0:3]) == `"""` { | |
234 | if len(line) > 3 && line[0:3] == `"""` { | |
229 | 235 | valQuote = `"""` |
230 | 236 | } else if line[0] == '`' { |
231 | 237 | valQuote = "`" |
282 | 288 | hasSurroundedQuote(line, '"')) && !p.options.PreserveSurroundedQuote { |
283 | 289 | line = line[1 : len(line)-1] |
284 | 290 | } 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, `\#`, "#") | |
291 | 293 | } else if p.options.AllowPythonMultilineValues && lastChar == '\n' { |
292 | 294 | return p.readPythonMultilines(line, bufferSize) |
293 | 295 | } |
299 | 301 | parserBufferPeekResult, _ := p.buf.Peek(bufferSize) |
300 | 302 | peekBuffer := bytes.NewBuffer(parserBufferPeekResult) |
301 | 303 | |
302 | indentSize := 0 | |
303 | 304 | for { |
304 | 305 | 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 { | |
311 | 307 | p.debug("readPythonMultilines: failed to peek with error: %v", peekErr) |
312 | 308 | return "", peekErr |
313 | 309 | } |
326 | 322 | return line, nil |
327 | 323 | } |
328 | 324 | |
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 | ||
342 | 325 | // Advance the parser reader (buffer) in-sync with the peek buffer. |
343 | 326 | _, err := p.buf.Discard(len(peekData)) |
344 | 327 | if err != nil { |
346 | 329 | return "", err |
347 | 330 | } |
348 | 331 | |
349 | // Handle indented empty line. | |
350 | line += "\n" + peekMatches[1][indentSize:] + peekMatches[2] | |
332 | line += "\n" + peekMatches[0] | |
351 | 333 | } |
352 | 334 | } |
353 | 335 | |
370 | 352 | |
371 | 353 | // Ignore error because default section name is never empty string. |
372 | 354 | name := DefaultSection |
373 | if f.options.Insensitive { | |
355 | if f.options.Insensitive || f.options.InsensitiveSections { | |
374 | 356 | name = strings.ToLower(DefaultSection) |
375 | 357 | } |
376 | 358 | section, _ := f.NewSection(name) |
412 | 394 | if f.options.AllowNestedValues && |
413 | 395 | isLastValueEmpty && len(line) > 0 { |
414 | 396 | 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 | } | |
416 | 401 | continue |
417 | 402 | } |
418 | 403 | } |
455 | 440 | // Reset auto-counter and comments |
456 | 441 | p.comment.Reset() |
457 | 442 | p.count = 1 |
443 | // Nested values can't span sections | |
444 | isLastValueEmpty = false | |
458 | 445 | |
459 | 446 | inUnparseableSection = false |
460 | 447 | for i := range f.options.UnparseableSections { |
461 | 448 | 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)) { | |
463 | 450 | inUnparseableSection = true |
464 | 451 | continue |
465 | 452 | } |
11 | 11 | // License for the specific language governing permissions and limitations |
12 | 12 | // under the License. |
13 | 13 | |
14 | package ini_test | |
14 | package ini | |
15 | 15 | |
16 | 16 | import ( |
17 | 17 | "testing" |
18 | 18 | |
19 | . "github.com/smartystreets/goconvey/convey" | |
20 | "gopkg.in/ini.v1" | |
19 | "github.com/stretchr/testify/assert" | |
20 | "github.com/stretchr/testify/require" | |
21 | 21 | ) |
22 | 22 | |
23 | 23 | 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) | |
29 | 29 | |
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()) | |
31 | 31 | }) |
32 | 32 | |
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) | |
37 | 37 | }) |
38 | 38 | |
39 | Convey("UTF-16-BE-BOM", func() { | |
39 | t.Run("UTF-16-BE-BOM", func(t *testing.T) { | |
40 | 40 | }) |
41 | 41 | }) |
42 | 42 | } |
43 | 43 | |
44 | 44 | 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) | |
49 | 49 | |
50 | _, err = ini.Load([]byte("[")) | |
51 | So(err, ShouldNotBeNil) | |
50 | _, err = Load([]byte("[")) | |
51 | require.Error(t, err) | |
52 | 52 | }) |
53 | 53 | |
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) | |
57 | 57 | |
58 | _, err = ini.Load([]byte(`"""name"""`)) | |
59 | So(err, ShouldNotBeNil) | |
58 | _, err = Load([]byte(`"""name"""`)) | |
59 | require.Error(t, err) | |
60 | 60 | |
61 | _, err = ini.Load([]byte(`""=1`)) | |
62 | So(err, ShouldNotBeNil) | |
61 | _, err = Load([]byte(`""=1`)) | |
62 | require.Error(t, err) | |
63 | 63 | |
64 | _, err = ini.Load([]byte(`=`)) | |
65 | So(err, ShouldNotBeNil) | |
64 | _, err = Load([]byte(`=`)) | |
65 | require.Error(t, err) | |
66 | 66 | |
67 | _, err = ini.Load([]byte(`name`)) | |
68 | So(err, ShouldNotBeNil) | |
67 | _, err = Load([]byte(`name`)) | |
68 | require.Error(t, err) | |
69 | 69 | }) |
70 | 70 | |
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) | |
74 | 74 | }) |
75 | 75 | }) |
76 | 76 | } |
65 | 65 | func (s *Section) NewKey(name, val string) (*Key, error) { |
66 | 66 | if len(name) == 0 { |
67 | 67 | 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 { | |
69 | 69 | name = strings.ToLower(name) |
70 | 70 | } |
71 | 71 | |
108 | 108 | if s.f.BlockMode { |
109 | 109 | s.f.lock.RLock() |
110 | 110 | } |
111 | if s.f.options.Insensitive { | |
111 | if s.f.options.Insensitive || s.f.options.InsensitiveKeys { | |
112 | 112 | name = strings.ToLower(name) |
113 | 113 | } |
114 | 114 | key := s.keys[name] |
120 | 120 | // Check if it is a child-section. |
121 | 121 | sname := s.name |
122 | 122 | for { |
123 | if i := strings.LastIndex(sname, "."); i > -1 { | |
123 | if i := strings.LastIndex(sname, s.f.options.ChildSectionDelimiter); i > -1 { | |
124 | 124 | sname = sname[:i] |
125 | 125 | sec, err := s.f.GetSection(sname) |
126 | 126 | if err != nil { |
187 | 187 | var parentKeys []*Key |
188 | 188 | sname := s.name |
189 | 189 | for { |
190 | if i := strings.LastIndex(sname, "."); i > -1 { | |
190 | if i := strings.LastIndex(sname, s.f.options.ChildSectionDelimiter); i > -1 { | |
191 | 191 | sname = sname[:i] |
192 | 192 | sec, err := s.f.GetSection(sname) |
193 | 193 | if err != nil { |
216 | 216 | defer s.f.lock.RUnlock() |
217 | 217 | } |
218 | 218 | |
219 | hash := map[string]string{} | |
219 | hash := make(map[string]string, len(s.keysHash)) | |
220 | 220 | for key, value := range s.keysHash { |
221 | 221 | hash[key] = value |
222 | 222 | } |
244 | 244 | // For example, "[parent.child1]" and "[parent.child12]" are child sections |
245 | 245 | // of section "[parent]". |
246 | 246 | func (s *Section) ChildSections() []*Section { |
247 | prefix := s.name + "." | |
247 | prefix := s.name + s.f.options.ChildSectionDelimiter | |
248 | 248 | children := make([]*Section, 0, 3) |
249 | 249 | for _, name := range s.f.sectionList { |
250 | 250 | if strings.HasPrefix(name, prefix) { |
11 | 11 | // License for the specific language governing permissions and limitations |
12 | 12 | // under the License. |
13 | 13 | |
14 | package ini_test | |
14 | package ini | |
15 | 15 | |
16 | 16 | import ( |
17 | 17 | "testing" |
18 | 18 | |
19 | . "github.com/smartystreets/goconvey/convey" | |
20 | "gopkg.in/ini.v1" | |
19 | "github.com/stretchr/testify/assert" | |
20 | "github.com/stretchr/testify/require" | |
21 | 21 | ) |
22 | 22 | |
23 | 23 | 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) | |
27 | 27 | |
28 | 28 | sec, err := f.NewRawSection("comments", `1111111111111111111000000000000000001110000 |
29 | 29 | 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()) | |
34 | 34 | |
35 | 35 | 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) { | |
39 | 39 | 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()) | |
43 | 43 | |
44 | 44 | sec.SetBody("1111111111111111111000000000000000001110000") |
45 | So(sec.Body(), ShouldBeEmpty) | |
45 | assert.Empty(t, sec.Body()) | |
46 | 46 | }) |
47 | 47 | }) |
48 | 48 | } |
49 | 49 | |
50 | 50 | 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) { | |
62 | 62 | 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) | |
65 | 65 | |
66 | 66 | // 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) { | |
71 | 71 | _, 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) | |
84 | 84 | 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()) | |
89 | 89 | }) |
90 | 90 | } |
91 | 91 | |
92 | 92 | 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) | |
96 | 96 | |
97 | 97 | 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) { | |
104 | 104 | _, err := f.Section("").NewBooleanKey("") |
105 | So(err, ShouldNotBeNil) | |
105 | require.Error(t, err) | |
106 | 106 | }) |
107 | 107 | }) |
108 | 108 | } |
109 | 109 | |
110 | 110 | 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) | |
118 | 118 | |
119 | 119 | 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) { | |
126 | 126 | _, 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) { | |
131 | 131 | 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) | |
134 | 134 | |
135 | 135 | 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()) | |
139 | 139 | }) |
140 | 140 | }) |
141 | 141 | } |
142 | 142 | |
143 | 143 | 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")) | |
156 | 156 | }) |
157 | 157 | } |
158 | 158 | |
159 | 159 | 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")) | |
170 | 170 | }) |
171 | 171 | } |
172 | 172 | |
173 | 173 | 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) | |
181 | 181 | |
182 | 182 | 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) { | |
188 | 188 | 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) { | |
194 | 194 | 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) | |
197 | 197 | |
198 | 198 | 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()) | |
201 | 201 | }) |
202 | 202 | }) |
203 | 203 | } |
204 | 204 | |
205 | 205 | 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) | |
213 | 213 | 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) | |
216 | 216 | 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) | |
219 | 219 | |
220 | 220 | keys := f.Section("").Keys() |
221 | 221 | names := []string{"NAME", "VERSION", "IMPORT_PATH"} |
222 | So(len(keys), ShouldEqual, len(names)) | |
222 | assert.Equal(t, len(names), len(keys)) | |
223 | 223 | for i, name := range names { |
224 | So(keys[i].Name(), ShouldEqual, name) | |
224 | assert.Equal(t, name, keys[i].Name()) | |
225 | 225 | } |
226 | 226 | }) |
227 | 227 | } |
228 | 228 | |
229 | 229 | 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) | |
233 | 233 | |
234 | 234 | 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) | |
237 | 237 | 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) | |
240 | 240 | 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) | |
243 | 243 | |
244 | 244 | keys := f.Section("package.sub.sub2").ParentKeys() |
245 | 245 | names := []string{"NAME", "VERSION", "IMPORT_PATH"} |
246 | So(len(keys), ShouldEqual, len(names)) | |
246 | assert.Equal(t, len(names), len(keys)) | |
247 | 247 | for i, name := range names { |
248 | So(keys[i].Name(), ShouldEqual, name) | |
248 | assert.Equal(t, name, keys[i].Name()) | |
249 | 249 | } |
250 | 250 | }) |
251 | 251 | } |
252 | 252 | |
253 | 253 | 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) | |
261 | 261 | 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) | |
264 | 264 | 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()) | |
269 | 269 | }) |
270 | 270 | } |
271 | 271 | |
272 | 272 | 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(` | |
275 | 275 | key = one |
276 | 276 | [log] |
277 | 277 | name = app |
282 | 282 | name = app2 |
283 | 283 | file = b.log |
284 | 284 | `)) |
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()) | |
289 | 289 | |
290 | 290 | hash := f.Section("log").KeysHash() |
291 | 291 | relation := map[string]string{ |
293 | 293 | "file": "b.log", |
294 | 294 | } |
295 | 295 | for k, v := range hash { |
296 | So(v, ShouldEqual, relation[k]) | |
296 | assert.Equal(t, relation[k], v) | |
297 | 297 | } |
298 | 298 | }) |
299 | 299 | } |
300 | 300 | |
301 | 301 | 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")) | |
311 | 311 | f.Section("").DeleteKey("NAME") |
312 | So(f.Section("").HasKey("NAME"), ShouldBeFalse) | |
313 | }) | |
314 | } | |
312 | assert.False(t, f.Section("").HasKey("NAME")) | |
313 | }) | |
314 | } |
262 | 262 | return nil |
263 | 263 | } |
264 | 264 | |
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) | |
267 | 267 | 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 { | |
281 | 280 | if val.Kind() == reflect.Ptr { |
282 | 281 | val = val.Elem() |
283 | 282 | } |
292 | 291 | continue |
293 | 292 | } |
294 | 293 | |
295 | rawName, _, allowShadow, allowNonUnique := parseTagOptions(tag) | |
294 | rawName, _, allowShadow, allowNonUnique, extends := parseTagOptions(tag) | |
296 | 295 | fieldName := s.parseFieldName(tpField.Name, rawName) |
297 | 296 | if len(fieldName) == 0 || !field.CanSet() { |
298 | 297 | continue |
300 | 299 | |
301 | 300 | isStruct := tpField.Type.Kind() == reflect.Struct |
302 | 301 | 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 { | |
305 | 304 | field.Set(reflect.New(tpField.Type.Elem())) |
306 | 305 | } |
307 | 306 | |
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 | } | |
310 | 326 | // Only set the field to non-nil struct value if we have a section for it. |
311 | 327 | // Otherwise, we end up with a non-nil struct ptr even though there is no data. |
312 | 328 | if isStructPtr && field.IsNil() { |
313 | 329 | field.Set(reflect.New(tpField.Type.Elem())) |
314 | 330 | } |
315 | if err = sec.mapToField(field, isStrict); err != nil { | |
331 | if err = secs[sectionIndex].mapToField(field, isStrict, sectionIndex, fieldName); err != nil { | |
316 | 332 | return fmt.Errorf("map to field %q: %v", fieldName, err) |
317 | 333 | } |
318 | 334 | continue |
349 | 365 | } |
350 | 366 | |
351 | 367 | typ := val.Type().Elem() |
352 | for _, sec := range secs { | |
368 | for i, sec := range secs { | |
353 | 369 | 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 { | |
355 | 371 | return reflect.Value{}, fmt.Errorf("map to field from section %q: %v", secName, err) |
356 | 372 | } |
357 | 373 | |
381 | 397 | return nil |
382 | 398 | } |
383 | 399 | |
384 | return s.mapToField(val, isStrict) | |
400 | return s.mapToField(val, isStrict, 0, s.name) | |
385 | 401 | } |
386 | 402 | |
387 | 403 | // MapTo maps section to given struct. |
473 | 489 | _ = keyWithShadows.AddShadow(val) |
474 | 490 | } |
475 | 491 | } |
476 | key = keyWithShadows | |
492 | *key = *keyWithShadows | |
477 | 493 | return nil |
478 | 494 | } |
479 | 495 | |
563 | 579 | typ := val.Type() |
564 | 580 | |
565 | 581 | for i := 0; i < typ.NumField(); i++ { |
582 | if !val.Field(i).CanInterface() { | |
583 | continue | |
584 | } | |
585 | ||
566 | 586 | field := val.Field(i) |
567 | 587 | tpField := typ.Field(i) |
568 | 588 | |
571 | 591 | continue |
572 | 592 | } |
573 | 593 | |
574 | rawName, omitEmpty, allowShadow, allowNonUnique := parseTagOptions(tag) | |
594 | rawName, omitEmpty, allowShadow, allowNonUnique, extends := parseTagOptions(tag) | |
575 | 595 | if omitEmpty && isEmptyValue(field) { |
576 | 596 | continue |
577 | 597 | } |
585 | 605 | continue |
586 | 606 | } |
587 | 607 | |
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) || | |
589 | 616 | (tpField.Type.Kind() == reflect.Struct && tpField.Type.Name() != "Time") { |
590 | 617 | // Note: The only error here is section doesn't exist. |
591 | 618 | sec, err := s.f.GetSection(fieldName) |
694 | 721 | } |
695 | 722 | |
696 | 723 | if typ.Kind() == reflect.Ptr { |
697 | typ = typ.Elem() | |
698 | 724 | val = val.Elem() |
699 | 725 | } else { |
700 | 726 | return errors.New("not a pointer to a struct") |
11 | 11 | // License for the specific language governing permissions and limitations |
12 | 12 | // under the License. |
13 | 13 | |
14 | package ini_test | |
14 | package ini | |
15 | 15 | |
16 | 16 | import ( |
17 | 17 | "bytes" |
20 | 20 | "testing" |
21 | 21 | "time" |
22 | 22 | |
23 | . "github.com/smartystreets/goconvey/convey" | |
24 | ||
25 | "gopkg.in/ini.v1" | |
23 | "github.com/stretchr/testify/assert" | |
24 | "github.com/stretchr/testify/require" | |
26 | 25 | ) |
27 | 26 | |
28 | 27 | type testNested struct { |
57 | 56 | Unused int `ini:"-"` |
58 | 57 | Unsigned uint |
59 | 58 | 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"` | |
62 | 61 | BoolPtr *bool |
63 | 62 | BoolPtrNil *bool |
64 | 63 | FloatPtr *float64 |
89 | 88 | |
90 | 89 | type testNonUniqueSectionsStruct struct { |
91 | 90 | 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 | |
93 | 101 | } |
94 | 102 | |
95 | 103 | const confDataStruct = ` |
140 | 148 | [foo.bar] |
141 | 149 | Here = there |
142 | 150 | When = then |
151 | ||
152 | [extended] | |
153 | Base = true | |
154 | Extend = true | |
143 | 155 | ` |
144 | 156 | |
145 | 157 | const confNonUniqueSectionDataStruct = `[Interface] |
201 | 213 | ` |
202 | 214 | |
203 | 215 | 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) { | |
206 | 218 | 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()) | |
218 | 230 | |
219 | 231 | 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) { | |
266 | 277 | 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) | |
287 | 298 | |
288 | 299 | f.NameMapper = func(raw string) string { |
289 | 300 | if raw == "Byte" { |
291 | 302 | } |
292 | 303 | return raw |
293 | 304 | } |
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) { | |
300 | 311 | 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) | |
309 | 320 | 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) | |
334 | 355 | }) |
335 | 356 | }) |
336 | 357 | |
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(` | |
339 | 360 | name=bruce |
340 | 361 | age=a30`)) |
341 | So(err, ShouldBeNil) | |
362 | require.NoError(t, err) | |
342 | 363 | |
343 | 364 | type Strict struct { |
344 | 365 | Name string `ini:"name"` |
346 | 367 | } |
347 | 368 | s := new(Strict) |
348 | 369 | |
349 | So(f.Section("").StrictMapTo(s), ShouldNotBeNil) | |
370 | assert.Error(t, f.Section("").StrictMapTo(s)) | |
350 | 371 | }) |
351 | 372 | |
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(` | |
354 | 375 | names=alice, bruce`)) |
355 | So(err, ShouldBeNil) | |
376 | require.NoError(t, err) | |
356 | 377 | |
357 | 378 | type Strict struct { |
358 | 379 | Names []string `ini:"names"` |
359 | 380 | } |
360 | 381 | s := new(Strict) |
361 | 382 | |
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)) | |
364 | 385 | }) |
365 | 386 | } |
366 | 387 | |
367 | 388 | 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) | |
372 | 393 | ts := new(testNonUniqueSectionsStruct) |
373 | 394 | |
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) { | |
392 | 413 | newPeer := new(testPeer) |
393 | 414 | newPeerSlice := make([]testPeer, 0) |
394 | 415 | |
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) | |
397 | 418 | |
398 | 419 | // 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]) | |
404 | 425 | |
405 | 426 | // 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) | |
416 | 481 | }) |
417 | 482 | }) |
418 | 483 | } |
419 | 484 | |
420 | 485 | func Test_ReflectFromStruct(t *testing.T) { |
421 | Convey("Reflect from struct", t, func() { | |
486 | t.Run("reflect from struct", func(t *testing.T) { | |
422 | 487 | type Embeded struct { |
423 | 488 | Dates []time.Time `delim:"|" comment:"Time data"` |
424 | 489 | Places []string |
439 | 504 | GPA float64 |
440 | 505 | Date time.Time |
441 | 506 | NeverMind string `ini:"-"` |
507 | ignored string | |
442 | 508 | *Embeded `ini:"infos" comment:"Embeded section"` |
443 | 509 | } |
444 | 510 | |
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", | |
448 | 514 | &Embeded{ |
449 | []time.Time{t, t}, | |
515 | []time.Time{ti, ti}, | |
450 | 516 | []string{"HangZhou", "Boston"}, |
451 | 517 | []int{1993, 1994}, |
452 | 518 | []int64{10010, 10086}, |
456 | 522 | []bool{true, false}, |
457 | 523 | []int{}, |
458 | 524 | }} |
459 | cfg := ini.Empty() | |
460 | So(ini.ReflectFrom(cfg, a), ShouldBeNil) | |
525 | cfg := Empty() | |
526 | assert.NoError(t, ReflectFrom(cfg, a)) | |
461 | 527 | |
462 | 528 | var buf bytes.Buffer |
463 | 529 | _, 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 | |
466 | 532 | Male = true |
467 | 533 | Optional = |
468 | 534 | ; Author's age |
484 | 550 | Flags = true,false |
485 | 551 | None = |
486 | 552 | |
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() | |
495 | 563 | type SpecialStruct struct { |
496 | 564 | FirstName string `ini:"first_name"` |
497 | LastName string `ini:"last_name"` | |
565 | LastName string `ini:"last_name,omitempty"` | |
498 | 566 | JustOmitMe string `ini:"omitempty"` |
499 | 567 | LastLogin time.Time `ini:"last_login,omitempty"` |
500 | 568 | LastLogin2 time.Time `ini:",omitempty"` |
501 | 569 | 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)) | |
505 | 584 | |
506 | 585 | var buf bytes.Buffer |
507 | 586 | _, err = cfg.WriteTo(&buf) |
508 | So(buf.String(), ShouldEqual, `first_name = John | |
587 | assert.Equal(t, `first_name = John | |
509 | 588 | last_name = Doe |
510 | 589 | omitempty = 9 |
511 | 590 | |
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 | ) | |
513 | 629 | }) |
514 | 630 | }) |
515 | 631 | } |
516 | 632 | |
517 | 633 | 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) { | |
519 | 635 | nonUnique := &testNonUniqueSectionsStruct{ |
520 | 636 | Interface: testInterface{ |
521 | 637 | Address: "10.2.0.1/24", |
536 | 652 | }, |
537 | 653 | } |
538 | 654 | |
539 | cfg := ini.Empty(ini.LoadOptions{ | |
655 | cfg := Empty(LoadOptions{ | |
540 | 656 | AllowNonUniqueSections: true, |
541 | 657 | }) |
542 | 658 | |
543 | So(ini.ReflectFrom(cfg, nonUnique), ShouldBeNil) | |
659 | assert.NoError(t, ReflectFrom(cfg, nonUnique)) | |
544 | 660 | |
545 | 661 | var buf bytes.Buffer |
546 | 662 | _, 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()) | |
549 | 665 | |
550 | 666 | // note: using ReflectFrom from should overwrite the existing sections |
551 | 667 | err = cfg.Section("Peer").ReflectFrom([]*testPeer{ |
561 | 677 | }, |
562 | 678 | }) |
563 | 679 | |
564 | So(err, ShouldBeNil) | |
680 | require.NoError(t, err) | |
565 | 681 | |
566 | 682 | buf = bytes.Buffer{} |
567 | 683 | _, err = cfg.WriteTo(&buf) |
568 | So(err, ShouldBeNil) | |
569 | So(buf.String(), ShouldEqual, `[Interface] | |
684 | require.NoError(t, err) | |
685 | assert.Equal(t, `[Interface] | |
570 | 686 | Address = 10.2.0.1/24 |
571 | 687 | ListenPort = 34777 |
572 | 688 | PrivateKey = privServerKey |
581 | 697 | PresharedKey = psKey4 |
582 | 698 | AllowedIPs = 10.2.0.5/32,fd00:2::5/128 |
583 | 699 | |
584 | `) | |
700 | `, | |
701 | buf.String(), | |
702 | ) | |
585 | 703 | |
586 | 704 | // note: using ReflectFrom from should overwrite the existing sections |
587 | 705 | err = cfg.Section("Peer").ReflectFrom(&testPeer{ |
590 | 708 | AllowedIPs: []string{"10.2.0.6/32,fd00:2::6/128"}, |
591 | 709 | }) |
592 | 710 | |
593 | So(err, ShouldBeNil) | |
711 | require.NoError(t, err) | |
594 | 712 | |
595 | 713 | buf = bytes.Buffer{} |
596 | 714 | _, err = cfg.WriteTo(&buf) |
597 | So(err, ShouldBeNil) | |
598 | So(buf.String(), ShouldEqual, `[Interface] | |
715 | require.NoError(t, err) | |
716 | assert.Equal(t, `[Interface] | |
599 | 717 | Address = 10.2.0.1/24 |
600 | 718 | ListenPort = 34777 |
601 | 719 | PrivateKey = privServerKey |
605 | 723 | PresharedKey = psKey5 |
606 | 724 | AllowedIPs = 10.2.0.6/32,fd00:2::6/128 |
607 | 725 | |
608 | `) | |
726 | `, | |
727 | buf.String(), | |
728 | ) | |
609 | 729 | }) |
610 | 730 | } |
611 | 731 | |
612 | 732 | // Inspired by https://github.com/go-ini/ini/issues/196 |
613 | 733 | 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) { | |
615 | 735 | type include struct { |
616 | 736 | Paths []string `ini:"path,omitempty,allowshadow"` |
617 | 737 | } |
618 | 738 | |
619 | cfg, err := ini.LoadSources(ini.LoadOptions{ | |
739 | cfg, err := LoadSources(LoadOptions{ | |
620 | 740 | AllowShadows: true, |
621 | 741 | }, []byte(` |
622 | 742 | [include] |
623 | 743 | path = /tmp/gpm-profiles/test5.profile |
624 | 744 | path = /tmp/gpm-profiles/test1.profile`)) |
625 | So(err, ShouldBeNil) | |
745 | require.NoError(t, err) | |
626 | 746 | |
627 | 747 | sec := cfg.Section("include") |
628 | 748 | inc := new(include) |
629 | 749 | err = sec.MapTo(inc) |
630 | So(err, ShouldBeNil) | |
750 | require.NoError(t, err) | |
631 | 751 | |
632 | 752 | err = sec.ReflectFrom(inc) |
633 | So(err, ShouldBeNil) | |
753 | require.NoError(t, err) | |
634 | 754 | |
635 | 755 | var buf bytes.Buffer |
636 | 756 | _, err = cfg.WriteTo(&buf) |
637 | So(err, ShouldBeNil) | |
638 | So(buf.String(), ShouldEqual, `[include] | |
757 | require.NoError(t, err) | |
758 | assert.Equal(t, `[include] | |
639 | 759 | path = /tmp/gpm-profiles/test5.profile |
640 | 760 | path = /tmp/gpm-profiles/test1.profile |
641 | 761 | |
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 | }) | |
643 | 830 | }) |
644 | 831 | } |
645 | 832 | |
648 | 835 | } |
649 | 836 | |
650 | 837 | 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 | |
659 | 846 | 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) | |
662 | 849 | }) |
663 | 850 | } |
664 | 851 | |
667 | 854 | } |
668 | 855 | |
669 | 856 | 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) { | |
671 | 858 | ds := new(testDurationStruct) |
672 | So(ini.MapTo(ds, []byte("Duration=16m49s")), ShouldBeNil) | |
859 | assert.NoError(t, MapTo(ds, []byte("Duration=16m49s"))) | |
673 | 860 | |
674 | 861 | 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()) | |
677 | 864 | }) |
678 | 865 | } |
679 | 866 | |
684 | 871 | |
685 | 872 | type Employers []*Employer |
686 | 873 | |
687 | func (es Employers) ReflectINIStruct(f *ini.File) error { | |
874 | func (es Employers) ReflectINIStruct(f *File) error { | |
688 | 875 | for _, e := range es { |
689 | 876 | f.Section(e.Name).Key("Title").SetValue(e.Title) |
690 | 877 | } |
693 | 880 | |
694 | 881 | // Inspired by https://github.com/go-ini/ini/issues/199 |
695 | 882 | 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) { | |
697 | 884 | p := &struct { |
698 | 885 | FirstName string |
699 | 886 | Employer Employers |
711 | 898 | }, |
712 | 899 | } |
713 | 900 | |
714 | f := ini.Empty() | |
715 | So(f.ReflectFrom(p), ShouldBeNil) | |
901 | f := Empty() | |
902 | assert.NoError(t, f.ReflectFrom(p)) | |
716 | 903 | |
717 | 904 | var buf bytes.Buffer |
718 | 905 | _, 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 | |
722 | 909 | |
723 | 910 | [Employer "VMware"] |
724 | 911 | Title = Staff II Engineer |
726 | 913 | [Employer "EMC"] |
727 | 914 | Title = Consultant Engineer |
728 | 915 | |
729 | `) | |
916 | `, | |
917 | buf.String(), | |
918 | ) | |
730 | 919 | }) |
731 | 920 | } |
83 | 83 | ADDRESS = """404 road, |
84 | 84 | NotFound, State, 50000""" |
85 | 85 | two_lines = how about continuation lines? |
86 | lots_of_lines = 1 2 3 4 | |
86 | lots_of_lines = "1 2 3 4 " | |
87 | 87 |
4 | 4 | |
5 | 5 | |
6 | 6 | 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 | |
9 | 9 | |
10 | 10 | value3 = . |
11 | 11 |