Merge tag 'upstream/1.28.2'
Nobuhiro Iwamatsu
6 years ago
4 | 4 | - 1.5.x |
5 | 5 | - 1.6.x |
6 | 6 | - 1.7.x |
7 | - 1.8.x | |
7 | 8 | - master |
8 | 9 | |
9 | 10 | script: |
10 | 11 | - go get golang.org/x/tools/cmd/cover |
11 | 12 | - go get github.com/smartystreets/goconvey |
12 | 13 | - go test -v -cover -race |
13 | ||
14 | notifications: | |
15 | email: | |
16 | - u@gogs.io |
82 | 82 | sec2, err := cfg.GetSection("SecTIOn") |
83 | 83 | |
84 | 84 | // key1 and key2 are the exactly same key object |
85 | key1, err := cfg.GetKey("Key") | |
86 | key2, err := cfg.GetKey("KeY") | |
85 | key1, err := sec1.GetKey("Key") | |
86 | key2, err := sec2.GetKey("KeY") | |
87 | 87 | ``` |
88 | 88 | |
89 | 89 | #### MySQL-like boolean key |
120 | 120 | 3. Words after section name (i.e words after `[some section name]`) |
121 | 121 | |
122 | 122 | If you want to save a value with `#` or `;`, please quote them with ``` ` ``` or ``` """ ```. |
123 | ||
124 | Alternatively, you can use following `LoadOptions` to completely ignore inline comments: | |
125 | ||
126 | ```go | |
127 | cfg, err := LoadSources(LoadOptions{IgnoreInlineComment: true}, "app.ini")) | |
128 | ``` | |
123 | 129 | |
124 | 130 | ### Working with sections |
125 | 131 |
75 | 75 | sec2, err := cfg.GetSection("SecTIOn") |
76 | 76 | |
77 | 77 | // key1 和 key2 指向同一个键对象 |
78 | key1, err := cfg.GetKey("Key") | |
79 | key2, err := cfg.GetKey("KeY") | |
78 | key1, err := sec1.GetKey("Key") | |
79 | key2, err := sec2.GetKey("KeY") | |
80 | 80 | ``` |
81 | 81 | |
82 | 82 | #### 类似 MySQL 配置中的布尔值键 |
113 | 113 | 3. 分区标签后的文字 (即 `[分区名]` 之后的内容) |
114 | 114 | |
115 | 115 | 如果你希望使用包含 `#` 或 `;` 的值,请使用 ``` ` ``` 或 ``` """ ``` 进行包覆。 |
116 | ||
117 | 除此之外,您还可以通过 `LoadOptions` 完全忽略行内注释: | |
118 | ||
119 | ```go | |
120 | cfg, err := LoadSources(LoadOptions{IgnoreInlineComment: true}, "app.ini")) | |
121 | ``` | |
116 | 122 | |
117 | 123 | ### 操作分区(Section) |
118 | 124 |
23 | 23 | "os" |
24 | 24 | "regexp" |
25 | 25 | "runtime" |
26 | "strconv" | |
27 | 26 | "strings" |
28 | 27 | "sync" |
29 | "time" | |
30 | 28 | ) |
31 | 29 | |
32 | 30 | const ( |
36 | 34 | |
37 | 35 | // Maximum allowed depth when recursively substituing variable names. |
38 | 36 | _DEPTH_VALUES = 99 |
39 | _VERSION = "1.27.0" | |
37 | _VERSION = "1.28.2" | |
40 | 38 | ) |
41 | 39 | |
42 | 40 | // Version returns current package version literal. |
59 | 57 | |
60 | 58 | // Explicitly write DEFAULT section header |
61 | 59 | DefaultHeader = false |
60 | ||
61 | // Indicate whether to put a line between sections | |
62 | PrettySection = true | |
62 | 63 | ) |
63 | 64 | |
64 | 65 | func init() { |
394 | 395 | return f.Reload() |
395 | 396 | } |
396 | 397 | |
397 | // WriteToIndent writes content into io.Writer with given indention. | |
398 | // If PrettyFormat has been set to be true, | |
399 | // it will align "=" sign with spaces under each section. | |
400 | func (f *File) WriteToIndent(w io.Writer, indent string) (n int64, err error) { | |
398 | func (f *File) writeToBuffer(indent string) (*bytes.Buffer, error) { | |
401 | 399 | equalSign := "=" |
402 | 400 | if PrettyFormat { |
403 | 401 | equalSign = " = " |
411 | 409 | if sec.Comment[0] != '#' && sec.Comment[0] != ';' { |
412 | 410 | sec.Comment = "; " + sec.Comment |
413 | 411 | } |
414 | if _, err = buf.WriteString(sec.Comment + LineBreak); err != nil { | |
415 | return 0, err | |
412 | if _, err := buf.WriteString(sec.Comment + LineBreak); err != nil { | |
413 | return nil, err | |
416 | 414 | } |
417 | 415 | } |
418 | 416 | |
419 | 417 | if i > 0 || DefaultHeader { |
420 | if _, err = buf.WriteString("[" + sname + "]" + LineBreak); err != nil { | |
421 | return 0, err | |
418 | if _, err := buf.WriteString("[" + sname + "]" + LineBreak); err != nil { | |
419 | return nil, err | |
422 | 420 | } |
423 | 421 | } else { |
424 | 422 | // Write nothing if default section is empty |
428 | 426 | } |
429 | 427 | |
430 | 428 | if sec.isRawSection { |
431 | if _, err = buf.WriteString(sec.rawBody); err != nil { | |
432 | return 0, err | |
429 | if _, err := buf.WriteString(sec.rawBody); err != nil { | |
430 | return nil, err | |
433 | 431 | } |
434 | 432 | continue |
435 | 433 | } |
465 | 463 | if key.Comment[0] != '#' && key.Comment[0] != ';' { |
466 | 464 | key.Comment = "; " + key.Comment |
467 | 465 | } |
468 | if _, err = buf.WriteString(key.Comment + LineBreak); err != nil { | |
469 | return 0, err | |
466 | if _, err := buf.WriteString(key.Comment + LineBreak); err != nil { | |
467 | return nil, err | |
470 | 468 | } |
471 | 469 | } |
472 | 470 | |
484 | 482 | } |
485 | 483 | |
486 | 484 | for _, val := range key.ValueWithShadows() { |
487 | if _, err = buf.WriteString(kname); err != nil { | |
488 | return 0, err | |
485 | if _, err := buf.WriteString(kname); err != nil { | |
486 | return nil, err | |
489 | 487 | } |
490 | 488 | |
491 | 489 | if key.isBooleanType { |
503 | 501 | // In case key value contains "\n", "`", "\"", "#" or ";" |
504 | 502 | if strings.ContainsAny(val, "\n`") { |
505 | 503 | val = `"""` + val + `"""` |
506 | } else if strings.ContainsAny(val, "#;") { | |
504 | } else if !f.options.IgnoreInlineComment && strings.ContainsAny(val, "#;") { | |
507 | 505 | val = "`" + val + "`" |
508 | 506 | } |
509 | if _, err = buf.WriteString(equalSign + val + LineBreak); err != nil { | |
510 | return 0, err | |
511 | } | |
512 | } | |
513 | } | |
514 | ||
515 | // Put a line between sections | |
516 | if _, err = buf.WriteString(LineBreak); err != nil { | |
517 | return 0, err | |
518 | } | |
519 | } | |
520 | ||
507 | if _, err := buf.WriteString(equalSign + val + LineBreak); err != nil { | |
508 | return nil, err | |
509 | } | |
510 | } | |
511 | } | |
512 | ||
513 | if PrettySection { | |
514 | // Put a line between sections | |
515 | if _, err := buf.WriteString(LineBreak); err != nil { | |
516 | return nil, err | |
517 | } | |
518 | } | |
519 | } | |
520 | ||
521 | return buf, nil | |
522 | } | |
523 | ||
524 | // WriteToIndent writes content into io.Writer with given indention. | |
525 | // If PrettyFormat has been set to be true, | |
526 | // it will align "=" sign with spaces under each section. | |
527 | func (f *File) WriteToIndent(w io.Writer, indent string) (int64, error) { | |
528 | buf, err := f.writeToBuffer(indent) | |
529 | if err != nil { | |
530 | return 0, err | |
531 | } | |
521 | 532 | return buf.WriteTo(w) |
522 | 533 | } |
523 | 534 | |
530 | 541 | func (f *File) SaveToIndent(filename, indent string) error { |
531 | 542 | // Note: Because we are truncating with os.Create, |
532 | 543 | // so it's safer to save to a temporary file location and rename afte done. |
533 | tmpPath := filename + "." + strconv.Itoa(time.Now().Nanosecond()) + ".tmp" | |
534 | defer os.Remove(tmpPath) | |
535 | ||
536 | fw, err := os.Create(tmpPath) | |
544 | buf, err := f.writeToBuffer(indent) | |
537 | 545 | if err != nil { |
538 | 546 | return err |
539 | 547 | } |
540 | 548 | |
541 | if _, err = f.WriteToIndent(fw, indent); err != nil { | |
542 | fw.Close() | |
543 | return err | |
544 | } | |
545 | fw.Close() | |
546 | ||
547 | // Remove old file and rename the new one. | |
548 | os.Remove(filename) | |
549 | return os.Rename(tmpPath, filename) | |
549 | return ioutil.WriteFile(filename, buf.Bytes(), 0666) | |
550 | 550 | } |
551 | 551 | |
552 | 552 | // SaveTo writes content to file system. |
214 | 214 | |
215 | 215 | So(cfg.Section("").Key("key1").String(), ShouldEqual, `value ;comment`) |
216 | 216 | So(cfg.Section("").Key("key2").String(), ShouldEqual, `value #comment2`) |
217 | ||
218 | var buf bytes.Buffer | |
219 | cfg.WriteTo(&buf) | |
220 | So(buf.String(), ShouldEqual, `key1 = value ;comment | |
221 | key2 = value #comment2 | |
222 | ||
223 | `) | |
217 | 224 | }) |
218 | 225 | |
219 | 226 | Convey("Load with boolean type keys", t, func() { |
188 | 188 | // are quotes \" or \'. |
189 | 189 | // It returns false if any other parts also contain same kind of quotes. |
190 | 190 | func hasSurroundedQuote(in string, quote byte) bool { |
191 | return len(in) > 2 && in[0] == quote && in[len(in)-1] == quote && | |
191 | return len(in) >= 2 && in[0] == quote && in[len(in)-1] == quote && | |
192 | 192 | strings.IndexByte(in[1:], quote) == len(in)-2 |
193 | 193 | } |
194 | 194 |
77 | 77 | var reflectTime = reflect.TypeOf(time.Now()).Kind() |
78 | 78 | |
79 | 79 | // setSliceWithProperType sets proper values to slice based on its type. |
80 | func setSliceWithProperType(key *Key, field reflect.Value, delim string, allowShadow bool) error { | |
80 | func setSliceWithProperType(key *Key, field reflect.Value, delim string, allowShadow, isStrict bool) error { | |
81 | 81 | var strs []string |
82 | 82 | if allowShadow { |
83 | 83 | strs = key.StringsWithShadows(delim) |
91 | 91 | } |
92 | 92 | |
93 | 93 | var vals interface{} |
94 | var err error | |
94 | 95 | |
95 | 96 | sliceOf := field.Type().Elem().Kind() |
96 | 97 | switch sliceOf { |
97 | 98 | case reflect.String: |
98 | 99 | vals = strs |
99 | 100 | case reflect.Int: |
100 | vals, _ = key.parseInts(strs, true, false) | |
101 | vals, err = key.parseInts(strs, true, false) | |
101 | 102 | case reflect.Int64: |
102 | vals, _ = key.parseInt64s(strs, true, false) | |
103 | vals, err = key.parseInt64s(strs, true, false) | |
103 | 104 | case reflect.Uint: |
104 | vals, _ = key.parseUints(strs, true, false) | |
105 | vals, err = key.parseUints(strs, true, false) | |
105 | 106 | case reflect.Uint64: |
106 | vals, _ = key.parseUint64s(strs, true, false) | |
107 | vals, err = key.parseUint64s(strs, true, false) | |
107 | 108 | case reflect.Float64: |
108 | vals, _ = key.parseFloat64s(strs, true, false) | |
109 | vals, err = key.parseFloat64s(strs, true, false) | |
109 | 110 | case reflectTime: |
110 | vals, _ = key.parseTimesFormat(time.RFC3339, strs, true, false) | |
111 | vals, err = key.parseTimesFormat(time.RFC3339, strs, true, false) | |
111 | 112 | default: |
112 | 113 | return fmt.Errorf("unsupported type '[]%s'", sliceOf) |
114 | } | |
115 | if isStrict { | |
116 | return err | |
113 | 117 | } |
114 | 118 | |
115 | 119 | slice := reflect.MakeSlice(field.Type(), numVals, numVals) |
135 | 139 | return nil |
136 | 140 | } |
137 | 141 | |
142 | func wrapStrictError(err error, isStrict bool) error { | |
143 | if isStrict { | |
144 | return err | |
145 | } | |
146 | return nil | |
147 | } | |
148 | ||
138 | 149 | // setWithProperType sets proper value to field based on its type, |
139 | 150 | // but it does not return error for failing parsing, |
140 | 151 | // because we want to use default value that is already assigned to strcut. |
141 | func setWithProperType(t reflect.Type, key *Key, field reflect.Value, delim string, allowShadow bool) error { | |
152 | func setWithProperType(t reflect.Type, key *Key, field reflect.Value, delim string, allowShadow, isStrict bool) error { | |
142 | 153 | switch t.Kind() { |
143 | 154 | case reflect.String: |
144 | 155 | if len(key.String()) == 0 { |
148 | 159 | case reflect.Bool: |
149 | 160 | boolVal, err := key.Bool() |
150 | 161 | if err != nil { |
151 | return nil | |
162 | return wrapStrictError(err, isStrict) | |
152 | 163 | } |
153 | 164 | field.SetBool(boolVal) |
154 | 165 | case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: |
160 | 171 | } |
161 | 172 | |
162 | 173 | intVal, err := key.Int64() |
163 | if err != nil || intVal == 0 { | |
164 | return nil | |
174 | if err != nil { | |
175 | return wrapStrictError(err, isStrict) | |
165 | 176 | } |
166 | 177 | field.SetInt(intVal) |
167 | 178 | // byte is an alias for uint8, so supporting uint8 breaks support for byte |
175 | 186 | |
176 | 187 | uintVal, err := key.Uint64() |
177 | 188 | if err != nil { |
178 | return nil | |
189 | return wrapStrictError(err, isStrict) | |
179 | 190 | } |
180 | 191 | field.SetUint(uintVal) |
181 | 192 | |
182 | 193 | case reflect.Float32, reflect.Float64: |
183 | 194 | floatVal, err := key.Float64() |
184 | 195 | if err != nil { |
185 | return nil | |
196 | return wrapStrictError(err, isStrict) | |
186 | 197 | } |
187 | 198 | field.SetFloat(floatVal) |
188 | 199 | case reflectTime: |
189 | 200 | timeVal, err := key.Time() |
190 | 201 | if err != nil { |
191 | return nil | |
202 | return wrapStrictError(err, isStrict) | |
192 | 203 | } |
193 | 204 | field.Set(reflect.ValueOf(timeVal)) |
194 | 205 | case reflect.Slice: |
195 | return setSliceWithProperType(key, field, delim, allowShadow) | |
206 | return setSliceWithProperType(key, field, delim, allowShadow, isStrict) | |
196 | 207 | default: |
197 | 208 | return fmt.Errorf("unsupported type '%s'", t) |
198 | 209 | } |
211 | 222 | return rawName, omitEmpty, allowShadow |
212 | 223 | } |
213 | 224 | |
214 | func (s *Section) mapTo(val reflect.Value) error { | |
225 | func (s *Section) mapTo(val reflect.Value, isStrict bool) error { | |
215 | 226 | if val.Kind() == reflect.Ptr { |
216 | 227 | val = val.Elem() |
217 | 228 | } |
240 | 251 | |
241 | 252 | if isAnonymous || isStruct { |
242 | 253 | if sec, err := s.f.GetSection(fieldName); err == nil { |
243 | if err = sec.mapTo(field); err != nil { | |
254 | if err = sec.mapTo(field, isStrict); err != nil { | |
244 | 255 | return fmt.Errorf("error mapping field(%s): %v", fieldName, err) |
245 | 256 | } |
246 | 257 | continue |
249 | 260 | |
250 | 261 | if key, err := s.GetKey(fieldName); err == nil { |
251 | 262 | delim := parseDelim(tpField.Tag.Get("delim")) |
252 | if err = setWithProperType(tpField.Type, key, field, delim, allowShadow); err != nil { | |
263 | if err = setWithProperType(tpField.Type, key, field, delim, allowShadow, isStrict); err != nil { | |
253 | 264 | return fmt.Errorf("error mapping field(%s): %v", fieldName, err) |
254 | 265 | } |
255 | 266 | } |
268 | 279 | return errors.New("cannot map to non-pointer struct") |
269 | 280 | } |
270 | 281 | |
271 | return s.mapTo(val) | |
282 | return s.mapTo(val, false) | |
283 | } | |
284 | ||
285 | // MapTo maps section to given struct in strict mode, | |
286 | // which returns all possible error including value parsing error. | |
287 | func (s *Section) StrictMapTo(v interface{}) error { | |
288 | typ := reflect.TypeOf(v) | |
289 | val := reflect.ValueOf(v) | |
290 | if typ.Kind() == reflect.Ptr { | |
291 | typ = typ.Elem() | |
292 | val = val.Elem() | |
293 | } else { | |
294 | return errors.New("cannot map to non-pointer struct") | |
295 | } | |
296 | ||
297 | return s.mapTo(val, true) | |
272 | 298 | } |
273 | 299 | |
274 | 300 | // MapTo maps file to given struct. |
275 | 301 | func (f *File) MapTo(v interface{}) error { |
276 | 302 | return f.Section("").MapTo(v) |
303 | } | |
304 | ||
305 | // MapTo maps file to given struct in strict mode, | |
306 | // which returns all possible error including value parsing error. | |
307 | func (f *File) StrictMapTo(v interface{}) error { | |
308 | return f.Section("").StrictMapTo(v) | |
277 | 309 | } |
278 | 310 | |
279 | 311 | // MapTo maps data sources to given struct with name mapper. |
286 | 318 | return cfg.MapTo(v) |
287 | 319 | } |
288 | 320 | |
321 | // StrictMapToWithMapper maps data sources to given struct with name mapper in strict mode, | |
322 | // which returns all possible error including value parsing error. | |
323 | func StrictMapToWithMapper(v interface{}, mapper NameMapper, source interface{}, others ...interface{}) error { | |
324 | cfg, err := Load(source, others...) | |
325 | if err != nil { | |
326 | return err | |
327 | } | |
328 | cfg.NameMapper = mapper | |
329 | return cfg.StrictMapTo(v) | |
330 | } | |
331 | ||
289 | 332 | // MapTo maps data sources to given struct. |
290 | 333 | func MapTo(v, source interface{}, others ...interface{}) error { |
291 | 334 | return MapToWithMapper(v, nil, source, others...) |
335 | } | |
336 | ||
337 | // StrictMapTo maps data sources to given struct in strict mode, | |
338 | // which returns all possible error including value parsing error. | |
339 | func StrictMapTo(v, source interface{}, others ...interface{}) error { | |
340 | return StrictMapToWithMapper(v, nil, source, others...) | |
292 | 341 | } |
293 | 342 | |
294 | 343 | // reflectSliceWithProperType does the opposite thing as setSliceWithProperType. |
358 | 407 | return v.Uint() == 0 |
359 | 408 | case reflect.Float32, reflect.Float64: |
360 | 409 | return v.Float() == 0 |
361 | case reflectTime: | |
362 | return v.Interface().(time.Time).IsZero() | |
363 | 410 | case reflect.Interface, reflect.Ptr: |
364 | 411 | return v.IsNil() |
412 | case reflectTime: | |
413 | t, ok := v.Interface().(time.Time) | |
414 | return ok && t.IsZero() | |
365 | 415 | } |
366 | 416 | return false |
367 | 417 | } |
226 | 226 | So(dv.Born.String(), ShouldEqual, t.String()) |
227 | 227 | So(strings.Join(dv.Cities, ","), ShouldEqual, "HangZhou,Boston") |
228 | 228 | }) |
229 | }) | |
230 | ||
231 | Convey("Map to struct in strict mode", t, func() { | |
232 | cfg, err := Load([]byte(` | |
233 | name=bruce | |
234 | age=a30`)) | |
235 | So(err, ShouldBeNil) | |
236 | ||
237 | type Strict struct { | |
238 | Name string `ini:"name"` | |
239 | Age int `ini:"age"` | |
240 | } | |
241 | s := new(Strict) | |
242 | ||
243 | So(cfg.Section("").StrictMapTo(s), ShouldNotBeNil) | |
229 | 244 | }) |
230 | 245 | |
231 | 246 | Convey("Reflect from struct", t, func() { |