1// Copyright 2017 Unknwon
2//
3// Licensed under the Apache License, Version 2.0 (the "License"): you may
4// not use this file except in compliance with the License. You may obtain
5// a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
11// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
12// License for the specific language governing permissions and limitations
13// under the License.
14
15package ini
16
17import (
18	"bytes"
19	"errors"
20	"fmt"
21	"io"
22	"io/ioutil"
23	"os"
24	"strings"
25	"sync"
26)
27
28// File represents a combination of a or more INI file(s) in memory.
29type File struct {
30	options     LoadOptions
31	dataSources []dataSource
32
33	// Should make things safe, but sometimes doesn't matter.
34	BlockMode bool
35	lock      sync.RWMutex
36
37	// To keep data in order.
38	sectionList []string
39	// Actual data is stored here.
40	sections map[string]*Section
41
42	NameMapper
43	ValueMapper
44}
45
46// newFile initializes File object with given data sources.
47func newFile(dataSources []dataSource, opts LoadOptions) *File {
48	if len(opts.KeyValueDelimiters) == 0 {
49		opts.KeyValueDelimiters = "=:"
50	}
51	return &File{
52		BlockMode:   true,
53		dataSources: dataSources,
54		sections:    make(map[string]*Section),
55		sectionList: make([]string, 0, 10),
56		options:     opts,
57	}
58}
59
60// Empty returns an empty file object.
61func Empty() *File {
62	// Ignore error here, we sure our data is good.
63	f, _ := Load([]byte(""))
64	return f
65}
66
67// NewSection creates a new section.
68func (f *File) NewSection(name string) (*Section, error) {
69	if len(name) == 0 {
70		return nil, errors.New("error creating new section: empty section name")
71	} else if f.options.Insensitive && name != DEFAULT_SECTION {
72		name = strings.ToLower(name)
73	}
74
75	if f.BlockMode {
76		f.lock.Lock()
77		defer f.lock.Unlock()
78	}
79
80	if inSlice(name, f.sectionList) {
81		return f.sections[name], nil
82	}
83
84	f.sectionList = append(f.sectionList, name)
85	f.sections[name] = newSection(f, name)
86	return f.sections[name], nil
87}
88
89// NewRawSection creates a new section with an unparseable body.
90func (f *File) NewRawSection(name, body string) (*Section, error) {
91	section, err := f.NewSection(name)
92	if err != nil {
93		return nil, err
94	}
95
96	section.isRawSection = true
97	section.rawBody = body
98	return section, nil
99}
100
101// NewSections creates a list of sections.
102func (f *File) NewSections(names ...string) (err error) {
103	for _, name := range names {
104		if _, err = f.NewSection(name); err != nil {
105			return err
106		}
107	}
108	return nil
109}
110
111// GetSection returns section by given name.
112func (f *File) GetSection(name string) (*Section, error) {
113	if len(name) == 0 {
114		name = DEFAULT_SECTION
115	}
116	if f.options.Insensitive {
117		name = strings.ToLower(name)
118	}
119
120	if f.BlockMode {
121		f.lock.RLock()
122		defer f.lock.RUnlock()
123	}
124
125	sec := f.sections[name]
126	if sec == nil {
127		return nil, fmt.Errorf("section '%s' does not exist", name)
128	}
129	return sec, nil
130}
131
132// Section assumes named section exists and returns a zero-value when not.
133func (f *File) Section(name string) *Section {
134	sec, err := f.GetSection(name)
135	if err != nil {
136		// Note: It's OK here because the only possible error is empty section name,
137		// but if it's empty, this piece of code won't be executed.
138		sec, _ = f.NewSection(name)
139		return sec
140	}
141	return sec
142}
143
144// Section returns list of Section.
145func (f *File) Sections() []*Section {
146	if f.BlockMode {
147		f.lock.RLock()
148		defer f.lock.RUnlock()
149	}
150
151	sections := make([]*Section, len(f.sectionList))
152	for i, name := range f.sectionList {
153		sections[i] = f.sections[name]
154	}
155	return sections
156}
157
158// ChildSections returns a list of child sections of given section name.
159func (f *File) ChildSections(name string) []*Section {
160	return f.Section(name).ChildSections()
161}
162
163// SectionStrings returns list of section names.
164func (f *File) SectionStrings() []string {
165	list := make([]string, len(f.sectionList))
166	copy(list, f.sectionList)
167	return list
168}
169
170// DeleteSection deletes a section.
171func (f *File) DeleteSection(name string) {
172	if f.BlockMode {
173		f.lock.Lock()
174		defer f.lock.Unlock()
175	}
176
177	if len(name) == 0 {
178		name = DEFAULT_SECTION
179	}
180
181	for i, s := range f.sectionList {
182		if s == name {
183			f.sectionList = append(f.sectionList[:i], f.sectionList[i+1:]...)
184			delete(f.sections, name)
185			return
186		}
187	}
188}
189
190func (f *File) reload(s dataSource) error {
191	r, err := s.ReadCloser()
192	if err != nil {
193		return err
194	}
195	defer r.Close()
196
197	return f.parse(r)
198}
199
200// Reload reloads and parses all data sources.
201func (f *File) Reload() (err error) {
202	for _, s := range f.dataSources {
203		if err = f.reload(s); err != nil {
204			// In loose mode, we create an empty default section for nonexistent files.
205			if os.IsNotExist(err) && f.options.Loose {
206				f.parse(bytes.NewBuffer(nil))
207				continue
208			}
209			return err
210		}
211	}
212	return nil
213}
214
215// Append appends one or more data sources and reloads automatically.
216func (f *File) Append(source interface{}, others ...interface{}) error {
217	ds, err := parseDataSource(source)
218	if err != nil {
219		return err
220	}
221	f.dataSources = append(f.dataSources, ds)
222	for _, s := range others {
223		ds, err = parseDataSource(s)
224		if err != nil {
225			return err
226		}
227		f.dataSources = append(f.dataSources, ds)
228	}
229	return f.Reload()
230}
231
232func (f *File) writeToBuffer(indent string) (*bytes.Buffer, error) {
233	equalSign := DefaultFormatLeft + "=" + DefaultFormatRight
234
235	if PrettyFormat || PrettyEqual {
236		equalSign = " = "
237	}
238
239	// Use buffer to make sure target is safe until finish encoding.
240	buf := bytes.NewBuffer(nil)
241	for i, sname := range f.sectionList {
242		sec := f.Section(sname)
243		if len(sec.Comment) > 0 {
244			// Support multiline comments
245			lines := strings.Split(sec.Comment, LineBreak)
246			for i := range lines {
247				if lines[i][0] != '#' && lines[i][0] != ';' {
248					lines[i] = "; " + lines[i]
249				} else {
250					lines[i] = lines[i][:1] + " " + strings.TrimSpace(lines[i][1:])
251				}
252
253				if _, err := buf.WriteString(lines[i] + LineBreak); err != nil {
254					return nil, err
255				}
256			}
257		}
258
259		if i > 0 || DefaultHeader {
260			if _, err := buf.WriteString("[" + sname + "]" + LineBreak); err != nil {
261				return nil, err
262			}
263		} else {
264			// Write nothing if default section is empty
265			if len(sec.keyList) == 0 {
266				continue
267			}
268		}
269
270		if sec.isRawSection {
271			if _, err := buf.WriteString(sec.rawBody); err != nil {
272				return nil, err
273			}
274
275			if PrettySection {
276				// Put a line between sections
277				if _, err := buf.WriteString(LineBreak); err != nil {
278					return nil, err
279				}
280			}
281			continue
282		}
283
284		// Count and generate alignment length and buffer spaces using the
285		// longest key. Keys may be modifed if they contain certain characters so
286		// we need to take that into account in our calculation.
287		alignLength := 0
288		if PrettyFormat {
289			for _, kname := range sec.keyList {
290				keyLength := len(kname)
291				// First case will surround key by ` and second by """
292				if strings.Contains(kname, "\"") || strings.ContainsAny(kname, f.options.KeyValueDelimiters) {
293					keyLength += 2
294				} else if strings.Contains(kname, "`") {
295					keyLength += 6
296				}
297
298				if keyLength > alignLength {
299					alignLength = keyLength
300				}
301			}
302		}
303		alignSpaces := bytes.Repeat([]byte(" "), alignLength)
304
305	KEY_LIST:
306		for _, kname := range sec.keyList {
307			key := sec.Key(kname)
308			if len(key.Comment) > 0 {
309				if len(indent) > 0 && sname != DEFAULT_SECTION {
310					buf.WriteString(indent)
311				}
312
313				// Support multiline comments
314				lines := strings.Split(key.Comment, LineBreak)
315				for i := range lines {
316					if lines[i][0] != '#' && lines[i][0] != ';' {
317						lines[i] = "; " + strings.TrimSpace(lines[i])
318					} else {
319						lines[i] = lines[i][:1] + " " + strings.TrimSpace(lines[i][1:])
320					}
321
322					if _, err := buf.WriteString(lines[i] + LineBreak); err != nil {
323						return nil, err
324					}
325				}
326			}
327
328			if len(indent) > 0 && sname != DEFAULT_SECTION {
329				buf.WriteString(indent)
330			}
331
332			switch {
333			case key.isAutoIncrement:
334				kname = "-"
335			case strings.Contains(kname, "\"") || strings.ContainsAny(kname, f.options.KeyValueDelimiters):
336				kname = "`" + kname + "`"
337			case strings.Contains(kname, "`"):
338				kname = `"""` + kname + `"""`
339			}
340
341			for _, val := range key.ValueWithShadows() {
342				if _, err := buf.WriteString(kname); err != nil {
343					return nil, err
344				}
345
346				if key.isBooleanType {
347					if kname != sec.keyList[len(sec.keyList)-1] {
348						buf.WriteString(LineBreak)
349					}
350					continue KEY_LIST
351				}
352
353				// Write out alignment spaces before "=" sign
354				if PrettyFormat {
355					buf.Write(alignSpaces[:alignLength-len(kname)])
356				}
357
358				// In case key value contains "\n", "`", "\"", "#" or ";"
359				if strings.ContainsAny(val, "\n`") {
360					val = `"""` + val + `"""`
361				} else if !f.options.IgnoreInlineComment && strings.ContainsAny(val, "#;") {
362					val = "`" + val + "`"
363				}
364				if _, err := buf.WriteString(equalSign + val + LineBreak); err != nil {
365					return nil, err
366				}
367			}
368
369			for _, val := range key.nestedValues {
370				if _, err := buf.WriteString(indent + "  " + val + LineBreak); err != nil {
371					return nil, err
372				}
373			}
374		}
375
376		if PrettySection {
377			// Put a line between sections
378			if _, err := buf.WriteString(LineBreak); err != nil {
379				return nil, err
380			}
381		}
382	}
383
384	return buf, nil
385}
386
387// WriteToIndent writes content into io.Writer with given indention.
388// If PrettyFormat has been set to be true,
389// it will align "=" sign with spaces under each section.
390func (f *File) WriteToIndent(w io.Writer, indent string) (int64, error) {
391	buf, err := f.writeToBuffer(indent)
392	if err != nil {
393		return 0, err
394	}
395	return buf.WriteTo(w)
396}
397
398// WriteTo writes file content into io.Writer.
399func (f *File) WriteTo(w io.Writer) (int64, error) {
400	return f.WriteToIndent(w, "")
401}
402
403// SaveToIndent writes content to file system with given value indention.
404func (f *File) SaveToIndent(filename, indent string) error {
405	// Note: Because we are truncating with os.Create,
406	// 	so it's safer to save to a temporary file location and rename afte done.
407	buf, err := f.writeToBuffer(indent)
408	if err != nil {
409		return err
410	}
411
412	return ioutil.WriteFile(filename, buf.Bytes(), 0666)
413}
414
415// SaveTo writes content to file system.
416func (f *File) SaveTo(filename string) error {
417	return f.SaveToIndent(filename, "")
418}
419