1 package ini
2
3 import (
4 "bytes"
5 "errors"
6 "fmt"
7 "io"
8 "os"
9 "strings"
10 "sync"
11 )
12
13
14 type File struct {
15 options LoadOptions
16 dataSources []dataSource
17
18
19 BlockMode bool
20 lock sync.RWMutex
21
22
23 sectionList []string
24
25
26 sectionIndexes []int
27
28
29 sections map[string][]*Section
30
31 NameMapper
32 ValueMapper
33 }
34
35
36 func newFile(dataSources []dataSource, opts LoadOptions) *File {
37 if len(opts.KeyValueDelimiters) == 0 {
38 opts.KeyValueDelimiters = "=:"
39 }
40 if len(opts.KeyValueDelimiterOnWrite) == 0 {
41 opts.KeyValueDelimiterOnWrite = "="
42 }
43 if len(opts.ChildSectionDelimiter) == 0 {
44 opts.ChildSectionDelimiter = "."
45 }
46
47 return &File{
48 BlockMode: true,
49 dataSources: dataSources,
50 sections: make(map[string][]*Section),
51 options: opts,
52 }
53 }
54
55
56 func Empty(opts ...LoadOptions) *File {
57 var opt LoadOptions
58 if len(opts) > 0 {
59 opt = opts[0]
60 }
61
62
63 f, _ := LoadSources(opt, []byte(""))
64 return f
65 }
66
67
68 func (f *File) NewSection(name string) (*Section, error) {
69 if len(name) == 0 {
70 return nil, errors.New("empty section name")
71 }
72
73 if (f.options.Insensitive || f.options.InsensitiveSections) && name != DefaultSection {
74 name = strings.ToLower(name)
75 }
76
77 if f.BlockMode {
78 f.lock.Lock()
79 defer f.lock.Unlock()
80 }
81
82 if !f.options.AllowNonUniqueSections && inSlice(name, f.sectionList) {
83 return f.sections[name][0], nil
84 }
85
86 f.sectionList = append(f.sectionList, name)
87
88
89
90 f.sectionIndexes = append(f.sectionIndexes, len(f.sections[name]))
91
92 sec := newSection(f, name)
93 f.sections[name] = append(f.sections[name], sec)
94
95 return sec, nil
96 }
97
98
99 func (f *File) NewRawSection(name, body string) (*Section, error) {
100 section, err := f.NewSection(name)
101 if err != nil {
102 return nil, err
103 }
104
105 section.isRawSection = true
106 section.rawBody = body
107 return section, nil
108 }
109
110
111 func (f *File) NewSections(names ...string) (err error) {
112 for _, name := range names {
113 if _, err = f.NewSection(name); err != nil {
114 return err
115 }
116 }
117 return nil
118 }
119
120
121 func (f *File) GetSection(name string) (*Section, error) {
122 secs, err := f.SectionsByName(name)
123 if err != nil {
124 return nil, err
125 }
126
127 return secs[0], err
128 }
129
130
131 func (f *File) HasSection(name string) bool {
132 section, _ := f.GetSection(name)
133 return section != nil
134 }
135
136
137 func (f *File) SectionsByName(name string) ([]*Section, error) {
138 if len(name) == 0 {
139 name = DefaultSection
140 }
141 if f.options.Insensitive || f.options.InsensitiveSections {
142 name = strings.ToLower(name)
143 }
144
145 if f.BlockMode {
146 f.lock.RLock()
147 defer f.lock.RUnlock()
148 }
149
150 secs := f.sections[name]
151 if len(secs) == 0 {
152 return nil, fmt.Errorf("section %q does not exist", name)
153 }
154
155 return secs, nil
156 }
157
158
159 func (f *File) Section(name string) *Section {
160 sec, err := f.GetSection(name)
161 if err != nil {
162 if name == "" {
163 name = DefaultSection
164 }
165 sec, _ = f.NewSection(name)
166 return sec
167 }
168 return sec
169 }
170
171
172 func (f *File) SectionWithIndex(name string, index int) *Section {
173 secs, err := f.SectionsByName(name)
174 if err != nil || len(secs) <= index {
175
176
177 newSec, _ := f.NewSection(name)
178 return newSec
179 }
180
181 return secs[index]
182 }
183
184
185 func (f *File) Sections() []*Section {
186 if f.BlockMode {
187 f.lock.RLock()
188 defer f.lock.RUnlock()
189 }
190
191 sections := make([]*Section, len(f.sectionList))
192 for i, name := range f.sectionList {
193 sections[i] = f.sections[name][f.sectionIndexes[i]]
194 }
195 return sections
196 }
197
198
199 func (f *File) ChildSections(name string) []*Section {
200 return f.Section(name).ChildSections()
201 }
202
203
204 func (f *File) SectionStrings() []string {
205 list := make([]string, len(f.sectionList))
206 copy(list, f.sectionList)
207 return list
208 }
209
210
211 func (f *File) DeleteSection(name string) {
212 secs, err := f.SectionsByName(name)
213 if err != nil {
214 return
215 }
216
217 for i := 0; i < len(secs); i++ {
218
219
220
221 _ = f.DeleteSectionWithIndex(name, 0)
222 }
223 }
224
225
226 func (f *File) DeleteSectionWithIndex(name string, index int) error {
227 if !f.options.AllowNonUniqueSections && index != 0 {
228 return fmt.Errorf("delete section with non-zero index is only allowed when non-unique sections is enabled")
229 }
230
231 if len(name) == 0 {
232 name = DefaultSection
233 }
234 if f.options.Insensitive || f.options.InsensitiveSections {
235 name = strings.ToLower(name)
236 }
237
238 if f.BlockMode {
239 f.lock.Lock()
240 defer f.lock.Unlock()
241 }
242
243
244 occurrences := 0
245
246 sectionListCopy := make([]string, len(f.sectionList))
247 copy(sectionListCopy, f.sectionList)
248
249 for i, s := range sectionListCopy {
250 if s != name {
251 continue
252 }
253
254 if occurrences == index {
255 if len(f.sections[name]) <= 1 {
256 delete(f.sections, name)
257 } else {
258 f.sections[name] = append(f.sections[name][:index], f.sections[name][index+1:]...)
259 }
260
261
262 f.sectionList = append(f.sectionList[:i], f.sectionList[i+1:]...)
263 f.sectionIndexes = append(f.sectionIndexes[:i], f.sectionIndexes[i+1:]...)
264
265 } else if occurrences > index {
266
267 f.sectionIndexes[i-1]--
268 }
269
270 occurrences++
271 }
272
273 return nil
274 }
275
276 func (f *File) reload(s dataSource) error {
277 r, err := s.ReadCloser()
278 if err != nil {
279 return err
280 }
281 defer r.Close()
282
283 return f.parse(r)
284 }
285
286
287 func (f *File) Reload() (err error) {
288 for _, s := range f.dataSources {
289 if err = f.reload(s); err != nil {
290
291 if os.IsNotExist(err) && f.options.Loose {
292 _ = f.parse(bytes.NewBuffer(nil))
293 continue
294 }
295 return err
296 }
297 if f.options.ShortCircuit {
298 return nil
299 }
300 }
301 return nil
302 }
303
304
305 func (f *File) Append(source interface{}, others ...interface{}) error {
306 ds, err := parseDataSource(source)
307 if err != nil {
308 return err
309 }
310 f.dataSources = append(f.dataSources, ds)
311 for _, s := range others {
312 ds, err = parseDataSource(s)
313 if err != nil {
314 return err
315 }
316 f.dataSources = append(f.dataSources, ds)
317 }
318 return f.Reload()
319 }
320
321 func (f *File) writeToBuffer(indent string) (*bytes.Buffer, error) {
322 equalSign := DefaultFormatLeft + f.options.KeyValueDelimiterOnWrite + DefaultFormatRight
323
324 if PrettyFormat || PrettyEqual {
325 equalSign = fmt.Sprintf(" %s ", f.options.KeyValueDelimiterOnWrite)
326 }
327
328
329 buf := bytes.NewBuffer(nil)
330 lastSectionIdx := len(f.sectionList) - 1
331 for i, sname := range f.sectionList {
332 sec := f.SectionWithIndex(sname, f.sectionIndexes[i])
333 if len(sec.Comment) > 0 {
334
335 lines := strings.Split(sec.Comment, LineBreak)
336 for i := range lines {
337 if lines[i][0] != '#' && lines[i][0] != ';' {
338 lines[i] = "; " + lines[i]
339 } else {
340 lines[i] = lines[i][:1] + " " + strings.TrimSpace(lines[i][1:])
341 }
342
343 if _, err := buf.WriteString(lines[i] + LineBreak); err != nil {
344 return nil, err
345 }
346 }
347 }
348
349 if i > 0 || DefaultHeader || (i == 0 && strings.ToUpper(sec.name) != DefaultSection) {
350 if _, err := buf.WriteString("[" + sname + "]" + LineBreak); err != nil {
351 return nil, err
352 }
353 } else {
354
355 if len(sec.keyList) == 0 {
356 continue
357 }
358 }
359
360 isLastSection := i == lastSectionIdx
361 if sec.isRawSection {
362 if _, err := buf.WriteString(sec.rawBody); err != nil {
363 return nil, err
364 }
365
366 if PrettySection && !isLastSection {
367
368 if _, err := buf.WriteString(LineBreak); err != nil {
369 return nil, err
370 }
371 }
372 continue
373 }
374
375
376
377
378 alignLength := 0
379 if PrettyFormat {
380 for _, kname := range sec.keyList {
381 keyLength := len(kname)
382
383 if strings.Contains(kname, "\"") || strings.ContainsAny(kname, f.options.KeyValueDelimiters) {
384 keyLength += 2
385 } else if strings.Contains(kname, "`") {
386 keyLength += 6
387 }
388
389 if keyLength > alignLength {
390 alignLength = keyLength
391 }
392 }
393 }
394 alignSpaces := bytes.Repeat([]byte(" "), alignLength)
395
396 KeyList:
397 for _, kname := range sec.keyList {
398 key := sec.Key(kname)
399 if len(key.Comment) > 0 {
400 if len(indent) > 0 && sname != DefaultSection {
401 buf.WriteString(indent)
402 }
403
404
405 lines := strings.Split(key.Comment, LineBreak)
406 for i := range lines {
407 if lines[i][0] != '#' && lines[i][0] != ';' {
408 lines[i] = "; " + strings.TrimSpace(lines[i])
409 }
410 if lines[i][1:] != "" {
411 lines[i] = lines[i][:1] + " " + strings.TrimSpace(lines[i][1:])
412 }
413
414 if _, err := buf.WriteString(lines[i] + LineBreak); err != nil {
415 return nil, err
416 }
417 }
418 }
419
420 if len(indent) > 0 && sname != DefaultSection {
421 buf.WriteString(indent)
422 }
423
424 switch {
425 case key.isAutoIncrement:
426 kname = "-"
427 case strings.Contains(kname, "\"") || strings.ContainsAny(kname, f.options.KeyValueDelimiters):
428 kname = "`" + kname + "`"
429 case strings.Contains(kname, "`"):
430 kname = `"""` + kname + `"""`
431 }
432
433 writeKeyValue := func(val string) (bool, error) {
434 if _, err := buf.WriteString(kname); err != nil {
435 return false, err
436 }
437
438 if key.isBooleanType {
439 buf.WriteString(LineBreak)
440 return true, nil
441 }
442
443
444 if PrettyFormat {
445 buf.Write(alignSpaces[:alignLength-len(kname)])
446 }
447
448
449 if strings.ContainsAny(val, "\n`") {
450 val = `"""` + val + `"""`
451 } else if !f.options.IgnoreInlineComment && strings.ContainsAny(val, "#;") {
452 val = "`" + val + "`"
453 } else if len(strings.TrimSpace(val)) != len(val) {
454 val = `"` + val + `"`
455 }
456
457
458 if len(val) == 0 {
459 if _, err := buf.WriteString(strings.TrimRight(equalSign, " ") + LineBreak); err != nil {
460 return false, err
461 }
462 return true, nil
463 }
464
465 if _, err := buf.WriteString(equalSign + val + LineBreak); err != nil {
466 return false, err
467 }
468 return false, nil
469 }
470
471 shadows := key.ValueWithShadows()
472 if len(shadows) == 0 {
473 if _, err := writeKeyValue(""); err != nil {
474 return nil, err
475 }
476 }
477
478 for _, val := range shadows {
479 exitLoop, err := writeKeyValue(val)
480 if err != nil {
481 return nil, err
482 } else if exitLoop {
483 continue KeyList
484 }
485 }
486
487 for _, val := range key.nestedValues {
488 if _, err := buf.WriteString(indent + " " + val + LineBreak); err != nil {
489 return nil, err
490 }
491 }
492 }
493
494 if PrettySection && !isLastSection {
495
496 if _, err := buf.WriteString(LineBreak); err != nil {
497 return nil, err
498 }
499 }
500 }
501
502 return buf, nil
503 }
504
505
506
507
508 func (f *File) WriteToIndent(w io.Writer, indent string) (int64, error) {
509 buf, err := f.writeToBuffer(indent)
510 if err != nil {
511 return 0, err
512 }
513 return buf.WriteTo(w)
514 }
515
516
517 func (f *File) WriteTo(w io.Writer) (int64, error) {
518 return f.WriteToIndent(w, "")
519 }
520
521
522 func (f *File) SaveToIndent(filename, indent string) error {
523
524
525 buf, err := f.writeToBuffer(indent)
526 if err != nil {
527 return err
528 }
529
530 return os.WriteFile(filename, buf.Bytes(), 0666)
531 }
532
533
534 func (f *File) SaveTo(filename string) error {
535 return f.SaveToIndent(filename, "")
536 }
537
View as plain text