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 }