1package ini
2
3import (
4	"fmt"
5	"io"
6	"io/ioutil"
7	"os"
8	"sync"
9	"time"
10)
11
12// Ini provides parsing and querying of INI format or simple name/value pairs
13// such as a simple config file.
14// A name/value pair format is just an INI with no sections, and properties can
15// be queried using an empty section name.
16type Ini struct {
17	mutex sync.RWMutex
18	m     map[string]*Section
19	lm    time.Time
20}
21
22// LoadFromFilespec loads an INI file from string containing path and filename.
23func (ini *Ini) LoadFromFilespec(filespec string) error {
24	f, err := os.Open(filespec)
25	if err != nil {
26		return err
27	}
28	return ini.LoadFromFile(f)
29}
30
31// LoadFromFile loads an INI file from `os.File`.
32func (ini *Ini) LoadFromFile(file *os.File) error {
33
34	fi, err := file.Stat()
35	if err != nil {
36		return err
37	}
38	lm := fi.ModTime()
39
40	if err := ini.LoadFromReader(file); err != nil {
41		return err
42	}
43	ini.lm = lm
44	return nil
45}
46
47// LoadFromReader loads an INI file from an `io.Reader`.
48func (ini *Ini) LoadFromReader(reader io.Reader) error {
49	data, err := ioutil.ReadAll(reader)
50	if err != nil {
51		return err
52	}
53	return ini.LoadFromString(string(data))
54}
55
56// LoadFromString parses an INI from a string .
57func (ini *Ini) LoadFromString(s string) error {
58	m, err := getSections(s)
59	if err != nil {
60		return err
61	}
62	ini.mutex.Lock()
63	ini.m = m
64	ini.lm = time.Now()
65	ini.mutex.Unlock()
66	return nil
67}
68
69// GetLastModified returns the last modified timestamp of the
70// INI contents.
71func (ini *Ini) GetLastModified() time.Time {
72	return ini.lm
73}
74
75// GetSectionNames returns the names of all sections in this INI.
76// Note, the returned section names are a snapshot in time, meaning
77// other goroutines may change the contents of this INI as soon as
78// the method returns.
79func (ini *Ini) GetSectionNames() []string {
80	ini.mutex.RLock()
81	defer ini.mutex.RUnlock()
82
83	arr := make([]string, 0, len(ini.m))
84	for key := range ini.m {
85		arr = append(arr, key)
86	}
87	return arr
88}
89
90// GetKeys returns the names of all keys in the specified section.
91// Note, the returned key names are a snapshot in time, meaning other
92// goroutines may change the contents of this INI as soon as the
93// method returns.
94func (ini *Ini) GetKeys(sectionName string) ([]string, error) {
95	sec, err := ini.getSection(sectionName)
96	if err != nil {
97		return nil, err
98	}
99	return sec.getKeys(), nil
100}
101
102// getSection returns the named section.
103func (ini *Ini) getSection(sectionName string) (*Section, error) {
104	ini.mutex.RLock()
105	defer ini.mutex.RUnlock()
106
107	sec, ok := ini.m[sectionName]
108	if !ok {
109		return nil, fmt.Errorf("section '%s' not found", sectionName)
110	}
111	return sec, nil
112}
113
114// GetFlattenedKeys returns all section names plus keys as one
115// flattened array.
116func (ini *Ini) GetFlattenedKeys() []string {
117	ini.mutex.RLock()
118	defer ini.mutex.RUnlock()
119
120	arr := make([]string, 0, len(ini.m)*2)
121	for _, section := range ini.m {
122		keys := section.getKeys()
123		for _, key := range keys {
124			name := section.GetName()
125			if name != "" {
126				key = name + "." + key
127			}
128			arr = append(arr, key)
129		}
130	}
131	return arr
132}
133
134// GetProp returns the value of the specified key in the named section.
135func (ini *Ini) GetProp(section string, key string) (val string, ok bool) {
136	sec, err := ini.getSection(section)
137	if err != nil {
138		return val, false
139	}
140	return sec.GetProp(key)
141}
142
143// ToMap returns a flattened map of the section name plus keys mapped
144// to values.
145func (ini *Ini) ToMap() map[string]string {
146	m := make(map[string]string)
147
148	ini.mutex.RLock()
149	defer ini.mutex.RUnlock()
150
151	for _, section := range ini.m {
152		for _, key := range section.getKeys() {
153			val, ok := section.GetProp(key)
154			if ok {
155				name := section.GetName()
156				var mapkey string
157				if name != "" {
158					mapkey = name + "." + key
159				} else {
160					mapkey = key
161				}
162				m[mapkey] = val
163			}
164		}
165	}
166	return m
167}
168