23 "os" |
23 "os" |
24 "strings" |
24 "strings" |
25 "sync" |
25 "sync" |
26 ) |
26 ) |
27 |
27 |
28 // File represents a combination of a or more INI file(s) in memory. |
28 // File represents a combination of one or more INI files in memory. |
29 type File struct { |
29 type File struct { |
30 options LoadOptions |
30 options LoadOptions |
31 dataSources []dataSource |
31 dataSources []dataSource |
32 |
32 |
33 // Should make things safe, but sometimes doesn't matter. |
33 // Should make things safe, but sometimes doesn't matter. |
34 BlockMode bool |
34 BlockMode bool |
35 lock sync.RWMutex |
35 lock sync.RWMutex |
36 |
36 |
37 // To keep data in order. |
37 // To keep data in order. |
38 sectionList []string |
38 sectionList []string |
|
39 // To keep track of the index of a section with same name. |
|
40 // This meta list is only used with non-unique section names are allowed. |
|
41 sectionIndexes []int |
|
42 |
39 // Actual data is stored here. |
43 // Actual data is stored here. |
40 sections map[string]*Section |
44 sections map[string][]*Section |
41 |
45 |
42 NameMapper |
46 NameMapper |
43 ValueMapper |
47 ValueMapper |
44 } |
48 } |
45 |
49 |
46 // newFile initializes File object with given data sources. |
50 // newFile initializes File object with given data sources. |
47 func newFile(dataSources []dataSource, opts LoadOptions) *File { |
51 func newFile(dataSources []dataSource, opts LoadOptions) *File { |
48 if len(opts.KeyValueDelimiters) == 0 { |
52 if len(opts.KeyValueDelimiters) == 0 { |
49 opts.KeyValueDelimiters = "=:" |
53 opts.KeyValueDelimiters = "=:" |
50 } |
54 } |
|
55 if len(opts.KeyValueDelimiterOnWrite) == 0 { |
|
56 opts.KeyValueDelimiterOnWrite = "=" |
|
57 } |
|
58 if len(opts.ChildSectionDelimiter) == 0 { |
|
59 opts.ChildSectionDelimiter = "." |
|
60 } |
|
61 |
51 return &File{ |
62 return &File{ |
52 BlockMode: true, |
63 BlockMode: true, |
53 dataSources: dataSources, |
64 dataSources: dataSources, |
54 sections: make(map[string]*Section), |
65 sections: make(map[string][]*Section), |
55 sectionList: make([]string, 0, 10), |
|
56 options: opts, |
66 options: opts, |
57 } |
67 } |
58 } |
68 } |
59 |
69 |
60 // Empty returns an empty file object. |
70 // Empty returns an empty file object. |
61 func Empty() *File { |
71 func Empty(opts ...LoadOptions) *File { |
62 // Ignore error here, we sure our data is good. |
72 var opt LoadOptions |
63 f, _ := Load([]byte("")) |
73 if len(opts) > 0 { |
|
74 opt = opts[0] |
|
75 } |
|
76 |
|
77 // Ignore error here, we are sure our data is good. |
|
78 f, _ := LoadSources(opt, []byte("")) |
64 return f |
79 return f |
65 } |
80 } |
66 |
81 |
67 // NewSection creates a new section. |
82 // NewSection creates a new section. |
68 func (f *File) NewSection(name string) (*Section, error) { |
83 func (f *File) NewSection(name string) (*Section, error) { |
69 if len(name) == 0 { |
84 if len(name) == 0 { |
70 return nil, errors.New("error creating new section: empty section name") |
85 return nil, errors.New("empty section name") |
71 } else if f.options.Insensitive && name != DefaultSection { |
86 } |
|
87 |
|
88 if (f.options.Insensitive || f.options.InsensitiveSections) && name != DefaultSection { |
72 name = strings.ToLower(name) |
89 name = strings.ToLower(name) |
73 } |
90 } |
74 |
91 |
75 if f.BlockMode { |
92 if f.BlockMode { |
76 f.lock.Lock() |
93 f.lock.Lock() |
77 defer f.lock.Unlock() |
94 defer f.lock.Unlock() |
78 } |
95 } |
79 |
96 |
80 if inSlice(name, f.sectionList) { |
97 if !f.options.AllowNonUniqueSections && inSlice(name, f.sectionList) { |
81 return f.sections[name], nil |
98 return f.sections[name][0], nil |
82 } |
99 } |
83 |
100 |
84 f.sectionList = append(f.sectionList, name) |
101 f.sectionList = append(f.sectionList, name) |
85 f.sections[name] = newSection(f, name) |
102 |
86 return f.sections[name], nil |
103 // NOTE: Append to indexes must happen before appending to sections, |
|
104 // otherwise index will have off-by-one problem. |
|
105 f.sectionIndexes = append(f.sectionIndexes, len(f.sections[name])) |
|
106 |
|
107 sec := newSection(f, name) |
|
108 f.sections[name] = append(f.sections[name], sec) |
|
109 |
|
110 return sec, nil |
87 } |
111 } |
88 |
112 |
89 // NewRawSection creates a new section with an unparseable body. |
113 // NewRawSection creates a new section with an unparseable body. |
90 func (f *File) NewRawSection(name, body string) (*Section, error) { |
114 func (f *File) NewRawSection(name, body string) (*Section, error) { |
91 section, err := f.NewSection(name) |
115 section, err := f.NewSection(name) |
108 return nil |
132 return nil |
109 } |
133 } |
110 |
134 |
111 // GetSection returns section by given name. |
135 // GetSection returns section by given name. |
112 func (f *File) GetSection(name string) (*Section, error) { |
136 func (f *File) GetSection(name string) (*Section, error) { |
|
137 secs, err := f.SectionsByName(name) |
|
138 if err != nil { |
|
139 return nil, err |
|
140 } |
|
141 |
|
142 return secs[0], err |
|
143 } |
|
144 |
|
145 // SectionsByName returns all sections with given name. |
|
146 func (f *File) SectionsByName(name string) ([]*Section, error) { |
113 if len(name) == 0 { |
147 if len(name) == 0 { |
114 name = DefaultSection |
148 name = DefaultSection |
115 } |
149 } |
116 if f.options.Insensitive { |
150 if f.options.Insensitive || f.options.InsensitiveSections { |
117 name = strings.ToLower(name) |
151 name = strings.ToLower(name) |
118 } |
152 } |
119 |
153 |
120 if f.BlockMode { |
154 if f.BlockMode { |
121 f.lock.RLock() |
155 f.lock.RLock() |
122 defer f.lock.RUnlock() |
156 defer f.lock.RUnlock() |
123 } |
157 } |
124 |
158 |
125 sec := f.sections[name] |
159 secs := f.sections[name] |
126 if sec == nil { |
160 if len(secs) == 0 { |
127 return nil, fmt.Errorf("section '%s' does not exist", name) |
161 return nil, fmt.Errorf("section %q does not exist", name) |
128 } |
162 } |
129 return sec, nil |
163 |
|
164 return secs, nil |
130 } |
165 } |
131 |
166 |
132 // Section assumes named section exists and returns a zero-value when not. |
167 // Section assumes named section exists and returns a zero-value when not. |
133 func (f *File) Section(name string) *Section { |
168 func (f *File) Section(name string) *Section { |
134 sec, err := f.GetSection(name) |
169 sec, err := f.GetSection(name) |
137 // but if it's empty, this piece of code won't be executed. |
172 // but if it's empty, this piece of code won't be executed. |
138 sec, _ = f.NewSection(name) |
173 sec, _ = f.NewSection(name) |
139 return sec |
174 return sec |
140 } |
175 } |
141 return sec |
176 return sec |
|
177 } |
|
178 |
|
179 // SectionWithIndex assumes named section exists and returns a new section when not. |
|
180 func (f *File) SectionWithIndex(name string, index int) *Section { |
|
181 secs, err := f.SectionsByName(name) |
|
182 if err != nil || len(secs) <= index { |
|
183 // NOTE: It's OK here because the only possible error is empty section name, |
|
184 // but if it's empty, this piece of code won't be executed. |
|
185 newSec, _ := f.NewSection(name) |
|
186 return newSec |
|
187 } |
|
188 |
|
189 return secs[index] |
142 } |
190 } |
143 |
191 |
144 // Sections returns a list of Section stored in the current instance. |
192 // Sections returns a list of Section stored in the current instance. |
145 func (f *File) Sections() []*Section { |
193 func (f *File) Sections() []*Section { |
146 if f.BlockMode { |
194 if f.BlockMode { |
165 list := make([]string, len(f.sectionList)) |
213 list := make([]string, len(f.sectionList)) |
166 copy(list, f.sectionList) |
214 copy(list, f.sectionList) |
167 return list |
215 return list |
168 } |
216 } |
169 |
217 |
170 // DeleteSection deletes a section. |
218 // DeleteSection deletes a section or all sections with given name. |
171 func (f *File) DeleteSection(name string) { |
219 func (f *File) DeleteSection(name string) { |
|
220 secs, err := f.SectionsByName(name) |
|
221 if err != nil { |
|
222 return |
|
223 } |
|
224 |
|
225 for i := 0; i < len(secs); i++ { |
|
226 // For non-unique sections, it is always needed to remove the first one so |
|
227 // in the next iteration, the subsequent section continue having index 0. |
|
228 // Ignoring the error as index 0 never returns an error. |
|
229 _ = f.DeleteSectionWithIndex(name, 0) |
|
230 } |
|
231 } |
|
232 |
|
233 // DeleteSectionWithIndex deletes a section with given name and index. |
|
234 func (f *File) DeleteSectionWithIndex(name string, index int) error { |
|
235 if !f.options.AllowNonUniqueSections && index != 0 { |
|
236 return fmt.Errorf("delete section with non-zero index is only allowed when non-unique sections is enabled") |
|
237 } |
|
238 |
|
239 if len(name) == 0 { |
|
240 name = DefaultSection |
|
241 } |
|
242 if f.options.Insensitive || f.options.InsensitiveSections { |
|
243 name = strings.ToLower(name) |
|
244 } |
|
245 |
172 if f.BlockMode { |
246 if f.BlockMode { |
173 f.lock.Lock() |
247 f.lock.Lock() |
174 defer f.lock.Unlock() |
248 defer f.lock.Unlock() |
175 } |
249 } |
176 |
250 |
177 if len(name) == 0 { |
251 // Count occurrences of the sections |
178 name = DefaultSection |
252 occurrences := 0 |
179 } |
253 |
180 |
254 sectionListCopy := make([]string, len(f.sectionList)) |
181 for i, s := range f.sectionList { |
255 copy(sectionListCopy, f.sectionList) |
182 if s == name { |
256 |
|
257 for i, s := range sectionListCopy { |
|
258 if s != name { |
|
259 continue |
|
260 } |
|
261 |
|
262 if occurrences == index { |
|
263 if len(f.sections[name]) <= 1 { |
|
264 delete(f.sections, name) // The last one in the map |
|
265 } else { |
|
266 f.sections[name] = append(f.sections[name][:index], f.sections[name][index+1:]...) |
|
267 } |
|
268 |
|
269 // Fix section lists |
183 f.sectionList = append(f.sectionList[:i], f.sectionList[i+1:]...) |
270 f.sectionList = append(f.sectionList[:i], f.sectionList[i+1:]...) |
184 delete(f.sections, name) |
271 f.sectionIndexes = append(f.sectionIndexes[:i], f.sectionIndexes[i+1:]...) |
185 return |
272 |
186 } |
273 } else if occurrences > index { |
187 } |
274 // Fix the indices of all following sections with this name. |
|
275 f.sectionIndexes[i-1]-- |
|
276 } |
|
277 |
|
278 occurrences++ |
|
279 } |
|
280 |
|
281 return nil |
188 } |
282 } |
189 |
283 |
190 func (f *File) reload(s dataSource) error { |
284 func (f *File) reload(s dataSource) error { |
191 r, err := s.ReadCloser() |
285 r, err := s.ReadCloser() |
192 if err != nil { |
286 if err != nil { |
228 } |
325 } |
229 return f.Reload() |
326 return f.Reload() |
230 } |
327 } |
231 |
328 |
232 func (f *File) writeToBuffer(indent string) (*bytes.Buffer, error) { |
329 func (f *File) writeToBuffer(indent string) (*bytes.Buffer, error) { |
233 equalSign := DefaultFormatLeft + "=" + DefaultFormatRight |
330 equalSign := DefaultFormatLeft + f.options.KeyValueDelimiterOnWrite + DefaultFormatRight |
234 |
331 |
235 if PrettyFormat || PrettyEqual { |
332 if PrettyFormat || PrettyEqual { |
236 equalSign = " = " |
333 equalSign = fmt.Sprintf(" %s ", f.options.KeyValueDelimiterOnWrite) |
237 } |
334 } |
238 |
335 |
239 // Use buffer to make sure target is safe until finish encoding. |
336 // Use buffer to make sure target is safe until finish encoding. |
240 buf := bytes.NewBuffer(nil) |
337 buf := bytes.NewBuffer(nil) |
241 for i, sname := range f.sectionList { |
338 for i, sname := range f.sectionList { |
242 sec := f.Section(sname) |
339 sec := f.SectionWithIndex(sname, f.sectionIndexes[i]) |
243 if len(sec.Comment) > 0 { |
340 if len(sec.Comment) > 0 { |
244 // Support multiline comments |
341 // Support multiline comments |
245 lines := strings.Split(sec.Comment, LineBreak) |
342 lines := strings.Split(sec.Comment, LineBreak) |
246 for i := range lines { |
343 for i := range lines { |
247 if lines[i][0] != '#' && lines[i][0] != ';' { |
344 if lines[i][0] != '#' && lines[i][0] != ';' { |
358 // In case key value contains "\n", "`", "\"", "#" or ";" |
455 // In case key value contains "\n", "`", "\"", "#" or ";" |
359 if strings.ContainsAny(val, "\n`") { |
456 if strings.ContainsAny(val, "\n`") { |
360 val = `"""` + val + `"""` |
457 val = `"""` + val + `"""` |
361 } else if !f.options.IgnoreInlineComment && strings.ContainsAny(val, "#;") { |
458 } else if !f.options.IgnoreInlineComment && strings.ContainsAny(val, "#;") { |
362 val = "`" + val + "`" |
459 val = "`" + val + "`" |
|
460 } else if len(strings.TrimSpace(val)) != len(val) { |
|
461 val = `"` + val + `"` |
363 } |
462 } |
364 if _, err := buf.WriteString(equalSign + val + LineBreak); err != nil { |
463 if _, err := buf.WriteString(equalSign + val + LineBreak); err != nil { |
365 return nil, err |
464 return nil, err |
366 } |
465 } |
367 } |
466 } |