diff -Nru golang-gopkg-ini.v1-1.52.0/codecov.yml golang-gopkg-ini.v1-1.57.0/codecov.yml --- golang-gopkg-ini.v1-1.52.0/codecov.yml 1970-01-01 00:00:00.000000000 +0000 +++ golang-gopkg-ini.v1-1.57.0/codecov.yml 2020-05-28 05:00:39.000000000 +0000 @@ -0,0 +1,9 @@ +coverage: + range: "60...95" + status: + project: + default: + threshold: 1% + +comment: + layout: 'diff, files' diff -Nru golang-gopkg-ini.v1-1.52.0/data_source.go golang-gopkg-ini.v1-1.57.0/data_source.go --- golang-gopkg-ini.v1-1.52.0/data_source.go 2020-01-31 12:28:19.000000000 +0000 +++ golang-gopkg-ini.v1-1.57.0/data_source.go 2020-05-28 05:00:39.000000000 +0000 @@ -68,6 +68,8 @@ return &sourceData{s}, nil case io.ReadCloser: return &sourceReadCloser{s}, nil + case io.Reader: + return &sourceReadCloser{ioutil.NopCloser(s)}, nil default: return nil, fmt.Errorf("error parsing data source: unknown type %q", s) } diff -Nru golang-gopkg-ini.v1-1.52.0/debian/changelog golang-gopkg-ini.v1-1.57.0/debian/changelog --- golang-gopkg-ini.v1-1.52.0/debian/changelog 2020-02-29 02:23:46.000000000 +0000 +++ golang-gopkg-ini.v1-1.57.0/debian/changelog 2020-07-15 16:39:53.000000000 +0000 @@ -1,3 +1,20 @@ +golang-gopkg-ini.v1 (1.57.0-1) unstable; urgency=medium + + * Team upload. + + [ Debian Janitor ] + * Use secure URI in Homepage field. + * Set upstream metadata fields: + Bug-Database, Bug-Submit, Repository, Repository-Browse. + + [ Shengjing Zhu ] + * New upstream release 1.57.0 + * Remove golang-github-smartystreets-goconvey-dev from Depends. + Only needed for testing + * Update debhelper-compat to 13 + + -- Shengjing Zhu Thu, 16 Jul 2020 00:39:53 +0800 + golang-gopkg-ini.v1 (1.52.0-2) unstable; urgency=medium * Update Maintainer email address to team+pkg-go@tracker.debian.org. diff -Nru golang-gopkg-ini.v1-1.52.0/debian/control golang-gopkg-ini.v1-1.57.0/debian/control --- golang-gopkg-ini.v1-1.52.0/debian/control 2020-02-29 02:23:32.000000000 +0000 +++ golang-gopkg-ini.v1-1.57.0/debian/control 2020-07-15 16:39:53.000000000 +0000 @@ -4,12 +4,12 @@ Maintainer: Debian Go Packaging Team Uploaders: Dmitry Smirnov , Anthony Fok , -Build-Depends: debhelper-compat (= 12), +Build-Depends: debhelper-compat (= 13), dh-golang (>= 1.39~), golang-any, golang-github-smartystreets-goconvey-dev, Standards-Version: 4.5.0 -Homepage: http://gopkg.in/ini.v1 +Homepage: https://gopkg.in/ini.v1 Vcs-Browser: https://salsa.debian.org/go-team/packages/golang-gopkg-ini.v1 Vcs-Git: https://salsa.debian.org/go-team/packages/golang-gopkg-ini.v1.git XS-Go-Import-Path: gopkg.in/ini.v1 @@ -19,6 +19,5 @@ Package: golang-gopkg-ini.v1-dev Architecture: all Depends: ${misc:Depends}, - golang-github-smartystreets-goconvey-dev, Description: INI file read and write functionality in Go Golang library providing INI file read and write functionality. diff -Nru golang-gopkg-ini.v1-1.52.0/debian/gbp.conf golang-gopkg-ini.v1-1.57.0/debian/gbp.conf --- golang-gopkg-ini.v1-1.52.0/debian/gbp.conf 2020-02-29 00:39:11.000000000 +0000 +++ golang-gopkg-ini.v1-1.57.0/debian/gbp.conf 2020-07-15 16:39:53.000000000 +0000 @@ -1,6 +1,5 @@ -[dch] -id-length = 0 +[DEFAULT] +pristine-tar = True [import-orig] -pristine-tar = True -merge = False +merge = False diff -Nru golang-gopkg-ini.v1-1.52.0/debian/upstream/metadata golang-gopkg-ini.v1-1.57.0/debian/upstream/metadata --- golang-gopkg-ini.v1-1.52.0/debian/upstream/metadata 1970-01-01 00:00:00.000000000 +0000 +++ golang-gopkg-ini.v1-1.57.0/debian/upstream/metadata 2020-07-15 16:39:53.000000000 +0000 @@ -0,0 +1,5 @@ +--- +Bug-Database: https://github.com/go-ini/ini/issues +Bug-Submit: https://github.com/go-ini/ini/issues/new +Repository: https://github.com/go-ini/ini.git +Repository-Browse: https://github.com/go-ini/ini diff -Nru golang-gopkg-ini.v1-1.52.0/file.go golang-gopkg-ini.v1-1.57.0/file.go --- golang-gopkg-ini.v1-1.52.0/file.go 2020-01-31 12:28:19.000000000 +0000 +++ golang-gopkg-ini.v1-1.57.0/file.go 2020-05-28 05:00:39.000000000 +0000 @@ -25,7 +25,7 @@ "sync" ) -// File represents a combination of a or more INI file(s) in memory. +// File represents a combination of one or more INI files in memory. type File struct { options LoadOptions dataSources []dataSource @@ -36,8 +36,12 @@ // 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 + sections map[string][]*Section NameMapper ValueMapper @@ -48,27 +52,37 @@ 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), - sectionList: make([]string, 0, 10), + sections: make(map[string][]*Section), options: opts, } } // Empty returns an empty file object. -func Empty() *File { - // Ignore error here, we sure our data is good. - f, _ := Load([]byte("")) +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("error creating new section: empty section name") - } else if f.options.Insensitive && name != DefaultSection { + return nil, errors.New("empty section name") + } + + if f.options.Insensitive && name != DefaultSection { name = strings.ToLower(name) } @@ -77,13 +91,20 @@ defer f.lock.Unlock() } - if inSlice(name, f.sectionList) { - return f.sections[name], nil + if !f.options.AllowNonUniqueSections && inSlice(name, f.sectionList) { + return f.sections[name][0], nil } f.sectionList = append(f.sectionList, name) - f.sections[name] = newSection(f, name) - return f.sections[name], nil + + // 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. @@ -110,6 +131,16 @@ // 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 } @@ -122,11 +153,12 @@ defer f.lock.RUnlock() } - sec := f.sections[name] - if sec == nil { - return nil, fmt.Errorf("section '%s' does not exist", name) + secs := f.sections[name] + if len(secs) == 0 { + return nil, fmt.Errorf("section %q does not exist", name) } - return sec, nil + + return secs, nil } // Section assumes named section exists and returns a zero-value when not. @@ -141,6 +173,19 @@ 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 { @@ -150,7 +195,7 @@ sections := make([]*Section, len(f.sectionList)) for i, name := range f.sectionList { - sections[i] = f.sections[name] + sections[i] = f.sections[name][f.sectionIndexes[i]] } return sections } @@ -167,24 +212,70 @@ return list } -// DeleteSection deletes a section. +// DeleteSection deletes a section or all sections with given name. func (f *File) DeleteSection(name string) { - if f.BlockMode { - f.lock.Lock() - defer f.lock.Unlock() + 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 + } - for i, s := range f.sectionList { - if s == name { + 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:]...) - delete(f.sections, name) - return + 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 { @@ -203,7 +294,7 @@ 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)) + _ = f.parse(bytes.NewBuffer(nil)) continue } return err @@ -230,16 +321,16 @@ } func (f *File) writeToBuffer(indent string) (*bytes.Buffer, error) { - equalSign := DefaultFormatLeft + "=" + DefaultFormatRight + equalSign := DefaultFormatLeft + f.options.KeyValueDelimiterOnWrite + DefaultFormatRight if PrettyFormat || PrettyEqual { - equalSign = " = " + 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.Section(sname) + sec := f.SectionWithIndex(sname, f.sectionIndexes[i]) if len(sec.Comment) > 0 { // Support multiline comments lines := strings.Split(sec.Comment, LineBreak) @@ -282,7 +373,7 @@ } // Count and generate alignment length and buffer spaces using the - // longest key. Keys may be modifed if they contain certain characters so + // 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 { diff -Nru golang-gopkg-ini.v1-1.52.0/file_test.go golang-gopkg-ini.v1-1.57.0/file_test.go --- golang-gopkg-ini.v1-1.52.0/file_test.go 2020-01-31 12:28:19.000000000 +0000 +++ golang-gopkg-ini.v1-1.57.0/file_test.go 2020-05-28 05:00:39.000000000 +0000 @@ -17,6 +17,7 @@ import ( "bytes" "io/ioutil" + "runtime" "testing" . "github.com/smartystreets/goconvey/convey" @@ -64,6 +65,116 @@ }) } +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() @@ -216,7 +327,7 @@ f := ini.Empty() So(f, ShouldNotBeNil) - f.NewSections("author", "package", "features") + _ = f.NewSections("author", "package", "features") f.DeleteSection("features") f.DeleteSection("") So(f.SectionStrings(), ShouldResemble, []string{"author", "package"}) @@ -240,6 +351,10 @@ } func TestFile_WriteTo(t *testing.T) { + if runtime.GOOS == "windows" { + t.Skip("Skipping testing on Windows") + } + Convey("Write content to somewhere", t, func() { f, err := ini.Load(fullConf) So(err, ShouldBeNil) @@ -248,8 +363,8 @@ 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") + _, _ = f.Section("note").NewBooleanKey("boolean_key") + _, _ = f.Section("note").NewKey("more", "notes") var buf bytes.Buffer _, err = f.WriteTo(&buf) @@ -257,7 +372,7 @@ golden := "testdata/TestFile_WriteTo.golden" if *update { - ioutil.WriteFile(golden, buf.Bytes(), 0644) + So(ioutil.WriteFile(golden, buf.Bytes(), 0644), ShouldBeNil) } expected, err := ioutil.ReadFile(golden) @@ -306,6 +421,43 @@ }) } +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() { diff -Nru golang-gopkg-ini.v1-1.52.0/.github/workflows/go.yml golang-gopkg-ini.v1-1.57.0/.github/workflows/go.yml --- golang-gopkg-ini.v1-1.52.0/.github/workflows/go.yml 1970-01-01 00:00:00.000000000 +0000 +++ golang-gopkg-ini.v1-1.57.0/.github/workflows/go.yml 2020-05-28 05:00:39.000000000 +0000 @@ -0,0 +1,49 @@ +name: Go +on: + push: + branches: [master] + pull_request: +env: + GOPROXY: "https://proxy.golang.org" + +jobs: + lint: + name: Lint + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - name: Init Go modules + run: go mod init gopkg.in/ini.v1 + - name: Run golangci-lint + uses: actions-contrib/golangci-lint@v1 + + test: + name: Test + strategy: + matrix: + go-version: [1.13.x, 1.14.x] + platform: [ubuntu-latest, macos-latest, windows-latest] + runs-on: ${{ matrix.platform }} + steps: + - name: Install Go + uses: actions/setup-go@v1 + with: + go-version: ${{ matrix.go-version }} + - name: Checkout code + uses: actions/checkout@v2 + - name: Run unit tests + run: | + go mod init gopkg.in/ini.v1 + go test -v -race -coverprofile=coverage -covermode=atomic ./... + - name: Upload coverage report to Codecov + uses: codecov/codecov-action@v1.0.6 + with: + file: ./coverage + flags: unittests + - name: Cache downloaded modules + uses: actions/cache@v1 + with: + path: ~/go/pkg/mod + key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }} + restore-keys: | + ${{ runner.os }}-go- diff -Nru golang-gopkg-ini.v1-1.52.0/.github/workflows/lsif.yml golang-gopkg-ini.v1-1.57.0/.github/workflows/lsif.yml --- golang-gopkg-ini.v1-1.52.0/.github/workflows/lsif.yml 1970-01-01 00:00:00.000000000 +0000 +++ golang-gopkg-ini.v1-1.57.0/.github/workflows/lsif.yml 2020-05-28 05:00:39.000000000 +0000 @@ -0,0 +1,17 @@ +name: 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 }} diff -Nru golang-gopkg-ini.v1-1.52.0/ini.go golang-gopkg-ini.v1-1.57.0/ini.go --- golang-gopkg-ini.v1-1.52.0/ini.go 2020-01-31 12:28:19.000000000 +0000 +++ golang-gopkg-ini.v1-1.57.0/ini.go 2020-05-28 05:00:39.000000000 +0000 @@ -18,8 +18,10 @@ package ini import ( + "os" "regexp" "runtime" + "strings" ) const ( @@ -29,14 +31,8 @@ // Maximum allowed depth when recursively substituing variable names. depthValues = 99 - version = "1.52.0" ) -// Version returns current package version literal. -func Version() string { - return version -} - 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. @@ -61,8 +57,10 @@ DefaultFormatRight = "" ) +var inTest = len(os.Args) > 0 && strings.HasSuffix(strings.TrimSuffix(os.Args[0], ".exe"), ".test") + func init() { - if runtime.GOOS == "windows" { + if runtime.GOOS == "windows" && !inTest { LineBreak = "\r\n" } } @@ -109,12 +107,16 @@ 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. diff -Nru golang-gopkg-ini.v1-1.52.0/ini_internal_test.go golang-gopkg-ini.v1-1.57.0/ini_internal_test.go --- golang-gopkg-ini.v1-1.52.0/ini_internal_test.go 2020-01-31 12:28:19.000000000 +0000 +++ golang-gopkg-ini.v1-1.57.0/ini_internal_test.go 1970-01-01 00:00:00.000000000 +0000 @@ -1,27 +0,0 @@ -// 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 ( - "testing" - - . "github.com/smartystreets/goconvey/convey" -) - -func Test_Version(t *testing.T) { - Convey("Get version", t, func() { - So(Version(), ShouldEqual, version) - }) -} diff -Nru golang-gopkg-ini.v1-1.52.0/ini_python_multiline_test.go golang-gopkg-ini.v1-1.57.0/ini_python_multiline_test.go --- golang-gopkg-ini.v1-1.52.0/ini_python_multiline_test.go 2020-01-31 12:28:19.000000000 +0000 +++ golang-gopkg-ini.v1-1.57.0/ini_python_multiline_test.go 2020-05-28 05:00:39.000000000 +0000 @@ -2,6 +2,7 @@ import ( "path/filepath" + "runtime" "testing" . "github.com/smartystreets/goconvey/convey" @@ -15,16 +16,15 @@ } func TestMultiline(t *testing.T) { + if runtime.GOOS == "windows" { + t.Skip("Skipping testing on Windows") + } + 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) diff -Nru golang-gopkg-ini.v1-1.52.0/ini_test.go golang-gopkg-ini.v1-1.57.0/ini_test.go --- golang-gopkg-ini.v1-1.52.0/ini_test.go 2020-01-31 12:28:19.000000000 +0000 +++ golang-gopkg-ini.v1-1.57.0/ini_test.go 2020-05-28 05:00:39.000000000 +0000 @@ -52,15 +52,11 @@ func TestLoad(t *testing.T) { Convey("Load from good data sources", t, func() { - f, err := ini.Load([]byte(` -NAME = ini -VERSION = v1 -IMPORT_PATH = gopkg.in/%(NAME)s.%(VERSION)s`), + f, err := ini.Load( "testdata/minimal.ini", - ioutil.NopCloser(bytes.NewReader([]byte(` -[author] -NAME = Unknwon -`))), + []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) diff -Nru golang-gopkg-ini.v1-1.52.0/key.go golang-gopkg-ini.v1-1.57.0/key.go --- golang-gopkg-ini.v1-1.52.0/key.go 2020-01-31 12:28:19.000000000 +0000 +++ golang-gopkg-ini.v1-1.57.0/key.go 2020-05-28 05:00:39.000000000 +0000 @@ -686,99 +686,127 @@ // 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 { + parser := func(str string) (interface{}, error) { val, err := parseBool(str) - if err != nil && returnOnInvalid { - return nil, err - } - if err == nil || addInvalid { - vals = append(vals, val) + return val, err + } + rawVals, err := k.doParse(strs, addInvalid, returnOnInvalid, parser) + if err == nil { + for _, val := range rawVals { + vals = append(vals, val.(bool)) } } - return vals, nil + return vals, err } // 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 { + parser := func(str string) (interface{}, error) { val, err := strconv.ParseFloat(str, 64) - if err != nil && returnOnInvalid { - return nil, err - } - if err == nil || addInvalid { - vals = append(vals, val) + return val, err + } + rawVals, err := k.doParse(strs, addInvalid, returnOnInvalid, parser) + if err == nil { + for _, val := range rawVals { + vals = append(vals, val.(float64)) } } - return vals, nil + return vals, err } // 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) + parser := func(str string) (interface{}, error) { + val, err := strconv.ParseInt(str, 0, 64) + return val, err + } + rawVals, err := k.doParse(strs, addInvalid, returnOnInvalid, parser) + if err == nil { + for _, val := range rawVals { + vals = append(vals, int(val.(int64))) } } - return vals, nil + return vals, err } // 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 { + parser := func(str string) (interface{}, error) { val, err := strconv.ParseInt(str, 0, 64) - if err != nil && returnOnInvalid { - return nil, err - } - if err == nil || addInvalid { - vals = append(vals, val) + return val, err + } + + rawVals, err := k.doParse(strs, addInvalid, returnOnInvalid, parser) + if err == nil { + for _, val := range rawVals { + vals = append(vals, val.(int64)) } } - return vals, nil + return vals, err } // 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)) + parser := func(str string) (interface{}, error) { + val, err := strconv.ParseUint(str, 0, 64) + return val, err + } + + rawVals, err := k.doParse(strs, addInvalid, returnOnInvalid, parser) + if err == nil { + for _, val := range rawVals { + vals = append(vals, uint(val.(uint64))) } } - return vals, nil + return vals, err } // 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 { + parser := func(str string) (interface{}, error) { val, err := strconv.ParseUint(str, 0, 64) - if err != nil && returnOnInvalid { - return nil, err - } - if err == nil || addInvalid { - vals = append(vals, val) + return val, err + } + rawVals, err := k.doParse(strs, addInvalid, returnOnInvalid, parser) + if err == nil { + for _, val := range rawVals { + vals = append(vals, val.(uint64)) } } - return vals, nil + return vals, err } + +type Parser func(str string) (interface{}, error) + + // 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 { + parser := func(str string) (interface{}, error) { val, err := time.Parse(format, str) + return val, err + } + rawVals, err := k.doParse(strs, addInvalid, returnOnInvalid, parser) + if err == nil { + for _, val := range rawVals { + vals = append(vals, val.(time.Time)) + } + } + return vals, err +} + + +// doParse transforms strings to different types +func (k *Key) doParse(strs []string, addInvalid, returnOnInvalid bool, parser Parser) ([]interface{}, error) { + vals := make([]interface{}, 0, len(strs)) + for _, str := range strs { + val, err := parser(str) if err != nil && returnOnInvalid { return nil, err } diff -Nru golang-gopkg-ini.v1-1.52.0/key_test.go golang-gopkg-ini.v1-1.57.0/key_test.go --- golang-gopkg-ini.v1-1.52.0/key_test.go 2020-01-31 12:28:19.000000000 +0000 +++ golang-gopkg-ini.v1-1.57.0/key_test.go 2020-05-28 05:00:39.000000000 +0000 @@ -17,6 +17,7 @@ import ( "bytes" "fmt" + "runtime" "strings" "testing" "time" @@ -184,6 +185,10 @@ }) Convey("Get multiple line value", func() { + if runtime.GOOS == "windows" { + t.Skip("Skipping testing on Windows") + } + So(f.Section("author").Key("BIO").String(), ShouldEqual, "Gopher.\nCoding addict.\nGood man.\n") }) diff -Nru golang-gopkg-ini.v1-1.52.0/Makefile golang-gopkg-ini.v1-1.57.0/Makefile --- golang-gopkg-ini.v1-1.52.0/Makefile 2020-01-31 12:28:19.000000000 +0000 +++ golang-gopkg-ini.v1-1.57.0/Makefile 2020-05-28 05:00:39.000000000 +0000 @@ -6,7 +6,7 @@ go test -v -cover -race bench: - go test -v -cover -race -test.bench=. -test.benchmem + go test -v -cover -test.bench=. -test.benchmem vet: go vet diff -Nru golang-gopkg-ini.v1-1.52.0/parser.go golang-gopkg-ini.v1-1.57.0/parser.go --- golang-gopkg-ini.v1-1.52.0/parser.go 2020-01-31 12:28:19.000000000 +0000 +++ golang-gopkg-ini.v1-1.57.0/parser.go 2020-05-28 05:00:39.000000000 +0000 @@ -84,7 +84,10 @@ case mask[0] == 254 && mask[1] == 255: fallthrough case mask[0] == 255 && mask[1] == 254: - p.buf.Read(mask) + _, err = p.buf.Read(mask) + if err != nil { + return err + } case mask[0] == 239 && mask[1] == 187: mask, err := p.buf.Peek(3) if err != nil && err != io.EOF { @@ -93,7 +96,10 @@ return nil } if mask[2] == 191 { - p.buf.Read(mask) + _, err = p.buf.Read(mask) + if err != nil { + return err + } } } return nil @@ -135,7 +141,7 @@ } // Get out key name - endIdx := -1 + var endIdx int if len(keyQuote) > 0 { startIdx := len(keyQuote) // FIXME: fail case -> """"""name"""=value @@ -181,7 +187,7 @@ } val += next if p.isEOF { - return "", fmt.Errorf("missing closing key quote from '%s' to '%s'", line, next) + return "", fmt.Errorf("missing closing key quote from %q to %q", line, next) } } return val, nil @@ -413,7 +419,10 @@ if f.options.AllowNestedValues && isLastValueEmpty && len(line) > 0 { if line[0] == ' ' || line[0] == '\t' { - lastRegularKey.addNestedValue(string(bytes.TrimSpace(line))) + err = lastRegularKey.addNestedValue(string(bytes.TrimSpace(line))) + if err != nil { + return err + } continue } } @@ -460,7 +469,7 @@ 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)) { + (f.options.Insensitive && strings.EqualFold(f.options.UnparseableSections[i], name)) { inUnparseableSection = true continue } diff -Nru golang-gopkg-ini.v1-1.52.0/README.md golang-gopkg-ini.v1-1.57.0/README.md --- golang-gopkg-ini.v1-1.52.0/README.md 2020-01-31 12:28:19.000000000 +0000 +++ golang-gopkg-ini.v1-1.57.0/README.md 2020-05-28 05:00:39.000000000 +0000 @@ -1,6 +1,9 @@ # 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) +[![GitHub Workflow Status](https://img.shields.io/github/workflow/status/go-ini/ini/Go?logo=github&style=for-the-badge)](https://github.com/go-ini/ini/actions?query=workflow%3AGo) +[![codecov](https://img.shields.io/codecov/c/github/go-ini/ini/master?logo=codecov&style=for-the-badge)](https://codecov.io/gh/go-ini/ini) +[![GoDoc](https://img.shields.io/badge/GoDoc-Reference-blue?style=for-the-badge&logo=go)](https://pkg.go.dev/github.com/go-ini/ini?tab=doc) +[![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) @@ -8,7 +11,7 @@ ## Features -- Load from multiple data sources(`[]byte`, file and `io.ReadCloser`) with overwrites. +- 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. @@ -33,6 +36,7 @@ - [Getting Started](https://ini.unknwon.io/docs/intro/getting_started) - [API Documentation](https://gowalker.org/gopkg.in/ini.v1) +- 中国大陆镜像:https://ini.unknwon.cn ## License diff -Nru golang-gopkg-ini.v1-1.52.0/section.go golang-gopkg-ini.v1-1.57.0/section.go --- golang-gopkg-ini.v1-1.52.0/section.go 2020-01-31 12:28:19.000000000 +0000 +++ golang-gopkg-ini.v1-1.57.0/section.go 2020-05-28 05:00:39.000000000 +0000 @@ -131,7 +131,7 @@ } break } - return nil, fmt.Errorf("error when getting key of section '%s': key '%s' not exists", s.name, name) + return nil, fmt.Errorf("error when getting key of section %q: key %q not exists", s.name, name) } return key, nil } @@ -249,7 +249,7 @@ children := make([]*Section, 0, 3) for _, name := range s.f.sectionList { if strings.HasPrefix(name, prefix) { - children = append(children, s.f.sections[name]) + children = append(children, s.f.sections[name]...) } } return children diff -Nru golang-gopkg-ini.v1-1.52.0/struct.go golang-gopkg-ini.v1-1.57.0/struct.go --- golang-gopkg-ini.v1-1.52.0/struct.go 2020-01-31 12:28:19.000000000 +0000 +++ golang-gopkg-ini.v1-1.57.0/struct.go 2020-05-28 05:00:39.000000000 +0000 @@ -258,13 +258,13 @@ case reflect.Slice: return setSliceWithProperType(key, field, delim, allowShadow, isStrict) default: - return fmt.Errorf("unsupported type '%s'", t) + return fmt.Errorf("unsupported type %q", t) } return nil } -func parseTagOptions(tag string) (rawName string, omitEmpty bool, allowShadow bool) { - opts := strings.SplitN(tag, ",", 3) +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" @@ -272,10 +272,15 @@ if len(opts) > 2 { allowShadow = opts[2] == "allowshadow" } - return rawName, omitEmpty, allowShadow + if len(opts) > 3 { + allowNonUnique = opts[3] == "nonunique" + } + return rawName, omitEmpty, allowShadow, allowNonUnique } -func (s *Section) mapTo(val reflect.Value, isStrict bool) error { +// mapToField maps the given value to the matching field of the given section. +// The sectionIndex is the index (if non unique sections are enabled) to which the value should be added. +func (s *Section) mapToField(val reflect.Value, isStrict bool, sectionIndex int) error { if val.Kind() == reflect.Ptr { val = val.Elem() } @@ -290,7 +295,7 @@ continue } - rawName, _, allowShadow := parseTagOptions(tag) + rawName, _, allowShadow, allowNonUnique := parseTagOptions(tag) fieldName := s.parseFieldName(tpField.Name, rawName) if len(fieldName) == 0 || !field.CanSet() { continue @@ -304,55 +309,96 @@ } if isAnonymous || isStruct || isStructPtr { - if sec, err := s.f.GetSection(fieldName); err == nil { + if secs, err := s.f.SectionsByName(fieldName); err == nil { + if len(secs) <= sectionIndex { + return fmt.Errorf("there are not enough sections (%d <= %d) for the field %q", len(secs), sectionIndex, fieldName) + } // 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.mapTo(field, isStrict); err != nil { - return fmt.Errorf("error mapping field %q: %v", fieldName, err) + if err = secs[sectionIndex].mapToField(field, isStrict, sectionIndex); 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("error mapping field %q: %v", fieldName, err) + return fmt.Errorf("set field %q: %v", fieldName, err) } } } return nil } -// MapTo maps section to given struct. -func (s *Section) MapTo(v interface{}) error { +// 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 i, sec := range secs { + elem := reflect.New(typ) + if err = sec.mapToField(elem, isStrict, i); 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("cannot map to non-pointer struct") + return errors.New("not a pointer to a struct") } - return s.mapTo(val, false) + 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, 0) +} + +// 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 { - typ := reflect.TypeOf(v) - val := reflect.ValueOf(v) - if typ.Kind() == reflect.Ptr { - typ = typ.Elem() - val = val.Elem() - } else { - return errors.New("cannot map to non-pointer struct") - } - - return s.mapTo(val, true) + return s.mapTo(v, true) } // MapTo maps file to given struct. @@ -430,7 +476,7 @@ if i == 0 { keyWithShadows = newKey(key.s, key.name, val) } else { - keyWithShadows.AddShadow(val) + _ = keyWithShadows.AddShadow(val) } } key = keyWithShadows @@ -483,7 +529,7 @@ return reflectWithProperType(t.Elem(), key, field.Elem(), delim, allowShadow) } default: - return fmt.Errorf("unsupported type '%s'", t) + return fmt.Errorf("unsupported type %q", t) } return nil } @@ -523,6 +569,10 @@ typ := val.Type() for i := 0; i < typ.NumField(); i++ { + if !val.Field(i).CanInterface() { + continue + } + field := val.Field(i) tpField := typ.Field(i) @@ -531,7 +581,7 @@ continue } - rawName, omitEmpty, allowShadow := parseTagOptions(tag) + rawName, omitEmpty, allowShadow, allowNonUnique := parseTagOptions(tag) if omitEmpty && isEmptyValue(field) { continue } @@ -560,11 +610,41 @@ } if err = sec.reflectFrom(field); err != nil { - return fmt.Errorf("error reflecting field %q: %v", fieldName, err) + 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, "") @@ -577,22 +657,56 @@ delim := parseDelim(tpField.Tag.Get("delim")) if err = reflectWithProperType(tpField.Type, key, field, delim, allowShadow); err != nil { - return fmt.Errorf("error reflecting field %q: %v", fieldName, err) + return fmt.Errorf("reflect field %q: %v", fieldName, err) } } return nil } -// ReflectFrom reflects secion from given struct. +// 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("cannot reflect from non-pointer struct") + return errors.New("not a pointer to a struct") } return s.reflectFrom(val) diff -Nru golang-gopkg-ini.v1-1.52.0/struct_test.go golang-gopkg-ini.v1-1.57.0/struct_test.go --- golang-gopkg-ini.v1-1.52.0/struct_test.go 2020-01-31 12:28:19.000000000 +0000 +++ golang-gopkg-ini.v1-1.57.0/struct_test.go 2020-05-28 05:00:39.000000000 +0000 @@ -22,6 +22,7 @@ "time" . "github.com/smartystreets/goconvey/convey" + "gopkg.in/ini.v1" ) @@ -75,6 +76,23 @@ 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 @@ -125,6 +143,23 @@ 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 } @@ -185,8 +220,8 @@ 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(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()) @@ -225,7 +260,6 @@ So(ts.TimePtrNil, ShouldEqual, nil) So(*ts.DurationPtr, ShouldEqual, 0) So(ts.DurationPtrNil, ShouldEqual, nil) - }) Convey("Map section to struct", func() { @@ -330,6 +364,103 @@ }) } +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") + }) + + Convey("Map non unique sections with subsections to struct", func() { + iniFile, err := ini.LoadSources(ini.LoadOptions{AllowNonUniqueSections: true}, strings.NewReader(` +[Section] +FieldInSubSection = 1 +FieldInSubSection2 = 2 +FieldInSection = 3 + +[Section] +FieldInSubSection = 4 +FieldInSubSection2 = 5 +FieldInSection = 6 +`)) + So(err, ShouldBeNil) + + type SubSection struct { + FieldInSubSection string `ini:"FieldInSubSection"` + } + type SubSection2 struct { + FieldInSubSection2 string `ini:"FieldInSubSection2"` + } + + type Section struct { + SubSection `ini:"Section"` + SubSection2 `ini:"Section"` + FieldInSection string `ini:"FieldInSection"` + } + + type File struct { + Sections []Section `ini:"Section,,,nonunique"` + } + + f := new(File) + err = iniFile.MapTo(f) + So(err, ShouldBeNil) + + So(f.Sections[0].FieldInSubSection, ShouldEqual, "1") + So(f.Sections[0].FieldInSubSection2, ShouldEqual, "2") + So(f.Sections[0].FieldInSection, ShouldEqual, "3") + + So(f.Sections[1].FieldInSubSection, ShouldEqual, "4") + So(f.Sections[1].FieldInSubSection2, ShouldEqual, "5") + So(f.Sections[1].FieldInSection, ShouldEqual, "6") + }) + }) +} + func Test_ReflectFromStruct(t *testing.T) { Convey("Reflect from struct", t, func() { type Embeded struct { @@ -352,12 +483,13 @@ GPA float64 Date time.Time NeverMind string `ini:"-"` + ignored string *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, "", + a := &Author{"Unknwon", true, nil, 21, 100, 2.8, t, "", "ignored", &Embeded{ []time.Time{t, t}, []string{"HangZhou", "Boston"}, @@ -427,6 +559,101 @@ }) } +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() { diff -Nru golang-gopkg-ini.v1-1.52.0/.travis.yml golang-gopkg-ini.v1-1.57.0/.travis.yml --- golang-gopkg-ini.v1-1.52.0/.travis.yml 2020-01-31 12:28:19.000000000 +0000 +++ golang-gopkg-ini.v1-1.57.0/.travis.yml 1970-01-01 00:00:00.000000000 +0000 @@ -1,20 +0,0 @@ -sudo: false -language: go -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 - -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