...

Source file src/edge-infra.dev/pkg/lib/ini/file.go

Documentation: edge-infra.dev/pkg/lib/ini

     1  package ini
     2  
     3  import (
     4  	"bytes"
     5  	"errors"
     6  	"fmt"
     7  	"io"
     8  	"os"
     9  	"strings"
    10  	"sync"
    11  )
    12  
    13  // File represents a combination of one or more INI files in memory.
    14  type File struct {
    15  	options     LoadOptions
    16  	dataSources []dataSource
    17  
    18  	// Should make things safe, but sometimes doesn't matter.
    19  	BlockMode bool
    20  	lock      sync.RWMutex
    21  
    22  	// To keep data in order.
    23  	sectionList []string
    24  	// To keep track of the index of a section with same name.
    25  	// This meta list is only used with non-unique section names are allowed.
    26  	sectionIndexes []int
    27  
    28  	// Actual data is stored here.
    29  	sections map[string][]*Section
    30  
    31  	NameMapper
    32  	ValueMapper
    33  }
    34  
    35  // newFile initializes File object with given data sources.
    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  // Empty returns an empty file object.
    56  func Empty(opts ...LoadOptions) *File {
    57  	var opt LoadOptions
    58  	if len(opts) > 0 {
    59  		opt = opts[0]
    60  	}
    61  
    62  	// Ignore error here, we are sure our data is good.
    63  	f, _ := LoadSources(opt, []byte(""))
    64  	return f
    65  }
    66  
    67  // NewSection creates a new section.
    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  	// NOTE: Append to indexes must happen before appending to sections,
    89  	// otherwise index will have off-by-one problem.
    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  // NewRawSection creates a new section with an unparseable body.
    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  // NewSections creates a list of sections.
   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  // GetSection returns section by given name.
   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  // HasSection returns true if the file contains a section with given name.
   131  func (f *File) HasSection(name string) bool {
   132  	section, _ := f.GetSection(name)
   133  	return section != nil
   134  }
   135  
   136  // SectionsByName returns all sections with given name.
   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  // Section assumes named section exists and returns a zero-value when not.
   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  // SectionWithIndex assumes named section exists and returns a new section when not.
   172  func (f *File) SectionWithIndex(name string, index int) *Section {
   173  	secs, err := f.SectionsByName(name)
   174  	if err != nil || len(secs) <= index {
   175  		// NOTE: It's OK here because the only possible error is empty section name,
   176  		// but if it's empty, this piece of code won't be executed.
   177  		newSec, _ := f.NewSection(name)
   178  		return newSec
   179  	}
   180  
   181  	return secs[index]
   182  }
   183  
   184  // Sections returns a list of Section stored in the current instance.
   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  // ChildSections returns a list of child sections of given section name.
   199  func (f *File) ChildSections(name string) []*Section {
   200  	return f.Section(name).ChildSections()
   201  }
   202  
   203  // SectionStrings returns list of section names.
   204  func (f *File) SectionStrings() []string {
   205  	list := make([]string, len(f.sectionList))
   206  	copy(list, f.sectionList)
   207  	return list
   208  }
   209  
   210  // DeleteSection deletes a section or all sections with given name.
   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  		// For non-unique sections, it is always needed to remove the first one so
   219  		// in the next iteration, the subsequent section continue having index 0.
   220  		// Ignoring the error as index 0 never returns an error.
   221  		_ = f.DeleteSectionWithIndex(name, 0)
   222  	}
   223  }
   224  
   225  // DeleteSectionWithIndex deletes a section with given name and index.
   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  	// Count occurrences of the sections
   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) // The last one in the map
   257  			} else {
   258  				f.sections[name] = append(f.sections[name][:index], f.sections[name][index+1:]...)
   259  			}
   260  
   261  			// Fix section lists
   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  			// Fix the indices of all following sections with this name.
   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  // Reload reloads and parses all data sources.
   287  func (f *File) Reload() (err error) {
   288  	for _, s := range f.dataSources {
   289  		if err = f.reload(s); err != nil {
   290  			// In loose mode, we create an empty default section for nonexistent files.
   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  // Append appends one or more data sources and reloads automatically.
   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  	// Use buffer to make sure target is safe until finish encoding.
   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  			// Support multiline comments
   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  			// Write nothing if default section is empty
   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  				// Put a line between sections
   368  				if _, err := buf.WriteString(LineBreak); err != nil {
   369  					return nil, err
   370  				}
   371  			}
   372  			continue
   373  		}
   374  
   375  		// Count and generate alignment length and buffer spaces using the
   376  		// longest key. Keys may be modified if they contain certain characters so
   377  		// we need to take that into account in our calculation.
   378  		alignLength := 0
   379  		if PrettyFormat {
   380  			for _, kname := range sec.keyList {
   381  				keyLength := len(kname)
   382  				// First case will surround key by ` and second by """
   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  				// Support multiline comments
   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  				// Write out alignment spaces before "=" sign
   444  				if PrettyFormat {
   445  					buf.Write(alignSpaces[:alignLength-len(kname)])
   446  				}
   447  
   448  				// In case key value contains "\n", "`", "\"", "#" or ";"
   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  				// compensate for default PrettyFormat = true
   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  			// Put a line between sections
   496  			if _, err := buf.WriteString(LineBreak); err != nil {
   497  				return nil, err
   498  			}
   499  		}
   500  	}
   501  
   502  	return buf, nil
   503  }
   504  
   505  // WriteToIndent writes content into io.Writer with given indention.
   506  // If PrettyFormat has been set to be true,
   507  // it will align "=" sign with spaces under each section.
   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  // WriteTo writes file content into io.Writer.
   517  func (f *File) WriteTo(w io.Writer) (int64, error) {
   518  	return f.WriteToIndent(w, "")
   519  }
   520  
   521  // SaveToIndent writes content to file system with given value indention.
   522  func (f *File) SaveToIndent(filename, indent string) error {
   523  	// Note: Because we are truncating with os.Create,
   524  	// 	so it's safer to save to a temporary file location and rename after done.
   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  // SaveTo writes content to file system.
   534  func (f *File) SaveTo(filename string) error {
   535  	return f.SaveToIndent(filename, "")
   536  }
   537  

View as plain text