file.go raw

   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  
  15  package ini
  16  
  17  import (
  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 one or more INI files in memory.
  29  type 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  	// 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  
  43  	// Actual data is stored here.
  44  	sections map[string][]*Section
  45  
  46  	NameMapper
  47  	ValueMapper
  48  }
  49  
  50  // newFile initializes File object with given data sources.
  51  func newFile(dataSources []dataSource, opts LoadOptions) *File {
  52  	if len(opts.KeyValueDelimiters) == 0 {
  53  		opts.KeyValueDelimiters = "=:"
  54  	}
  55  	if len(opts.KeyValueDelimiterOnWrite) == 0 {
  56  		opts.KeyValueDelimiterOnWrite = "="
  57  	}
  58  	if len(opts.ChildSectionDelimiter) == 0 {
  59  		opts.ChildSectionDelimiter = "."
  60  	}
  61  
  62  	return &File{
  63  		BlockMode:   true,
  64  		dataSources: dataSources,
  65  		sections:    make(map[string][]*Section),
  66  		options:     opts,
  67  	}
  68  }
  69  
  70  // Empty returns an empty file object.
  71  func Empty(opts ...LoadOptions) *File {
  72  	var opt LoadOptions
  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(""))
  79  	return f
  80  }
  81  
  82  // NewSection creates a new section.
  83  func (f *File) NewSection(name string) (*Section, error) {
  84  	if len(name) == 0 {
  85  		return nil, errors.New("empty section name")
  86  	}
  87  
  88  	if (f.options.Insensitive || f.options.InsensitiveSections) && name != DefaultSection {
  89  		name = strings.ToLower(name)
  90  	}
  91  
  92  	if f.BlockMode {
  93  		f.lock.Lock()
  94  		defer f.lock.Unlock()
  95  	}
  96  
  97  	if !f.options.AllowNonUniqueSections && inSlice(name, f.sectionList) {
  98  		return f.sections[name][0], nil
  99  	}
 100  
 101  	f.sectionList = append(f.sectionList, name)
 102  
 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
 111  }
 112  
 113  // NewRawSection creates a new section with an unparseable body.
 114  func (f *File) NewRawSection(name, body string) (*Section, error) {
 115  	section, err := f.NewSection(name)
 116  	if err != nil {
 117  		return nil, err
 118  	}
 119  
 120  	section.isRawSection = true
 121  	section.rawBody = body
 122  	return section, nil
 123  }
 124  
 125  // NewSections creates a list of sections.
 126  func (f *File) NewSections(names ...string) (err error) {
 127  	for _, name := range names {
 128  		if _, err = f.NewSection(name); err != nil {
 129  			return err
 130  		}
 131  	}
 132  	return nil
 133  }
 134  
 135  // GetSection returns section by given name.
 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  // HasSection returns true if the file contains a section with given name.
 146  func (f *File) HasSection(name string) bool {
 147  	section, _ := f.GetSection(name)
 148  	return section != nil
 149  }
 150  
 151  // SectionsByName returns all sections with given name.
 152  func (f *File) SectionsByName(name string) ([]*Section, error) {
 153  	if len(name) == 0 {
 154  		name = DefaultSection
 155  	}
 156  	if f.options.Insensitive || f.options.InsensitiveSections {
 157  		name = strings.ToLower(name)
 158  	}
 159  
 160  	if f.BlockMode {
 161  		f.lock.RLock()
 162  		defer f.lock.RUnlock()
 163  	}
 164  
 165  	secs := f.sections[name]
 166  	if len(secs) == 0 {
 167  		return nil, fmt.Errorf("section %q does not exist", name)
 168  	}
 169  
 170  	return secs, nil
 171  }
 172  
 173  // Section assumes named section exists and returns a zero-value when not.
 174  func (f *File) Section(name string) *Section {
 175  	sec, err := f.GetSection(name)
 176  	if err != nil {
 177  		if name == "" {
 178  			name = DefaultSection
 179  		}
 180  		sec, _ = f.NewSection(name)
 181  		return sec
 182  	}
 183  	return sec
 184  }
 185  
 186  // SectionWithIndex assumes named section exists and returns a new section when not.
 187  func (f *File) SectionWithIndex(name string, index int) *Section {
 188  	secs, err := f.SectionsByName(name)
 189  	if err != nil || len(secs) <= index {
 190  		// NOTE: It's OK here because the only possible error is empty section name,
 191  		// but if it's empty, this piece of code won't be executed.
 192  		newSec, _ := f.NewSection(name)
 193  		return newSec
 194  	}
 195  
 196  	return secs[index]
 197  }
 198  
 199  // Sections returns a list of Section stored in the current instance.
 200  func (f *File) Sections() []*Section {
 201  	if f.BlockMode {
 202  		f.lock.RLock()
 203  		defer f.lock.RUnlock()
 204  	}
 205  
 206  	sections := make([]*Section, len(f.sectionList))
 207  	for i, name := range f.sectionList {
 208  		sections[i] = f.sections[name][f.sectionIndexes[i]]
 209  	}
 210  	return sections
 211  }
 212  
 213  // ChildSections returns a list of child sections of given section name.
 214  func (f *File) ChildSections(name string) []*Section {
 215  	return f.Section(name).ChildSections()
 216  }
 217  
 218  // SectionStrings returns list of section names.
 219  func (f *File) SectionStrings() []string {
 220  	list := make([]string, len(f.sectionList))
 221  	copy(list, f.sectionList)
 222  	return list
 223  }
 224  
 225  // DeleteSection deletes a section or all sections with given name.
 226  func (f *File) DeleteSection(name string) {
 227  	secs, err := f.SectionsByName(name)
 228  	if err != nil {
 229  		return
 230  	}
 231  
 232  	for i := 0; i < len(secs); i++ {
 233  		// For non-unique sections, it is always needed to remove the first one so
 234  		// in the next iteration, the subsequent section continue having index 0.
 235  		// Ignoring the error as index 0 never returns an error.
 236  		_ = f.DeleteSectionWithIndex(name, 0)
 237  	}
 238  }
 239  
 240  // DeleteSectionWithIndex deletes a section with given name and index.
 241  func (f *File) DeleteSectionWithIndex(name string, index int) error {
 242  	if !f.options.AllowNonUniqueSections && index != 0 {
 243  		return fmt.Errorf("delete section with non-zero index is only allowed when non-unique sections is enabled")
 244  	}
 245  
 246  	if len(name) == 0 {
 247  		name = DefaultSection
 248  	}
 249  	if f.options.Insensitive || f.options.InsensitiveSections {
 250  		name = strings.ToLower(name)
 251  	}
 252  
 253  	if f.BlockMode {
 254  		f.lock.Lock()
 255  		defer f.lock.Unlock()
 256  	}
 257  
 258  	// Count occurrences of the sections
 259  	occurrences := 0
 260  
 261  	sectionListCopy := make([]string, len(f.sectionList))
 262  	copy(sectionListCopy, f.sectionList)
 263  
 264  	for i, s := range sectionListCopy {
 265  		if s != name {
 266  			continue
 267  		}
 268  
 269  		if occurrences == index {
 270  			if len(f.sections[name]) <= 1 {
 271  				delete(f.sections, name) // The last one in the map
 272  			} else {
 273  				f.sections[name] = append(f.sections[name][:index], f.sections[name][index+1:]...)
 274  			}
 275  
 276  			// Fix section lists
 277  			f.sectionList = append(f.sectionList[:i], f.sectionList[i+1:]...)
 278  			f.sectionIndexes = append(f.sectionIndexes[:i], f.sectionIndexes[i+1:]...)
 279  
 280  		} else if occurrences > index {
 281  			// Fix the indices of all following sections with this name.
 282  			f.sectionIndexes[i-1]--
 283  		}
 284  
 285  		occurrences++
 286  	}
 287  
 288  	return nil
 289  }
 290  
 291  func (f *File) reload(s dataSource) error {
 292  	r, err := s.ReadCloser()
 293  	if err != nil {
 294  		return err
 295  	}
 296  	defer r.Close()
 297  
 298  	return f.parse(r)
 299  }
 300  
 301  // Reload reloads and parses all data sources.
 302  func (f *File) Reload() (err error) {
 303  	for _, s := range f.dataSources {
 304  		if err = f.reload(s); err != nil {
 305  			// In loose mode, we create an empty default section for nonexistent files.
 306  			if os.IsNotExist(err) && f.options.Loose {
 307  				_ = f.parse(bytes.NewBuffer(nil))
 308  				continue
 309  			}
 310  			return err
 311  		}
 312  		if f.options.ShortCircuit {
 313  			return nil
 314  		}
 315  	}
 316  	return nil
 317  }
 318  
 319  // Append appends one or more data sources and reloads automatically.
 320  func (f *File) Append(source interface{}, others ...interface{}) error {
 321  	ds, err := parseDataSource(source)
 322  	if err != nil {
 323  		return err
 324  	}
 325  	f.dataSources = append(f.dataSources, ds)
 326  	for _, s := range others {
 327  		ds, err = parseDataSource(s)
 328  		if err != nil {
 329  			return err
 330  		}
 331  		f.dataSources = append(f.dataSources, ds)
 332  	}
 333  	return f.Reload()
 334  }
 335  
 336  func (f *File) writeToBuffer(indent string) (*bytes.Buffer, error) {
 337  	equalSign := DefaultFormatLeft + f.options.KeyValueDelimiterOnWrite + DefaultFormatRight
 338  
 339  	if PrettyFormat || PrettyEqual {
 340  		equalSign = fmt.Sprintf(" %s ", f.options.KeyValueDelimiterOnWrite)
 341  	}
 342  
 343  	// Use buffer to make sure target is safe until finish encoding.
 344  	buf := bytes.NewBuffer(nil)
 345  	lastSectionIdx := len(f.sectionList) - 1
 346  	for i, sname := range f.sectionList {
 347  		sec := f.SectionWithIndex(sname, f.sectionIndexes[i])
 348  		if len(sec.Comment) > 0 {
 349  			// Support multiline comments
 350  			lines := strings.Split(sec.Comment, LineBreak)
 351  			for i := range lines {
 352  				if lines[i][0] != '#' && lines[i][0] != ';' {
 353  					lines[i] = "; " + lines[i]
 354  				} else {
 355  					lines[i] = lines[i][:1] + " " + strings.TrimSpace(lines[i][1:])
 356  				}
 357  
 358  				if _, err := buf.WriteString(lines[i] + LineBreak); err != nil {
 359  					return nil, err
 360  				}
 361  			}
 362  		}
 363  
 364  		if i > 0 || DefaultHeader || (i == 0 && strings.ToUpper(sec.name) != DefaultSection) {
 365  			if _, err := buf.WriteString("[" + sname + "]" + LineBreak); err != nil {
 366  				return nil, err
 367  			}
 368  		} else {
 369  			// Write nothing if default section is empty
 370  			if len(sec.keyList) == 0 {
 371  				continue
 372  			}
 373  		}
 374  
 375  		isLastSection := i == lastSectionIdx
 376  		if sec.isRawSection {
 377  			if _, err := buf.WriteString(sec.rawBody); err != nil {
 378  				return nil, err
 379  			}
 380  
 381  			if PrettySection && !isLastSection {
 382  				// Put a line between sections
 383  				if _, err := buf.WriteString(LineBreak); err != nil {
 384  					return nil, err
 385  				}
 386  			}
 387  			continue
 388  		}
 389  
 390  		// Count and generate alignment length and buffer spaces using the
 391  		// longest key. Keys may be modified if they contain certain characters so
 392  		// we need to take that into account in our calculation.
 393  		alignLength := 0
 394  		if PrettyFormat {
 395  			for _, kname := range sec.keyList {
 396  				keyLength := len(kname)
 397  				// First case will surround key by ` and second by """
 398  				if strings.Contains(kname, "\"") || strings.ContainsAny(kname, f.options.KeyValueDelimiters) {
 399  					keyLength += 2
 400  				} else if strings.Contains(kname, "`") {
 401  					keyLength += 6
 402  				}
 403  
 404  				if keyLength > alignLength {
 405  					alignLength = keyLength
 406  				}
 407  			}
 408  		}
 409  		alignSpaces := bytes.Repeat([]byte(" "), alignLength)
 410  
 411  	KeyList:
 412  		for _, kname := range sec.keyList {
 413  			key := sec.Key(kname)
 414  			if len(key.Comment) > 0 {
 415  				if len(indent) > 0 && sname != DefaultSection {
 416  					buf.WriteString(indent)
 417  				}
 418  
 419  				// Support multiline comments
 420  				lines := strings.Split(key.Comment, LineBreak)
 421  				for i := range lines {
 422  					if lines[i][0] != '#' && lines[i][0] != ';' {
 423  						lines[i] = "; " + strings.TrimSpace(lines[i])
 424  					} else {
 425  						lines[i] = lines[i][:1] + " " + strings.TrimSpace(lines[i][1:])
 426  					}
 427  
 428  					if _, err := buf.WriteString(lines[i] + LineBreak); err != nil {
 429  						return nil, err
 430  					}
 431  				}
 432  			}
 433  
 434  			if len(indent) > 0 && sname != DefaultSection {
 435  				buf.WriteString(indent)
 436  			}
 437  
 438  			switch {
 439  			case key.isAutoIncrement:
 440  				kname = "-"
 441  			case strings.Contains(kname, "\"") || strings.ContainsAny(kname, f.options.KeyValueDelimiters):
 442  				kname = "`" + kname + "`"
 443  			case strings.Contains(kname, "`"):
 444  				kname = `"""` + kname + `"""`
 445  			}
 446  
 447  			writeKeyValue := func(val string) (bool, error) {
 448  				if _, err := buf.WriteString(kname); err != nil {
 449  					return false, err
 450  				}
 451  
 452  				if key.isBooleanType {
 453  					buf.WriteString(LineBreak)
 454  					return true, nil
 455  				}
 456  
 457  				// Write out alignment spaces before "=" sign
 458  				if PrettyFormat {
 459  					buf.Write(alignSpaces[:alignLength-len(kname)])
 460  				}
 461  
 462  				// In case key value contains "\n", "`", "\"", "#" or ";"
 463  				if strings.ContainsAny(val, "\n`") {
 464  					val = `"""` + val + `"""`
 465  				} else if !f.options.IgnoreInlineComment && strings.ContainsAny(val, "#;") {
 466  					val = "`" + val + "`"
 467  				} else if len(strings.TrimSpace(val)) != len(val) {
 468  					val = `"` + val + `"`
 469  				}
 470  				if _, err := buf.WriteString(equalSign + val + LineBreak); err != nil {
 471  					return false, err
 472  				}
 473  				return false, nil
 474  			}
 475  
 476  			shadows := key.ValueWithShadows()
 477  			if len(shadows) == 0 {
 478  				if _, err := writeKeyValue(""); err != nil {
 479  					return nil, err
 480  				}
 481  			}
 482  
 483  			for _, val := range shadows {
 484  				exitLoop, err := writeKeyValue(val)
 485  				if err != nil {
 486  					return nil, err
 487  				} else if exitLoop {
 488  					continue KeyList
 489  				}
 490  			}
 491  
 492  			for _, val := range key.nestedValues {
 493  				if _, err := buf.WriteString(indent + "  " + val + LineBreak); err != nil {
 494  					return nil, err
 495  				}
 496  			}
 497  		}
 498  
 499  		if PrettySection && !isLastSection {
 500  			// Put a line between sections
 501  			if _, err := buf.WriteString(LineBreak); err != nil {
 502  				return nil, err
 503  			}
 504  		}
 505  	}
 506  
 507  	return buf, nil
 508  }
 509  
 510  // WriteToIndent writes content into io.Writer with given indention.
 511  // If PrettyFormat has been set to be true,
 512  // it will align "=" sign with spaces under each section.
 513  func (f *File) WriteToIndent(w io.Writer, indent string) (int64, error) {
 514  	buf, err := f.writeToBuffer(indent)
 515  	if err != nil {
 516  		return 0, err
 517  	}
 518  	return buf.WriteTo(w)
 519  }
 520  
 521  // WriteTo writes file content into io.Writer.
 522  func (f *File) WriteTo(w io.Writer) (int64, error) {
 523  	return f.WriteToIndent(w, "")
 524  }
 525  
 526  // SaveToIndent writes content to file system with given value indention.
 527  func (f *File) SaveToIndent(filename, indent string) error {
 528  	// Note: Because we are truncating with os.Create,
 529  	// 	so it's safer to save to a temporary file location and rename after done.
 530  	buf, err := f.writeToBuffer(indent)
 531  	if err != nil {
 532  		return err
 533  	}
 534  
 535  	return ioutil.WriteFile(filename, buf.Bytes(), 0666)
 536  }
 537  
 538  // SaveTo writes content to file system.
 539  func (f *File) SaveTo(filename string) error {
 540  	return f.SaveToIndent(filename, "")
 541  }
 542