pax_global_header00006660000000000000000000000064136334225730014522gustar00rootroot0000000000000052 comment=39bc4ddcb8b9d0100f7a040816380ccda878b94a ini-1.55.0/000077500000000000000000000000001363342257300123715ustar00rootroot00000000000000ini-1.55.0/.github/000077500000000000000000000000001363342257300137315ustar00rootroot00000000000000ini-1.55.0/.github/ISSUE_TEMPLATE/000077500000000000000000000000001363342257300161145ustar00rootroot00000000000000ini-1.55.0/.github/ISSUE_TEMPLATE/bug_report.md000066400000000000000000000010151363342257300206030ustar00rootroot00000000000000--- name: Bug report about: Create a report to help us improve title: '' labels: '' assignees: '' --- **Describe the bug** A clear and concise description of what the bug is. **To Reproduce** A code snippet to reproduce the problem described above. **Expected behavior** A clear and concise description of what you expected to happen. **Screenshots** If applicable, add screenshots to help explain your problem. **Additional context** Add any other context about the problem here, or any suggestion to fix the problem. ini-1.55.0/.github/ISSUE_TEMPLATE/feature_request.md000066400000000000000000000011231363342257300216360ustar00rootroot00000000000000--- name: Feature request about: Suggest an idea for this project title: '' labels: '' assignees: '' --- **Is your feature request related to a problem? Please describe.** A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] **Describe the solution you'd like** A clear and concise description of what you want to happen. **Describe alternatives you've considered** A clear and concise description of any alternative solutions or features you've considered. **Additional context** Add any other context or screenshots about the feature request here. ini-1.55.0/.github/PULL_REQUEST_TEMPLATE.md000066400000000000000000000001271363342257300175320ustar00rootroot00000000000000### What problem should be fixed? ### Have you added test cases to catch the problem? ini-1.55.0/.github/workflows/000077500000000000000000000000001363342257300157665ustar00rootroot00000000000000ini-1.55.0/.github/workflows/lsif.yml000066400000000000000000000007071363342257300174520ustar00rootroot00000000000000name: LSIF on: [push] jobs: build: runs-on: ubuntu-latest steps: - uses: actions/checkout@v1 - name: Generate LSIF data uses: sourcegraph/lsif-go-action@master with: verbose: 'true' - name: Upload LSIF data uses: sourcegraph/lsif-upload-action@master continue-on-error: true with: endpoint: https://sourcegraph.com github_token: ${{ secrets.GITHUB_TOKEN }} ini-1.55.0/.gitignore000066400000000000000000000001511363342257300143560ustar00rootroot00000000000000testdata/conf_out.ini ini.sublime-project ini.sublime-workspace testdata/conf_reflect.ini .idea /.vscode ini-1.55.0/.travis.yml000066400000000000000000000006571363342257300145120ustar00rootroot00000000000000language: go os: linux dist: xenial go: - 1.6.x - 1.7.x - 1.8.x - 1.9.x - 1.10.x - 1.11.x - 1.12.x - 1.13.x - 1.14.x install: skip script: - go get golang.org/x/tools/cmd/cover - go get github.com/smartystreets/goconvey - mkdir -p $HOME/gopath/src/gopkg.in - ln -s $HOME/gopath/src/github.com/go-ini/ini $HOME/gopath/src/gopkg.in/ini.v1 - cd $HOME/gopath/src/gopkg.in/ini.v1 - go test -v -cover -race ini-1.55.0/LICENSE000066400000000000000000000240151363342257300134000ustar00rootroot00000000000000Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: You must give any other recipients of the Work or Derivative Works a copy of this License; and You must cause any modified files to carry prominent notices stating that You changed the files; and You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS APPENDIX: How to apply the Apache License to your work To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. Copyright 2014 Unknwon Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. ini-1.55.0/Makefile000066400000000000000000000003571363342257300140360ustar00rootroot00000000000000.PHONY: build test bench vet coverage build: vet bench test: go test -v -cover -race bench: go test -v -cover -test.bench=. -test.benchmem vet: go vet coverage: go test -coverprofile=c.out && go tool cover -html=c.out && rm c.out ini-1.55.0/README.md000066400000000000000000000024721363342257300136550ustar00rootroot00000000000000# INI [![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) ![](https://avatars0.githubusercontent.com/u/10216035?v=3&s=200) Package ini provides INI file read and write functionality in Go. ## Features - Load from multiple data sources(file, `[]byte`, `io.Reader` and `io.ReadCloser`) with overwrites. - Read with recursion values. - Read with parent-child sections. - Read with auto-increment key names. - Read with multiple-line values. - Read with tons of helper methods. - Read and convert values to Go types. - Read and **WRITE** comments of sections and keys. - Manipulate sections, keys and comments with ease. - Keep sections and keys in order as you parse and save. ## Installation The minimum requirement of Go is **1.6**. ```sh $ go get gopkg.in/ini.v1 ``` Please add `-u` flag to update in the future. ## Getting Help - [Getting Started](https://ini.unknwon.io/docs/intro/getting_started) - [API Documentation](https://gowalker.org/gopkg.in/ini.v1) ## License This project is under Apache v2 License. See the [LICENSE](LICENSE) file for the full license text. ini-1.55.0/bench_test.go000066400000000000000000000050771363342257300150470ustar00rootroot00000000000000// Copyright 2017 Unknwon // // Licensed under the Apache License, Version 2.0 (the "License"): you may // not use this file except in compliance with the License. You may obtain // a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the // License for the specific language governing permissions and limitations // under the License. package ini_test import ( "testing" "gopkg.in/ini.v1" ) func newTestFile(block bool) *ini.File { c, _ := ini.Load([]byte(confData)) c.BlockMode = block return c } func Benchmark_Key_Value(b *testing.B) { c := newTestFile(true) for i := 0; i < b.N; i++ { c.Section("").Key("NAME").Value() } } func Benchmark_Key_Value_NonBlock(b *testing.B) { c := newTestFile(false) for i := 0; i < b.N; i++ { c.Section("").Key("NAME").Value() } } func Benchmark_Key_Value_ViaSection(b *testing.B) { c := newTestFile(true) sec := c.Section("") for i := 0; i < b.N; i++ { sec.Key("NAME").Value() } } func Benchmark_Key_Value_ViaSection_NonBlock(b *testing.B) { c := newTestFile(false) sec := c.Section("") for i := 0; i < b.N; i++ { sec.Key("NAME").Value() } } func Benchmark_Key_Value_Direct(b *testing.B) { c := newTestFile(true) key := c.Section("").Key("NAME") for i := 0; i < b.N; i++ { key.Value() } } func Benchmark_Key_Value_Direct_NonBlock(b *testing.B) { c := newTestFile(false) key := c.Section("").Key("NAME") for i := 0; i < b.N; i++ { key.Value() } } func Benchmark_Key_String(b *testing.B) { c := newTestFile(true) for i := 0; i < b.N; i++ { _ = c.Section("").Key("NAME").String() } } func Benchmark_Key_String_NonBlock(b *testing.B) { c := newTestFile(false) for i := 0; i < b.N; i++ { _ = c.Section("").Key("NAME").String() } } func Benchmark_Key_String_ViaSection(b *testing.B) { c := newTestFile(true) sec := c.Section("") for i := 0; i < b.N; i++ { _ = sec.Key("NAME").String() } } func Benchmark_Key_String_ViaSection_NonBlock(b *testing.B) { c := newTestFile(false) sec := c.Section("") for i := 0; i < b.N; i++ { _ = sec.Key("NAME").String() } } func Benchmark_Key_SetValue(b *testing.B) { c := newTestFile(true) for i := 0; i < b.N; i++ { c.Section("").Key("NAME").SetValue("10") } } func Benchmark_Key_SetValue_VisSection(b *testing.B) { c := newTestFile(true) sec := c.Section("") for i := 0; i < b.N; i++ { sec.Key("NAME").SetValue("10") } } ini-1.55.0/data_source.go000066400000000000000000000036541363342257300152210ustar00rootroot00000000000000// Copyright 2019 Unknwon // // Licensed under the Apache License, Version 2.0 (the "License"): you may // not use this file except in compliance with the License. You may obtain // a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the // License for the specific language governing permissions and limitations // under the License. package ini import ( "bytes" "fmt" "io" "io/ioutil" "os" ) var ( _ dataSource = (*sourceFile)(nil) _ dataSource = (*sourceData)(nil) _ dataSource = (*sourceReadCloser)(nil) ) // dataSource is an interface that returns object which can be read and closed. type dataSource interface { ReadCloser() (io.ReadCloser, error) } // sourceFile represents an object that contains content on the local file system. type sourceFile struct { name string } func (s sourceFile) ReadCloser() (_ io.ReadCloser, err error) { return os.Open(s.name) } // sourceData represents an object that contains content in memory. type sourceData struct { data []byte } func (s *sourceData) ReadCloser() (io.ReadCloser, error) { return ioutil.NopCloser(bytes.NewReader(s.data)), nil } // sourceReadCloser represents an input stream with Close method. type sourceReadCloser struct { reader io.ReadCloser } func (s *sourceReadCloser) ReadCloser() (io.ReadCloser, error) { return s.reader, nil } func parseDataSource(source interface{}) (dataSource, error) { switch s := source.(type) { case string: return sourceFile{s}, nil case []byte: return &sourceData{s}, nil case io.Reader: return &sourceReadCloser{ioutil.NopCloser(s)}, nil case io.ReadCloser: return &sourceReadCloser{s}, nil default: return nil, fmt.Errorf("error parsing data source: unknown type %q", s) } } ini-1.55.0/deprecated.go000066400000000000000000000014421363342257300150210ustar00rootroot00000000000000// Copyright 2019 Unknwon // // Licensed under the Apache License, Version 2.0 (the "License"): you may // not use this file except in compliance with the License. You may obtain // a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the // License for the specific language governing permissions and limitations // under the License. package ini const ( // Deprecated: Use "DefaultSection" instead. DEFAULT_SECTION = DefaultSection ) var ( // Deprecated: AllCapsUnderscore converts to format ALL_CAPS_UNDERSCORE. AllCapsUnderscore = SnackCase ) ini-1.55.0/error.go000066400000000000000000000020721363342257300140520ustar00rootroot00000000000000// Copyright 2016 Unknwon // // Licensed under the Apache License, Version 2.0 (the "License"): you may // not use this file except in compliance with the License. You may obtain // a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the // License for the specific language governing permissions and limitations // under the License. package ini import ( "fmt" ) // ErrDelimiterNotFound indicates the error type of no delimiter is found which there should be one. type ErrDelimiterNotFound struct { Line string } // IsErrDelimiterNotFound returns true if the given error is an instance of ErrDelimiterNotFound. func IsErrDelimiterNotFound(err error) bool { _, ok := err.(ErrDelimiterNotFound) return ok } func (err ErrDelimiterNotFound) Error() string { return fmt.Sprintf("key-value delimiter not found: %s", err.Line) } ini-1.55.0/file.go000066400000000000000000000314421363342257300136430ustar00rootroot00000000000000// Copyright 2017 Unknwon // // Licensed under the Apache License, Version 2.0 (the "License"): you may // not use this file except in compliance with the License. You may obtain // a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the // License for the specific language governing permissions and limitations // under the License. package ini import ( "bytes" "errors" "fmt" "io" "io/ioutil" "os" "strings" "sync" ) // File represents a combination of one or more INI files in memory. type File struct { options LoadOptions dataSources []dataSource // Should make things safe, but sometimes doesn't matter. BlockMode bool lock sync.RWMutex // To keep data in order. sectionList []string // To keep track of the index of a section with same name. // This meta list is only used with non-unique section names are allowed. sectionIndexes []int // Actual data is stored here. sections map[string][]*Section NameMapper ValueMapper } // newFile initializes File object with given data sources. func newFile(dataSources []dataSource, opts LoadOptions) *File { if len(opts.KeyValueDelimiters) == 0 { opts.KeyValueDelimiters = "=:" } if len(opts.KeyValueDelimiterOnWrite) == 0 { opts.KeyValueDelimiterOnWrite = "=" } return &File{ BlockMode: true, dataSources: dataSources, sections: make(map[string][]*Section), options: opts, } } // Empty returns an empty file object. func Empty(opts ...LoadOptions) *File { var opt LoadOptions if len(opts) > 0 { opt = opts[0] } // Ignore error here, we are sure our data is good. f, _ := LoadSources(opt, []byte("")) return f } // NewSection creates a new section. func (f *File) NewSection(name string) (*Section, error) { if len(name) == 0 { return nil, errors.New("empty section name") } if f.options.Insensitive && name != DefaultSection { name = strings.ToLower(name) } if f.BlockMode { f.lock.Lock() defer f.lock.Unlock() } if !f.options.AllowNonUniqueSections && inSlice(name, f.sectionList) { return f.sections[name][0], nil } f.sectionList = append(f.sectionList, name) // NOTE: Append to indexes must happen before appending to sections, // otherwise index will have off-by-one problem. f.sectionIndexes = append(f.sectionIndexes, len(f.sections[name])) sec := newSection(f, name) f.sections[name] = append(f.sections[name], sec) return sec, nil } // NewRawSection creates a new section with an unparseable body. func (f *File) NewRawSection(name, body string) (*Section, error) { section, err := f.NewSection(name) if err != nil { return nil, err } section.isRawSection = true section.rawBody = body return section, nil } // NewSections creates a list of sections. func (f *File) NewSections(names ...string) (err error) { for _, name := range names { if _, err = f.NewSection(name); err != nil { return err } } return nil } // GetSection returns section by given name. func (f *File) GetSection(name string) (*Section, error) { secs, err := f.SectionsByName(name) if err != nil { return nil, err } return secs[0], err } // SectionsByName returns all sections with given name. func (f *File) SectionsByName(name string) ([]*Section, error) { if len(name) == 0 { name = DefaultSection } if f.options.Insensitive { name = strings.ToLower(name) } if f.BlockMode { f.lock.RLock() defer f.lock.RUnlock() } secs := f.sections[name] if len(secs) == 0 { return nil, fmt.Errorf("section %q does not exist", name) } return secs, nil } // Section assumes named section exists and returns a zero-value when not. func (f *File) Section(name string) *Section { sec, err := f.GetSection(name) if err != nil { // Note: It's OK here because the only possible error is empty section name, // but if it's empty, this piece of code won't be executed. sec, _ = f.NewSection(name) return sec } return sec } // SectionWithIndex assumes named section exists and returns a new section when not. func (f *File) SectionWithIndex(name string, index int) *Section { secs, err := f.SectionsByName(name) if err != nil || len(secs) <= index { // NOTE: It's OK here because the only possible error is empty section name, // but if it's empty, this piece of code won't be executed. newSec, _ := f.NewSection(name) return newSec } return secs[index] } // Sections returns a list of Section stored in the current instance. func (f *File) Sections() []*Section { if f.BlockMode { f.lock.RLock() defer f.lock.RUnlock() } sections := make([]*Section, len(f.sectionList)) for i, name := range f.sectionList { sections[i] = f.sections[name][f.sectionIndexes[i]] } return sections } // ChildSections returns a list of child sections of given section name. func (f *File) ChildSections(name string) []*Section { return f.Section(name).ChildSections() } // SectionStrings returns list of section names. func (f *File) SectionStrings() []string { list := make([]string, len(f.sectionList)) copy(list, f.sectionList) return list } // DeleteSection deletes a section or all sections with given name. func (f *File) DeleteSection(name string) { secs, err := f.SectionsByName(name) if err != nil { return } for i := 0; i < len(secs); i++ { // For non-unique sections, it is always needed to remove the first one so // in the next iteration, the subsequent section continue having index 0. // Ignoring the error as index 0 never returns an error. _ = f.DeleteSectionWithIndex(name, 0) } } // DeleteSectionWithIndex deletes a section with given name and index. func (f *File) DeleteSectionWithIndex(name string, index int) error { if !f.options.AllowNonUniqueSections && index != 0 { return fmt.Errorf("delete section with non-zero index is only allowed when non-unique sections is enabled") } if len(name) == 0 { name = DefaultSection } if f.options.Insensitive { name = strings.ToLower(name) } if f.BlockMode { f.lock.Lock() defer f.lock.Unlock() } // Count occurrences of the sections occurrences := 0 sectionListCopy := make([]string, len(f.sectionList)) copy(sectionListCopy, f.sectionList) for i, s := range sectionListCopy { if s != name { continue } if occurrences == index { if len(f.sections[name]) <= 1 { delete(f.sections, name) // The last one in the map } else { f.sections[name] = append(f.sections[name][:index], f.sections[name][index+1:]...) } // Fix section lists f.sectionList = append(f.sectionList[:i], f.sectionList[i+1:]...) f.sectionIndexes = append(f.sectionIndexes[:i], f.sectionIndexes[i+1:]...) } else if occurrences > index { // Fix the indices of all following sections with this name. f.sectionIndexes[i-1]-- } occurrences++ } return nil } func (f *File) reload(s dataSource) error { r, err := s.ReadCloser() if err != nil { return err } defer r.Close() return f.parse(r) } // Reload reloads and parses all data sources. func (f *File) Reload() (err error) { for _, s := range f.dataSources { if err = f.reload(s); err != nil { // In loose mode, we create an empty default section for nonexistent files. if os.IsNotExist(err) && f.options.Loose { _ = f.parse(bytes.NewBuffer(nil)) continue } return err } } return nil } // Append appends one or more data sources and reloads automatically. func (f *File) Append(source interface{}, others ...interface{}) error { ds, err := parseDataSource(source) if err != nil { return err } f.dataSources = append(f.dataSources, ds) for _, s := range others { ds, err = parseDataSource(s) if err != nil { return err } f.dataSources = append(f.dataSources, ds) } return f.Reload() } func (f *File) writeToBuffer(indent string) (*bytes.Buffer, error) { equalSign := DefaultFormatLeft + f.options.KeyValueDelimiterOnWrite + DefaultFormatRight if PrettyFormat || PrettyEqual { equalSign = fmt.Sprintf(" %s ", f.options.KeyValueDelimiterOnWrite) } // Use buffer to make sure target is safe until finish encoding. buf := bytes.NewBuffer(nil) for i, sname := range f.sectionList { sec := f.SectionWithIndex(sname, f.sectionIndexes[i]) if len(sec.Comment) > 0 { // Support multiline comments lines := strings.Split(sec.Comment, LineBreak) for i := range lines { if lines[i][0] != '#' && lines[i][0] != ';' { lines[i] = "; " + lines[i] } else { lines[i] = lines[i][:1] + " " + strings.TrimSpace(lines[i][1:]) } if _, err := buf.WriteString(lines[i] + LineBreak); err != nil { return nil, err } } } if i > 0 || DefaultHeader { if _, err := buf.WriteString("[" + sname + "]" + LineBreak); err != nil { return nil, err } } else { // Write nothing if default section is empty if len(sec.keyList) == 0 { continue } } if sec.isRawSection { if _, err := buf.WriteString(sec.rawBody); err != nil { return nil, err } if PrettySection { // Put a line between sections if _, err := buf.WriteString(LineBreak); err != nil { return nil, err } } continue } // Count and generate alignment length and buffer spaces using the // longest key. Keys may be modified if they contain certain characters so // we need to take that into account in our calculation. alignLength := 0 if PrettyFormat { for _, kname := range sec.keyList { keyLength := len(kname) // First case will surround key by ` and second by """ if strings.Contains(kname, "\"") || strings.ContainsAny(kname, f.options.KeyValueDelimiters) { keyLength += 2 } else if strings.Contains(kname, "`") { keyLength += 6 } if keyLength > alignLength { alignLength = keyLength } } } alignSpaces := bytes.Repeat([]byte(" "), alignLength) KeyList: for _, kname := range sec.keyList { key := sec.Key(kname) if len(key.Comment) > 0 { if len(indent) > 0 && sname != DefaultSection { buf.WriteString(indent) } // Support multiline comments lines := strings.Split(key.Comment, LineBreak) for i := range lines { if lines[i][0] != '#' && lines[i][0] != ';' { lines[i] = "; " + strings.TrimSpace(lines[i]) } else { lines[i] = lines[i][:1] + " " + strings.TrimSpace(lines[i][1:]) } if _, err := buf.WriteString(lines[i] + LineBreak); err != nil { return nil, err } } } if len(indent) > 0 && sname != DefaultSection { buf.WriteString(indent) } switch { case key.isAutoIncrement: kname = "-" case strings.Contains(kname, "\"") || strings.ContainsAny(kname, f.options.KeyValueDelimiters): kname = "`" + kname + "`" case strings.Contains(kname, "`"): kname = `"""` + kname + `"""` } for _, val := range key.ValueWithShadows() { if _, err := buf.WriteString(kname); err != nil { return nil, err } if key.isBooleanType { if kname != sec.keyList[len(sec.keyList)-1] { buf.WriteString(LineBreak) } continue KeyList } // Write out alignment spaces before "=" sign if PrettyFormat { buf.Write(alignSpaces[:alignLength-len(kname)]) } // In case key value contains "\n", "`", "\"", "#" or ";" if strings.ContainsAny(val, "\n`") { val = `"""` + val + `"""` } else if !f.options.IgnoreInlineComment && strings.ContainsAny(val, "#;") { val = "`" + val + "`" } if _, err := buf.WriteString(equalSign + val + LineBreak); err != nil { return nil, err } } for _, val := range key.nestedValues { if _, err := buf.WriteString(indent + " " + val + LineBreak); err != nil { return nil, err } } } if PrettySection { // Put a line between sections if _, err := buf.WriteString(LineBreak); err != nil { return nil, err } } } return buf, nil } // WriteToIndent writes content into io.Writer with given indention. // If PrettyFormat has been set to be true, // it will align "=" sign with spaces under each section. func (f *File) WriteToIndent(w io.Writer, indent string) (int64, error) { buf, err := f.writeToBuffer(indent) if err != nil { return 0, err } return buf.WriteTo(w) } // WriteTo writes file content into io.Writer. func (f *File) WriteTo(w io.Writer) (int64, error) { return f.WriteToIndent(w, "") } // SaveToIndent writes content to file system with given value indention. func (f *File) SaveToIndent(filename, indent string) error { // Note: Because we are truncating with os.Create, // so it's safer to save to a temporary file location and rename afte done. buf, err := f.writeToBuffer(indent) if err != nil { return err } return ioutil.WriteFile(filename, buf.Bytes(), 0666) } // SaveTo writes content to file system. func (f *File) SaveTo(filename string) error { return f.SaveToIndent(filename, "") } ini-1.55.0/file_test.go000066400000000000000000000272321363342257300147040ustar00rootroot00000000000000// Copyright 2017 Unknwon // // Licensed under the Apache License, Version 2.0 (the "License"): you may // not use this file except in compliance with the License. You may obtain // a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the // License for the specific language governing permissions and limitations // under the License. package ini_test import ( "bytes" "io/ioutil" "testing" . "github.com/smartystreets/goconvey/convey" "gopkg.in/ini.v1" ) func TestEmpty(t *testing.T) { Convey("Create an empty object", t, func() { f := ini.Empty() So(f, ShouldNotBeNil) // Should only have the default section So(len(f.Sections()), ShouldEqual, 1) // Default section should not contain any key So(len(f.Section("").Keys()), ShouldBeZeroValue) }) } func TestFile_NewSection(t *testing.T) { Convey("Create a new section", t, func() { f := ini.Empty() So(f, ShouldNotBeNil) sec, err := f.NewSection("author") So(err, ShouldBeNil) So(sec, ShouldNotBeNil) So(sec.Name(), ShouldEqual, "author") So(f.SectionStrings(), ShouldResemble, []string{ini.DefaultSection, "author"}) Convey("With duplicated name", func() { sec, err := f.NewSection("author") So(err, ShouldBeNil) So(sec, ShouldNotBeNil) // Does nothing if section already exists So(f.SectionStrings(), ShouldResemble, []string{ini.DefaultSection, "author"}) }) Convey("With empty string", func() { _, err := f.NewSection("") So(err, ShouldNotBeNil) }) }) } func TestFile_NonUniqueSection(t *testing.T) { Convey("Read and write non-unique sections", t, func() { f, err := ini.LoadSources(ini.LoadOptions{ AllowNonUniqueSections: true, }, []byte(`[Interface] Address = 192.168.2.1 PrivateKey = ListenPort = 51820 [Peer] PublicKey = AllowedIPs = 192.168.2.2/32 [Peer] PublicKey = AllowedIPs = 192.168.2.3/32`)) So(err, ShouldBeNil) So(f, ShouldNotBeNil) sec, err := f.NewSection("Peer") So(err, ShouldBeNil) So(f, ShouldNotBeNil) sec.NewKey("PublicKey", "") sec.NewKey("AllowedIPs", "192.168.2.4/32") var buf bytes.Buffer _, err = f.WriteTo(&buf) So(err, ShouldBeNil) str := buf.String() So(str, ShouldEqual, `[Interface] Address = 192.168.2.1 PrivateKey = ListenPort = 51820 [Peer] PublicKey = AllowedIPs = 192.168.2.2/32 [Peer] PublicKey = AllowedIPs = 192.168.2.3/32 [Peer] PublicKey = AllowedIPs = 192.168.2.4/32 `) }) Convey("Delete non-unique section", t, func() { f, err := ini.LoadSources(ini.LoadOptions{ AllowNonUniqueSections: true, }, []byte(`[Interface] Address = 192.168.2.1 PrivateKey = ListenPort = 51820 [Peer] PublicKey = AllowedIPs = 192.168.2.2/32 [Peer] PublicKey = AllowedIPs = 192.168.2.3/32 [Peer] PublicKey = AllowedIPs = 192.168.2.4/32 `)) So(err, ShouldBeNil) So(f, ShouldNotBeNil) err = f.DeleteSectionWithIndex("Peer", 1) So(err, ShouldBeNil) var buf bytes.Buffer _, err = f.WriteTo(&buf) So(err, ShouldBeNil) str := buf.String() So(str, ShouldEqual, `[Interface] Address = 192.168.2.1 PrivateKey = ListenPort = 51820 [Peer] PublicKey = AllowedIPs = 192.168.2.2/32 [Peer] PublicKey = AllowedIPs = 192.168.2.4/32 `) }) Convey("Delete all sections", t, func() { f := ini.Empty(ini.LoadOptions{ AllowNonUniqueSections: true, }) So(f, ShouldNotBeNil) f.NewSections("Interface", "Peer", "Peer") So(f.SectionStrings(), ShouldResemble, []string{ini.DefaultSection, "Interface", "Peer", "Peer"}) f.DeleteSection("Peer") So(f.SectionStrings(), ShouldResemble, []string{ini.DefaultSection, "Interface"}) }) } func TestFile_NewRawSection(t *testing.T) { Convey("Create a new raw section", t, func() { f := ini.Empty() So(f, ShouldNotBeNil) sec, err := f.NewRawSection("comments", `1111111111111111111000000000000000001110000 111111111111111111100000000000111000000000`) So(err, ShouldBeNil) So(sec, ShouldNotBeNil) So(sec.Name(), ShouldEqual, "comments") So(f.SectionStrings(), ShouldResemble, []string{ini.DefaultSection, "comments"}) So(f.Section("comments").Body(), ShouldEqual, `1111111111111111111000000000000000001110000 111111111111111111100000000000111000000000`) Convey("With duplicated name", func() { sec, err := f.NewRawSection("comments", `1111111111111111111000000000000000001110000`) So(err, ShouldBeNil) So(sec, ShouldNotBeNil) So(f.SectionStrings(), ShouldResemble, []string{ini.DefaultSection, "comments"}) // Overwrite previous existed section So(f.Section("comments").Body(), ShouldEqual, `1111111111111111111000000000000000001110000`) }) Convey("With empty string", func() { _, err := f.NewRawSection("", "") So(err, ShouldNotBeNil) }) }) } func TestFile_NewSections(t *testing.T) { Convey("Create new sections", t, func() { f := ini.Empty() So(f, ShouldNotBeNil) So(f.NewSections("package", "author"), ShouldBeNil) So(f.SectionStrings(), ShouldResemble, []string{ini.DefaultSection, "package", "author"}) Convey("With duplicated name", func() { So(f.NewSections("author", "features"), ShouldBeNil) // Ignore section already exists So(f.SectionStrings(), ShouldResemble, []string{ini.DefaultSection, "package", "author", "features"}) }) Convey("With empty string", func() { So(f.NewSections("", ""), ShouldNotBeNil) }) }) } func TestFile_GetSection(t *testing.T) { Convey("Get a section", t, func() { f, err := ini.Load(fullConf) So(err, ShouldBeNil) So(f, ShouldNotBeNil) sec, err := f.GetSection("author") So(err, ShouldBeNil) So(sec, ShouldNotBeNil) So(sec.Name(), ShouldEqual, "author") Convey("Section not exists", func() { _, err := f.GetSection("404") So(err, ShouldNotBeNil) }) }) } func TestFile_Section(t *testing.T) { Convey("Get a section", t, func() { f, err := ini.Load(fullConf) So(err, ShouldBeNil) So(f, ShouldNotBeNil) sec := f.Section("author") So(sec, ShouldNotBeNil) So(sec.Name(), ShouldEqual, "author") Convey("Section not exists", func() { sec := f.Section("404") So(sec, ShouldNotBeNil) So(sec.Name(), ShouldEqual, "404") }) }) Convey("Get default section in lower case with insensitive load", t, func() { f, err := ini.InsensitiveLoad([]byte(` [default] NAME = ini VERSION = v1`)) So(err, ShouldBeNil) So(f, ShouldNotBeNil) So(f.Section("").Key("name").String(), ShouldEqual, "ini") So(f.Section("").Key("version").String(), ShouldEqual, "v1") }) } func TestFile_Sections(t *testing.T) { Convey("Get all sections", t, func() { f, err := ini.Load(fullConf) So(err, ShouldBeNil) So(f, ShouldNotBeNil) secs := f.Sections() names := []string{ini.DefaultSection, "author", "package", "package.sub", "features", "types", "array", "note", "comments", "string escapes", "advance"} So(len(secs), ShouldEqual, len(names)) for i, name := range names { So(secs[i].Name(), ShouldEqual, name) } }) } func TestFile_ChildSections(t *testing.T) { Convey("Get child sections by parent name", t, func() { f, err := ini.Load([]byte(` [node] [node.biz1] [node.biz2] [node.biz3] [node.bizN] `)) So(err, ShouldBeNil) So(f, ShouldNotBeNil) children := f.ChildSections("node") names := []string{"node.biz1", "node.biz2", "node.biz3", "node.bizN"} So(len(children), ShouldEqual, len(names)) for i, name := range names { So(children[i].Name(), ShouldEqual, name) } }) } func TestFile_SectionStrings(t *testing.T) { Convey("Get all section names", t, func() { f, err := ini.Load(fullConf) So(err, ShouldBeNil) So(f, ShouldNotBeNil) So(f.SectionStrings(), ShouldResemble, []string{ini.DefaultSection, "author", "package", "package.sub", "features", "types", "array", "note", "comments", "string escapes", "advance"}) }) } func TestFile_DeleteSection(t *testing.T) { Convey("Delete a section", t, func() { f := ini.Empty() So(f, ShouldNotBeNil) f.NewSections("author", "package", "features") f.DeleteSection("features") f.DeleteSection("") So(f.SectionStrings(), ShouldResemble, []string{"author", "package"}) }) } func TestFile_Append(t *testing.T) { Convey("Append a data source", t, func() { f := ini.Empty() So(f, ShouldNotBeNil) So(f.Append(minimalConf, []byte(` [author] NAME = Unknwon`)), ShouldBeNil) Convey("With bad input", func() { So(f.Append(123), ShouldNotBeNil) So(f.Append(minimalConf, 123), ShouldNotBeNil) }) }) } func TestFile_WriteTo(t *testing.T) { Convey("Write content to somewhere", t, func() { f, err := ini.Load(fullConf) So(err, ShouldBeNil) So(f, ShouldNotBeNil) f.Section("author").Comment = `Information about package author # Bio can be written in multiple lines.` f.Section("author").Key("NAME").Comment = "This is author name" f.Section("note").NewBooleanKey("boolean_key") f.Section("note").NewKey("more", "notes") var buf bytes.Buffer _, err = f.WriteTo(&buf) So(err, ShouldBeNil) golden := "testdata/TestFile_WriteTo.golden" if *update { ioutil.WriteFile(golden, buf.Bytes(), 0644) } expected, err := ioutil.ReadFile(golden) So(err, ShouldBeNil) So(buf.String(), ShouldEqual, string(expected)) }) Convey("Support multiline comments", t, func() { f, err := ini.Load([]byte(` # # general.domain # # Domain name of XX system. domain = mydomain.com `)) So(err, ShouldBeNil) f.Section("").Key("test").Comment = "Multiline\nComment" var buf bytes.Buffer _, err = f.WriteTo(&buf) So(err, ShouldBeNil) So(buf.String(), ShouldEqual, `# # general.domain # # Domain name of XX system. domain = mydomain.com ; Multiline ; Comment test = `) }) } func TestFile_SaveTo(t *testing.T) { Convey("Write content to somewhere", t, func() { f, err := ini.Load(fullConf) So(err, ShouldBeNil) So(f, ShouldNotBeNil) So(f.SaveTo("testdata/conf_out.ini"), ShouldBeNil) So(f.SaveToIndent("testdata/conf_out.ini", "\t"), ShouldBeNil) }) } func TestFile_WriteToWithOutputDelimiter(t *testing.T) { Convey("Write content to somewhere using a custom output delimiter", t, func() { f, err := ini.LoadSources(ini.LoadOptions{ KeyValueDelimiterOnWrite: "->", }, []byte(`[Others] Cities = HangZhou|Boston Visits = 1993-10-07T20:17:05Z, 1993-10-07T20:17:05Z Years = 1993,1994 Numbers = 10010,10086 Ages = 18,19 Populations = 12345678,98765432 Coordinates = 192.168,10.11 Flags = true,false Note = Hello world!`)) So(err, ShouldBeNil) So(f, ShouldNotBeNil) var actual bytes.Buffer var expected = []byte(`[Others] Cities -> HangZhou|Boston Visits -> 1993-10-07T20:17:05Z, 1993-10-07T20:17:05Z Years -> 1993,1994 Numbers -> 10010,10086 Ages -> 18,19 Populations -> 12345678,98765432 Coordinates -> 192.168,10.11 Flags -> true,false Note -> Hello world! `) _, err = f.WriteTo(&actual) So(err, ShouldBeNil) So(bytes.Equal(expected, actual.Bytes()), ShouldBeTrue) }) } // Inspired by https://github.com/go-ini/ini/issues/207 func TestReloadAfterShadowLoad(t *testing.T) { Convey("Reload file after ShadowLoad", t, func() { f, err := ini.ShadowLoad([]byte(` [slice] v = 1 v = 2 v = 3 `)) So(err, ShouldBeNil) So(f, ShouldNotBeNil) So(f.Section("slice").Key("v").ValueWithShadows(), ShouldResemble, []string{"1", "2", "3"}) So(f.Reload(), ShouldBeNil) So(f.Section("slice").Key("v").ValueWithShadows(), ShouldResemble, []string{"1", "2", "3"}) }) } ini-1.55.0/helper.go000066400000000000000000000013171363342257300142010ustar00rootroot00000000000000// Copyright 2019 Unknwon // // Licensed under the Apache License, Version 2.0 (the "License"): you may // not use this file except in compliance with the License. You may obtain // a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the // License for the specific language governing permissions and limitations // under the License. package ini func inSlice(str string, s []string) bool { for _, v := range s { if str == v { return true } } return false } ini-1.55.0/helper_test.go000066400000000000000000000015501363342257300152370ustar00rootroot00000000000000// Copyright 2019 Unknwon // // Licensed under the Apache License, Version 2.0 (the "License"): you may // not use this file except in compliance with the License. You may obtain // a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the // License for the specific language governing permissions and limitations // under the License. package ini import ( "testing" . "github.com/smartystreets/goconvey/convey" ) func Test_isSlice(t *testing.T) { Convey("Check if a string is in the slice", t, func() { ss := []string{"a", "b", "c"} So(inSlice("a", ss), ShouldBeTrue) So(inSlice("d", ss), ShouldBeFalse) }) } ini-1.55.0/ini.go000066400000000000000000000162111363342257300135000ustar00rootroot00000000000000// +build go1.6 // Copyright 2014 Unknwon // // Licensed under the Apache License, Version 2.0 (the "License"): you may // not use this file except in compliance with the License. You may obtain // a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the // License for the specific language governing permissions and limitations // under the License. // Package ini provides INI file read and write functionality in Go. package ini import ( "regexp" "runtime" ) const ( // DefaultSection is the name of default section. You can use this constant or the string literal. // In most of cases, an empty string is all you need to access the section. DefaultSection = "DEFAULT" // Maximum allowed depth when recursively substituing variable names. depthValues = 99 ) var ( // LineBreak is the delimiter to determine or compose a new line. // This variable will be changed to "\r\n" automatically on Windows at package init time. LineBreak = "\n" // Variable regexp pattern: %(variable)s varPattern = regexp.MustCompile(`%\(([^)]+)\)s`) // DefaultHeader explicitly writes default section header. DefaultHeader = false // PrettySection indicates whether to put a line between sections. PrettySection = true // PrettyFormat indicates whether to align "=" sign with spaces to produce pretty output // or reduce all possible spaces for compact format. PrettyFormat = true // PrettyEqual places spaces around "=" sign even when PrettyFormat is false. PrettyEqual = false // DefaultFormatLeft places custom spaces on the left when PrettyFormat and PrettyEqual are both disabled. DefaultFormatLeft = "" // DefaultFormatRight places custom spaces on the right when PrettyFormat and PrettyEqual are both disabled. DefaultFormatRight = "" ) func init() { if runtime.GOOS == "windows" { LineBreak = "\r\n" } } // LoadOptions contains all customized options used for load data source(s). type LoadOptions struct { // Loose indicates whether the parser should ignore nonexistent files or return error. Loose bool // Insensitive indicates whether the parser forces all section and key names to lowercase. Insensitive bool // IgnoreContinuation indicates whether to ignore continuation lines while parsing. IgnoreContinuation bool // IgnoreInlineComment indicates whether to ignore comments at the end of value and treat it as part of value. IgnoreInlineComment bool // SkipUnrecognizableLines indicates whether to skip unrecognizable lines that do not conform to key/value pairs. SkipUnrecognizableLines bool // AllowBooleanKeys indicates whether to allow boolean type keys or treat as value is missing. // This type of keys are mostly used in my.cnf. AllowBooleanKeys bool // AllowShadows indicates whether to keep track of keys with same name under same section. AllowShadows bool // AllowNestedValues indicates whether to allow AWS-like nested values. // Docs: http://docs.aws.amazon.com/cli/latest/topic/config-vars.html#nested-values AllowNestedValues bool // AllowPythonMultilineValues indicates whether to allow Python-like multi-line values. // Docs: https://docs.python.org/3/library/configparser.html#supported-ini-file-structure // Relevant quote: Values can also span multiple lines, as long as they are indented deeper // than the first line of the value. AllowPythonMultilineValues bool // SpaceBeforeInlineComment indicates whether to allow comment symbols (\# and \;) inside value. // Docs: https://docs.python.org/2/library/configparser.html // Quote: Comments may appear on their own in an otherwise empty line, or may be entered in lines holding values or section names. // In the latter case, they need to be preceded by a whitespace character to be recognized as a comment. SpaceBeforeInlineComment bool // UnescapeValueDoubleQuotes indicates whether to unescape double quotes inside value to regular format // when value is surrounded by double quotes, e.g. key="a \"value\"" => key=a "value" UnescapeValueDoubleQuotes bool // UnescapeValueCommentSymbols indicates to unescape comment symbols (\# and \;) inside value to regular format // when value is NOT surrounded by any quotes. // Note: UNSTABLE, behavior might change to only unescape inside double quotes but may noy necessary at all. UnescapeValueCommentSymbols bool // UnparseableSections stores a list of blocks that are allowed with raw content which do not otherwise // conform to key/value pairs. Specify the names of those blocks here. UnparseableSections []string // KeyValueDelimiters is the sequence of delimiters that are used to separate key and value. By default, it is "=:". KeyValueDelimiters string // KeyValueDelimiters is the delimiter that are used to separate key and value output. By default, it is "=". KeyValueDelimiterOnWrite string // PreserveSurroundedQuote indicates whether to preserve surrounded quote (single and double quotes). PreserveSurroundedQuote bool // DebugFunc is called to collect debug information (currently only useful to debug parsing Python-style multiline values). DebugFunc DebugFunc // ReaderBufferSize is the buffer size of the reader in bytes. ReaderBufferSize int // AllowNonUniqueSections indicates whether to allow sections with the same name multiple times. AllowNonUniqueSections bool } // DebugFunc is the type of function called to log parse events. type DebugFunc func(message string) // LoadSources allows caller to apply customized options for loading from data source(s). func LoadSources(opts LoadOptions, source interface{}, others ...interface{}) (_ *File, err error) { sources := make([]dataSource, len(others)+1) sources[0], err = parseDataSource(source) if err != nil { return nil, err } for i := range others { sources[i+1], err = parseDataSource(others[i]) if err != nil { return nil, err } } f := newFile(sources, opts) if err = f.Reload(); err != nil { return nil, err } return f, nil } // Load loads and parses from INI data sources. // Arguments can be mixed of file name with string type, or raw data in []byte. // It will return error if list contains nonexistent files. func Load(source interface{}, others ...interface{}) (*File, error) { return LoadSources(LoadOptions{}, source, others...) } // LooseLoad has exactly same functionality as Load function // except it ignores nonexistent files instead of returning error. func LooseLoad(source interface{}, others ...interface{}) (*File, error) { return LoadSources(LoadOptions{Loose: true}, source, others...) } // InsensitiveLoad has exactly same functionality as Load function // except it forces all section and key names to be lowercased. func InsensitiveLoad(source interface{}, others ...interface{}) (*File, error) { return LoadSources(LoadOptions{Insensitive: true}, source, others...) } // ShadowLoad has exactly same functionality as Load function // except it allows have shadow keys. func ShadowLoad(source interface{}, others ...interface{}) (*File, error) { return LoadSources(LoadOptions{AllowShadows: true}, source, others...) } ini-1.55.0/ini_python_multiline_test.go000066400000000000000000000231671363342257300202320ustar00rootroot00000000000000package ini_test import ( "path/filepath" "testing" . "github.com/smartystreets/goconvey/convey" "gopkg.in/ini.v1" ) type testData struct { Value1 string `ini:"value1"` Value2 string `ini:"value2"` Value3 string `ini:"value3"` } func TestMultiline(t *testing.T) { Convey("Parse Python-style multiline values", t, func() { path := filepath.Join("testdata", "multiline.ini") f, err := ini.LoadSources(ini.LoadOptions{ AllowPythonMultilineValues: true, ReaderBufferSize: 64 * 1024, /* Debug: func(m string) { fmt.Println(m) }, */ }, path) So(err, ShouldBeNil) So(f, ShouldNotBeNil) So(len(f.Sections()), ShouldEqual, 1) defaultSection := f.Section("") So(f.Section(""), ShouldNotBeNil) var testData testData err = defaultSection.MapTo(&testData) So(err, ShouldBeNil) So(testData.Value1, ShouldEqual, "some text here\nsome more text here\n\nthere is an empty line above and below\n") So(testData.Value2, ShouldEqual, "there is an empty line above\nthat is not indented so it should not be part\nof the value") So(testData.Value3, ShouldEqual, `. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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.`) }) } ini-1.55.0/ini_test.go000066400000000000000000000763761363342257300145610ustar00rootroot00000000000000// Copyright 2014 Unknwon // // Licensed under the Apache License, Version 2.0 (the "License"): you may // not use this file except in compliance with the License. You may obtain // a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the // License for the specific language governing permissions and limitations // under the License. package ini_test import ( "bytes" "flag" "io/ioutil" "testing" . "github.com/smartystreets/goconvey/convey" "gopkg.in/ini.v1" ) const ( confData = ` ; Package name NAME = ini ; Package version VERSION = v1 ; Package import path IMPORT_PATH = gopkg.in/%(NAME)s.%(VERSION)s # Information about package author # Bio can be written in multiple lines. [author] NAME = Unknwon ; Succeeding comment E-MAIL = fake@localhost GITHUB = https://github.com/%(NAME)s BIO = """Gopher. Coding addict. Good man. """ # Succeeding comment` minimalConf = "testdata/minimal.ini" fullConf = "testdata/full.ini" notFoundConf = "testdata/404.ini" ) var update = flag.Bool("update", false, "Update .golden files") func TestLoad(t *testing.T) { Convey("Load from good data sources", t, func() { f, err := ini.Load( "testdata/minimal.ini", []byte("NAME = ini\nIMPORT_PATH = gopkg.in/%(NAME)s.%(VERSION)s"), bytes.NewReader([]byte(`VERSION = v1`)), ioutil.NopCloser(bytes.NewReader([]byte("[author]\nNAME = Unknwon"))), ) So(err, ShouldBeNil) So(f, ShouldNotBeNil) // Validate values make sure all sources are loaded correctly sec := f.Section("") So(sec.Key("NAME").String(), ShouldEqual, "ini") So(sec.Key("VERSION").String(), ShouldEqual, "v1") So(sec.Key("IMPORT_PATH").String(), ShouldEqual, "gopkg.in/ini.v1") sec = f.Section("author") So(sec.Key("NAME").String(), ShouldEqual, "Unknwon") So(sec.Key("E-MAIL").String(), ShouldEqual, "u@gogs.io") }) Convey("Load from bad data sources", t, func() { Convey("Invalid input", func() { _, err := ini.Load(notFoundConf) So(err, ShouldNotBeNil) }) Convey("Unsupported type", func() { _, err := ini.Load(123) So(err, ShouldNotBeNil) }) }) Convey("Can't properly parse INI files containing `#` or `;` in value", t, func() { f, err := ini.Load([]byte(` [author] NAME = U#n#k#n#w#o#n GITHUB = U;n;k;n;w;o;n `)) So(err, ShouldBeNil) So(f, ShouldNotBeNil) sec := f.Section("author") nameValue := sec.Key("NAME").String() githubValue := sec.Key("GITHUB").String() So(nameValue, ShouldEqual, "U") So(githubValue, ShouldEqual, "U") }) Convey("Can't parse small python-compatible INI files", t, func() { f, err := ini.Load([]byte(` [long] long_rsa_private_key = -----BEGIN RSA PRIVATE KEY----- foo bar foobar barfoo -----END RSA PRIVATE KEY----- `)) So(err, ShouldNotBeNil) So(f, ShouldBeNil) So(err.Error(), ShouldEqual, "key-value delimiter not found: foo\n") }) Convey("Can't parse big python-compatible INI files", t, func() { f, err := ini.Load([]byte(` [long] long_rsa_private_key = -----BEGIN RSA PRIVATE KEY----- 1foo 2bar 3foobar 4barfoo 5foo 6bar 7foobar 8barfoo 9foo 10bar 11foobar 12barfoo 13foo 14bar 15foobar 16barfoo 17foo 18bar 19foobar 20barfoo 21foo 22bar 23foobar 24barfoo 25foo 26bar 27foobar 28barfoo 29foo 30bar 31foobar 32barfoo 33foo 34bar 35foobar 36barfoo 37foo 38bar 39foobar 40barfoo 41foo 42bar 43foobar 44barfoo 45foo 46bar 47foobar 48barfoo 49foo 50bar 51foobar 52barfoo 53foo 54bar 55foobar 56barfoo 57foo 58bar 59foobar 60barfoo 61foo 62bar 63foobar 64barfoo 65foo 66bar 67foobar 68barfoo 69foo 70bar 71foobar 72barfoo 73foo 74bar 75foobar 76barfoo 77foo 78bar 79foobar 80barfoo 81foo 82bar 83foobar 84barfoo 85foo 86bar 87foobar 88barfoo 89foo 90bar 91foobar 92barfoo 93foo 94bar 95foobar 96barfoo -----END RSA PRIVATE KEY----- `)) So(err, ShouldNotBeNil) So(f, ShouldBeNil) So(err.Error(), ShouldEqual, "key-value delimiter not found: 1foo\n") }) } func TestLooseLoad(t *testing.T) { Convey("Load from data sources with option `Loose` true", t, func() { f, err := ini.LoadSources(ini.LoadOptions{Loose: true}, notFoundConf, minimalConf) So(err, ShouldBeNil) So(f, ShouldNotBeNil) Convey("Inverse case", func() { _, err = ini.Load(notFoundConf) So(err, ShouldNotBeNil) }) }) } func TestInsensitiveLoad(t *testing.T) { Convey("Insensitive to section and key names", t, func() { f, err := ini.InsensitiveLoad(minimalConf) So(err, ShouldBeNil) So(f, ShouldNotBeNil) So(f.Section("Author").Key("e-mail").String(), ShouldEqual, "u@gogs.io") Convey("Write out", func() { var buf bytes.Buffer _, err := f.WriteTo(&buf) So(err, ShouldBeNil) So(buf.String(), ShouldEqual, `[author] e-mail = u@gogs.io `) }) Convey("Inverse case", func() { f, err := ini.Load(minimalConf) So(err, ShouldBeNil) So(f, ShouldNotBeNil) So(f.Section("Author").Key("e-mail").String(), ShouldBeEmpty) }) }) // Ref: https://github.com/go-ini/ini/issues/198 Convey("Insensitive load with default section", t, func() { f, err := ini.InsensitiveLoad([]byte(` user = unknwon [profile] email = unknwon@local `)) So(err, ShouldBeNil) So(f, ShouldNotBeNil) So(f.Section(ini.DefaultSection).Key("user").String(), ShouldEqual, "unknwon") }) } func TestLoadSources(t *testing.T) { Convey("Load from data sources with options", t, func() { Convey("with true `AllowPythonMultilineValues`", func() { Convey("Ignore nonexistent files", func() { f, err := ini.LoadSources(ini.LoadOptions{AllowPythonMultilineValues: true, Loose: true}, notFoundConf, minimalConf) So(err, ShouldBeNil) So(f, ShouldNotBeNil) Convey("Inverse case", func() { _, err = ini.LoadSources(ini.LoadOptions{AllowPythonMultilineValues: true}, notFoundConf) So(err, ShouldNotBeNil) }) }) Convey("Insensitive to section and key names", func() { f, err := ini.LoadSources(ini.LoadOptions{AllowPythonMultilineValues: true, Insensitive: true}, minimalConf) So(err, ShouldBeNil) So(f, ShouldNotBeNil) So(f.Section("Author").Key("e-mail").String(), ShouldEqual, "u@gogs.io") Convey("Write out", func() { var buf bytes.Buffer _, err := f.WriteTo(&buf) So(err, ShouldBeNil) So(buf.String(), ShouldEqual, `[author] e-mail = u@gogs.io `) }) Convey("Inverse case", func() { f, err := ini.LoadSources(ini.LoadOptions{AllowPythonMultilineValues: true}, minimalConf) So(err, ShouldBeNil) So(f, ShouldNotBeNil) So(f.Section("Author").Key("e-mail").String(), ShouldBeEmpty) }) }) Convey("Ignore continuation lines", func() { f, err := ini.LoadSources(ini.LoadOptions{ AllowPythonMultilineValues: true, IgnoreContinuation: true, }, []byte(` key1=a\b\ key2=c\d\ key3=value`)) So(err, ShouldBeNil) So(f, ShouldNotBeNil) So(f.Section("").Key("key1").String(), ShouldEqual, `a\b\`) So(f.Section("").Key("key2").String(), ShouldEqual, `c\d\`) So(f.Section("").Key("key3").String(), ShouldEqual, "value") Convey("Inverse case", func() { f, err := ini.LoadSources(ini.LoadOptions{AllowPythonMultilineValues: true}, []byte(` key1=a\b\ key2=c\d\`)) So(err, ShouldBeNil) So(f, ShouldNotBeNil) So(f.Section("").Key("key1").String(), ShouldEqual, `a\bkey2=c\d`) }) }) Convey("Ignore inline comments", func() { f, err := ini.LoadSources(ini.LoadOptions{ AllowPythonMultilineValues: true, IgnoreInlineComment: true, }, []byte(` key1=value ;comment key2=value2 #comment2 key3=val#ue #comment3`)) So(err, ShouldBeNil) So(f, ShouldNotBeNil) So(f.Section("").Key("key1").String(), ShouldEqual, `value ;comment`) So(f.Section("").Key("key2").String(), ShouldEqual, `value2 #comment2`) So(f.Section("").Key("key3").String(), ShouldEqual, `val#ue #comment3`) Convey("Inverse case", func() { f, err := ini.LoadSources(ini.LoadOptions{AllowPythonMultilineValues: true}, []byte(` key1=value ;comment key2=value2 #comment2`)) So(err, ShouldBeNil) So(f, ShouldNotBeNil) So(f.Section("").Key("key1").String(), ShouldEqual, `value`) So(f.Section("").Key("key1").Comment, ShouldEqual, `;comment`) So(f.Section("").Key("key2").String(), ShouldEqual, `value2`) So(f.Section("").Key("key2").Comment, ShouldEqual, `#comment2`) }) }) Convey("Skip unrecognizable lines", func() { f, err := ini.LoadSources(ini.LoadOptions{ SkipUnrecognizableLines: true, }, []byte(` GenerationDepth: 13 BiomeRarityScale: 100 ################ # Biome Groups # ################ BiomeGroup(NormalBiomes, 3, 99, RoofedForestEnchanted, ForestSakura, FloatingJungle BiomeGroup(IceBiomes, 4, 85, Ice Plains) `)) So(err, ShouldBeNil) So(f, ShouldNotBeNil) So(f.Section("").Key("GenerationDepth").String(), ShouldEqual, "13") So(f.Section("").Key("BiomeRarityScale").String(), ShouldEqual, "100") So(f.Section("").HasKey("BiomeGroup"), ShouldBeFalse) }) Convey("Allow boolean type keys", func() { f, err := ini.LoadSources(ini.LoadOptions{ AllowPythonMultilineValues: true, AllowBooleanKeys: true, }, []byte(` key1=hello #key2 key3`)) So(err, ShouldBeNil) So(f, ShouldNotBeNil) So(f.Section("").KeyStrings(), ShouldResemble, []string{"key1", "key3"}) So(f.Section("").Key("key3").MustBool(false), ShouldBeTrue) Convey("Write out", func() { var buf bytes.Buffer _, err := f.WriteTo(&buf) So(err, ShouldBeNil) So(buf.String(), ShouldEqual, `key1 = hello # key2 key3 `) }) Convey("Inverse case", func() { _, err := ini.LoadSources(ini.LoadOptions{AllowPythonMultilineValues: true}, []byte(` key1=hello #key2 key3`)) So(err, ShouldNotBeNil) }) }) Convey("Allow shadow keys", func() { f, err := ini.LoadSources(ini.LoadOptions{AllowShadows: true, AllowPythonMultilineValues: true}, []byte(` [remote "origin"] url = https://github.com/Antergone/test1.git url = https://github.com/Antergone/test2.git fetch = +refs/heads/*:refs/remotes/origin/*`)) So(err, ShouldBeNil) So(f, ShouldNotBeNil) So(f.Section(`remote "origin"`).Key("url").String(), ShouldEqual, "https://github.com/Antergone/test1.git") So(f.Section(`remote "origin"`).Key("url").ValueWithShadows(), ShouldResemble, []string{ "https://github.com/Antergone/test1.git", "https://github.com/Antergone/test2.git", }) So(f.Section(`remote "origin"`).Key("fetch").String(), ShouldEqual, "+refs/heads/*:refs/remotes/origin/*") Convey("Write out", func() { var buf bytes.Buffer _, err := f.WriteTo(&buf) So(err, ShouldBeNil) So(buf.String(), ShouldEqual, `[remote "origin"] url = https://github.com/Antergone/test1.git url = https://github.com/Antergone/test2.git fetch = +refs/heads/*:refs/remotes/origin/* `) }) Convey("Inverse case", func() { f, err := ini.LoadSources(ini.LoadOptions{AllowPythonMultilineValues: true}, []byte(` [remote "origin"] url = https://github.com/Antergone/test1.git url = https://github.com/Antergone/test2.git`)) So(err, ShouldBeNil) So(f, ShouldNotBeNil) So(f.Section(`remote "origin"`).Key("url").String(), ShouldEqual, "https://github.com/Antergone/test2.git") }) }) Convey("Unescape double quotes inside value", func() { f, err := ini.LoadSources(ini.LoadOptions{ AllowPythonMultilineValues: true, UnescapeValueDoubleQuotes: true, }, []byte(` create_repo="创建了仓库 %s"`)) So(err, ShouldBeNil) So(f, ShouldNotBeNil) So(f.Section("").Key("create_repo").String(), ShouldEqual, `创建了仓库 %s`) Convey("Inverse case", func() { f, err := ini.LoadSources(ini.LoadOptions{AllowPythonMultilineValues: true}, []byte(` create_repo="创建了仓库 %s"`)) So(err, ShouldBeNil) So(f, ShouldNotBeNil) So(f.Section("").Key("create_repo").String(), ShouldEqual, `"创建了仓库 %s"`) }) }) Convey("Unescape comment symbols inside value", func() { f, err := ini.LoadSources(ini.LoadOptions{ AllowPythonMultilineValues: true, IgnoreInlineComment: true, UnescapeValueCommentSymbols: true, }, []byte(` key = test value more text `)) So(err, ShouldBeNil) So(f, ShouldNotBeNil) So(f.Section("").Key("key").String(), ShouldEqual, `test value more text`) }) Convey("Can parse small python-compatible INI files", func() { f, err := ini.LoadSources(ini.LoadOptions{ AllowPythonMultilineValues: true, Insensitive: true, UnparseableSections: []string{"core_lesson", "comments"}, }, []byte(` [long] long_rsa_private_key = -----BEGIN RSA PRIVATE KEY----- foo bar foobar barfoo -----END RSA PRIVATE KEY----- multiline_list = first second third `)) So(err, ShouldBeNil) So(f, ShouldNotBeNil) So(f.Section("long").Key("long_rsa_private_key").String(), ShouldEqual, "-----BEGIN RSA PRIVATE KEY-----\nfoo\nbar\nfoobar\nbarfoo\n-----END RSA PRIVATE KEY-----") So(f.Section("long").Key("multiline_list").String(), ShouldEqual, "\nfirst\nsecond\nthird") }) Convey("Can parse big python-compatible INI files", func() { f, err := ini.LoadSources(ini.LoadOptions{ AllowPythonMultilineValues: true, Insensitive: true, UnparseableSections: []string{"core_lesson", "comments"}, }, []byte(` [long] long_rsa_private_key = -----BEGIN RSA PRIVATE KEY----- 1foo 2bar 3foobar 4barfoo 5foo 6bar 7foobar 8barfoo 9foo 10bar 11foobar 12barfoo 13foo 14bar 15foobar 16barfoo 17foo 18bar 19foobar 20barfoo 21foo 22bar 23foobar 24barfoo 25foo 26bar 27foobar 28barfoo 29foo 30bar 31foobar 32barfoo 33foo 34bar 35foobar 36barfoo 37foo 38bar 39foobar 40barfoo 41foo 42bar 43foobar 44barfoo 45foo 46bar 47foobar 48barfoo 49foo 50bar 51foobar 52barfoo 53foo 54bar 55foobar 56barfoo 57foo 58bar 59foobar 60barfoo 61foo 62bar 63foobar 64barfoo 65foo 66bar 67foobar 68barfoo 69foo 70bar 71foobar 72barfoo 73foo 74bar 75foobar 76barfoo 77foo 78bar 79foobar 80barfoo 81foo 82bar 83foobar 84barfoo 85foo 86bar 87foobar 88barfoo 89foo 90bar 91foobar 92barfoo 93foo 94bar 95foobar 96barfoo -----END RSA PRIVATE KEY----- `)) So(err, ShouldBeNil) So(f, ShouldNotBeNil) So(f.Section("long").Key("long_rsa_private_key").String(), ShouldEqual, `-----BEGIN RSA PRIVATE KEY----- 1foo 2bar 3foobar 4barfoo 5foo 6bar 7foobar 8barfoo 9foo 10bar 11foobar 12barfoo 13foo 14bar 15foobar 16barfoo 17foo 18bar 19foobar 20barfoo 21foo 22bar 23foobar 24barfoo 25foo 26bar 27foobar 28barfoo 29foo 30bar 31foobar 32barfoo 33foo 34bar 35foobar 36barfoo 37foo 38bar 39foobar 40barfoo 41foo 42bar 43foobar 44barfoo 45foo 46bar 47foobar 48barfoo 49foo 50bar 51foobar 52barfoo 53foo 54bar 55foobar 56barfoo 57foo 58bar 59foobar 60barfoo 61foo 62bar 63foobar 64barfoo 65foo 66bar 67foobar 68barfoo 69foo 70bar 71foobar 72barfoo 73foo 74bar 75foobar 76barfoo 77foo 78bar 79foobar 80barfoo 81foo 82bar 83foobar 84barfoo 85foo 86bar 87foobar 88barfoo 89foo 90bar 91foobar 92barfoo 93foo 94bar 95foobar 96barfoo -----END RSA PRIVATE KEY-----`) }) Convey("Allow unparsable sections", func() { f, err := ini.LoadSources(ini.LoadOptions{ AllowPythonMultilineValues: true, Insensitive: true, UnparseableSections: []string{"core_lesson", "comments"}, }, []byte(` Lesson_Location = 87 Lesson_Status = C Score = 3 Time = 00:02:30 [CORE_LESSON] my lesson state data – 1111111111111111111000000000000000001110000 111111111111111111100000000000111000000000 – end my lesson state data [COMMENTS] <1> This slide has the fuel listed in the wrong units `)) So(err, ShouldBeNil) So(f, ShouldNotBeNil) So(f.Section("").Key("score").String(), ShouldEqual, "3") So(f.Section("").Body(), ShouldBeEmpty) So(f.Section("core_lesson").Body(), ShouldEqual, `my lesson state data – 1111111111111111111000000000000000001110000 111111111111111111100000000000111000000000 – end my lesson state data`) So(f.Section("comments").Body(), ShouldEqual, `<1> This slide has the fuel listed in the wrong units `) Convey("Write out", func() { var buf bytes.Buffer _, err := f.WriteTo(&buf) So(err, ShouldBeNil) So(buf.String(), ShouldEqual, `lesson_location = 87 lesson_status = C score = 3 time = 00:02:30 [core_lesson] my lesson state data – 1111111111111111111000000000000000001110000 111111111111111111100000000000111000000000 – end my lesson state data [comments] <1> This slide has the fuel listed in the wrong units `) }) Convey("Inverse case", func() { _, err := ini.LoadSources(ini.LoadOptions{AllowPythonMultilineValues: true}, []byte(` [CORE_LESSON] my lesson state data – 1111111111111111111000000000000000001110000 111111111111111111100000000000111000000000 – end my lesson state data`)) So(err, ShouldNotBeNil) }) }) Convey("And false `SpaceBeforeInlineComment`", func() { Convey("Can't parse INI files containing `#` or `;` in value", func() { f, err := ini.LoadSources( ini.LoadOptions{AllowPythonMultilineValues: false, SpaceBeforeInlineComment: false}, []byte(` [author] NAME = U#n#k#n#w#o#n GITHUB = U;n;k;n;w;o;n `)) So(err, ShouldBeNil) So(f, ShouldNotBeNil) sec := f.Section("author") nameValue := sec.Key("NAME").String() githubValue := sec.Key("GITHUB").String() So(nameValue, ShouldEqual, "U") So(githubValue, ShouldEqual, "U") }) }) Convey("And true `SpaceBeforeInlineComment`", func() { Convey("Can parse INI files containing `#` or `;` in value", func() { f, err := ini.LoadSources( ini.LoadOptions{AllowPythonMultilineValues: false, SpaceBeforeInlineComment: true}, []byte(` [author] NAME = U#n#k#n#w#o#n GITHUB = U;n;k;n;w;o;n `)) So(err, ShouldBeNil) So(f, ShouldNotBeNil) sec := f.Section("author") nameValue := sec.Key("NAME").String() githubValue := sec.Key("GITHUB").String() So(nameValue, ShouldEqual, "U#n#k#n#w#o#n") So(githubValue, ShouldEqual, "U;n;k;n;w;o;n") }) }) }) Convey("with false `AllowPythonMultilineValues`", func() { Convey("Ignore nonexistent files", func() { f, err := ini.LoadSources(ini.LoadOptions{ AllowPythonMultilineValues: false, Loose: true, }, notFoundConf, minimalConf) So(err, ShouldBeNil) So(f, ShouldNotBeNil) Convey("Inverse case", func() { _, err = ini.LoadSources(ini.LoadOptions{ AllowPythonMultilineValues: false, }, notFoundConf) So(err, ShouldNotBeNil) }) }) Convey("Insensitive to section and key names", func() { f, err := ini.LoadSources(ini.LoadOptions{ AllowPythonMultilineValues: false, Insensitive: true, }, minimalConf) So(err, ShouldBeNil) So(f, ShouldNotBeNil) So(f.Section("Author").Key("e-mail").String(), ShouldEqual, "u@gogs.io") Convey("Write out", func() { var buf bytes.Buffer _, err := f.WriteTo(&buf) So(err, ShouldBeNil) So(buf.String(), ShouldEqual, `[author] e-mail = u@gogs.io `) }) Convey("Inverse case", func() { f, err := ini.LoadSources(ini.LoadOptions{ AllowPythonMultilineValues: false, }, minimalConf) So(err, ShouldBeNil) So(f, ShouldNotBeNil) So(f.Section("Author").Key("e-mail").String(), ShouldBeEmpty) }) }) Convey("Ignore continuation lines", func() { f, err := ini.LoadSources(ini.LoadOptions{ AllowPythonMultilineValues: false, IgnoreContinuation: true, }, []byte(` key1=a\b\ key2=c\d\ key3=value`)) So(err, ShouldBeNil) So(f, ShouldNotBeNil) So(f.Section("").Key("key1").String(), ShouldEqual, `a\b\`) So(f.Section("").Key("key2").String(), ShouldEqual, `c\d\`) So(f.Section("").Key("key3").String(), ShouldEqual, "value") Convey("Inverse case", func() { f, err := ini.LoadSources(ini.LoadOptions{AllowPythonMultilineValues: false}, []byte(` key1=a\b\ key2=c\d\`)) So(err, ShouldBeNil) So(f, ShouldNotBeNil) So(f.Section("").Key("key1").String(), ShouldEqual, `a\bkey2=c\d`) }) }) Convey("Ignore inline comments", func() { f, err := ini.LoadSources(ini.LoadOptions{ AllowPythonMultilineValues: false, IgnoreInlineComment: true, }, []byte(` key1=value ;comment key2=value2 #comment2 key3=val#ue #comment3`)) So(err, ShouldBeNil) So(f, ShouldNotBeNil) So(f.Section("").Key("key1").String(), ShouldEqual, `value ;comment`) So(f.Section("").Key("key2").String(), ShouldEqual, `value2 #comment2`) So(f.Section("").Key("key3").String(), ShouldEqual, `val#ue #comment3`) Convey("Inverse case", func() { f, err := ini.LoadSources(ini.LoadOptions{AllowPythonMultilineValues: false}, []byte(` key1=value ;comment key2=value2 #comment2`)) So(err, ShouldBeNil) So(f, ShouldNotBeNil) So(f.Section("").Key("key1").String(), ShouldEqual, `value`) So(f.Section("").Key("key1").Comment, ShouldEqual, `;comment`) So(f.Section("").Key("key2").String(), ShouldEqual, `value2`) So(f.Section("").Key("key2").Comment, ShouldEqual, `#comment2`) }) }) Convey("Allow boolean type keys", func() { f, err := ini.LoadSources(ini.LoadOptions{ AllowPythonMultilineValues: false, AllowBooleanKeys: true, }, []byte(` key1=hello #key2 key3`)) So(err, ShouldBeNil) So(f, ShouldNotBeNil) So(f.Section("").KeyStrings(), ShouldResemble, []string{"key1", "key3"}) So(f.Section("").Key("key3").MustBool(false), ShouldBeTrue) Convey("Write out", func() { var buf bytes.Buffer _, err := f.WriteTo(&buf) So(err, ShouldBeNil) So(buf.String(), ShouldEqual, `key1 = hello # key2 key3 `) }) Convey("Inverse case", func() { _, err := ini.LoadSources(ini.LoadOptions{AllowPythonMultilineValues: false}, []byte(` key1=hello #key2 key3`)) So(err, ShouldNotBeNil) }) }) Convey("Allow shadow keys", func() { f, err := ini.LoadSources(ini.LoadOptions{AllowPythonMultilineValues: false, AllowShadows: true}, []byte(` [remote "origin"] url = https://github.com/Antergone/test1.git url = https://github.com/Antergone/test2.git fetch = +refs/heads/*:refs/remotes/origin/*`)) So(err, ShouldBeNil) So(f, ShouldNotBeNil) So(f.Section(`remote "origin"`).Key("url").String(), ShouldEqual, "https://github.com/Antergone/test1.git") So(f.Section(`remote "origin"`).Key("url").ValueWithShadows(), ShouldResemble, []string{ "https://github.com/Antergone/test1.git", "https://github.com/Antergone/test2.git", }) So(f.Section(`remote "origin"`).Key("fetch").String(), ShouldEqual, "+refs/heads/*:refs/remotes/origin/*") Convey("Write out", func() { var buf bytes.Buffer _, err := f.WriteTo(&buf) So(err, ShouldBeNil) So(buf.String(), ShouldEqual, `[remote "origin"] url = https://github.com/Antergone/test1.git url = https://github.com/Antergone/test2.git fetch = +refs/heads/*:refs/remotes/origin/* `) }) Convey("Inverse case", func() { f, err := ini.LoadSources(ini.LoadOptions{AllowPythonMultilineValues: false}, []byte(` [remote "origin"] url = https://github.com/Antergone/test1.git url = https://github.com/Antergone/test2.git`)) So(err, ShouldBeNil) So(f, ShouldNotBeNil) So(f.Section(`remote "origin"`).Key("url").String(), ShouldEqual, "https://github.com/Antergone/test2.git") }) }) Convey("Unescape double quotes inside value", func() { f, err := ini.LoadSources(ini.LoadOptions{ AllowPythonMultilineValues: false, UnescapeValueDoubleQuotes: true, }, []byte(` create_repo="创建了仓库 %s"`)) So(err, ShouldBeNil) So(f, ShouldNotBeNil) So(f.Section("").Key("create_repo").String(), ShouldEqual, `创建了仓库 %s`) Convey("Inverse case", func() { f, err := ini.LoadSources(ini.LoadOptions{AllowPythonMultilineValues: false}, []byte(` create_repo="创建了仓库 %s"`)) So(err, ShouldBeNil) So(f, ShouldNotBeNil) So(f.Section("").Key("create_repo").String(), ShouldEqual, `"创建了仓库 %s"`) }) }) Convey("Unescape comment symbols inside value", func() { f, err := ini.LoadSources(ini.LoadOptions{ AllowPythonMultilineValues: false, IgnoreInlineComment: true, UnescapeValueCommentSymbols: true, }, []byte(` key = test value more text `)) So(err, ShouldBeNil) So(f, ShouldNotBeNil) So(f.Section("").Key("key").String(), ShouldEqual, `test value more text`) }) Convey("Can't parse small python-compatible INI files", func() { f, err := ini.LoadSources(ini.LoadOptions{AllowPythonMultilineValues: false}, []byte(` [long] long_rsa_private_key = -----BEGIN RSA PRIVATE KEY----- foo bar foobar barfoo -----END RSA PRIVATE KEY----- `)) So(err, ShouldNotBeNil) So(f, ShouldBeNil) So(err.Error(), ShouldEqual, "key-value delimiter not found: foo\n") }) Convey("Can't parse big python-compatible INI files", func() { f, err := ini.LoadSources(ini.LoadOptions{AllowPythonMultilineValues: false}, []byte(` [long] long_rsa_private_key = -----BEGIN RSA PRIVATE KEY----- 1foo 2bar 3foobar 4barfoo 5foo 6bar 7foobar 8barfoo 9foo 10bar 11foobar 12barfoo 13foo 14bar 15foobar 16barfoo 17foo 18bar 19foobar 20barfoo 21foo 22bar 23foobar 24barfoo 25foo 26bar 27foobar 28barfoo 29foo 30bar 31foobar 32barfoo 33foo 34bar 35foobar 36barfoo 37foo 38bar 39foobar 40barfoo 41foo 42bar 43foobar 44barfoo 45foo 46bar 47foobar 48barfoo 49foo 50bar 51foobar 52barfoo 53foo 54bar 55foobar 56barfoo 57foo 58bar 59foobar 60barfoo 61foo 62bar 63foobar 64barfoo 65foo 66bar 67foobar 68barfoo 69foo 70bar 71foobar 72barfoo 73foo 74bar 75foobar 76barfoo 77foo 78bar 79foobar 80barfoo 81foo 82bar 83foobar 84barfoo 85foo 86bar 87foobar 88barfoo 89foo 90bar 91foobar 92barfoo 93foo 94bar 95foobar 96barfoo -----END RSA PRIVATE KEY----- `)) So(err, ShouldNotBeNil) So(f, ShouldBeNil) So(err.Error(), ShouldEqual, "key-value delimiter not found: 1foo\n") }) Convey("Allow unparsable sections", func() { f, err := ini.LoadSources(ini.LoadOptions{ AllowPythonMultilineValues: false, Insensitive: true, UnparseableSections: []string{"core_lesson", "comments"}, }, []byte(` Lesson_Location = 87 Lesson_Status = C Score = 3 Time = 00:02:30 [CORE_LESSON] my lesson state data – 1111111111111111111000000000000000001110000 111111111111111111100000000000111000000000 – end my lesson state data [COMMENTS] <1> This slide has the fuel listed in the wrong units `)) So(err, ShouldBeNil) So(f, ShouldNotBeNil) So(f.Section("").Key("score").String(), ShouldEqual, "3") So(f.Section("").Body(), ShouldBeEmpty) So(f.Section("core_lesson").Body(), ShouldEqual, `my lesson state data – 1111111111111111111000000000000000001110000 111111111111111111100000000000111000000000 – end my lesson state data`) So(f.Section("comments").Body(), ShouldEqual, `<1> This slide has the fuel listed in the wrong units `) Convey("Write out", func() { var buf bytes.Buffer _, err := f.WriteTo(&buf) So(err, ShouldBeNil) So(buf.String(), ShouldEqual, `lesson_location = 87 lesson_status = C score = 3 time = 00:02:30 [core_lesson] my lesson state data – 1111111111111111111000000000000000001110000 111111111111111111100000000000111000000000 – end my lesson state data [comments] <1> This slide has the fuel listed in the wrong units `) }) Convey("Inverse case", func() { _, err := ini.LoadSources(ini.LoadOptions{AllowPythonMultilineValues: false}, []byte(` [CORE_LESSON] my lesson state data – 1111111111111111111000000000000000001110000 111111111111111111100000000000111000000000 – end my lesson state data`)) So(err, ShouldNotBeNil) }) }) Convey("And false `SpaceBeforeInlineComment`", func() { Convey("Can't parse INI files containing `#` or `;` in value", func() { f, err := ini.LoadSources( ini.LoadOptions{AllowPythonMultilineValues: true, SpaceBeforeInlineComment: false}, []byte(` [author] NAME = U#n#k#n#w#o#n GITHUB = U;n;k;n;w;o;n `)) So(err, ShouldBeNil) So(f, ShouldNotBeNil) sec := f.Section("author") nameValue := sec.Key("NAME").String() githubValue := sec.Key("GITHUB").String() So(nameValue, ShouldEqual, "U") So(githubValue, ShouldEqual, "U") }) }) Convey("And true `SpaceBeforeInlineComment`", func() { Convey("Can parse INI files containing `#` or `;` in value", func() { f, err := ini.LoadSources( ini.LoadOptions{AllowPythonMultilineValues: true, SpaceBeforeInlineComment: true}, []byte(` [author] NAME = U#n#k#n#w#o#n GITHUB = U;n;k;n;w;o;n `)) So(err, ShouldBeNil) So(f, ShouldNotBeNil) sec := f.Section("author") nameValue := sec.Key("NAME").String() githubValue := sec.Key("GITHUB").String() So(nameValue, ShouldEqual, "U#n#k#n#w#o#n") So(githubValue, ShouldEqual, "U;n;k;n;w;o;n") }) }) }) }) } func Test_KeyValueDelimiters(t *testing.T) { Convey("Custom key-value delimiters", t, func() { f, err := ini.LoadSources(ini.LoadOptions{ KeyValueDelimiters: "?!", }, []byte(` [section] key1?value1 key2!value2 `)) So(err, ShouldBeNil) So(f, ShouldNotBeNil) So(f.Section("section").Key("key1").String(), ShouldEqual, "value1") So(f.Section("section").Key("key2").String(), ShouldEqual, "value2") }) } func Test_PreserveSurroundedQuote(t *testing.T) { Convey("Preserve surrounded quote test", t, func() { f, err := ini.LoadSources(ini.LoadOptions{ PreserveSurroundedQuote: true, }, []byte(` [section] key1 = "value1" key2 = value2 `)) So(err, ShouldBeNil) So(f, ShouldNotBeNil) So(f.Section("section").Key("key1").String(), ShouldEqual, "\"value1\"") So(f.Section("section").Key("key2").String(), ShouldEqual, "value2") }) Convey("Preserve surrounded quote test inverse test", t, func() { f, err := ini.LoadSources(ini.LoadOptions{ PreserveSurroundedQuote: false, }, []byte(` [section] key1 = "value1" key2 = value2 `)) So(err, ShouldBeNil) So(f, ShouldNotBeNil) So(f.Section("section").Key("key1").String(), ShouldEqual, "value1") So(f.Section("section").Key("key2").String(), ShouldEqual, "value2") }) } ini-1.55.0/key.go000066400000000000000000000552001363342257300135120ustar00rootroot00000000000000// Copyright 2014 Unknwon // // Licensed under the Apache License, Version 2.0 (the "License"): you may // not use this file except in compliance with the License. You may obtain // a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the // License for the specific language governing permissions and limitations // under the License. package ini import ( "bytes" "errors" "fmt" "strconv" "strings" "time" ) // Key represents a key under a section. type Key struct { s *Section Comment string name string value string isAutoIncrement bool isBooleanType bool isShadow bool shadows []*Key nestedValues []string } // newKey simply return a key object with given values. func newKey(s *Section, name, val string) *Key { return &Key{ s: s, name: name, value: val, } } func (k *Key) addShadow(val string) error { if k.isShadow { return errors.New("cannot add shadow to another shadow key") } else if k.isAutoIncrement || k.isBooleanType { return errors.New("cannot add shadow to auto-increment or boolean key") } // Deduplicate shadows based on their values. if k.value == val { return nil } for i := range k.shadows { if k.shadows[i].value == val { return nil } } shadow := newKey(k.s, k.name, val) shadow.isShadow = true k.shadows = append(k.shadows, shadow) return nil } // AddShadow adds a new shadow key to itself. func (k *Key) AddShadow(val string) error { if !k.s.f.options.AllowShadows { return errors.New("shadow key is not allowed") } return k.addShadow(val) } func (k *Key) addNestedValue(val string) error { if k.isAutoIncrement || k.isBooleanType { return errors.New("cannot add nested value to auto-increment or boolean key") } k.nestedValues = append(k.nestedValues, val) return nil } // AddNestedValue adds a nested value to the key. func (k *Key) AddNestedValue(val string) error { if !k.s.f.options.AllowNestedValues { return errors.New("nested value is not allowed") } return k.addNestedValue(val) } // ValueMapper represents a mapping function for values, e.g. os.ExpandEnv type ValueMapper func(string) string // Name returns name of key. func (k *Key) Name() string { return k.name } // Value returns raw value of key for performance purpose. func (k *Key) Value() string { return k.value } // ValueWithShadows returns raw values of key and its shadows if any. func (k *Key) ValueWithShadows() []string { if len(k.shadows) == 0 { return []string{k.value} } vals := make([]string, len(k.shadows)+1) vals[0] = k.value for i := range k.shadows { vals[i+1] = k.shadows[i].value } return vals } // NestedValues returns nested values stored in the key. // It is possible returned value is nil if no nested values stored in the key. func (k *Key) NestedValues() []string { return k.nestedValues } // transformValue takes a raw value and transforms to its final string. func (k *Key) transformValue(val string) string { if k.s.f.ValueMapper != nil { val = k.s.f.ValueMapper(val) } // Fail-fast if no indicate char found for recursive value if !strings.Contains(val, "%") { return val } for i := 0; i < depthValues; i++ { vr := varPattern.FindString(val) if len(vr) == 0 { break } // Take off leading '%(' and trailing ')s'. noption := vr[2 : len(vr)-2] // Search in the same section. // If not found or found the key itself, then search again in default section. nk, err := k.s.GetKey(noption) if err != nil || k == nk { nk, _ = k.s.f.Section("").GetKey(noption) if nk == nil { // Stop when no results found in the default section, // and returns the value as-is. break } } // Substitute by new value and take off leading '%(' and trailing ')s'. val = strings.Replace(val, vr, nk.value, -1) } return val } // String returns string representation of value. func (k *Key) String() string { return k.transformValue(k.value) } // Validate accepts a validate function which can // return modifed result as key value. func (k *Key) Validate(fn func(string) string) string { return fn(k.String()) } // parseBool returns the boolean value represented by the string. // // It accepts 1, t, T, TRUE, true, True, YES, yes, Yes, y, ON, on, On, // 0, f, F, FALSE, false, False, NO, no, No, n, OFF, off, Off. // Any other value returns an error. func parseBool(str string) (value bool, err error) { switch str { case "1", "t", "T", "true", "TRUE", "True", "YES", "yes", "Yes", "y", "ON", "on", "On": return true, nil case "0", "f", "F", "false", "FALSE", "False", "NO", "no", "No", "n", "OFF", "off", "Off": return false, nil } return false, fmt.Errorf("parsing \"%s\": invalid syntax", str) } // Bool returns bool type value. func (k *Key) Bool() (bool, error) { return parseBool(k.String()) } // Float64 returns float64 type value. func (k *Key) Float64() (float64, error) { return strconv.ParseFloat(k.String(), 64) } // Int returns int type value. func (k *Key) Int() (int, error) { v, err := strconv.ParseInt(k.String(), 0, 64) return int(v), err } // Int64 returns int64 type value. func (k *Key) Int64() (int64, error) { return strconv.ParseInt(k.String(), 0, 64) } // Uint returns uint type valued. func (k *Key) Uint() (uint, error) { u, e := strconv.ParseUint(k.String(), 0, 64) return uint(u), e } // Uint64 returns uint64 type value. func (k *Key) Uint64() (uint64, error) { return strconv.ParseUint(k.String(), 0, 64) } // Duration returns time.Duration type value. func (k *Key) Duration() (time.Duration, error) { return time.ParseDuration(k.String()) } // TimeFormat parses with given format and returns time.Time type value. func (k *Key) TimeFormat(format string) (time.Time, error) { return time.Parse(format, k.String()) } // Time parses with RFC3339 format and returns time.Time type value. func (k *Key) Time() (time.Time, error) { return k.TimeFormat(time.RFC3339) } // MustString returns default value if key value is empty. func (k *Key) MustString(defaultVal string) string { val := k.String() if len(val) == 0 { k.value = defaultVal return defaultVal } return val } // MustBool always returns value without error, // it returns false if error occurs. func (k *Key) MustBool(defaultVal ...bool) bool { val, err := k.Bool() if len(defaultVal) > 0 && err != nil { k.value = strconv.FormatBool(defaultVal[0]) return defaultVal[0] } return val } // MustFloat64 always returns value without error, // it returns 0.0 if error occurs. func (k *Key) MustFloat64(defaultVal ...float64) float64 { val, err := k.Float64() if len(defaultVal) > 0 && err != nil { k.value = strconv.FormatFloat(defaultVal[0], 'f', -1, 64) return defaultVal[0] } return val } // MustInt always returns value without error, // it returns 0 if error occurs. func (k *Key) MustInt(defaultVal ...int) int { val, err := k.Int() if len(defaultVal) > 0 && err != nil { k.value = strconv.FormatInt(int64(defaultVal[0]), 10) return defaultVal[0] } return val } // MustInt64 always returns value without error, // it returns 0 if error occurs. func (k *Key) MustInt64(defaultVal ...int64) int64 { val, err := k.Int64() if len(defaultVal) > 0 && err != nil { k.value = strconv.FormatInt(defaultVal[0], 10) return defaultVal[0] } return val } // MustUint always returns value without error, // it returns 0 if error occurs. func (k *Key) MustUint(defaultVal ...uint) uint { val, err := k.Uint() if len(defaultVal) > 0 && err != nil { k.value = strconv.FormatUint(uint64(defaultVal[0]), 10) return defaultVal[0] } return val } // MustUint64 always returns value without error, // it returns 0 if error occurs. func (k *Key) MustUint64(defaultVal ...uint64) uint64 { val, err := k.Uint64() if len(defaultVal) > 0 && err != nil { k.value = strconv.FormatUint(defaultVal[0], 10) return defaultVal[0] } return val } // MustDuration always returns value without error, // it returns zero value if error occurs. func (k *Key) MustDuration(defaultVal ...time.Duration) time.Duration { val, err := k.Duration() if len(defaultVal) > 0 && err != nil { k.value = defaultVal[0].String() return defaultVal[0] } return val } // MustTimeFormat always parses with given format and returns value without error, // it returns zero value if error occurs. func (k *Key) MustTimeFormat(format string, defaultVal ...time.Time) time.Time { val, err := k.TimeFormat(format) if len(defaultVal) > 0 && err != nil { k.value = defaultVal[0].Format(format) return defaultVal[0] } return val } // MustTime always parses with RFC3339 format and returns value without error, // it returns zero value if error occurs. func (k *Key) MustTime(defaultVal ...time.Time) time.Time { return k.MustTimeFormat(time.RFC3339, defaultVal...) } // In always returns value without error, // it returns default value if error occurs or doesn't fit into candidates. func (k *Key) In(defaultVal string, candidates []string) string { val := k.String() for _, cand := range candidates { if val == cand { return val } } return defaultVal } // InFloat64 always returns value without error, // it returns default value if error occurs or doesn't fit into candidates. func (k *Key) InFloat64(defaultVal float64, candidates []float64) float64 { val := k.MustFloat64() for _, cand := range candidates { if val == cand { return val } } return defaultVal } // InInt always returns value without error, // it returns default value if error occurs or doesn't fit into candidates. func (k *Key) InInt(defaultVal int, candidates []int) int { val := k.MustInt() for _, cand := range candidates { if val == cand { return val } } return defaultVal } // InInt64 always returns value without error, // it returns default value if error occurs or doesn't fit into candidates. func (k *Key) InInt64(defaultVal int64, candidates []int64) int64 { val := k.MustInt64() for _, cand := range candidates { if val == cand { return val } } return defaultVal } // InUint always returns value without error, // it returns default value if error occurs or doesn't fit into candidates. func (k *Key) InUint(defaultVal uint, candidates []uint) uint { val := k.MustUint() for _, cand := range candidates { if val == cand { return val } } return defaultVal } // InUint64 always returns value without error, // it returns default value if error occurs or doesn't fit into candidates. func (k *Key) InUint64(defaultVal uint64, candidates []uint64) uint64 { val := k.MustUint64() for _, cand := range candidates { if val == cand { return val } } return defaultVal } // InTimeFormat always parses with given format and returns value without error, // it returns default value if error occurs or doesn't fit into candidates. func (k *Key) InTimeFormat(format string, defaultVal time.Time, candidates []time.Time) time.Time { val := k.MustTimeFormat(format) for _, cand := range candidates { if val == cand { return val } } return defaultVal } // InTime always parses with RFC3339 format and returns value without error, // it returns default value if error occurs or doesn't fit into candidates. func (k *Key) InTime(defaultVal time.Time, candidates []time.Time) time.Time { return k.InTimeFormat(time.RFC3339, defaultVal, candidates) } // RangeFloat64 checks if value is in given range inclusively, // and returns default value if it's not. func (k *Key) RangeFloat64(defaultVal, min, max float64) float64 { val := k.MustFloat64() if val < min || val > max { return defaultVal } return val } // RangeInt checks if value is in given range inclusively, // and returns default value if it's not. func (k *Key) RangeInt(defaultVal, min, max int) int { val := k.MustInt() if val < min || val > max { return defaultVal } return val } // RangeInt64 checks if value is in given range inclusively, // and returns default value if it's not. func (k *Key) RangeInt64(defaultVal, min, max int64) int64 { val := k.MustInt64() if val < min || val > max { return defaultVal } return val } // RangeTimeFormat checks if value with given format is in given range inclusively, // and returns default value if it's not. func (k *Key) RangeTimeFormat(format string, defaultVal, min, max time.Time) time.Time { val := k.MustTimeFormat(format) if val.Unix() < min.Unix() || val.Unix() > max.Unix() { return defaultVal } return val } // RangeTime checks if value with RFC3339 format is in given range inclusively, // and returns default value if it's not. func (k *Key) RangeTime(defaultVal, min, max time.Time) time.Time { return k.RangeTimeFormat(time.RFC3339, defaultVal, min, max) } // Strings returns list of string divided by given delimiter. func (k *Key) Strings(delim string) []string { str := k.String() if len(str) == 0 { return []string{} } runes := []rune(str) vals := make([]string, 0, 2) var buf bytes.Buffer escape := false idx := 0 for { if escape { escape = false if runes[idx] != '\\' && !strings.HasPrefix(string(runes[idx:]), delim) { buf.WriteRune('\\') } buf.WriteRune(runes[idx]) } else { if runes[idx] == '\\' { escape = true } else if strings.HasPrefix(string(runes[idx:]), delim) { idx += len(delim) - 1 vals = append(vals, strings.TrimSpace(buf.String())) buf.Reset() } else { buf.WriteRune(runes[idx]) } } idx++ if idx == len(runes) { break } } if buf.Len() > 0 { vals = append(vals, strings.TrimSpace(buf.String())) } return vals } // StringsWithShadows returns list of string divided by given delimiter. // Shadows will also be appended if any. func (k *Key) StringsWithShadows(delim string) []string { vals := k.ValueWithShadows() results := make([]string, 0, len(vals)*2) for i := range vals { if len(vals) == 0 { continue } results = append(results, strings.Split(vals[i], delim)...) } for i := range results { results[i] = k.transformValue(strings.TrimSpace(results[i])) } return results } // Float64s returns list of float64 divided by given delimiter. Any invalid input will be treated as zero value. func (k *Key) Float64s(delim string) []float64 { vals, _ := k.parseFloat64s(k.Strings(delim), true, false) return vals } // Ints returns list of int divided by given delimiter. Any invalid input will be treated as zero value. func (k *Key) Ints(delim string) []int { vals, _ := k.parseInts(k.Strings(delim), true, false) return vals } // Int64s returns list of int64 divided by given delimiter. Any invalid input will be treated as zero value. func (k *Key) Int64s(delim string) []int64 { vals, _ := k.parseInt64s(k.Strings(delim), true, false) return vals } // Uints returns list of uint divided by given delimiter. Any invalid input will be treated as zero value. func (k *Key) Uints(delim string) []uint { vals, _ := k.parseUints(k.Strings(delim), true, false) return vals } // Uint64s returns list of uint64 divided by given delimiter. Any invalid input will be treated as zero value. func (k *Key) Uint64s(delim string) []uint64 { vals, _ := k.parseUint64s(k.Strings(delim), true, false) return vals } // Bools returns list of bool divided by given delimiter. Any invalid input will be treated as zero value. func (k *Key) Bools(delim string) []bool { vals, _ := k.parseBools(k.Strings(delim), true, false) return vals } // TimesFormat parses with given format and returns list of time.Time divided by given delimiter. // Any invalid input will be treated as zero value (0001-01-01 00:00:00 +0000 UTC). func (k *Key) TimesFormat(format, delim string) []time.Time { vals, _ := k.parseTimesFormat(format, k.Strings(delim), true, false) return vals } // Times parses with RFC3339 format and returns list of time.Time divided by given delimiter. // Any invalid input will be treated as zero value (0001-01-01 00:00:00 +0000 UTC). func (k *Key) Times(delim string) []time.Time { return k.TimesFormat(time.RFC3339, delim) } // ValidFloat64s returns list of float64 divided by given delimiter. If some value is not float, then // it will not be included to result list. func (k *Key) ValidFloat64s(delim string) []float64 { vals, _ := k.parseFloat64s(k.Strings(delim), false, false) return vals } // ValidInts returns list of int divided by given delimiter. If some value is not integer, then it will // not be included to result list. func (k *Key) ValidInts(delim string) []int { vals, _ := k.parseInts(k.Strings(delim), false, false) return vals } // ValidInt64s returns list of int64 divided by given delimiter. If some value is not 64-bit integer, // then it will not be included to result list. func (k *Key) ValidInt64s(delim string) []int64 { vals, _ := k.parseInt64s(k.Strings(delim), false, false) return vals } // ValidUints returns list of uint divided by given delimiter. If some value is not unsigned integer, // then it will not be included to result list. func (k *Key) ValidUints(delim string) []uint { vals, _ := k.parseUints(k.Strings(delim), false, false) return vals } // ValidUint64s returns list of uint64 divided by given delimiter. If some value is not 64-bit unsigned // integer, then it will not be included to result list. func (k *Key) ValidUint64s(delim string) []uint64 { vals, _ := k.parseUint64s(k.Strings(delim), false, false) return vals } // ValidBools returns list of bool divided by given delimiter. If some value is not 64-bit unsigned // integer, then it will not be included to result list. func (k *Key) ValidBools(delim string) []bool { vals, _ := k.parseBools(k.Strings(delim), false, false) return vals } // ValidTimesFormat parses with given format and returns list of time.Time divided by given delimiter. func (k *Key) ValidTimesFormat(format, delim string) []time.Time { vals, _ := k.parseTimesFormat(format, k.Strings(delim), false, false) return vals } // ValidTimes parses with RFC3339 format and returns list of time.Time divided by given delimiter. func (k *Key) ValidTimes(delim string) []time.Time { return k.ValidTimesFormat(time.RFC3339, delim) } // StrictFloat64s returns list of float64 divided by given delimiter or error on first invalid input. func (k *Key) StrictFloat64s(delim string) ([]float64, error) { return k.parseFloat64s(k.Strings(delim), false, true) } // StrictInts returns list of int divided by given delimiter or error on first invalid input. func (k *Key) StrictInts(delim string) ([]int, error) { return k.parseInts(k.Strings(delim), false, true) } // StrictInt64s returns list of int64 divided by given delimiter or error on first invalid input. func (k *Key) StrictInt64s(delim string) ([]int64, error) { return k.parseInt64s(k.Strings(delim), false, true) } // StrictUints returns list of uint divided by given delimiter or error on first invalid input. func (k *Key) StrictUints(delim string) ([]uint, error) { return k.parseUints(k.Strings(delim), false, true) } // StrictUint64s returns list of uint64 divided by given delimiter or error on first invalid input. func (k *Key) StrictUint64s(delim string) ([]uint64, error) { return k.parseUint64s(k.Strings(delim), false, true) } // StrictBools returns list of bool divided by given delimiter or error on first invalid input. func (k *Key) StrictBools(delim string) ([]bool, error) { return k.parseBools(k.Strings(delim), false, true) } // StrictTimesFormat parses with given format and returns list of time.Time divided by given delimiter // or error on first invalid input. func (k *Key) StrictTimesFormat(format, delim string) ([]time.Time, error) { return k.parseTimesFormat(format, k.Strings(delim), false, true) } // StrictTimes parses with RFC3339 format and returns list of time.Time divided by given delimiter // or error on first invalid input. func (k *Key) StrictTimes(delim string) ([]time.Time, error) { return k.StrictTimesFormat(time.RFC3339, delim) } // parseBools transforms strings to bools. func (k *Key) parseBools(strs []string, addInvalid, returnOnInvalid bool) ([]bool, error) { vals := make([]bool, 0, len(strs)) for _, str := range strs { val, err := parseBool(str) if err != nil && returnOnInvalid { return nil, err } if err == nil || addInvalid { vals = append(vals, val) } } return vals, nil } // parseFloat64s transforms strings to float64s. func (k *Key) parseFloat64s(strs []string, addInvalid, returnOnInvalid bool) ([]float64, error) { vals := make([]float64, 0, len(strs)) for _, str := range strs { val, err := strconv.ParseFloat(str, 64) if err != nil && returnOnInvalid { return nil, err } if err == nil || addInvalid { vals = append(vals, val) } } return vals, nil } // parseInts transforms strings to ints. func (k *Key) parseInts(strs []string, addInvalid, returnOnInvalid bool) ([]int, error) { vals := make([]int, 0, len(strs)) for _, str := range strs { valInt64, err := strconv.ParseInt(str, 0, 64) val := int(valInt64) if err != nil && returnOnInvalid { return nil, err } if err == nil || addInvalid { vals = append(vals, val) } } return vals, nil } // parseInt64s transforms strings to int64s. func (k *Key) parseInt64s(strs []string, addInvalid, returnOnInvalid bool) ([]int64, error) { vals := make([]int64, 0, len(strs)) for _, str := range strs { val, err := strconv.ParseInt(str, 0, 64) if err != nil && returnOnInvalid { return nil, err } if err == nil || addInvalid { vals = append(vals, val) } } return vals, nil } // parseUints transforms strings to uints. func (k *Key) parseUints(strs []string, addInvalid, returnOnInvalid bool) ([]uint, error) { vals := make([]uint, 0, len(strs)) for _, str := range strs { val, err := strconv.ParseUint(str, 0, 0) if err != nil && returnOnInvalid { return nil, err } if err == nil || addInvalid { vals = append(vals, uint(val)) } } return vals, nil } // parseUint64s transforms strings to uint64s. func (k *Key) parseUint64s(strs []string, addInvalid, returnOnInvalid bool) ([]uint64, error) { vals := make([]uint64, 0, len(strs)) for _, str := range strs { val, err := strconv.ParseUint(str, 0, 64) if err != nil && returnOnInvalid { return nil, err } if err == nil || addInvalid { vals = append(vals, val) } } return vals, nil } // parseTimesFormat transforms strings to times in given format. func (k *Key) parseTimesFormat(format string, strs []string, addInvalid, returnOnInvalid bool) ([]time.Time, error) { vals := make([]time.Time, 0, len(strs)) for _, str := range strs { val, err := time.Parse(format, str) if err != nil && returnOnInvalid { return nil, err } if err == nil || addInvalid { vals = append(vals, val) } } return vals, nil } // SetValue changes key value. func (k *Key) SetValue(v string) { if k.s.f.BlockMode { k.s.f.lock.Lock() defer k.s.f.lock.Unlock() } k.value = v k.s.keysHash[k.name] = v } ini-1.55.0/key_test.go000066400000000000000000000431531363342257300145550ustar00rootroot00000000000000// Copyright 2014 Unknwon // // Licensed under the Apache License, Version 2.0 (the "License"): you may // not use this file except in compliance with the License. You may obtain // a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the // License for the specific language governing permissions and limitations // under the License. package ini_test import ( "bytes" "fmt" "strings" "testing" "time" . "github.com/smartystreets/goconvey/convey" "gopkg.in/ini.v1" ) func TestKey_AddShadow(t *testing.T) { Convey("Add shadow to a key", t, func() { f, err := ini.ShadowLoad([]byte(` [notes] -: note1`)) So(err, ShouldBeNil) So(f, ShouldNotBeNil) k, err := f.Section("").NewKey("NAME", "ini") So(err, ShouldBeNil) So(k, ShouldNotBeNil) So(k.AddShadow("ini.v1"), ShouldBeNil) So(k.ValueWithShadows(), ShouldResemble, []string{"ini", "ini.v1"}) Convey("Add shadow to boolean key", func() { k, err := f.Section("").NewBooleanKey("published") So(err, ShouldBeNil) So(k, ShouldNotBeNil) So(k.AddShadow("beta"), ShouldNotBeNil) }) Convey("Add shadow to auto-increment key", func() { So(f.Section("notes").Key("#1").AddShadow("beta"), ShouldNotBeNil) }) }) Convey("Shadow is not allowed", t, func() { f := ini.Empty() So(f, ShouldNotBeNil) k, err := f.Section("").NewKey("NAME", "ini") So(err, ShouldBeNil) So(k, ShouldNotBeNil) So(k.AddShadow("ini.v1"), ShouldNotBeNil) }) } // Helpers for slice tests. func float64sEqual(values []float64, expected ...float64) { So(values, ShouldHaveLength, len(expected)) for i, v := range expected { So(values[i], ShouldEqual, v) } } func intsEqual(values []int, expected ...int) { So(values, ShouldHaveLength, len(expected)) for i, v := range expected { So(values[i], ShouldEqual, v) } } func int64sEqual(values []int64, expected ...int64) { So(values, ShouldHaveLength, len(expected)) for i, v := range expected { So(values[i], ShouldEqual, v) } } func uintsEqual(values []uint, expected ...uint) { So(values, ShouldHaveLength, len(expected)) for i, v := range expected { So(values[i], ShouldEqual, v) } } func uint64sEqual(values []uint64, expected ...uint64) { So(values, ShouldHaveLength, len(expected)) for i, v := range expected { So(values[i], ShouldEqual, v) } } func boolsEqual(values []bool, expected ...bool) { So(values, ShouldHaveLength, len(expected)) for i, v := range expected { So(values[i], ShouldEqual, v) } } func timesEqual(values []time.Time, expected ...time.Time) { So(values, ShouldHaveLength, len(expected)) for i, v := range expected { So(values[i].String(), ShouldEqual, v.String()) } } func TestKey_Helpers(t *testing.T) { Convey("Getting and setting values", t, func() { f, err := ini.Load(fullConf) So(err, ShouldBeNil) So(f, ShouldNotBeNil) Convey("Get string representation", func() { sec := f.Section("") So(sec, ShouldNotBeNil) So(sec.Key("NAME").Value(), ShouldEqual, "ini") So(sec.Key("NAME").String(), ShouldEqual, "ini") So(sec.Key("NAME").Validate(func(in string) string { return in }), ShouldEqual, "ini") So(sec.Key("NAME").Comment, ShouldEqual, "; Package name") So(sec.Key("IMPORT_PATH").String(), ShouldEqual, "gopkg.in/ini.v1") Convey("With ValueMapper", func() { f.ValueMapper = func(in string) string { if in == "gopkg.in/%(NAME)s.%(VERSION)s" { return "github.com/go-ini/ini" } return in } So(sec.Key("IMPORT_PATH").String(), ShouldEqual, "github.com/go-ini/ini") }) }) Convey("Get values in non-default section", func() { sec := f.Section("author") So(sec, ShouldNotBeNil) So(sec.Key("NAME").String(), ShouldEqual, "Unknwon") So(sec.Key("GITHUB").String(), ShouldEqual, "https://github.com/Unknwon") sec = f.Section("package") So(sec, ShouldNotBeNil) So(sec.Key("CLONE_URL").String(), ShouldEqual, "https://gopkg.in/ini.v1") }) Convey("Get auto-increment key names", func() { keys := f.Section("features").Keys() for i, k := range keys { So(k.Name(), ShouldEqual, fmt.Sprintf("#%d", i+1)) } }) Convey("Get parent-keys that are available to the child section", func() { parentKeys := f.Section("package.sub").ParentKeys() for _, k := range parentKeys { So(k.Name(), ShouldEqual, "CLONE_URL") } }) Convey("Get overwrite value", func() { So(f.Section("author").Key("E-MAIL").String(), ShouldEqual, "u@gogs.io") }) Convey("Get sections", func() { sections := f.Sections() for i, name := range []string{ini.DefaultSection, "author", "package", "package.sub", "features", "types", "array", "note", "comments", "string escapes", "advance"} { So(sections[i].Name(), ShouldEqual, name) } }) Convey("Get parent section value", func() { So(f.Section("package.sub").Key("CLONE_URL").String(), ShouldEqual, "https://gopkg.in/ini.v1") So(f.Section("package.fake.sub").Key("CLONE_URL").String(), ShouldEqual, "https://gopkg.in/ini.v1") }) Convey("Get multiple line value", func() { So(f.Section("author").Key("BIO").String(), ShouldEqual, "Gopher.\nCoding addict.\nGood man.\n") }) Convey("Get values with type", func() { sec := f.Section("types") v1, err := sec.Key("BOOL").Bool() So(err, ShouldBeNil) So(v1, ShouldBeTrue) v1, err = sec.Key("BOOL_FALSE").Bool() So(err, ShouldBeNil) So(v1, ShouldBeFalse) v2, err := sec.Key("FLOAT64").Float64() So(err, ShouldBeNil) So(v2, ShouldEqual, 1.25) v3, err := sec.Key("INT").Int() So(err, ShouldBeNil) So(v3, ShouldEqual, 10) v4, err := sec.Key("INT").Int64() So(err, ShouldBeNil) So(v4, ShouldEqual, 10) v5, err := sec.Key("UINT").Uint() So(err, ShouldBeNil) So(v5, ShouldEqual, 3) v6, err := sec.Key("UINT").Uint64() So(err, ShouldBeNil) So(v6, ShouldEqual, 3) t, err := time.Parse(time.RFC3339, "2015-01-01T20:17:05Z") So(err, ShouldBeNil) v7, err := sec.Key("TIME").Time() So(err, ShouldBeNil) So(v7.String(), ShouldEqual, t.String()) v8, err := sec.Key("HEX_NUMBER").Int() So(err, ShouldBeNil) So(v8, ShouldEqual, 0x3000) Convey("Must get values with type", func() { So(sec.Key("STRING").MustString("404"), ShouldEqual, "str") So(sec.Key("BOOL").MustBool(), ShouldBeTrue) So(sec.Key("FLOAT64").MustFloat64(), ShouldEqual, 1.25) So(sec.Key("INT").MustInt(), ShouldEqual, 10) So(sec.Key("INT").MustInt64(), ShouldEqual, 10) So(sec.Key("UINT").MustUint(), ShouldEqual, 3) So(sec.Key("UINT").MustUint64(), ShouldEqual, 3) So(sec.Key("TIME").MustTime().String(), ShouldEqual, t.String()) So(sec.Key("HEX_NUMBER").MustInt(), ShouldEqual, 0x3000) dur, err := time.ParseDuration("2h45m") So(err, ShouldBeNil) So(sec.Key("DURATION").MustDuration().Seconds(), ShouldEqual, dur.Seconds()) Convey("Must get values with default value", func() { So(sec.Key("STRING_404").MustString("404"), ShouldEqual, "404") So(sec.Key("BOOL_404").MustBool(true), ShouldBeTrue) So(sec.Key("FLOAT64_404").MustFloat64(2.5), ShouldEqual, 2.5) So(sec.Key("INT_404").MustInt(15), ShouldEqual, 15) So(sec.Key("INT64_404").MustInt64(15), ShouldEqual, 15) So(sec.Key("UINT_404").MustUint(6), ShouldEqual, 6) So(sec.Key("UINT64_404").MustUint64(6), ShouldEqual, 6) So(sec.Key("HEX_NUMBER_404").MustInt(0x3001), ShouldEqual, 0x3001) t, err := time.Parse(time.RFC3339, "2014-01-01T20:17:05Z") So(err, ShouldBeNil) So(sec.Key("TIME_404").MustTime(t).String(), ShouldEqual, t.String()) So(sec.Key("DURATION_404").MustDuration(dur).Seconds(), ShouldEqual, dur.Seconds()) Convey("Must should set default as key value", func() { So(sec.Key("STRING_404").String(), ShouldEqual, "404") So(sec.Key("BOOL_404").String(), ShouldEqual, "true") So(sec.Key("FLOAT64_404").String(), ShouldEqual, "2.5") So(sec.Key("INT_404").String(), ShouldEqual, "15") So(sec.Key("INT64_404").String(), ShouldEqual, "15") So(sec.Key("UINT_404").String(), ShouldEqual, "6") So(sec.Key("UINT64_404").String(), ShouldEqual, "6") So(sec.Key("TIME_404").String(), ShouldEqual, "2014-01-01T20:17:05Z") So(sec.Key("DURATION_404").String(), ShouldEqual, "2h45m0s") So(sec.Key("HEX_NUMBER_404").String(), ShouldEqual, "12289") }) }) }) }) Convey("Get value with candidates", func() { sec := f.Section("types") So(sec.Key("STRING").In("", []string{"str", "arr", "types"}), ShouldEqual, "str") So(sec.Key("FLOAT64").InFloat64(0, []float64{1.25, 2.5, 3.75}), ShouldEqual, 1.25) So(sec.Key("INT").InInt(0, []int{10, 20, 30}), ShouldEqual, 10) So(sec.Key("INT").InInt64(0, []int64{10, 20, 30}), ShouldEqual, 10) So(sec.Key("UINT").InUint(0, []uint{3, 6, 9}), ShouldEqual, 3) So(sec.Key("UINT").InUint64(0, []uint64{3, 6, 9}), ShouldEqual, 3) zt, err := time.Parse(time.RFC3339, "0001-01-01T01:00:00Z") So(err, ShouldBeNil) t, err := time.Parse(time.RFC3339, "2015-01-01T20:17:05Z") So(err, ShouldBeNil) So(sec.Key("TIME").InTime(zt, []time.Time{t, time.Now(), time.Now().Add(1 * time.Second)}).String(), ShouldEqual, t.String()) Convey("Get value with candidates and default value", func() { So(sec.Key("STRING_404").In("str", []string{"str", "arr", "types"}), ShouldEqual, "str") So(sec.Key("FLOAT64_404").InFloat64(1.25, []float64{1.25, 2.5, 3.75}), ShouldEqual, 1.25) So(sec.Key("INT_404").InInt(10, []int{10, 20, 30}), ShouldEqual, 10) So(sec.Key("INT64_404").InInt64(10, []int64{10, 20, 30}), ShouldEqual, 10) So(sec.Key("UINT_404").InUint(3, []uint{3, 6, 9}), ShouldEqual, 3) So(sec.Key("UINT_404").InUint64(3, []uint64{3, 6, 9}), ShouldEqual, 3) So(sec.Key("TIME_404").InTime(t, []time.Time{time.Now(), time.Now(), time.Now().Add(1 * time.Second)}).String(), ShouldEqual, t.String()) }) }) Convey("Get values in range", func() { sec := f.Section("types") So(sec.Key("FLOAT64").RangeFloat64(0, 1, 2), ShouldEqual, 1.25) So(sec.Key("INT").RangeInt(0, 10, 20), ShouldEqual, 10) So(sec.Key("INT").RangeInt64(0, 10, 20), ShouldEqual, 10) minT, err := time.Parse(time.RFC3339, "0001-01-01T01:00:00Z") So(err, ShouldBeNil) midT, err := time.Parse(time.RFC3339, "2013-01-01T01:00:00Z") So(err, ShouldBeNil) maxT, err := time.Parse(time.RFC3339, "9999-01-01T01:00:00Z") So(err, ShouldBeNil) t, err := time.Parse(time.RFC3339, "2015-01-01T20:17:05Z") So(err, ShouldBeNil) So(sec.Key("TIME").RangeTime(t, minT, maxT).String(), ShouldEqual, t.String()) Convey("Get value in range with default value", func() { So(sec.Key("FLOAT64").RangeFloat64(5, 0, 1), ShouldEqual, 5) So(sec.Key("INT").RangeInt(7, 0, 5), ShouldEqual, 7) So(sec.Key("INT").RangeInt64(7, 0, 5), ShouldEqual, 7) So(sec.Key("TIME").RangeTime(t, minT, midT).String(), ShouldEqual, t.String()) }) }) Convey("Get values into slice", func() { sec := f.Section("array") So(strings.Join(sec.Key("STRINGS").Strings(","), ","), ShouldEqual, "en,zh,de") So(len(sec.Key("STRINGS_404").Strings(",")), ShouldEqual, 0) vals1 := sec.Key("FLOAT64S").Float64s(",") float64sEqual(vals1, 1.1, 2.2, 3.3) vals2 := sec.Key("INTS").Ints(",") intsEqual(vals2, 1, 2, 3) vals3 := sec.Key("INTS").Int64s(",") int64sEqual(vals3, 1, 2, 3) vals4 := sec.Key("UINTS").Uints(",") uintsEqual(vals4, 1, 2, 3) vals5 := sec.Key("UINTS").Uint64s(",") uint64sEqual(vals5, 1, 2, 3) vals6 := sec.Key("BOOLS").Bools(",") boolsEqual(vals6, true, false, false) t, err := time.Parse(time.RFC3339, "2015-01-01T20:17:05Z") So(err, ShouldBeNil) vals7 := sec.Key("TIMES").Times(",") timesEqual(vals7, t, t, t) }) Convey("Test string slice escapes", func() { sec := f.Section("string escapes") So(sec.Key("key1").Strings(","), ShouldResemble, []string{"value1", "value2", "value3"}) So(sec.Key("key2").Strings(","), ShouldResemble, []string{"value1, value2"}) So(sec.Key("key3").Strings(","), ShouldResemble, []string{`val\ue1`, "value2"}) So(sec.Key("key4").Strings(","), ShouldResemble, []string{`value1\`, `value\\2`}) So(sec.Key("key5").Strings(",,"), ShouldResemble, []string{"value1,, value2"}) So(sec.Key("key6").Strings(" "), ShouldResemble, []string{"aaa", "bbb and space", "ccc"}) }) Convey("Get valid values into slice", func() { sec := f.Section("array") vals1 := sec.Key("FLOAT64S").ValidFloat64s(",") float64sEqual(vals1, 1.1, 2.2, 3.3) vals2 := sec.Key("INTS").ValidInts(",") intsEqual(vals2, 1, 2, 3) vals3 := sec.Key("INTS").ValidInt64s(",") int64sEqual(vals3, 1, 2, 3) vals4 := sec.Key("UINTS").ValidUints(",") uintsEqual(vals4, 1, 2, 3) vals5 := sec.Key("UINTS").ValidUint64s(",") uint64sEqual(vals5, 1, 2, 3) vals6 := sec.Key("BOOLS").ValidBools(",") boolsEqual(vals6, true, false, false) t, err := time.Parse(time.RFC3339, "2015-01-01T20:17:05Z") So(err, ShouldBeNil) vals7 := sec.Key("TIMES").ValidTimes(",") timesEqual(vals7, t, t, t) }) Convey("Get values one type into slice of another type", func() { sec := f.Section("array") vals1 := sec.Key("STRINGS").ValidFloat64s(",") So(vals1, ShouldBeEmpty) vals2 := sec.Key("STRINGS").ValidInts(",") So(vals2, ShouldBeEmpty) vals3 := sec.Key("STRINGS").ValidInt64s(",") So(vals3, ShouldBeEmpty) vals4 := sec.Key("STRINGS").ValidUints(",") So(vals4, ShouldBeEmpty) vals5 := sec.Key("STRINGS").ValidUint64s(",") So(vals5, ShouldBeEmpty) vals6 := sec.Key("STRINGS").ValidBools(",") So(vals6, ShouldBeEmpty) vals7 := sec.Key("STRINGS").ValidTimes(",") So(vals7, ShouldBeEmpty) }) Convey("Get valid values into slice without errors", func() { sec := f.Section("array") vals1, err := sec.Key("FLOAT64S").StrictFloat64s(",") So(err, ShouldBeNil) float64sEqual(vals1, 1.1, 2.2, 3.3) vals2, err := sec.Key("INTS").StrictInts(",") So(err, ShouldBeNil) intsEqual(vals2, 1, 2, 3) vals3, err := sec.Key("INTS").StrictInt64s(",") So(err, ShouldBeNil) int64sEqual(vals3, 1, 2, 3) vals4, err := sec.Key("UINTS").StrictUints(",") So(err, ShouldBeNil) uintsEqual(vals4, 1, 2, 3) vals5, err := sec.Key("UINTS").StrictUint64s(",") So(err, ShouldBeNil) uint64sEqual(vals5, 1, 2, 3) vals6, err := sec.Key("BOOLS").StrictBools(",") So(err, ShouldBeNil) boolsEqual(vals6, true, false, false) t, err := time.Parse(time.RFC3339, "2015-01-01T20:17:05Z") So(err, ShouldBeNil) vals7, err := sec.Key("TIMES").StrictTimes(",") So(err, ShouldBeNil) timesEqual(vals7, t, t, t) }) Convey("Get invalid values into slice", func() { sec := f.Section("array") vals1, err := sec.Key("STRINGS").StrictFloat64s(",") So(vals1, ShouldBeEmpty) So(err, ShouldNotBeNil) vals2, err := sec.Key("STRINGS").StrictInts(",") So(vals2, ShouldBeEmpty) So(err, ShouldNotBeNil) vals3, err := sec.Key("STRINGS").StrictInt64s(",") So(vals3, ShouldBeEmpty) So(err, ShouldNotBeNil) vals4, err := sec.Key("STRINGS").StrictUints(",") So(vals4, ShouldBeEmpty) So(err, ShouldNotBeNil) vals5, err := sec.Key("STRINGS").StrictUint64s(",") So(vals5, ShouldBeEmpty) So(err, ShouldNotBeNil) vals6, err := sec.Key("STRINGS").StrictBools(",") So(vals6, ShouldBeEmpty) So(err, ShouldNotBeNil) vals7, err := sec.Key("STRINGS").StrictTimes(",") So(vals7, ShouldBeEmpty) So(err, ShouldNotBeNil) }) }) } func TestKey_StringsWithShadows(t *testing.T) { Convey("Get strings of shadows of a key", t, func() { f, err := ini.ShadowLoad([]byte("")) So(err, ShouldBeNil) So(f, ShouldNotBeNil) k, err := f.Section("").NewKey("NUMS", "1,2") So(err, ShouldBeNil) So(k, ShouldNotBeNil) k, err = f.Section("").NewKey("NUMS", "4,5,6") So(err, ShouldBeNil) So(k, ShouldNotBeNil) So(k.StringsWithShadows(","), ShouldResemble, []string{"1", "2", "4", "5", "6"}) }) } func TestKey_SetValue(t *testing.T) { Convey("Set value of key", t, func() { f := ini.Empty() So(f, ShouldNotBeNil) k, err := f.Section("").NewKey("NAME", "ini") So(err, ShouldBeNil) So(k, ShouldNotBeNil) So(k.Value(), ShouldEqual, "ini") k.SetValue("ini.v1") So(k.Value(), ShouldEqual, "ini.v1") }) } func TestKey_NestedValues(t *testing.T) { Convey("Read and write nested values", t, func() { f, err := ini.LoadSources(ini.LoadOptions{ AllowNestedValues: true, }, []byte(` aws_access_key_id = foo aws_secret_access_key = bar region = us-west-2 s3 = max_concurrent_requests=10 max_queue_size=1000`)) So(err, ShouldBeNil) So(f, ShouldNotBeNil) So(f.Section("").Key("s3").NestedValues(), ShouldResemble, []string{"max_concurrent_requests=10", "max_queue_size=1000"}) var buf bytes.Buffer _, err = f.WriteTo(&buf) So(err, ShouldBeNil) So(buf.String(), ShouldEqual, `aws_access_key_id = foo aws_secret_access_key = bar region = us-west-2 s3 = max_concurrent_requests=10 max_queue_size=1000 `) }) } func TestRecursiveValues(t *testing.T) { Convey("Recursive values should not reflect on same key", t, func() { f, err := ini.Load([]byte(` NAME = ini expires = yes [package] NAME = %(NAME)s expires = %(expires)s`)) So(err, ShouldBeNil) So(f, ShouldNotBeNil) So(f.Section("package").Key("NAME").String(), ShouldEqual, "ini") So(f.Section("package").Key("expires").String(), ShouldEqual, "yes") }) Convey("Recursive value with no target found", t, func() { f, err := ini.Load([]byte(` [foo] bar = %(missing)s `)) So(err, ShouldBeNil) So(f, ShouldNotBeNil) So(f.Section("foo").Key("bar").String(), ShouldEqual, "%(missing)s") }) } ini-1.55.0/parser.go000066400000000000000000000321641363342257300142220ustar00rootroot00000000000000// Copyright 2015 Unknwon // // Licensed under the Apache License, Version 2.0 (the "License"): you may // not use this file except in compliance with the License. You may obtain // a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the // License for the specific language governing permissions and limitations // under the License. package ini import ( "bufio" "bytes" "fmt" "io" "regexp" "strconv" "strings" "unicode" ) const minReaderBufferSize = 4096 var pythonMultiline = regexp.MustCompile(`^([\t\f ]+)(.*)`) type parserOptions struct { IgnoreContinuation bool IgnoreInlineComment bool AllowPythonMultilineValues bool SpaceBeforeInlineComment bool UnescapeValueDoubleQuotes bool UnescapeValueCommentSymbols bool PreserveSurroundedQuote bool DebugFunc DebugFunc ReaderBufferSize int } type parser struct { buf *bufio.Reader options parserOptions isEOF bool count int comment *bytes.Buffer } func (p *parser) debug(format string, args ...interface{}) { if p.options.DebugFunc != nil { p.options.DebugFunc(fmt.Sprintf(format, args...)) } } func newParser(r io.Reader, opts parserOptions) *parser { size := opts.ReaderBufferSize if size < minReaderBufferSize { size = minReaderBufferSize } return &parser{ buf: bufio.NewReaderSize(r, size), options: opts, count: 1, comment: &bytes.Buffer{}, } } // BOM handles header of UTF-8, UTF-16 LE and UTF-16 BE's BOM format. // http://en.wikipedia.org/wiki/Byte_order_mark#Representations_of_byte_order_marks_by_encoding func (p *parser) BOM() error { mask, err := p.buf.Peek(2) if err != nil && err != io.EOF { return err } else if len(mask) < 2 { return nil } switch { case mask[0] == 254 && mask[1] == 255: fallthrough case mask[0] == 255 && mask[1] == 254: p.buf.Read(mask) case mask[0] == 239 && mask[1] == 187: mask, err := p.buf.Peek(3) if err != nil && err != io.EOF { return err } else if len(mask) < 3 { return nil } if mask[2] == 191 { p.buf.Read(mask) } } return nil } func (p *parser) readUntil(delim byte) ([]byte, error) { data, err := p.buf.ReadBytes(delim) if err != nil { if err == io.EOF { p.isEOF = true } else { return nil, err } } return data, nil } func cleanComment(in []byte) ([]byte, bool) { i := bytes.IndexAny(in, "#;") if i == -1 { return nil, false } return in[i:], true } func readKeyName(delimiters string, in []byte) (string, int, error) { line := string(in) // Check if key name surrounded by quotes. var keyQuote string if line[0] == '"' { if len(line) > 6 && string(line[0:3]) == `"""` { keyQuote = `"""` } else { keyQuote = `"` } } else if line[0] == '`' { keyQuote = "`" } // Get out key name endIdx := -1 if len(keyQuote) > 0 { startIdx := len(keyQuote) // FIXME: fail case -> """"""name"""=value pos := strings.Index(line[startIdx:], keyQuote) if pos == -1 { return "", -1, fmt.Errorf("missing closing key quote: %s", line) } pos += startIdx // Find key-value delimiter i := strings.IndexAny(line[pos+startIdx:], delimiters) if i < 0 { return "", -1, ErrDelimiterNotFound{line} } endIdx = pos + i return strings.TrimSpace(line[startIdx:pos]), endIdx + startIdx + 1, nil } endIdx = strings.IndexAny(line, delimiters) if endIdx < 0 { return "", -1, ErrDelimiterNotFound{line} } return strings.TrimSpace(line[0:endIdx]), endIdx + 1, nil } func (p *parser) readMultilines(line, val, valQuote string) (string, error) { for { data, err := p.readUntil('\n') if err != nil { return "", err } next := string(data) pos := strings.LastIndex(next, valQuote) if pos > -1 { val += next[:pos] comment, has := cleanComment([]byte(next[pos:])) if has { p.comment.Write(bytes.TrimSpace(comment)) } break } val += next if p.isEOF { return "", fmt.Errorf("missing closing key quote from %q to %q", line, next) } } return val, nil } func (p *parser) readContinuationLines(val string) (string, error) { for { data, err := p.readUntil('\n') if err != nil { return "", err } next := strings.TrimSpace(string(data)) if len(next) == 0 { break } val += next if val[len(val)-1] != '\\' { break } val = val[:len(val)-1] } return val, nil } // hasSurroundedQuote check if and only if the first and last characters // are quotes \" or \'. // It returns false if any other parts also contain same kind of quotes. func hasSurroundedQuote(in string, quote byte) bool { return len(in) >= 2 && in[0] == quote && in[len(in)-1] == quote && strings.IndexByte(in[1:], quote) == len(in)-2 } func (p *parser) readValue(in []byte, bufferSize int) (string, error) { line := strings.TrimLeftFunc(string(in), unicode.IsSpace) if len(line) == 0 { if p.options.AllowPythonMultilineValues && len(in) > 0 && in[len(in)-1] == '\n' { return p.readPythonMultilines(line, bufferSize) } return "", nil } var valQuote string if len(line) > 3 && string(line[0:3]) == `"""` { valQuote = `"""` } else if line[0] == '`' { valQuote = "`" } else if p.options.UnescapeValueDoubleQuotes && line[0] == '"' { valQuote = `"` } if len(valQuote) > 0 { startIdx := len(valQuote) pos := strings.LastIndex(line[startIdx:], valQuote) // Check for multi-line value if pos == -1 { return p.readMultilines(line, line[startIdx:], valQuote) } if p.options.UnescapeValueDoubleQuotes && valQuote == `"` { return strings.Replace(line[startIdx:pos+startIdx], `\"`, `"`, -1), nil } return line[startIdx : pos+startIdx], nil } lastChar := line[len(line)-1] // Won't be able to reach here if value only contains whitespace line = strings.TrimSpace(line) trimmedLastChar := line[len(line)-1] // Check continuation lines when desired if !p.options.IgnoreContinuation && trimmedLastChar == '\\' { return p.readContinuationLines(line[:len(line)-1]) } // Check if ignore inline comment if !p.options.IgnoreInlineComment { var i int if p.options.SpaceBeforeInlineComment { i = strings.Index(line, " #") if i == -1 { i = strings.Index(line, " ;") } } else { i = strings.IndexAny(line, "#;") } if i > -1 { p.comment.WriteString(line[i:]) line = strings.TrimSpace(line[:i]) } } // Trim single and double quotes if (hasSurroundedQuote(line, '\'') || hasSurroundedQuote(line, '"')) && !p.options.PreserveSurroundedQuote { line = line[1 : len(line)-1] } else if len(valQuote) == 0 && p.options.UnescapeValueCommentSymbols { if strings.Contains(line, `\;`) { line = strings.Replace(line, `\;`, ";", -1) } if strings.Contains(line, `\#`) { line = strings.Replace(line, `\#`, "#", -1) } } else if p.options.AllowPythonMultilineValues && lastChar == '\n' { return p.readPythonMultilines(line, bufferSize) } return line, nil } func (p *parser) readPythonMultilines(line string, bufferSize int) (string, error) { parserBufferPeekResult, _ := p.buf.Peek(bufferSize) peekBuffer := bytes.NewBuffer(parserBufferPeekResult) indentSize := 0 for { peekData, peekErr := peekBuffer.ReadBytes('\n') if peekErr != nil { if peekErr == io.EOF { p.debug("readPythonMultilines: io.EOF, peekData: %q, line: %q", string(peekData), line) return line, nil } p.debug("readPythonMultilines: failed to peek with error: %v", peekErr) return "", peekErr } p.debug("readPythonMultilines: parsing %q", string(peekData)) peekMatches := pythonMultiline.FindStringSubmatch(string(peekData)) p.debug("readPythonMultilines: matched %d parts", len(peekMatches)) for n, v := range peekMatches { p.debug(" %d: %q", n, v) } // Return if not a Python multiline value. if len(peekMatches) != 3 { p.debug("readPythonMultilines: end of value, got: %q", line) return line, nil } // Determine indent size and line prefix. currentIndentSize := len(peekMatches[1]) if indentSize < 1 { indentSize = currentIndentSize p.debug("readPythonMultilines: indent size is %d", indentSize) } // Make sure each line is indented at least as far as first line. if currentIndentSize < indentSize { p.debug("readPythonMultilines: end of value, current indent: %d, expected indent: %d, line: %q", currentIndentSize, indentSize, line) return line, nil } // Advance the parser reader (buffer) in-sync with the peek buffer. _, err := p.buf.Discard(len(peekData)) if err != nil { p.debug("readPythonMultilines: failed to skip to the end, returning error") return "", err } // Handle indented empty line. line += "\n" + peekMatches[1][indentSize:] + peekMatches[2] } } // parse parses data through an io.Reader. func (f *File) parse(reader io.Reader) (err error) { p := newParser(reader, parserOptions{ IgnoreContinuation: f.options.IgnoreContinuation, IgnoreInlineComment: f.options.IgnoreInlineComment, AllowPythonMultilineValues: f.options.AllowPythonMultilineValues, SpaceBeforeInlineComment: f.options.SpaceBeforeInlineComment, UnescapeValueDoubleQuotes: f.options.UnescapeValueDoubleQuotes, UnescapeValueCommentSymbols: f.options.UnescapeValueCommentSymbols, PreserveSurroundedQuote: f.options.PreserveSurroundedQuote, DebugFunc: f.options.DebugFunc, ReaderBufferSize: f.options.ReaderBufferSize, }) if err = p.BOM(); err != nil { return fmt.Errorf("BOM: %v", err) } // Ignore error because default section name is never empty string. name := DefaultSection if f.options.Insensitive { name = strings.ToLower(DefaultSection) } section, _ := f.NewSection(name) // This "last" is not strictly equivalent to "previous one" if current key is not the first nested key var isLastValueEmpty bool var lastRegularKey *Key var line []byte var inUnparseableSection bool // NOTE: Iterate and increase `currentPeekSize` until // the size of the parser buffer is found. // TODO(unknwon): When Golang 1.10 is the lowest version supported, replace with `parserBufferSize := p.buf.Size()`. parserBufferSize := 0 // NOTE: Peek 4kb at a time. currentPeekSize := minReaderBufferSize if f.options.AllowPythonMultilineValues { for { peekBytes, _ := p.buf.Peek(currentPeekSize) peekBytesLength := len(peekBytes) if parserBufferSize >= peekBytesLength { break } currentPeekSize *= 2 parserBufferSize = peekBytesLength } } for !p.isEOF { line, err = p.readUntil('\n') if err != nil { return err } if f.options.AllowNestedValues && isLastValueEmpty && len(line) > 0 { if line[0] == ' ' || line[0] == '\t' { lastRegularKey.addNestedValue(string(bytes.TrimSpace(line))) continue } } line = bytes.TrimLeftFunc(line, unicode.IsSpace) if len(line) == 0 { continue } // Comments if line[0] == '#' || line[0] == ';' { // Note: we do not care ending line break, // it is needed for adding second line, // so just clean it once at the end when set to value. p.comment.Write(line) continue } // Section if line[0] == '[' { // Read to the next ']' (TODO: support quoted strings) closeIdx := bytes.LastIndexByte(line, ']') if closeIdx == -1 { return fmt.Errorf("unclosed section: %s", line) } name := string(line[1:closeIdx]) section, err = f.NewSection(name) if err != nil { return err } comment, has := cleanComment(line[closeIdx+1:]) if has { p.comment.Write(comment) } section.Comment = strings.TrimSpace(p.comment.String()) // Reset auto-counter and comments p.comment.Reset() p.count = 1 inUnparseableSection = false for i := range f.options.UnparseableSections { if f.options.UnparseableSections[i] == name || (f.options.Insensitive && strings.ToLower(f.options.UnparseableSections[i]) == strings.ToLower(name)) { inUnparseableSection = true continue } } continue } if inUnparseableSection { section.isRawSection = true section.rawBody += string(line) continue } kname, offset, err := readKeyName(f.options.KeyValueDelimiters, line) if err != nil { // Treat as boolean key when desired, and whole line is key name. if IsErrDelimiterNotFound(err) { switch { case f.options.AllowBooleanKeys: kname, err := p.readValue(line, parserBufferSize) if err != nil { return err } key, err := section.NewBooleanKey(kname) if err != nil { return err } key.Comment = strings.TrimSpace(p.comment.String()) p.comment.Reset() continue case f.options.SkipUnrecognizableLines: continue } } return err } // Auto increment. isAutoIncr := false if kname == "-" { isAutoIncr = true kname = "#" + strconv.Itoa(p.count) p.count++ } value, err := p.readValue(line[offset:], parserBufferSize) if err != nil { return err } isLastValueEmpty = len(value) == 0 key, err := section.NewKey(kname, value) if err != nil { return err } key.isAutoIncrement = isAutoIncr key.Comment = strings.TrimSpace(p.comment.String()) p.comment.Reset() lastRegularKey = key } return nil } ini-1.55.0/parser_test.go000066400000000000000000000035541363342257300152620ustar00rootroot00000000000000// Copyright 2016 Unknwon // // Licensed under the Apache License, Version 2.0 (the "License"): you may // not use this file except in compliance with the License. You may obtain // a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the // License for the specific language governing permissions and limitations // under the License. package ini_test import ( "testing" . "github.com/smartystreets/goconvey/convey" "gopkg.in/ini.v1" ) func TestBOM(t *testing.T) { Convey("Test handling BOM", t, func() { Convey("UTF-8-BOM", func() { f, err := ini.Load("testdata/UTF-8-BOM.ini") So(err, ShouldBeNil) So(f, ShouldNotBeNil) So(f.Section("author").Key("E-MAIL").String(), ShouldEqual, "example@email.com") }) Convey("UTF-16-LE-BOM", func() { f, err := ini.Load("testdata/UTF-16-LE-BOM.ini") So(err, ShouldBeNil) So(f, ShouldNotBeNil) }) Convey("UTF-16-BE-BOM", func() { }) }) } func TestBadLoad(t *testing.T) { Convey("Load with bad data", t, func() { Convey("Bad section name", func() { _, err := ini.Load([]byte("[]")) So(err, ShouldNotBeNil) _, err = ini.Load([]byte("[")) So(err, ShouldNotBeNil) }) Convey("Bad keys", func() { _, err := ini.Load([]byte(`"""name`)) So(err, ShouldNotBeNil) _, err = ini.Load([]byte(`"""name"""`)) So(err, ShouldNotBeNil) _, err = ini.Load([]byte(`""=1`)) So(err, ShouldNotBeNil) _, err = ini.Load([]byte(`=`)) So(err, ShouldNotBeNil) _, err = ini.Load([]byte(`name`)) So(err, ShouldNotBeNil) }) Convey("Bad values", func() { _, err := ini.Load([]byte(`name="""Unknwon`)) So(err, ShouldNotBeNil) }) }) } ini-1.55.0/section.go000066400000000000000000000133761363342257300143760ustar00rootroot00000000000000// Copyright 2014 Unknwon // // Licensed under the Apache License, Version 2.0 (the "License"): you may // not use this file except in compliance with the License. You may obtain // a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the // License for the specific language governing permissions and limitations // under the License. package ini import ( "errors" "fmt" "strings" ) // Section represents a config section. type Section struct { f *File Comment string name string keys map[string]*Key keyList []string keysHash map[string]string isRawSection bool rawBody string } func newSection(f *File, name string) *Section { return &Section{ f: f, name: name, keys: make(map[string]*Key), keyList: make([]string, 0, 10), keysHash: make(map[string]string), } } // Name returns name of Section. func (s *Section) Name() string { return s.name } // Body returns rawBody of Section if the section was marked as unparseable. // It still follows the other rules of the INI format surrounding leading/trailing whitespace. func (s *Section) Body() string { return strings.TrimSpace(s.rawBody) } // SetBody updates body content only if section is raw. func (s *Section) SetBody(body string) { if !s.isRawSection { return } s.rawBody = body } // NewKey creates a new key to given section. func (s *Section) NewKey(name, val string) (*Key, error) { if len(name) == 0 { return nil, errors.New("error creating new key: empty key name") } else if s.f.options.Insensitive { name = strings.ToLower(name) } if s.f.BlockMode { s.f.lock.Lock() defer s.f.lock.Unlock() } if inSlice(name, s.keyList) { if s.f.options.AllowShadows { if err := s.keys[name].addShadow(val); err != nil { return nil, err } } else { s.keys[name].value = val s.keysHash[name] = val } return s.keys[name], nil } s.keyList = append(s.keyList, name) s.keys[name] = newKey(s, name, val) s.keysHash[name] = val return s.keys[name], nil } // NewBooleanKey creates a new boolean type key to given section. func (s *Section) NewBooleanKey(name string) (*Key, error) { key, err := s.NewKey(name, "true") if err != nil { return nil, err } key.isBooleanType = true return key, nil } // GetKey returns key in section by given name. func (s *Section) GetKey(name string) (*Key, error) { if s.f.BlockMode { s.f.lock.RLock() } if s.f.options.Insensitive { name = strings.ToLower(name) } key := s.keys[name] if s.f.BlockMode { s.f.lock.RUnlock() } if key == nil { // Check if it is a child-section. sname := s.name for { if i := strings.LastIndex(sname, "."); i > -1 { sname = sname[:i] sec, err := s.f.GetSection(sname) if err != nil { continue } return sec.GetKey(name) } break } return nil, fmt.Errorf("error when getting key of section %q: key %q not exists", s.name, name) } return key, nil } // HasKey returns true if section contains a key with given name. func (s *Section) HasKey(name string) bool { key, _ := s.GetKey(name) return key != nil } // Deprecated: Use "HasKey" instead. func (s *Section) Haskey(name string) bool { return s.HasKey(name) } // HasValue returns true if section contains given raw value. func (s *Section) HasValue(value string) bool { if s.f.BlockMode { s.f.lock.RLock() defer s.f.lock.RUnlock() } for _, k := range s.keys { if value == k.value { return true } } return false } // Key assumes named Key exists in section and returns a zero-value when not. func (s *Section) Key(name string) *Key { key, err := s.GetKey(name) if err != nil { // It's OK here because the only possible error is empty key name, // but if it's empty, this piece of code won't be executed. key, _ = s.NewKey(name, "") return key } return key } // Keys returns list of keys of section. func (s *Section) Keys() []*Key { keys := make([]*Key, len(s.keyList)) for i := range s.keyList { keys[i] = s.Key(s.keyList[i]) } return keys } // ParentKeys returns list of keys of parent section. func (s *Section) ParentKeys() []*Key { var parentKeys []*Key sname := s.name for { if i := strings.LastIndex(sname, "."); i > -1 { sname = sname[:i] sec, err := s.f.GetSection(sname) if err != nil { continue } parentKeys = append(parentKeys, sec.Keys()...) } else { break } } return parentKeys } // KeyStrings returns list of key names of section. func (s *Section) KeyStrings() []string { list := make([]string, len(s.keyList)) copy(list, s.keyList) return list } // KeysHash returns keys hash consisting of names and values. func (s *Section) KeysHash() map[string]string { if s.f.BlockMode { s.f.lock.RLock() defer s.f.lock.RUnlock() } hash := map[string]string{} for key, value := range s.keysHash { hash[key] = value } return hash } // DeleteKey deletes a key from section. func (s *Section) DeleteKey(name string) { if s.f.BlockMode { s.f.lock.Lock() defer s.f.lock.Unlock() } for i, k := range s.keyList { if k == name { s.keyList = append(s.keyList[:i], s.keyList[i+1:]...) delete(s.keys, name) delete(s.keysHash, name) return } } } // ChildSections returns a list of child sections of current section. // For example, "[parent.child1]" and "[parent.child12]" are child sections // of section "[parent]". func (s *Section) ChildSections() []*Section { prefix := s.name + "." children := make([]*Section, 0, 3) for _, name := range s.f.sectionList { if strings.HasPrefix(name, prefix) { children = append(children, s.f.sections[name]...) } } return children } ini-1.55.0/section_test.go000066400000000000000000000177041363342257300154340ustar00rootroot00000000000000// Copyright 2014 Unknwon // // Licensed under the Apache License, Version 2.0 (the "License"): you may // not use this file except in compliance with the License. You may obtain // a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the // License for the specific language governing permissions and limitations // under the License. package ini_test import ( "testing" . "github.com/smartystreets/goconvey/convey" "gopkg.in/ini.v1" ) func TestSection_SetBody(t *testing.T) { Convey("Set body of raw section", t, func() { f := ini.Empty() So(f, ShouldNotBeNil) sec, err := f.NewRawSection("comments", `1111111111111111111000000000000000001110000 111111111111111111100000000000111000000000`) So(err, ShouldBeNil) So(sec, ShouldNotBeNil) So(sec.Body(), ShouldEqual, `1111111111111111111000000000000000001110000 111111111111111111100000000000111000000000`) sec.SetBody("1111111111111111111000000000000000001110000") So(sec.Body(), ShouldEqual, `1111111111111111111000000000000000001110000`) Convey("Set for non-raw section", func() { sec, err := f.NewSection("author") So(err, ShouldBeNil) So(sec, ShouldNotBeNil) So(sec.Body(), ShouldBeEmpty) sec.SetBody("1111111111111111111000000000000000001110000") So(sec.Body(), ShouldBeEmpty) }) }) } func TestSection_NewKey(t *testing.T) { Convey("Create a new key", t, func() { f := ini.Empty() So(f, ShouldNotBeNil) k, err := f.Section("").NewKey("NAME", "ini") So(err, ShouldBeNil) So(k, ShouldNotBeNil) So(k.Name(), ShouldEqual, "NAME") So(k.Value(), ShouldEqual, "ini") Convey("With duplicated name", func() { k, err := f.Section("").NewKey("NAME", "ini.v1") So(err, ShouldBeNil) So(k, ShouldNotBeNil) // Overwrite previous existed key So(k.Value(), ShouldEqual, "ini.v1") }) Convey("With empty string", func() { _, err := f.Section("").NewKey("", "") So(err, ShouldNotBeNil) }) }) Convey("Create keys with same name and allow shadow", t, func() { f, err := ini.ShadowLoad([]byte("")) So(err, ShouldBeNil) So(f, ShouldNotBeNil) k, err := f.Section("").NewKey("NAME", "ini") So(err, ShouldBeNil) So(k, ShouldNotBeNil) k, err = f.Section("").NewKey("NAME", "ini.v1") So(err, ShouldBeNil) So(k, ShouldNotBeNil) So(k.ValueWithShadows(), ShouldResemble, []string{"ini", "ini.v1"}) }) } func TestSection_NewBooleanKey(t *testing.T) { Convey("Create a new boolean key", t, func() { f := ini.Empty() So(f, ShouldNotBeNil) k, err := f.Section("").NewBooleanKey("start-ssh-server") So(err, ShouldBeNil) So(k, ShouldNotBeNil) So(k.Name(), ShouldEqual, "start-ssh-server") So(k.Value(), ShouldEqual, "true") Convey("With empty string", func() { _, err := f.Section("").NewBooleanKey("") So(err, ShouldNotBeNil) }) }) } func TestSection_GetKey(t *testing.T) { Convey("Get a key", t, func() { f := ini.Empty() So(f, ShouldNotBeNil) k, err := f.Section("").NewKey("NAME", "ini") So(err, ShouldBeNil) So(k, ShouldNotBeNil) k, err = f.Section("").GetKey("NAME") So(err, ShouldBeNil) So(k, ShouldNotBeNil) So(k.Name(), ShouldEqual, "NAME") So(k.Value(), ShouldEqual, "ini") Convey("Key not exists", func() { _, err := f.Section("").GetKey("404") So(err, ShouldNotBeNil) }) Convey("Key exists in parent section", func() { k, err := f.Section("parent").NewKey("AGE", "18") So(err, ShouldBeNil) So(k, ShouldNotBeNil) k, err = f.Section("parent.child.son").GetKey("AGE") So(err, ShouldBeNil) So(k, ShouldNotBeNil) So(k.Value(), ShouldEqual, "18") }) }) } func TestSection_HasKey(t *testing.T) { Convey("Check if a key exists", t, func() { f := ini.Empty() So(f, ShouldNotBeNil) k, err := f.Section("").NewKey("NAME", "ini") So(err, ShouldBeNil) So(k, ShouldNotBeNil) So(f.Section("").HasKey("NAME"), ShouldBeTrue) So(f.Section("").HasKey("NAME"), ShouldBeTrue) So(f.Section("").HasKey("404"), ShouldBeFalse) So(f.Section("").HasKey("404"), ShouldBeFalse) }) } func TestSection_HasValue(t *testing.T) { Convey("Check if contains a value in any key", t, func() { f := ini.Empty() So(f, ShouldNotBeNil) k, err := f.Section("").NewKey("NAME", "ini") So(err, ShouldBeNil) So(k, ShouldNotBeNil) So(f.Section("").HasValue("ini"), ShouldBeTrue) So(f.Section("").HasValue("404"), ShouldBeFalse) }) } func TestSection_Key(t *testing.T) { Convey("Get a key", t, func() { f := ini.Empty() So(f, ShouldNotBeNil) k, err := f.Section("").NewKey("NAME", "ini") So(err, ShouldBeNil) So(k, ShouldNotBeNil) k = f.Section("").Key("NAME") So(k, ShouldNotBeNil) So(k.Name(), ShouldEqual, "NAME") So(k.Value(), ShouldEqual, "ini") Convey("Key not exists", func() { k := f.Section("").Key("404") So(k, ShouldNotBeNil) So(k.Name(), ShouldEqual, "404") }) Convey("Key exists in parent section", func() { k, err := f.Section("parent").NewKey("AGE", "18") So(err, ShouldBeNil) So(k, ShouldNotBeNil) k = f.Section("parent.child.son").Key("AGE") So(k, ShouldNotBeNil) So(k.Value(), ShouldEqual, "18") }) }) } func TestSection_Keys(t *testing.T) { Convey("Get all keys in a section", t, func() { f := ini.Empty() So(f, ShouldNotBeNil) k, err := f.Section("").NewKey("NAME", "ini") So(err, ShouldBeNil) So(k, ShouldNotBeNil) k, err = f.Section("").NewKey("VERSION", "v1") So(err, ShouldBeNil) So(k, ShouldNotBeNil) k, err = f.Section("").NewKey("IMPORT_PATH", "gopkg.in/ini.v1") So(err, ShouldBeNil) So(k, ShouldNotBeNil) keys := f.Section("").Keys() names := []string{"NAME", "VERSION", "IMPORT_PATH"} So(len(keys), ShouldEqual, len(names)) for i, name := range names { So(keys[i].Name(), ShouldEqual, name) } }) } func TestSection_ParentKeys(t *testing.T) { Convey("Get all keys of parent sections", t, func() { f := ini.Empty() So(f, ShouldNotBeNil) k, err := f.Section("package").NewKey("NAME", "ini") So(err, ShouldBeNil) So(k, ShouldNotBeNil) k, err = f.Section("package").NewKey("VERSION", "v1") So(err, ShouldBeNil) So(k, ShouldNotBeNil) k, err = f.Section("package").NewKey("IMPORT_PATH", "gopkg.in/ini.v1") So(err, ShouldBeNil) So(k, ShouldNotBeNil) keys := f.Section("package.sub.sub2").ParentKeys() names := []string{"NAME", "VERSION", "IMPORT_PATH"} So(len(keys), ShouldEqual, len(names)) for i, name := range names { So(keys[i].Name(), ShouldEqual, name) } }) } func TestSection_KeyStrings(t *testing.T) { Convey("Get all key names in a section", t, func() { f := ini.Empty() So(f, ShouldNotBeNil) k, err := f.Section("").NewKey("NAME", "ini") So(err, ShouldBeNil) So(k, ShouldNotBeNil) k, err = f.Section("").NewKey("VERSION", "v1") So(err, ShouldBeNil) So(k, ShouldNotBeNil) k, err = f.Section("").NewKey("IMPORT_PATH", "gopkg.in/ini.v1") So(err, ShouldBeNil) So(k, ShouldNotBeNil) So(f.Section("").KeyStrings(), ShouldResemble, []string{"NAME", "VERSION", "IMPORT_PATH"}) }) } func TestSection_KeyHash(t *testing.T) { Convey("Get clone of key hash", t, func() { f, err := ini.Load([]byte(` key = one [log] name = app file = a.log `), []byte(` key = two [log] name = app2 file = b.log `)) So(err, ShouldBeNil) So(f, ShouldNotBeNil) So(f.Section("").Key("key").String(), ShouldEqual, "two") hash := f.Section("log").KeysHash() relation := map[string]string{ "name": "app2", "file": "b.log", } for k, v := range hash { So(v, ShouldEqual, relation[k]) } }) } func TestSection_DeleteKey(t *testing.T) { Convey("Delete a key", t, func() { f := ini.Empty() So(f, ShouldNotBeNil) k, err := f.Section("").NewKey("NAME", "ini") So(err, ShouldBeNil) So(k, ShouldNotBeNil) So(f.Section("").HasKey("NAME"), ShouldBeTrue) f.Section("").DeleteKey("NAME") So(f.Section("").HasKey("NAME"), ShouldBeFalse) }) } ini-1.55.0/struct.go000066400000000000000000000464411363342257300142550ustar00rootroot00000000000000// Copyright 2014 Unknwon // // Licensed under the Apache License, Version 2.0 (the "License"): you may // not use this file except in compliance with the License. You may obtain // a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the // License for the specific language governing permissions and limitations // under the License. package ini import ( "bytes" "errors" "fmt" "reflect" "strings" "time" "unicode" ) // NameMapper represents a ini tag name mapper. type NameMapper func(string) string // Built-in name getters. var ( // SnackCase converts to format SNACK_CASE. SnackCase NameMapper = func(raw string) string { newstr := make([]rune, 0, len(raw)) for i, chr := range raw { if isUpper := 'A' <= chr && chr <= 'Z'; isUpper { if i > 0 { newstr = append(newstr, '_') } } newstr = append(newstr, unicode.ToUpper(chr)) } return string(newstr) } // TitleUnderscore converts to format title_underscore. TitleUnderscore NameMapper = func(raw string) string { newstr := make([]rune, 0, len(raw)) for i, chr := range raw { if isUpper := 'A' <= chr && chr <= 'Z'; isUpper { if i > 0 { newstr = append(newstr, '_') } chr -= 'A' - 'a' } newstr = append(newstr, chr) } return string(newstr) } ) func (s *Section) parseFieldName(raw, actual string) string { if len(actual) > 0 { return actual } if s.f.NameMapper != nil { return s.f.NameMapper(raw) } return raw } func parseDelim(actual string) string { if len(actual) > 0 { return actual } return "," } var reflectTime = reflect.TypeOf(time.Now()).Kind() // setSliceWithProperType sets proper values to slice based on its type. func setSliceWithProperType(key *Key, field reflect.Value, delim string, allowShadow, isStrict bool) error { var strs []string if allowShadow { strs = key.StringsWithShadows(delim) } else { strs = key.Strings(delim) } numVals := len(strs) if numVals == 0 { return nil } var vals interface{} var err error sliceOf := field.Type().Elem().Kind() switch sliceOf { case reflect.String: vals = strs case reflect.Int: vals, err = key.parseInts(strs, true, false) case reflect.Int64: vals, err = key.parseInt64s(strs, true, false) case reflect.Uint: vals, err = key.parseUints(strs, true, false) case reflect.Uint64: vals, err = key.parseUint64s(strs, true, false) case reflect.Float64: vals, err = key.parseFloat64s(strs, true, false) case reflect.Bool: vals, err = key.parseBools(strs, true, false) case reflectTime: vals, err = key.parseTimesFormat(time.RFC3339, strs, true, false) default: return fmt.Errorf("unsupported type '[]%s'", sliceOf) } if err != nil && isStrict { return err } slice := reflect.MakeSlice(field.Type(), numVals, numVals) for i := 0; i < numVals; i++ { switch sliceOf { case reflect.String: slice.Index(i).Set(reflect.ValueOf(vals.([]string)[i])) case reflect.Int: slice.Index(i).Set(reflect.ValueOf(vals.([]int)[i])) case reflect.Int64: slice.Index(i).Set(reflect.ValueOf(vals.([]int64)[i])) case reflect.Uint: slice.Index(i).Set(reflect.ValueOf(vals.([]uint)[i])) case reflect.Uint64: slice.Index(i).Set(reflect.ValueOf(vals.([]uint64)[i])) case reflect.Float64: slice.Index(i).Set(reflect.ValueOf(vals.([]float64)[i])) case reflect.Bool: slice.Index(i).Set(reflect.ValueOf(vals.([]bool)[i])) case reflectTime: slice.Index(i).Set(reflect.ValueOf(vals.([]time.Time)[i])) } } field.Set(slice) return nil } func wrapStrictError(err error, isStrict bool) error { if isStrict { return err } return nil } // setWithProperType sets proper value to field based on its type, // but it does not return error for failing parsing, // because we want to use default value that is already assigned to struct. func setWithProperType(t reflect.Type, key *Key, field reflect.Value, delim string, allowShadow, isStrict bool) error { vt := t isPtr := t.Kind() == reflect.Ptr if isPtr { vt = t.Elem() } switch vt.Kind() { case reflect.String: stringVal := key.String() if isPtr { field.Set(reflect.ValueOf(&stringVal)) } else if len(stringVal) > 0 { field.SetString(key.String()) } case reflect.Bool: boolVal, err := key.Bool() if err != nil { return wrapStrictError(err, isStrict) } if isPtr { field.Set(reflect.ValueOf(&boolVal)) } else { field.SetBool(boolVal) } case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: // ParseDuration will not return err for `0`, so check the type name if vt.Name() == "Duration" { durationVal, err := key.Duration() if err != nil { if intVal, err := key.Int64(); err == nil { field.SetInt(intVal) return nil } return wrapStrictError(err, isStrict) } if isPtr { field.Set(reflect.ValueOf(&durationVal)) } else if int64(durationVal) > 0 { field.Set(reflect.ValueOf(durationVal)) } return nil } intVal, err := key.Int64() if err != nil { return wrapStrictError(err, isStrict) } if isPtr { pv := reflect.New(t.Elem()) pv.Elem().SetInt(intVal) field.Set(pv) } else { field.SetInt(intVal) } // byte is an alias for uint8, so supporting uint8 breaks support for byte case reflect.Uint, reflect.Uint16, reflect.Uint32, reflect.Uint64: durationVal, err := key.Duration() // Skip zero value if err == nil && uint64(durationVal) > 0 { if isPtr { field.Set(reflect.ValueOf(&durationVal)) } else { field.Set(reflect.ValueOf(durationVal)) } return nil } uintVal, err := key.Uint64() if err != nil { return wrapStrictError(err, isStrict) } if isPtr { pv := reflect.New(t.Elem()) pv.Elem().SetUint(uintVal) field.Set(pv) } else { field.SetUint(uintVal) } case reflect.Float32, reflect.Float64: floatVal, err := key.Float64() if err != nil { return wrapStrictError(err, isStrict) } if isPtr { pv := reflect.New(t.Elem()) pv.Elem().SetFloat(floatVal) field.Set(pv) } else { field.SetFloat(floatVal) } case reflectTime: timeVal, err := key.Time() if err != nil { return wrapStrictError(err, isStrict) } if isPtr { field.Set(reflect.ValueOf(&timeVal)) } else { field.Set(reflect.ValueOf(timeVal)) } case reflect.Slice: return setSliceWithProperType(key, field, delim, allowShadow, isStrict) default: return fmt.Errorf("unsupported type %q", t) } return nil } func parseTagOptions(tag string) (rawName string, omitEmpty bool, allowShadow bool, allowNonUnique bool) { opts := strings.SplitN(tag, ",", 4) rawName = opts[0] if len(opts) > 1 { omitEmpty = opts[1] == "omitempty" } if len(opts) > 2 { allowShadow = opts[2] == "allowshadow" } if len(opts) > 3 { allowNonUnique = opts[3] == "nonunique" } return rawName, omitEmpty, allowShadow, allowNonUnique } func (s *Section) mapToField(val reflect.Value, isStrict bool) error { if val.Kind() == reflect.Ptr { val = val.Elem() } typ := val.Type() for i := 0; i < typ.NumField(); i++ { field := val.Field(i) tpField := typ.Field(i) tag := tpField.Tag.Get("ini") if tag == "-" { continue } rawName, _, allowShadow, allowNonUnique := parseTagOptions(tag) fieldName := s.parseFieldName(tpField.Name, rawName) if len(fieldName) == 0 || !field.CanSet() { continue } isStruct := tpField.Type.Kind() == reflect.Struct isStructPtr := tpField.Type.Kind() == reflect.Ptr && tpField.Type.Elem().Kind() == reflect.Struct isAnonymous := tpField.Type.Kind() == reflect.Ptr && tpField.Anonymous if isAnonymous { field.Set(reflect.New(tpField.Type.Elem())) } if isAnonymous || isStruct || isStructPtr { if sec, err := s.f.GetSection(fieldName); err == nil { // Only set the field to non-nil struct value if we have a section for it. // Otherwise, we end up with a non-nil struct ptr even though there is no data. if isStructPtr && field.IsNil() { field.Set(reflect.New(tpField.Type.Elem())) } if err = sec.mapToField(field, isStrict); err != nil { return fmt.Errorf("map to field %q: %v", fieldName, err) } continue } } // Map non-unique sections if allowNonUnique && tpField.Type.Kind() == reflect.Slice { newField, err := s.mapToSlice(fieldName, field, isStrict) if err != nil { return fmt.Errorf("map to slice %q: %v", fieldName, err) } field.Set(newField) continue } if key, err := s.GetKey(fieldName); err == nil { delim := parseDelim(tpField.Tag.Get("delim")) if err = setWithProperType(tpField.Type, key, field, delim, allowShadow, isStrict); err != nil { return fmt.Errorf("set field %q: %v", fieldName, err) } } } return nil } // mapToSlice maps all sections with the same name and returns the new value. // The type of the Value must be a slice. func (s *Section) mapToSlice(secName string, val reflect.Value, isStrict bool) (reflect.Value, error) { secs, err := s.f.SectionsByName(secName) if err != nil { return reflect.Value{}, err } typ := val.Type().Elem() for _, sec := range secs { elem := reflect.New(typ) if err = sec.mapToField(elem, isStrict); err != nil { return reflect.Value{}, fmt.Errorf("map to field from section %q: %v", secName, err) } val = reflect.Append(val, elem.Elem()) } return val, nil } // mapTo maps a section to object v. func (s *Section) mapTo(v interface{}, isStrict bool) error { typ := reflect.TypeOf(v) val := reflect.ValueOf(v) if typ.Kind() == reflect.Ptr { typ = typ.Elem() val = val.Elem() } else { return errors.New("not a pointer to a struct") } if typ.Kind() == reflect.Slice { newField, err := s.mapToSlice(s.name, val, isStrict) if err != nil { return err } val.Set(newField) return nil } return s.mapToField(val, isStrict) } // MapTo maps section to given struct. func (s *Section) MapTo(v interface{}) error { return s.mapTo(v, false) } // StrictMapTo maps section to given struct in strict mode, // which returns all possible error including value parsing error. func (s *Section) StrictMapTo(v interface{}) error { return s.mapTo(v, true) } // MapTo maps file to given struct. func (f *File) MapTo(v interface{}) error { return f.Section("").MapTo(v) } // StrictMapTo maps file to given struct in strict mode, // which returns all possible error including value parsing error. func (f *File) StrictMapTo(v interface{}) error { return f.Section("").StrictMapTo(v) } // MapToWithMapper maps data sources to given struct with name mapper. func MapToWithMapper(v interface{}, mapper NameMapper, source interface{}, others ...interface{}) error { cfg, err := Load(source, others...) if err != nil { return err } cfg.NameMapper = mapper return cfg.MapTo(v) } // StrictMapToWithMapper maps data sources to given struct with name mapper in strict mode, // which returns all possible error including value parsing error. func StrictMapToWithMapper(v interface{}, mapper NameMapper, source interface{}, others ...interface{}) error { cfg, err := Load(source, others...) if err != nil { return err } cfg.NameMapper = mapper return cfg.StrictMapTo(v) } // MapTo maps data sources to given struct. func MapTo(v, source interface{}, others ...interface{}) error { return MapToWithMapper(v, nil, source, others...) } // StrictMapTo maps data sources to given struct in strict mode, // which returns all possible error including value parsing error. func StrictMapTo(v, source interface{}, others ...interface{}) error { return StrictMapToWithMapper(v, nil, source, others...) } // reflectSliceWithProperType does the opposite thing as setSliceWithProperType. func reflectSliceWithProperType(key *Key, field reflect.Value, delim string, allowShadow bool) error { slice := field.Slice(0, field.Len()) if field.Len() == 0 { return nil } sliceOf := field.Type().Elem().Kind() if allowShadow { var keyWithShadows *Key for i := 0; i < field.Len(); i++ { var val string switch sliceOf { case reflect.String: val = slice.Index(i).String() case reflect.Int, reflect.Int64: val = fmt.Sprint(slice.Index(i).Int()) case reflect.Uint, reflect.Uint64: val = fmt.Sprint(slice.Index(i).Uint()) case reflect.Float64: val = fmt.Sprint(slice.Index(i).Float()) case reflect.Bool: val = fmt.Sprint(slice.Index(i).Bool()) case reflectTime: val = slice.Index(i).Interface().(time.Time).Format(time.RFC3339) default: return fmt.Errorf("unsupported type '[]%s'", sliceOf) } if i == 0 { keyWithShadows = newKey(key.s, key.name, val) } else { _ = keyWithShadows.AddShadow(val) } } key = keyWithShadows return nil } var buf bytes.Buffer for i := 0; i < field.Len(); i++ { switch sliceOf { case reflect.String: buf.WriteString(slice.Index(i).String()) case reflect.Int, reflect.Int64: buf.WriteString(fmt.Sprint(slice.Index(i).Int())) case reflect.Uint, reflect.Uint64: buf.WriteString(fmt.Sprint(slice.Index(i).Uint())) case reflect.Float64: buf.WriteString(fmt.Sprint(slice.Index(i).Float())) case reflect.Bool: buf.WriteString(fmt.Sprint(slice.Index(i).Bool())) case reflectTime: buf.WriteString(slice.Index(i).Interface().(time.Time).Format(time.RFC3339)) default: return fmt.Errorf("unsupported type '[]%s'", sliceOf) } buf.WriteString(delim) } key.SetValue(buf.String()[:buf.Len()-len(delim)]) return nil } // reflectWithProperType does the opposite thing as setWithProperType. func reflectWithProperType(t reflect.Type, key *Key, field reflect.Value, delim string, allowShadow bool) error { switch t.Kind() { case reflect.String: key.SetValue(field.String()) case reflect.Bool: key.SetValue(fmt.Sprint(field.Bool())) case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: key.SetValue(fmt.Sprint(field.Int())) case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: key.SetValue(fmt.Sprint(field.Uint())) case reflect.Float32, reflect.Float64: key.SetValue(fmt.Sprint(field.Float())) case reflectTime: key.SetValue(fmt.Sprint(field.Interface().(time.Time).Format(time.RFC3339))) case reflect.Slice: return reflectSliceWithProperType(key, field, delim, allowShadow) case reflect.Ptr: if !field.IsNil() { return reflectWithProperType(t.Elem(), key, field.Elem(), delim, allowShadow) } default: return fmt.Errorf("unsupported type %q", t) } return nil } // CR: copied from encoding/json/encode.go with modifications of time.Time support. // TODO: add more test coverage. func isEmptyValue(v reflect.Value) bool { switch v.Kind() { case reflect.Array, reflect.Map, reflect.Slice, reflect.String: return v.Len() == 0 case reflect.Bool: return !v.Bool() case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: return v.Int() == 0 case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: return v.Uint() == 0 case reflect.Float32, reflect.Float64: return v.Float() == 0 case reflect.Interface, reflect.Ptr: return v.IsNil() case reflectTime: t, ok := v.Interface().(time.Time) return ok && t.IsZero() } return false } // StructReflector is the interface implemented by struct types that can extract themselves into INI objects. type StructReflector interface { ReflectINIStruct(*File) error } func (s *Section) reflectFrom(val reflect.Value) error { if val.Kind() == reflect.Ptr { val = val.Elem() } typ := val.Type() for i := 0; i < typ.NumField(); i++ { field := val.Field(i) tpField := typ.Field(i) tag := tpField.Tag.Get("ini") if tag == "-" { continue } rawName, omitEmpty, allowShadow, allowNonUnique := parseTagOptions(tag) if omitEmpty && isEmptyValue(field) { continue } if r, ok := field.Interface().(StructReflector); ok { return r.ReflectINIStruct(s.f) } fieldName := s.parseFieldName(tpField.Name, rawName) if len(fieldName) == 0 || !field.CanSet() { continue } if (tpField.Type.Kind() == reflect.Ptr && tpField.Anonymous) || (tpField.Type.Kind() == reflect.Struct && tpField.Type.Name() != "Time") { // Note: The only error here is section doesn't exist. sec, err := s.f.GetSection(fieldName) if err != nil { // Note: fieldName can never be empty here, ignore error. sec, _ = s.f.NewSection(fieldName) } // Add comment from comment tag if len(sec.Comment) == 0 { sec.Comment = tpField.Tag.Get("comment") } if err = sec.reflectFrom(field); err != nil { return fmt.Errorf("reflect from field %q: %v", fieldName, err) } continue } if allowNonUnique && tpField.Type.Kind() == reflect.Slice { slice := field.Slice(0, field.Len()) if field.Len() == 0 { return nil } sliceOf := field.Type().Elem().Kind() for i := 0; i < field.Len(); i++ { if sliceOf != reflect.Struct && sliceOf != reflect.Ptr { return fmt.Errorf("field %q is not a slice of pointer or struct", fieldName) } sec, err := s.f.NewSection(fieldName) if err != nil { return err } // Add comment from comment tag if len(sec.Comment) == 0 { sec.Comment = tpField.Tag.Get("comment") } if err := sec.reflectFrom(slice.Index(i)); err != nil { return fmt.Errorf("reflect from field %q: %v", fieldName, err) } } continue } // Note: Same reason as section. key, err := s.GetKey(fieldName) if err != nil { key, _ = s.NewKey(fieldName, "") } // Add comment from comment tag if len(key.Comment) == 0 { key.Comment = tpField.Tag.Get("comment") } delim := parseDelim(tpField.Tag.Get("delim")) if err = reflectWithProperType(tpField.Type, key, field, delim, allowShadow); err != nil { return fmt.Errorf("reflect field %q: %v", fieldName, err) } } return nil } // ReflectFrom reflects section from given struct. It overwrites existing ones. func (s *Section) ReflectFrom(v interface{}) error { typ := reflect.TypeOf(v) val := reflect.ValueOf(v) if s.name != DefaultSection && s.f.options.AllowNonUniqueSections && (typ.Kind() == reflect.Slice || typ.Kind() == reflect.Ptr) { // Clear sections to make sure none exists before adding the new ones s.f.DeleteSection(s.name) if typ.Kind() == reflect.Ptr { sec, err := s.f.NewSection(s.name) if err != nil { return err } return sec.reflectFrom(val.Elem()) } slice := val.Slice(0, val.Len()) sliceOf := val.Type().Elem().Kind() if sliceOf != reflect.Ptr { return fmt.Errorf("not a slice of pointers") } for i := 0; i < slice.Len(); i++ { sec, err := s.f.NewSection(s.name) if err != nil { return err } err = sec.reflectFrom(slice.Index(i)) if err != nil { return fmt.Errorf("reflect from %dth field: %v", i, err) } } return nil } if typ.Kind() == reflect.Ptr { typ = typ.Elem() val = val.Elem() } else { return errors.New("not a pointer to a struct") } return s.reflectFrom(val) } // ReflectFrom reflects file from given struct. func (f *File) ReflectFrom(v interface{}) error { return f.Section("").ReflectFrom(v) } // ReflectFromWithMapper reflects data sources from given struct with name mapper. func ReflectFromWithMapper(cfg *File, v interface{}, mapper NameMapper) error { cfg.NameMapper = mapper return cfg.ReflectFrom(v) } // ReflectFrom reflects data sources from given struct. func ReflectFrom(cfg *File, v interface{}) error { return ReflectFromWithMapper(cfg, v, nil) } ini-1.55.0/struct_test.go000066400000000000000000000442761363342257300153200ustar00rootroot00000000000000// Copyright 2014 Unknwon // // Licensed under the Apache License, Version 2.0 (the "License"): you may // not use this file except in compliance with the License. You may obtain // a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the // License for the specific language governing permissions and limitations // under the License. package ini_test import ( "bytes" "fmt" "strings" "testing" "time" . "github.com/smartystreets/goconvey/convey" "gopkg.in/ini.v1" ) type testNested struct { Cities []string `delim:"|"` Visits []time.Time Years []int Numbers []int64 Ages []uint Populations []uint64 Coordinates []float64 Flags []bool Note string Unused int `ini:"-"` } type TestEmbeded struct { GPA float64 } type testStruct struct { Name string `ini:"NAME"` Age int Male bool Money float64 Born time.Time Time time.Duration `ini:"Duration"` OldVersionTime time.Duration Others testNested OthersPtr *testNested NilPtr *testNested *TestEmbeded `ini:"grade"` Unused int `ini:"-"` Unsigned uint Omitted bool `ini:"omitthis,omitempty"` Shadows []string `ini:",,allowshadow"` ShadowInts []int `ini:"Shadows,,allowshadow"` BoolPtr *bool BoolPtrNil *bool FloatPtr *float64 FloatPtrNil *float64 IntPtr *int IntPtrNil *int UintPtr *uint UintPtrNil *uint StringPtr *string StringPtrNil *string TimePtr *time.Time TimePtrNil *time.Time DurationPtr *time.Duration DurationPtrNil *time.Duration } type testInterface struct { Address string ListenPort int PrivateKey string } type testPeer struct { PublicKey string PresharedKey string AllowedIPs []string `delim:","` } type testNonUniqueSectionsStruct struct { Interface testInterface Peer []testPeer `ini:",,,nonunique"` } const confDataStruct = ` NAME = Unknwon Age = 21 Male = true Money = 1.25 Born = 1993-10-07T20:17:05Z Duration = 2h45m OldVersionTime = 30 Unsigned = 3 omitthis = true Shadows = 1, 2 Shadows = 3, 4 BoolPtr = false FloatPtr = 0 IntPtr = 0 UintPtr = 0 StringPtr = "" TimePtr = 0001-01-01T00:00:00Z DurationPtr = 0s [Others] Cities = HangZhou|Boston Visits = 1993-10-07T20:17:05Z, 1993-10-07T20:17:05Z Years = 1993,1994 Numbers = 10010,10086 Ages = 18,19 Populations = 12345678,98765432 Coordinates = 192.168,10.11 Flags = true,false Note = Hello world! [OthersPtr] Cities = HangZhou|Boston Visits = 1993-10-07T20:17:05Z, 1993-10-07T20:17:05Z Years = 1993,1994 Numbers = 10010,10086 Ages = 18,19 Populations = 12345678,98765432 Coordinates = 192.168,10.11 Flags = true,false Note = Hello world! [grade] GPA = 2.8 [foo.bar] Here = there When = then ` const confNonUniqueSectionDataStruct = `[Interface] Address = 10.2.0.1/24 ListenPort = 34777 PrivateKey = privServerKey [Peer] PublicKey = pubClientKey PresharedKey = psKey AllowedIPs = 10.2.0.2/32,fd00:2::2/128 [Peer] PublicKey = pubClientKey2 PresharedKey = psKey2 AllowedIPs = 10.2.0.3/32,fd00:2::3/128 ` type unsupport struct { Byte byte } type unsupport2 struct { Others struct { Cities byte } } type Unsupport3 struct { Cities byte } type unsupport4 struct { *Unsupport3 `ini:"Others"` } type defaultValue struct { Name string Age int Male bool Optional *bool Money float64 Born time.Time Cities []string } type fooBar struct { Here, When string } const invalidDataConfStruct = ` Name = Age = age Male = 123 Money = money Born = nil Cities = ` func Test_MapToStruct(t *testing.T) { Convey("Map to struct", t, func() { Convey("Map file to struct", func() { ts := new(testStruct) So(ini.MapTo(ts, []byte(confDataStruct)), ShouldBeNil) So(ts.Name, ShouldEqual, "Unknwon") So(ts.Age, ShouldEqual, 21) So(ts.Male, ShouldBeTrue) So(ts.Money, ShouldEqual, 1.25) So(ts.Unsigned, ShouldEqual, 3) t, err := time.Parse(time.RFC3339, "1993-10-07T20:17:05Z") So(err, ShouldBeNil) So(ts.Born.String(), ShouldEqual, t.String()) dur, err := time.ParseDuration("2h45m") So(err, ShouldBeNil) So(ts.Time.Seconds(), ShouldEqual, dur.Seconds()) So(ts.OldVersionTime*time.Second, ShouldEqual, 30*time.Second) So(strings.Join(ts.Others.Cities, ","), ShouldEqual, "HangZhou,Boston") So(ts.Others.Visits[0].String(), ShouldEqual, t.String()) So(fmt.Sprint(ts.Others.Years), ShouldEqual, "[1993 1994]") So(fmt.Sprint(ts.Others.Numbers), ShouldEqual, "[10010 10086]") So(fmt.Sprint(ts.Others.Ages), ShouldEqual, "[18 19]") So(fmt.Sprint(ts.Others.Populations), ShouldEqual, "[12345678 98765432]") So(fmt.Sprint(ts.Others.Coordinates), ShouldEqual, "[192.168 10.11]") So(fmt.Sprint(ts.Others.Flags), ShouldEqual, "[true false]") So(ts.Others.Note, ShouldEqual, "Hello world!") So(ts.TestEmbeded.GPA, ShouldEqual, 2.8) So(strings.Join(ts.OthersPtr.Cities, ","), ShouldEqual, "HangZhou,Boston") So(ts.OthersPtr.Visits[0].String(), ShouldEqual, t.String()) So(fmt.Sprint(ts.OthersPtr.Years), ShouldEqual, "[1993 1994]") So(fmt.Sprint(ts.OthersPtr.Numbers), ShouldEqual, "[10010 10086]") So(fmt.Sprint(ts.OthersPtr.Ages), ShouldEqual, "[18 19]") So(fmt.Sprint(ts.OthersPtr.Populations), ShouldEqual, "[12345678 98765432]") So(fmt.Sprint(ts.OthersPtr.Coordinates), ShouldEqual, "[192.168 10.11]") So(fmt.Sprint(ts.OthersPtr.Flags), ShouldEqual, "[true false]") So(ts.OthersPtr.Note, ShouldEqual, "Hello world!") So(ts.NilPtr, ShouldBeNil) So(*ts.BoolPtr, ShouldEqual, false) So(ts.BoolPtrNil, ShouldEqual, nil) So(*ts.FloatPtr, ShouldEqual, 0) So(ts.FloatPtrNil, ShouldEqual, nil) So(*ts.IntPtr, ShouldEqual, 0) So(ts.IntPtrNil, ShouldEqual, nil) So(*ts.UintPtr, ShouldEqual, 0) So(ts.UintPtrNil, ShouldEqual, nil) So(*ts.StringPtr, ShouldEqual, "") So(ts.StringPtrNil, ShouldEqual, nil) So(*ts.TimePtr, ShouldNotEqual, nil) So(ts.TimePtrNil, ShouldEqual, nil) So(*ts.DurationPtr, ShouldEqual, 0) So(ts.DurationPtrNil, ShouldEqual, nil) }) Convey("Map section to struct", func() { foobar := new(fooBar) f, err := ini.Load([]byte(confDataStruct)) So(err, ShouldBeNil) So(f.Section("foo.bar").MapTo(foobar), ShouldBeNil) So(foobar.Here, ShouldEqual, "there") So(foobar.When, ShouldEqual, "then") }) Convey("Map to non-pointer struct", func() { f, err := ini.Load([]byte(confDataStruct)) So(err, ShouldBeNil) So(f, ShouldNotBeNil) So(f.MapTo(testStruct{}), ShouldNotBeNil) }) Convey("Map to unsupported type", func() { f, err := ini.Load([]byte(confDataStruct)) So(err, ShouldBeNil) So(f, ShouldNotBeNil) f.NameMapper = func(raw string) string { if raw == "Byte" { return "NAME" } return raw } So(f.MapTo(&unsupport{}), ShouldNotBeNil) So(f.MapTo(&unsupport2{}), ShouldNotBeNil) So(f.MapTo(&unsupport4{}), ShouldNotBeNil) }) Convey("Map to omitempty field", func() { ts := new(testStruct) So(ini.MapTo(ts, []byte(confDataStruct)), ShouldBeNil) So(ts.Omitted, ShouldEqual, true) }) Convey("Map with shadows", func() { f, err := ini.LoadSources(ini.LoadOptions{AllowShadows: true}, []byte(confDataStruct)) So(err, ShouldBeNil) ts := new(testStruct) So(f.MapTo(ts), ShouldBeNil) So(strings.Join(ts.Shadows, " "), ShouldEqual, "1 2 3 4") So(fmt.Sprintf("%v", ts.ShadowInts), ShouldEqual, "[1 2 3 4]") }) Convey("Map from invalid data source", func() { So(ini.MapTo(&testStruct{}, "hi"), ShouldNotBeNil) }) Convey("Map to wrong types and gain default values", func() { f, err := ini.Load([]byte(invalidDataConfStruct)) So(err, ShouldBeNil) t, err := time.Parse(time.RFC3339, "1993-10-07T20:17:05Z") So(err, ShouldBeNil) dv := &defaultValue{"Joe", 10, true, nil, 1.25, t, []string{"HangZhou", "Boston"}} So(f.MapTo(dv), ShouldBeNil) So(dv.Name, ShouldEqual, "Joe") So(dv.Age, ShouldEqual, 10) So(dv.Male, ShouldBeTrue) So(dv.Money, ShouldEqual, 1.25) So(dv.Born.String(), ShouldEqual, t.String()) So(strings.Join(dv.Cities, ","), ShouldEqual, "HangZhou,Boston") }) }) Convey("Map to struct in strict mode", t, func() { f, err := ini.Load([]byte(` name=bruce age=a30`)) So(err, ShouldBeNil) type Strict struct { Name string `ini:"name"` Age int `ini:"age"` } s := new(Strict) So(f.Section("").StrictMapTo(s), ShouldNotBeNil) }) Convey("Map slice in strict mode", t, func() { f, err := ini.Load([]byte(` names=alice, bruce`)) So(err, ShouldBeNil) type Strict struct { Names []string `ini:"names"` } s := new(Strict) So(f.Section("").StrictMapTo(s), ShouldBeNil) So(fmt.Sprint(s.Names), ShouldEqual, "[alice bruce]") }) } func Test_MapToStructNonUniqueSections(t *testing.T) { Convey("Map to struct non unique", t, func() { Convey("Map file to struct non unique", func() { f, err := ini.LoadSources(ini.LoadOptions{AllowNonUniqueSections: true}, []byte(confNonUniqueSectionDataStruct)) So(err, ShouldBeNil) ts := new(testNonUniqueSectionsStruct) So(f.MapTo(ts), ShouldBeNil) So(ts.Interface.Address, ShouldEqual, "10.2.0.1/24") So(ts.Interface.ListenPort, ShouldEqual, 34777) So(ts.Interface.PrivateKey, ShouldEqual, "privServerKey") So(ts.Peer[0].PublicKey, ShouldEqual, "pubClientKey") So(ts.Peer[0].PresharedKey, ShouldEqual, "psKey") So(ts.Peer[0].AllowedIPs[0], ShouldEqual, "10.2.0.2/32") So(ts.Peer[0].AllowedIPs[1], ShouldEqual, "fd00:2::2/128") So(ts.Peer[1].PublicKey, ShouldEqual, "pubClientKey2") So(ts.Peer[1].PresharedKey, ShouldEqual, "psKey2") So(ts.Peer[1].AllowedIPs[0], ShouldEqual, "10.2.0.3/32") So(ts.Peer[1].AllowedIPs[1], ShouldEqual, "fd00:2::3/128") }) Convey("Map non unique section to struct", func() { newPeer := new(testPeer) newPeerSlice := make([]testPeer, 0) f, err := ini.LoadSources(ini.LoadOptions{AllowNonUniqueSections: true}, []byte(confNonUniqueSectionDataStruct)) So(err, ShouldBeNil) // try only first one So(f.Section("Peer").MapTo(newPeer), ShouldBeNil) So(newPeer.PublicKey, ShouldEqual, "pubClientKey") So(newPeer.PresharedKey, ShouldEqual, "psKey") So(newPeer.AllowedIPs[0], ShouldEqual, "10.2.0.2/32") So(newPeer.AllowedIPs[1], ShouldEqual, "fd00:2::2/128") // try all So(f.Section("Peer").MapTo(&newPeerSlice), ShouldBeNil) So(newPeerSlice[0].PublicKey, ShouldEqual, "pubClientKey") So(newPeerSlice[0].PresharedKey, ShouldEqual, "psKey") So(newPeerSlice[0].AllowedIPs[0], ShouldEqual, "10.2.0.2/32") So(newPeerSlice[0].AllowedIPs[1], ShouldEqual, "fd00:2::2/128") So(newPeerSlice[1].PublicKey, ShouldEqual, "pubClientKey2") So(newPeerSlice[1].PresharedKey, ShouldEqual, "psKey2") So(newPeerSlice[1].AllowedIPs[0], ShouldEqual, "10.2.0.3/32") So(newPeerSlice[1].AllowedIPs[1], ShouldEqual, "fd00:2::3/128") }) }) } func Test_ReflectFromStruct(t *testing.T) { Convey("Reflect from struct", t, func() { type Embeded struct { Dates []time.Time `delim:"|" comment:"Time data"` Places []string Years []int Numbers []int64 Ages []uint Populations []uint64 Coordinates []float64 Flags []bool None []int } type Author struct { Name string `ini:"NAME"` Male bool Optional *bool Age int `comment:"Author's age"` Height uint GPA float64 Date time.Time NeverMind string `ini:"-"` *Embeded `ini:"infos" comment:"Embeded section"` } t, err := time.Parse(time.RFC3339, "1993-10-07T20:17:05Z") So(err, ShouldBeNil) a := &Author{"Unknwon", true, nil, 21, 100, 2.8, t, "", &Embeded{ []time.Time{t, t}, []string{"HangZhou", "Boston"}, []int{1993, 1994}, []int64{10010, 10086}, []uint{18, 19}, []uint64{12345678, 98765432}, []float64{192.168, 10.11}, []bool{true, false}, []int{}, }} cfg := ini.Empty() So(ini.ReflectFrom(cfg, a), ShouldBeNil) var buf bytes.Buffer _, err = cfg.WriteTo(&buf) So(err, ShouldBeNil) So(buf.String(), ShouldEqual, `NAME = Unknwon Male = true Optional = ; Author's age Age = 21 Height = 100 GPA = 2.8 Date = 1993-10-07T20:17:05Z ; Embeded section [infos] ; Time data Dates = 1993-10-07T20:17:05Z|1993-10-07T20:17:05Z Places = HangZhou,Boston Years = 1993,1994 Numbers = 10010,10086 Ages = 18,19 Populations = 12345678,98765432 Coordinates = 192.168,10.11 Flags = true,false None = `) Convey("Reflect from non-point struct", func() { So(ini.ReflectFrom(cfg, Author{}), ShouldNotBeNil) }) Convey("Reflect from struct with omitempty", func() { cfg := ini.Empty() type SpecialStruct struct { FirstName string `ini:"first_name"` LastName string `ini:"last_name"` JustOmitMe string `ini:"omitempty"` LastLogin time.Time `ini:"last_login,omitempty"` LastLogin2 time.Time `ini:",omitempty"` NotEmpty int `ini:"omitempty"` } So(ini.ReflectFrom(cfg, &SpecialStruct{FirstName: "John", LastName: "Doe", NotEmpty: 9}), ShouldBeNil) var buf bytes.Buffer _, err = cfg.WriteTo(&buf) So(buf.String(), ShouldEqual, `first_name = John last_name = Doe omitempty = 9 `) }) }) } func Test_ReflectFromStructNonUniqueSections(t *testing.T) { Convey("Reflect from struct with non unique sections", t, func() { nonUnique := &testNonUniqueSectionsStruct{ Interface: testInterface{ Address: "10.2.0.1/24", ListenPort: 34777, PrivateKey: "privServerKey", }, Peer: []testPeer{ { PublicKey: "pubClientKey", PresharedKey: "psKey", AllowedIPs: []string{"10.2.0.2/32,fd00:2::2/128"}, }, { PublicKey: "pubClientKey2", PresharedKey: "psKey2", AllowedIPs: []string{"10.2.0.3/32,fd00:2::3/128"}, }, }, } cfg := ini.Empty(ini.LoadOptions{ AllowNonUniqueSections: true, }) So(ini.ReflectFrom(cfg, nonUnique), ShouldBeNil) var buf bytes.Buffer _, err := cfg.WriteTo(&buf) So(err, ShouldBeNil) So(buf.String(), ShouldEqual, confNonUniqueSectionDataStruct) // note: using ReflectFrom from should overwrite the existing sections err = cfg.Section("Peer").ReflectFrom([]*testPeer{ { PublicKey: "pubClientKey3", PresharedKey: "psKey3", AllowedIPs: []string{"10.2.0.4/32,fd00:2::4/128"}, }, { PublicKey: "pubClientKey4", PresharedKey: "psKey4", AllowedIPs: []string{"10.2.0.5/32,fd00:2::5/128"}, }, }) So(err, ShouldBeNil) buf = bytes.Buffer{} _, err = cfg.WriteTo(&buf) So(err, ShouldBeNil) So(buf.String(), ShouldEqual, `[Interface] Address = 10.2.0.1/24 ListenPort = 34777 PrivateKey = privServerKey [Peer] PublicKey = pubClientKey3 PresharedKey = psKey3 AllowedIPs = 10.2.0.4/32,fd00:2::4/128 [Peer] PublicKey = pubClientKey4 PresharedKey = psKey4 AllowedIPs = 10.2.0.5/32,fd00:2::5/128 `) // note: using ReflectFrom from should overwrite the existing sections err = cfg.Section("Peer").ReflectFrom(&testPeer{ PublicKey: "pubClientKey5", PresharedKey: "psKey5", AllowedIPs: []string{"10.2.0.6/32,fd00:2::6/128"}, }) So(err, ShouldBeNil) buf = bytes.Buffer{} _, err = cfg.WriteTo(&buf) So(err, ShouldBeNil) So(buf.String(), ShouldEqual, `[Interface] Address = 10.2.0.1/24 ListenPort = 34777 PrivateKey = privServerKey [Peer] PublicKey = pubClientKey5 PresharedKey = psKey5 AllowedIPs = 10.2.0.6/32,fd00:2::6/128 `) }) } // Inspired by https://github.com/go-ini/ini/issues/196 func TestMapToAndReflectFromStructWithShadows(t *testing.T) { Convey("Map to struct and then reflect with shadows should generate original config content", t, func() { type include struct { Paths []string `ini:"path,omitempty,allowshadow"` } cfg, err := ini.LoadSources(ini.LoadOptions{ AllowShadows: true, }, []byte(` [include] path = /tmp/gpm-profiles/test5.profile path = /tmp/gpm-profiles/test1.profile`)) So(err, ShouldBeNil) sec := cfg.Section("include") inc := new(include) err = sec.MapTo(inc) So(err, ShouldBeNil) err = sec.ReflectFrom(inc) So(err, ShouldBeNil) var buf bytes.Buffer _, err = cfg.WriteTo(&buf) So(err, ShouldBeNil) So(buf.String(), ShouldEqual, `[include] path = /tmp/gpm-profiles/test5.profile path = /tmp/gpm-profiles/test1.profile `) }) } type testMapper struct { PackageName string } func Test_NameGetter(t *testing.T) { Convey("Test name mappers", t, func() { So(ini.MapToWithMapper(&testMapper{}, ini.TitleUnderscore, []byte("packag_name=ini")), ShouldBeNil) cfg, err := ini.Load([]byte("PACKAGE_NAME=ini")) So(err, ShouldBeNil) So(cfg, ShouldNotBeNil) cfg.NameMapper = ini.SnackCase tg := new(testMapper) So(cfg.MapTo(tg), ShouldBeNil) So(tg.PackageName, ShouldEqual, "ini") }) } type testDurationStruct struct { Duration time.Duration `ini:"Duration"` } func Test_Duration(t *testing.T) { Convey("Duration less than 16m50s", t, func() { ds := new(testDurationStruct) So(ini.MapTo(ds, []byte("Duration=16m49s")), ShouldBeNil) dur, err := time.ParseDuration("16m49s") So(err, ShouldBeNil) So(ds.Duration.Seconds(), ShouldEqual, dur.Seconds()) }) } type Employer struct { Name string Title string } type Employers []*Employer func (es Employers) ReflectINIStruct(f *ini.File) error { for _, e := range es { f.Section(e.Name).Key("Title").SetValue(e.Title) } return nil } // Inspired by https://github.com/go-ini/ini/issues/199 func Test_StructReflector(t *testing.T) { Convey("Reflect with StructReflector interface", t, func() { p := &struct { FirstName string Employer Employers }{ FirstName: "Andrew", Employer: []*Employer{ { Name: `Employer "VMware"`, Title: "Staff II Engineer", }, { Name: `Employer "EMC"`, Title: "Consultant Engineer", }, }, } f := ini.Empty() So(f.ReflectFrom(p), ShouldBeNil) var buf bytes.Buffer _, err := f.WriteTo(&buf) So(err, ShouldBeNil) So(buf.String(), ShouldEqual, `FirstName = Andrew [Employer "VMware"] Title = Staff II Engineer [Employer "EMC"] Title = Consultant Engineer `) }) } ini-1.55.0/testdata/000077500000000000000000000000001363342257300142025ustar00rootroot00000000000000ini-1.55.0/testdata/TestFile_WriteTo.golden000066400000000000000000000036421363342257300205750ustar00rootroot00000000000000; Package name NAME = ini ; Package version VERSION = v1 ; Package import path IMPORT_PATH = gopkg.in/%(NAME)s.%(VERSION)s ; Information about package author # Bio can be written in multiple lines. [author] ; This is author name NAME = Unknwon E-MAIL = u@gogs.io GITHUB = https://github.com/%(NAME)s # Succeeding comment BIO = """Gopher. Coding addict. Good man. """ [package] CLONE_URL = https://%(IMPORT_PATH)s [package.sub] UNUSED_KEY = should be deleted [features] - = Support read/write comments of keys and sections - = Support auto-increment of key names - = Support load multiple files to overwrite key values [types] STRING = str BOOL = true BOOL_FALSE = false FLOAT64 = 1.25 INT = 10 TIME = 2015-01-01T20:17:05Z DURATION = 2h45m UINT = 3 HEX_NUMBER = 0x3000 [array] STRINGS = en, zh, de FLOAT64S = 1.1, 2.2, 3.3 INTS = 1, 2, 3 UINTS = 1, 2, 3 TIMES = 2015-01-01T20:17:05Z,2015-01-01T20:17:05Z,2015-01-01T20:17:05Z BOOLS = true, false, false [note] empty_lines = next line is empty boolean_key more = notes ; Comment before the section ; This is a comment for the section too [comments] ; Comment before key key = value ; This is a comment for key2 key2 = value2 key3 = "one", "two", "three" [string escapes] key1 = value1, value2, value3 key2 = value1\, value2 key3 = val\ue1, value2 key4 = value1\\, value\\\\2 key5 = value1\,, value2 key6 = aaa bbb\ and\ space ccc [advance] value with quotes = some value value quote2 again = some value includes comment sign = `my#password` includes comment sign2 = `my;password` true = 2+3=5 `1+1=2` = true `6+1=7` = true """`5+5`""" = 10 `"6+6"` = 12 `7-2=4` = false ADDRESS = """404 road, NotFound, State, 50000""" two_lines = how about continuation lines? lots_of_lines = 1 2 3 4 ini-1.55.0/testdata/UTF-16-BE-BOM.ini000066400000000000000000000001101363342257300164540ustar00rootroot00000000000000þÿ[author] E-MAIL = example@email.comini-1.55.0/testdata/UTF-16-LE-BOM.ini000066400000000000000000000001101363342257300164660ustar00rootroot00000000000000ÿþ[author] E-MAIL = example@email.comini-1.55.0/testdata/UTF-8-BOM.ini000066400000000000000000000000461363342257300161210ustar00rootroot00000000000000[author] E-MAIL = example@email.comini-1.55.0/testdata/full.ini000066400000000000000000000035421363342257300156510ustar00rootroot00000000000000; Package name NAME = ini ; Package version VERSION = v1 ; Package import path IMPORT_PATH = gopkg.in/%(NAME)s.%(VERSION)s # Information about package author # Bio can be written in multiple lines. [author] NAME = Unknwon E-MAIL = u@gogs.io GITHUB = https://github.com/%(NAME)s BIO = """Gopher. Coding addict. Good man. """ # Succeeding comment [package] CLONE_URL = https://%(IMPORT_PATH)s [package.sub] UNUSED_KEY = should be deleted [features] -: Support read/write comments of keys and sections -: Support auto-increment of key names -: Support load multiple files to overwrite key values [types] STRING = str BOOL = true BOOL_FALSE = false FLOAT64 = 1.25 INT = 10 TIME = 2015-01-01T20:17:05Z DURATION = 2h45m UINT = 3 HEX_NUMBER = 0x3000 [array] STRINGS = en, zh, de FLOAT64S = 1.1, 2.2, 3.3 INTS = 1, 2, 3 UINTS = 1, 2, 3 TIMES = 2015-01-01T20:17:05Z,2015-01-01T20:17:05Z,2015-01-01T20:17:05Z BOOLS = true, false, false [note] empty_lines = next line is empty\ ; Comment before the section [comments] ; This is a comment for the section too ; Comment before key key = "value" key2 = "value2" ; This is a comment for key2 key3 = "one", "two", "three" [string escapes] key1 = value1, value2, value3 key2 = value1\, value2 key3 = val\ue1, value2 key4 = value1\\, value\\\\2 key5 = value1\,, value2 key6 = aaa bbb\ and\ space ccc [advance] value with quotes = "some value" value quote2 again = 'some value' includes comment sign = `my#password` includes comment sign2 = `my;password` true = 2+3=5 "1+1=2" = true """6+1=7""" = true """`5+5`""" = 10 `"6+6"` = 12 `7-2=4` = false ADDRESS = `404 road, NotFound, State, 50000` two_lines = how about \ continuation lines? lots_of_lines = 1 \ 2 \ 3 \ 4 \ ini-1.55.0/testdata/minimal.ini000066400000000000000000000000331363342257300163250ustar00rootroot00000000000000[author] E-MAIL = u@gogs.ioini-1.55.0/testdata/multiline.ini000066400000000000000000000213571363342257300167150ustar00rootroot00000000000000value1 = some text here some more text here there is an empty line above and below value2 = there is an empty line above that is not indented so it should not be part of the value value3 = . 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. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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.