1 // Copyright 2014 Unknwon
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
7 // http://www.apache.org/licenses/LICENSE-2.0
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
15 // Package ini provides INI file read and write functionality in Go.
34 // Name for default section. You can use this constant or the string literal.
35 // In most of cases, an empty string is all you need to access the section.
36 DEFAULT_SECTION = "DEFAULT"
38 // Maximum allowed depth when recursively substituing variable names.
43 // Version returns current package version literal.
44 func Version() string {
49 // Delimiter to determine or compose a new line.
50 // This variable will be changed to "\r\n" automatically on Windows
51 // at package init time.
54 // Variable regexp pattern: %(variable)s
55 varPattern = regexp.MustCompile(`%\(([^\)]+)\)s`)
57 // Indicate whether to align "=" sign with spaces to produce pretty output
58 // or reduce all possible spaces for compact format.
61 // Explicitly write DEFAULT section header
66 if runtime.GOOS == "windows" {
71 func inSlice(str string, s []string) bool {
80 // dataSource is an interface that returns object which can be read and closed.
81 type dataSource interface {
82 ReadCloser() (io.ReadCloser, error)
85 // sourceFile represents an object that contains content on the local file system.
86 type sourceFile struct {
90 func (s sourceFile) ReadCloser() (_ io.ReadCloser, err error) {
91 return os.Open(s.name)
94 type bytesReadCloser struct {
98 func (rc *bytesReadCloser) Read(p []byte) (n int, err error) {
99 return rc.reader.Read(p)
102 func (rc *bytesReadCloser) Close() error {
106 // sourceData represents an object that contains content in memory.
107 type sourceData struct {
111 func (s *sourceData) ReadCloser() (io.ReadCloser, error) {
112 return ioutil.NopCloser(bytes.NewReader(s.data)), nil
115 // sourceReadCloser represents an input stream with Close method.
116 type sourceReadCloser struct {
120 func (s *sourceReadCloser) ReadCloser() (io.ReadCloser, error) {
124 // File represents a combination of a or more INI file(s) in memory.
126 // Should make things safe, but sometimes doesn't matter.
128 // Make sure data is safe in multiple goroutines.
131 // Allow combination of multiple data sources.
132 dataSources []dataSource
133 // Actual data is stored here.
134 sections map[string]*Section
136 // To keep data in order.
145 // newFile initializes File object with given data sources.
146 func newFile(dataSources []dataSource, opts LoadOptions) *File {
149 dataSources: dataSources,
150 sections: make(map[string]*Section),
151 sectionList: make([]string, 0, 10),
156 func parseDataSource(source interface{}) (dataSource, error) {
157 switch s := source.(type) {
159 return sourceFile{s}, nil
161 return &sourceData{s}, nil
163 return &sourceReadCloser{s}, nil
165 return nil, fmt.Errorf("error parsing data source: unknown type '%s'", s)
169 type LoadOptions struct {
170 // Loose indicates whether the parser should ignore nonexistent files or return error.
172 // Insensitive indicates whether the parser forces all section and key names to lowercase.
174 // IgnoreContinuation indicates whether to ignore continuation lines while parsing.
175 IgnoreContinuation bool
176 // AllowBooleanKeys indicates whether to allow boolean type keys or treat as value is missing.
177 // This type of keys are mostly used in my.cnf.
178 AllowBooleanKeys bool
179 // AllowShadows indicates whether to keep track of keys with same name under same section.
181 // Some INI formats allow group blocks that store a block of raw content that doesn't otherwise
182 // conform to key/value pairs. Specify the names of those blocks here.
183 UnparseableSections []string
186 func LoadSources(opts LoadOptions, source interface{}, others ...interface{}) (_ *File, err error) {
187 sources := make([]dataSource, len(others)+1)
188 sources[0], err = parseDataSource(source)
192 for i := range others {
193 sources[i+1], err = parseDataSource(others[i])
198 f := newFile(sources, opts)
199 if err = f.Reload(); err != nil {
205 // Load loads and parses from INI data sources.
206 // Arguments can be mixed of file name with string type, or raw data in []byte.
207 // It will return error if list contains nonexistent files.
208 func Load(source interface{}, others ...interface{}) (*File, error) {
209 return LoadSources(LoadOptions{}, source, others...)
212 // LooseLoad has exactly same functionality as Load function
213 // except it ignores nonexistent files instead of returning error.
214 func LooseLoad(source interface{}, others ...interface{}) (*File, error) {
215 return LoadSources(LoadOptions{Loose: true}, source, others...)
218 // InsensitiveLoad has exactly same functionality as Load function
219 // except it forces all section and key names to be lowercased.
220 func InsensitiveLoad(source interface{}, others ...interface{}) (*File, error) {
221 return LoadSources(LoadOptions{Insensitive: true}, source, others...)
224 // InsensitiveLoad has exactly same functionality as Load function
225 // except it allows have shadow keys.
226 func ShadowLoad(source interface{}, others ...interface{}) (*File, error) {
227 return LoadSources(LoadOptions{AllowShadows: true}, source, others...)
230 // Empty returns an empty file object.
232 // Ignore error here, we sure our data is good.
233 f, _ := Load([]byte(""))
237 // NewSection creates a new section.
238 func (f *File) NewSection(name string) (*Section, error) {
240 return nil, errors.New("error creating new section: empty section name")
241 } else if f.options.Insensitive && name != DEFAULT_SECTION {
242 name = strings.ToLower(name)
247 defer f.lock.Unlock()
250 if inSlice(name, f.sectionList) {
251 return f.sections[name], nil
254 f.sectionList = append(f.sectionList, name)
255 f.sections[name] = newSection(f, name)
256 return f.sections[name], nil
259 // NewRawSection creates a new section with an unparseable body.
260 func (f *File) NewRawSection(name, body string) (*Section, error) {
261 section, err := f.NewSection(name)
266 section.isRawSection = true
267 section.rawBody = body
271 // NewSections creates a list of sections.
272 func (f *File) NewSections(names ...string) (err error) {
273 for _, name := range names {
274 if _, err = f.NewSection(name); err != nil {
281 // GetSection returns section by given name.
282 func (f *File) GetSection(name string) (*Section, error) {
284 name = DEFAULT_SECTION
285 } else if f.options.Insensitive {
286 name = strings.ToLower(name)
291 defer f.lock.RUnlock()
294 sec := f.sections[name]
296 return nil, fmt.Errorf("section '%s' does not exist", name)
301 // Section assumes named section exists and returns a zero-value when not.
302 func (f *File) Section(name string) *Section {
303 sec, err := f.GetSection(name)
305 // Note: It's OK here because the only possible error is empty section name,
306 // but if it's empty, this piece of code won't be executed.
307 sec, _ = f.NewSection(name)
313 // Section returns list of Section.
314 func (f *File) Sections() []*Section {
315 sections := make([]*Section, len(f.sectionList))
316 for i := range f.sectionList {
317 sections[i] = f.Section(f.sectionList[i])
322 // SectionStrings returns list of section names.
323 func (f *File) SectionStrings() []string {
324 list := make([]string, len(f.sectionList))
325 copy(list, f.sectionList)
329 // DeleteSection deletes a section.
330 func (f *File) DeleteSection(name string) {
333 defer f.lock.Unlock()
337 name = DEFAULT_SECTION
340 for i, s := range f.sectionList {
342 f.sectionList = append(f.sectionList[:i], f.sectionList[i+1:]...)
343 delete(f.sections, name)
349 func (f *File) reload(s dataSource) error {
350 r, err := s.ReadCloser()
359 // Reload reloads and parses all data sources.
360 func (f *File) Reload() (err error) {
361 for _, s := range f.dataSources {
362 if err = f.reload(s); err != nil {
363 // In loose mode, we create an empty default section for nonexistent files.
364 if os.IsNotExist(err) && f.options.Loose {
365 f.parse(bytes.NewBuffer(nil))
374 // Append appends one or more data sources and reloads automatically.
375 func (f *File) Append(source interface{}, others ...interface{}) error {
376 ds, err := parseDataSource(source)
380 f.dataSources = append(f.dataSources, ds)
381 for _, s := range others {
382 ds, err = parseDataSource(s)
386 f.dataSources = append(f.dataSources, ds)
391 // WriteToIndent writes content into io.Writer with given indention.
392 // If PrettyFormat has been set to be true,
393 // it will align "=" sign with spaces under each section.
394 func (f *File) WriteToIndent(w io.Writer, indent string) (n int64, err error) {
400 // Use buffer to make sure target is safe until finish encoding.
401 buf := bytes.NewBuffer(nil)
402 for i, sname := range f.sectionList {
403 sec := f.Section(sname)
404 if len(sec.Comment) > 0 {
405 if sec.Comment[0] != '#' && sec.Comment[0] != ';' {
406 sec.Comment = "; " + sec.Comment
408 if _, err = buf.WriteString(sec.Comment + LineBreak); err != nil {
413 if i > 0 || DefaultHeader {
414 if _, err = buf.WriteString("[" + sname + "]" + LineBreak); err != nil {
418 // Write nothing if default section is empty
419 if len(sec.keyList) == 0 {
424 if sec.isRawSection {
425 if _, err = buf.WriteString(sec.rawBody); err != nil {
431 // Count and generate alignment length and buffer spaces using the
432 // longest key. Keys may be modifed if they contain certain characters so
433 // we need to take that into account in our calculation.
436 for _, kname := range sec.keyList {
437 keyLength := len(kname)
438 // First case will surround key by ` and second by """
439 if strings.ContainsAny(kname, "\"=:") {
441 } else if strings.Contains(kname, "`") {
445 if keyLength > alignLength {
446 alignLength = keyLength
450 alignSpaces := bytes.Repeat([]byte(" "), alignLength)
453 for _, kname := range sec.keyList {
454 key := sec.Key(kname)
455 if len(key.Comment) > 0 {
456 if len(indent) > 0 && sname != DEFAULT_SECTION {
457 buf.WriteString(indent)
459 if key.Comment[0] != '#' && key.Comment[0] != ';' {
460 key.Comment = "; " + key.Comment
462 if _, err = buf.WriteString(key.Comment + LineBreak); err != nil {
467 if len(indent) > 0 && sname != DEFAULT_SECTION {
468 buf.WriteString(indent)
472 case key.isAutoIncrement:
474 case strings.ContainsAny(kname, "\"=:"):
475 kname = "`" + kname + "`"
476 case strings.Contains(kname, "`"):
477 kname = `"""` + kname + `"""`
480 for _, val := range key.ValueWithShadows() {
481 if _, err = buf.WriteString(kname); err != nil {
485 if key.isBooleanType {
486 if kname != sec.keyList[len(sec.keyList)-1] {
487 buf.WriteString(LineBreak)
492 // Write out alignment spaces before "=" sign
494 buf.Write(alignSpaces[:alignLength-len(kname)])
497 // In case key value contains "\n", "`", "\"", "#" or ";"
498 if strings.ContainsAny(val, "\n`") {
499 val = `"""` + val + `"""`
500 } else if strings.ContainsAny(val, "#;") {
501 val = "`" + val + "`"
503 if _, err = buf.WriteString(equalSign + val + LineBreak); err != nil {
509 // Put a line between sections
510 if _, err = buf.WriteString(LineBreak); err != nil {
515 return buf.WriteTo(w)
518 // WriteTo writes file content into io.Writer.
519 func (f *File) WriteTo(w io.Writer) (int64, error) {
520 return f.WriteToIndent(w, "")
523 // SaveToIndent writes content to file system with given value indention.
524 func (f *File) SaveToIndent(filename, indent string) error {
525 // Note: Because we are truncating with os.Create,
526 // so it's safer to save to a temporary file location and rename afte done.
527 tmpPath := filename + "." + strconv.Itoa(time.Now().Nanosecond()) + ".tmp"
528 defer os.Remove(tmpPath)
530 fw, err := os.Create(tmpPath)
535 if _, err = f.WriteToIndent(fw, indent); err != nil {
541 // Remove old file and rename the new one.
543 return os.Rename(tmpPath, filename)
546 // SaveTo writes content to file system.
547 func (f *File) SaveTo(filename string) error {
548 return f.SaveToIndent(filename, "")