github.com/coyove/common@v0.0.0-20240403014525-f70e643f9de8/config/conf.go (about)

     1  package config
     2  
     3  import (
     4  	"bytes"
     5  	"fmt"
     6  	"regexp"
     7  	"strconv"
     8  	"strings"
     9  )
    10  
    11  var splitLines = regexp.MustCompile(`\r?\n[\s\t]*`)
    12  
    13  type ConfError struct {
    14  	line  int
    15  	index int
    16  	text  string
    17  }
    18  
    19  func (e *ConfError) Error() string {
    20  	return fmt.Sprintf("unexpected %s at line %d:%d", e.text, e.line, e.index)
    21  }
    22  
    23  type conf_t map[string]map[string]interface{}
    24  
    25  func (c *conf_t) getSection(section string) map[string]interface{} {
    26  	if sec, ok := (*c)[section]; ok {
    27  		return sec
    28  	} else {
    29  		return (*c)["default"] // return a dummy so Get* functions won't panic
    30  	}
    31  }
    32  
    33  func (c *conf_t) HasSection(section string) bool {
    34  	_, ok := (*c)[section]
    35  	return ok
    36  }
    37  
    38  func (c *conf_t) Iterate(section string, callback func(key string)) {
    39  	for k := range c.getSection(section) {
    40  		callback(k)
    41  	}
    42  }
    43  
    44  func (c *conf_t) GetString(section, key string, defaultvalue string) string {
    45  	if s, ok := c.getSection(section)[key].(string); ok {
    46  		return s
    47  	}
    48  	return defaultvalue
    49  }
    50  
    51  func (c *conf_t) GetInt(section, key string, defaultvalue int64) int64 {
    52  	if s, ok := c.getSection(section)[key].(float64); ok {
    53  		return int64(s)
    54  	}
    55  	return defaultvalue
    56  }
    57  
    58  func (c *conf_t) GetFloat(section, key string, defaultvalue float64) float64 {
    59  	if s, ok := c.getSection(section)[key].(float64); ok {
    60  		return s
    61  	}
    62  	return defaultvalue
    63  }
    64  
    65  func (c *conf_t) GetBool(section, key string, defaultvalue bool) bool {
    66  	if s, ok := c.getSection(section)[key].(bool); ok {
    67  		return s
    68  	}
    69  	return defaultvalue
    70  }
    71  
    72  func (c *conf_t) GetArray(section, key string) []interface{} {
    73  	if s, ok := c.getSection(section)[key].([]interface{}); ok {
    74  		return s
    75  	}
    76  	return nil
    77  }
    78  
    79  func ParseConf(str string) (*conf_t, error) {
    80  	key, value, value2 := &bytes.Buffer{}, &bytes.Buffer{}, &bytes.Buffer{}
    81  	config := make(conf_t)
    82  	curSection := make(map[string]interface{})
    83  	config["default"] = curSection
    84  
    85  	for ln, line := range splitLines.Split(str, -1) {
    86  		key.Reset()
    87  		value.Reset()
    88  
    89  		idx, p, quote := 0, key, byte(0)
    90  
    91  	L:
    92  		for idx < len(line) {
    93  			c := line[idx]
    94  
    95  			switch c {
    96  			case '[':
    97  				if quote == 0 {
    98  					if e := strings.Index(line, "]"); e > 0 {
    99  						sec := line[1:e]
   100  						curSection = config[sec]
   101  						if curSection == nil {
   102  							curSection = make(map[string]interface{})
   103  							config[sec] = curSection
   104  						}
   105  						break L
   106  					} else {
   107  						return nil, &ConfError{ln, idx, string(c)}
   108  					}
   109  				} else {
   110  					p.WriteByte(c)
   111  				}
   112  			case ' ', '\t':
   113  				if quote != 0 {
   114  					p.WriteByte(c)
   115  				}
   116  			case '\'', '"':
   117  				if idx > 0 && line[idx-1] == '\\' {
   118  					// escape
   119  				} else if quote == 0 {
   120  					quote = c
   121  				} else if quote == c {
   122  					quote = 0
   123  				} else {
   124  					return nil, &ConfError{ln, idx, string(c)}
   125  				}
   126  
   127  				p.WriteByte(c)
   128  			case '#':
   129  				if quote == 0 {
   130  					break L
   131  				} else {
   132  					p.WriteByte(c)
   133  				}
   134  			case '=':
   135  				if quote != 0 {
   136  					p.WriteByte(c)
   137  				} else if p != value {
   138  					p = value
   139  				} else {
   140  					return nil, &ConfError{ln, idx, "="}
   141  				}
   142  			default:
   143  				p.WriteByte(c)
   144  			}
   145  
   146  			idx++
   147  		}
   148  
   149  		if quote != 0 {
   150  			return nil, &ConfError{ln, idx, string(quote)}
   151  		}
   152  
   153  		k := key.String()
   154  		if curSection == nil || k == "" {
   155  			continue
   156  		}
   157  
   158  		value2.Reset()
   159  		v, idx := value.Bytes(), 0
   160  
   161  		for idx < len(v) {
   162  			if v[idx] == '\\' {
   163  				if idx == len(v)-1 {
   164  					return nil, &ConfError{ln, idx, value.String()}
   165  				}
   166  
   167  				switch v[idx+1] {
   168  				case 'n':
   169  					value2.WriteByte('\n')
   170  				case 'r':
   171  					value2.WriteByte('\r')
   172  				case 't':
   173  					value2.WriteByte('\t')
   174  				default:
   175  					value2.WriteByte(v[idx+1])
   176  				}
   177  				idx += 2
   178  			} else {
   179  				value2.WriteByte(v[idx])
   180  				idx++
   181  			}
   182  		}
   183  
   184  		v2 := value2.String()
   185  
   186  		_append := func(v interface{}) {
   187  			if ov, existed := curSection[k]; existed {
   188  				if arr, ok := ov.([]interface{}); ok {
   189  					arr = append(arr, v)
   190  				} else {
   191  					curSection[k] = []interface{}{ov, v}
   192  				}
   193  			} else {
   194  				curSection[k] = v
   195  			}
   196  		}
   197  
   198  		switch v2 {
   199  		case "on", "yes", "true":
   200  			_append(true)
   201  		case "off", "no", "false":
   202  			_append(false)
   203  		default:
   204  			if len(v2) >= 2 && (v2[0] == '\'' || v2[0] == '"') {
   205  				v2 = v2[1 : len(v2)-1]
   206  			}
   207  
   208  			if num, err := strconv.ParseFloat(v2, 64); err == nil {
   209  				_append(num)
   210  			} else {
   211  				_append(v2)
   212  			}
   213  		}
   214  
   215  	}
   216  
   217  	return &config, nil
   218  }