github.com/eframework-cn/EP.GO.UTIL@v1.0.0/xconfig/ini.go (about)

     1  // Copyright 2014 beego Author. All Rights Reserved.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain 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,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package xconfig
    16  
    17  import (
    18  	"bufio"
    19  	"bytes"
    20  	"errors"
    21  	"io"
    22  	"io/ioutil"
    23  	"os"
    24  	"os/user"
    25  	"path/filepath"
    26  	"strconv"
    27  	"strings"
    28  	"sync"
    29  )
    30  
    31  var (
    32  	defaultSection = "default"   // default section means if some ini items not in a section, make them in default section,
    33  	bNumComment    = []byte{'#'} // number signal
    34  	bSemComment    = []byte{';'} // semicolon signal
    35  	bEmpty         = []byte{}
    36  	bEqual         = []byte{'='} // equal signal
    37  	bDQuote        = []byte{'"'} // quote signal
    38  	sectionStart   = []byte{'['} // section start signal
    39  	sectionEnd     = []byte{']'} // section end signal
    40  	lineBreak      = "\n"
    41  )
    42  
    43  // IniConfig implements Config to parse ini file.
    44  type IniConfig struct {
    45  }
    46  
    47  // Parse creates a new Config and parses the file configuration from the named file.
    48  func (ini *IniConfig) Parse(name string) (Configer, error) {
    49  	return ini.parseFile(name)
    50  }
    51  
    52  func (ini *IniConfig) parseFile(name string) (*IniConfigContainer, error) {
    53  	data, err := ioutil.ReadFile(name)
    54  	if err != nil {
    55  		return nil, err
    56  	}
    57  
    58  	return ini.parseData(filepath.Dir(name), data)
    59  }
    60  
    61  func (ini *IniConfig) parseData(dir string, data []byte) (*IniConfigContainer, error) {
    62  	cfg := &IniConfigContainer{
    63  		data:           make(map[string]map[string]string),
    64  		sectionComment: make(map[string]string),
    65  		keyComment:     make(map[string]string),
    66  		RWMutex:        sync.RWMutex{},
    67  	}
    68  	cfg.Lock()
    69  	defer cfg.Unlock()
    70  
    71  	var comment bytes.Buffer
    72  	buf := bufio.NewReader(bytes.NewBuffer(data))
    73  	// check the BOM
    74  	head, err := buf.Peek(3)
    75  	if err == nil && head[0] == 239 && head[1] == 187 && head[2] == 191 {
    76  		for i := 1; i <= 3; i++ {
    77  			buf.ReadByte()
    78  		}
    79  	}
    80  	section := defaultSection
    81  	tmpBuf := bytes.NewBuffer(nil)
    82  	for {
    83  		tmpBuf.Reset()
    84  
    85  		shouldBreak := false
    86  		for {
    87  			tmp, isPrefix, err := buf.ReadLine()
    88  			if err == io.EOF {
    89  				shouldBreak = true
    90  				break
    91  			}
    92  
    93  			//It might be a good idea to throw a error on all unknonw errors?
    94  			if _, ok := err.(*os.PathError); ok {
    95  				return nil, err
    96  			}
    97  
    98  			tmpBuf.Write(tmp)
    99  			if isPrefix {
   100  				continue
   101  			}
   102  
   103  			if !isPrefix {
   104  				break
   105  			}
   106  		}
   107  		if shouldBreak {
   108  			break
   109  		}
   110  
   111  		line := tmpBuf.Bytes()
   112  		line = bytes.TrimSpace(line)
   113  		if bytes.Equal(line, bEmpty) {
   114  			continue
   115  		}
   116  		var bComment []byte
   117  		switch {
   118  		case bytes.HasPrefix(line, bNumComment):
   119  			bComment = bNumComment
   120  		case bytes.HasPrefix(line, bSemComment):
   121  			bComment = bSemComment
   122  		}
   123  		if bComment != nil {
   124  			line = bytes.TrimLeft(line, string(bComment))
   125  			// Need append to a new line if multi-line comments.
   126  			if comment.Len() > 0 {
   127  				comment.WriteByte('\n')
   128  			}
   129  			comment.Write(line)
   130  			continue
   131  		}
   132  
   133  		if bytes.HasPrefix(line, sectionStart) && bytes.HasSuffix(line, sectionEnd) {
   134  			section = strings.ToLower(string(line[1 : len(line)-1])) // section name case insensitive
   135  			if comment.Len() > 0 {
   136  				cfg.sectionComment[section] = comment.String()
   137  				comment.Reset()
   138  			}
   139  			if _, ok := cfg.data[section]; !ok {
   140  				cfg.data[section] = make(map[string]string)
   141  			}
   142  			continue
   143  		}
   144  
   145  		if _, ok := cfg.data[section]; !ok {
   146  			cfg.data[section] = make(map[string]string)
   147  		}
   148  		keyValue := bytes.SplitN(line, bEqual, 2)
   149  
   150  		key := string(bytes.TrimSpace(keyValue[0])) // key name case insensitive
   151  		key = strings.ToLower(key)
   152  
   153  		// handle include "other.conf"
   154  		if len(keyValue) == 1 && strings.HasPrefix(key, "include") {
   155  
   156  			includefiles := strings.Fields(key)
   157  			if includefiles[0] == "include" && len(includefiles) == 2 {
   158  
   159  				otherfile := strings.Trim(includefiles[1], "\"")
   160  				if !filepath.IsAbs(otherfile) {
   161  					otherfile = filepath.Join(dir, otherfile)
   162  				}
   163  
   164  				i, err := ini.parseFile(otherfile)
   165  				if err != nil {
   166  					return nil, err
   167  				}
   168  
   169  				for sec, dt := range i.data {
   170  					if _, ok := cfg.data[sec]; !ok {
   171  						cfg.data[sec] = make(map[string]string)
   172  					}
   173  					for k, v := range dt {
   174  						cfg.data[sec][k] = v
   175  					}
   176  				}
   177  
   178  				for sec, comm := range i.sectionComment {
   179  					cfg.sectionComment[sec] = comm
   180  				}
   181  
   182  				for k, comm := range i.keyComment {
   183  					cfg.keyComment[k] = comm
   184  				}
   185  
   186  				continue
   187  			}
   188  		}
   189  
   190  		if len(keyValue) != 2 {
   191  			return nil, errors.New("read the content error: \"" + string(line) + "\", should key = val")
   192  		}
   193  		val := bytes.TrimSpace(keyValue[1])
   194  		if bytes.HasPrefix(val, bDQuote) {
   195  			val = bytes.Trim(val, `"`)
   196  		}
   197  
   198  		cfg.data[section][key] = ExpandValueEnv(string(val))
   199  		if comment.Len() > 0 {
   200  			cfg.keyComment[section+"."+key] = comment.String()
   201  			comment.Reset()
   202  		}
   203  
   204  	}
   205  	return cfg, nil
   206  }
   207  
   208  // ParseData parse ini the data
   209  // When include other.conf,other.conf is either absolute directory
   210  // or under beego in default temporary directory(/tmp/beego[-username]).
   211  func (ini *IniConfig) ParseData(data []byte) (Configer, error) {
   212  	dir := "beego"
   213  	currentUser, err := user.Current()
   214  	if err == nil {
   215  		dir = "beego-" + currentUser.Username
   216  	}
   217  	dir = filepath.Join(os.TempDir(), dir)
   218  	if err = os.MkdirAll(dir, os.ModePerm); err != nil {
   219  		return nil, err
   220  	}
   221  
   222  	return ini.parseData(dir, data)
   223  }
   224  
   225  // IniConfigContainer A Config represents the ini configuration.
   226  // When set and get value, support key as section:name type.
   227  type IniConfigContainer struct {
   228  	data           map[string]map[string]string // section=> key:val
   229  	sectionComment map[string]string            // section : comment
   230  	keyComment     map[string]string            // id: []{comment, key...}; id 1 is for main comment.
   231  	sync.RWMutex
   232  }
   233  
   234  // Bool returns the boolean value for a given key.
   235  func (c *IniConfigContainer) Bool(key string) (bool, error) {
   236  	return ParseBool(c.getdata(key))
   237  }
   238  
   239  // DefaultBool returns the boolean value for a given key.
   240  // if err != nil return defaultval
   241  func (c *IniConfigContainer) DefaultBool(key string, defaultval bool) bool {
   242  	v, err := c.Bool(key)
   243  	if err != nil {
   244  		return defaultval
   245  	}
   246  	return v
   247  }
   248  
   249  // Int returns the integer value for a given key.
   250  func (c *IniConfigContainer) Int(key string) (int, error) {
   251  	return strconv.Atoi(c.getdata(key))
   252  }
   253  
   254  // DefaultInt returns the integer value for a given key.
   255  // if err != nil return defaultval
   256  func (c *IniConfigContainer) DefaultInt(key string, defaultval int) int {
   257  	v, err := c.Int(key)
   258  	if err != nil {
   259  		return defaultval
   260  	}
   261  	return v
   262  }
   263  
   264  // Int64 returns the int64 value for a given key.
   265  func (c *IniConfigContainer) Int64(key string) (int64, error) {
   266  	return strconv.ParseInt(c.getdata(key), 10, 64)
   267  }
   268  
   269  // DefaultInt64 returns the int64 value for a given key.
   270  // if err != nil return defaultval
   271  func (c *IniConfigContainer) DefaultInt64(key string, defaultval int64) int64 {
   272  	v, err := c.Int64(key)
   273  	if err != nil {
   274  		return defaultval
   275  	}
   276  	return v
   277  }
   278  
   279  // Float returns the float value for a given key.
   280  func (c *IniConfigContainer) Float(key string) (float64, error) {
   281  	return strconv.ParseFloat(c.getdata(key), 64)
   282  }
   283  
   284  // DefaultFloat returns the float64 value for a given key.
   285  // if err != nil return defaultval
   286  func (c *IniConfigContainer) DefaultFloat(key string, defaultval float64) float64 {
   287  	v, err := c.Float(key)
   288  	if err != nil {
   289  		return defaultval
   290  	}
   291  	return v
   292  }
   293  
   294  // String returns the string value for a given key.
   295  func (c *IniConfigContainer) String(key string) string {
   296  	return c.getdata(key)
   297  }
   298  
   299  // DefaultString returns the string value for a given key.
   300  // if err != nil return defaultval
   301  func (c *IniConfigContainer) DefaultString(key string, defaultval string) string {
   302  	v := c.String(key)
   303  	if v == "" {
   304  		return defaultval
   305  	}
   306  	return v
   307  }
   308  
   309  // Strings returns the []string value for a given key.
   310  // Return nil if config value does not exist or is empty.
   311  func (c *IniConfigContainer) Strings(key string) []string {
   312  	v := c.String(key)
   313  	if v == "" {
   314  		return nil
   315  	}
   316  	return strings.Split(v, ";")
   317  }
   318  
   319  // DefaultStrings returns the []string value for a given key.
   320  // if err != nil return defaultval
   321  func (c *IniConfigContainer) DefaultStrings(key string, defaultval []string) []string {
   322  	v := c.Strings(key)
   323  	if v == nil {
   324  		return defaultval
   325  	}
   326  	return v
   327  }
   328  
   329  // GetSection returns map for the given section
   330  func (c *IniConfigContainer) GetSection(section string) (map[string]string, error) {
   331  	if v, ok := c.data[section]; ok {
   332  		return v, nil
   333  	}
   334  	return nil, errors.New("not exist section")
   335  }
   336  
   337  // SaveConfigFile save the config into file.
   338  //
   339  // BUG(env): The environment variable config item will be saved with real value in SaveConfigFile Function.
   340  func (c *IniConfigContainer) SaveConfigFile(filename string) (err error) {
   341  	// Write configuration file by filename.
   342  	f, err := os.Create(filename)
   343  	if err != nil {
   344  		return err
   345  	}
   346  	defer f.Close()
   347  
   348  	// Get section or key comments. Fixed #1607
   349  	getCommentStr := func(section, key string) string {
   350  		var (
   351  			comment string
   352  			ok      bool
   353  		)
   354  		if len(key) == 0 {
   355  			comment, ok = c.sectionComment[section]
   356  		} else {
   357  			comment, ok = c.keyComment[section+"."+key]
   358  		}
   359  
   360  		if ok {
   361  			// Empty comment
   362  			if len(comment) == 0 || len(strings.TrimSpace(comment)) == 0 {
   363  				return string(bNumComment)
   364  			}
   365  			prefix := string(bNumComment)
   366  			// Add the line head character "#"
   367  			return prefix + strings.Replace(comment, lineBreak, lineBreak+prefix, -1)
   368  		}
   369  		return ""
   370  	}
   371  
   372  	buf := bytes.NewBuffer(nil)
   373  	// Save default section at first place
   374  	if dt, ok := c.data[defaultSection]; ok {
   375  		for key, val := range dt {
   376  			if key != " " {
   377  				// Write key comments.
   378  				if v := getCommentStr(defaultSection, key); len(v) > 0 {
   379  					if _, err = buf.WriteString(v + lineBreak); err != nil {
   380  						return err
   381  					}
   382  				}
   383  
   384  				// Write key and value.
   385  				if _, err = buf.WriteString(key + string(bEqual) + val + lineBreak); err != nil {
   386  					return err
   387  				}
   388  			}
   389  		}
   390  
   391  		// Put a line between sections.
   392  		if _, err = buf.WriteString(lineBreak); err != nil {
   393  			return err
   394  		}
   395  	}
   396  	// Save named sections
   397  	for section, dt := range c.data {
   398  		if section != defaultSection {
   399  			// Write section comments.
   400  			if v := getCommentStr(section, ""); len(v) > 0 {
   401  				if _, err = buf.WriteString(v + lineBreak); err != nil {
   402  					return err
   403  				}
   404  			}
   405  
   406  			// Write section name.
   407  			if _, err = buf.WriteString(string(sectionStart) + section + string(sectionEnd) + lineBreak); err != nil {
   408  				return err
   409  			}
   410  
   411  			for key, val := range dt {
   412  				if key != " " {
   413  					// Write key comments.
   414  					if v := getCommentStr(section, key); len(v) > 0 {
   415  						if _, err = buf.WriteString(v + lineBreak); err != nil {
   416  							return err
   417  						}
   418  					}
   419  
   420  					// Write key and value.
   421  					if _, err = buf.WriteString(key + string(bEqual) + val + lineBreak); err != nil {
   422  						return err
   423  					}
   424  				}
   425  			}
   426  
   427  			// Put a line between sections.
   428  			if _, err = buf.WriteString(lineBreak); err != nil {
   429  				return err
   430  			}
   431  		}
   432  	}
   433  	_, err = buf.WriteTo(f)
   434  	return err
   435  }
   436  
   437  // Set writes a new value for key.
   438  // if write to one section, the key need be "section::key".
   439  // if the section is not existed, it panics.
   440  func (c *IniConfigContainer) Set(key, value string) error {
   441  	c.Lock()
   442  	defer c.Unlock()
   443  	if len(key) == 0 {
   444  		return errors.New("key is empty")
   445  	}
   446  
   447  	var (
   448  		section, k string
   449  		sectionKey = strings.Split(strings.ToLower(key), "::")
   450  	)
   451  
   452  	if len(sectionKey) >= 2 {
   453  		section = sectionKey[0]
   454  		k = sectionKey[1]
   455  	} else {
   456  		section = defaultSection
   457  		k = sectionKey[0]
   458  	}
   459  
   460  	if _, ok := c.data[section]; !ok {
   461  		c.data[section] = make(map[string]string)
   462  	}
   463  	c.data[section][k] = value
   464  	return nil
   465  }
   466  
   467  // DIY returns the raw value by a given key.
   468  func (c *IniConfigContainer) DIY(key string) (v interface{}, err error) {
   469  	if v, ok := c.data[strings.ToLower(key)]; ok {
   470  		return v, nil
   471  	}
   472  	return v, errors.New("key not find")
   473  }
   474  
   475  // section.key or key
   476  func (c *IniConfigContainer) getdata(key string) string {
   477  	if len(key) == 0 {
   478  		return ""
   479  	}
   480  	c.RLock()
   481  	defer c.RUnlock()
   482  
   483  	var (
   484  		section, k string
   485  		sectionKey = strings.Split(strings.ToLower(key), "::")
   486  	)
   487  	if len(sectionKey) >= 2 {
   488  		section = sectionKey[0]
   489  		k = sectionKey[1]
   490  	} else {
   491  		section = defaultSection
   492  		k = sectionKey[0]
   493  	}
   494  	if v, ok := c.data[section]; ok {
   495  		if vv, ok := v[k]; ok {
   496  			return vv
   497  		}
   498  	}
   499  	return ""
   500  }
   501  
   502  func init() {
   503  	Register("ini", &IniConfig{})
   504  }