diff -r 4f153a23adab -r 6d9efbef00a9 vendor/gopkg.in/ini.v1/file.go --- a/vendor/gopkg.in/ini.v1/file.go Mon Jun 07 20:58:18 2021 +0200 +++ b/vendor/gopkg.in/ini.v1/file.go Sun Jul 11 10:35:56 2021 +0200 @@ -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,40 @@ if len(opts.KeyValueDelimiters) == 0 { opts.KeyValueDelimiters = "=:" } + if len(opts.KeyValueDelimiterOnWrite) == 0 { + opts.KeyValueDelimiterOnWrite = "=" + } + if len(opts.ChildSectionDelimiter) == 0 { + opts.ChildSectionDelimiter = "." + } + 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 || f.options.InsensitiveSections) && name != DefaultSection { name = strings.ToLower(name) } @@ -77,13 +94,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,10 +134,20 @@ // 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 { + if f.options.Insensitive || f.options.InsensitiveSections { name = strings.ToLower(name) } @@ -122,11 +156,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 +176,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 +198,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 +215,70 @@ return list } -// DeleteSection deletes a section. +// 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 || f.options.InsensitiveSections { + name = strings.ToLower(name) + } + if f.BlockMode { f.lock.Lock() defer f.lock.Unlock() } - if len(name) == 0 { - name = DefaultSection + // 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++ } - for i, s := range f.sectionList { - if s == name { - f.sectionList = append(f.sectionList[:i], f.sectionList[i+1:]...) - delete(f.sections, name) - return - } - } + return nil } func (f *File) reload(s dataSource) error { @@ -203,11 +297,14 @@ 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 } + if f.options.ShortCircuit { + return nil + } } return nil } @@ -230,16 +327,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) @@ -256,7 +353,7 @@ } } - if i > 0 || DefaultHeader { + if i > 0 || DefaultHeader || (i == 0 && strings.ToUpper(sec.name) != DefaultSection) { if _, err := buf.WriteString("[" + sname + "]" + LineBreak); err != nil { return nil, err } @@ -282,7 +379,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 { @@ -360,6 +457,8 @@ val = `"""` + val + `"""` } else if !f.options.IgnoreInlineComment && strings.ContainsAny(val, "#;") { val = "`" + val + "`" + } else if len(strings.TrimSpace(val)) != len(val) { + val = `"` + val + `"` } if _, err := buf.WriteString(equalSign + val + LineBreak); err != nil { return nil, err @@ -403,7 +502,7 @@ // 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. + // so it's safer to save to a temporary file location and rename after done. buf, err := f.writeToBuffer(indent) if err != nil { return err