Codebase list golang-github-go-ini-ini / 62d693e
Merge tag 'upstream/1.28.2' Nobuhiro Iwamatsu 6 years ago
8 changed file(s) with 154 addition(s) and 73 deletion(s). Raw diff Collapse all Expand all
44 - 1.5.x
55 - 1.6.x
66 - 1.7.x
7 - 1.8.x
78 - master
89
910 script:
1011 - go get golang.org/x/tools/cmd/cover
1112 - go get github.com/smartystreets/goconvey
1213 - go test -v -cover -race
13
14 notifications:
15 email:
16 - u@gogs.io
8282 sec2, err := cfg.GetSection("SecTIOn")
8383
8484 // 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")
8787 ```
8888
8989 #### MySQL-like boolean key
120120 3. Words after section name (i.e words after `[some section name]`)
121121
122122 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 ```
123129
124130 ### Working with sections
125131
7575 sec2, err := cfg.GetSection("SecTIOn")
7676
7777 // 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")
8080 ```
8181
8282 #### 类似 MySQL 配置中的布尔值键
113113 3. 分区标签后的文字 (即 `[分区名]` 之后的内容)
114114
115115 如果你希望使用包含 `#` 或 `;` 的值,请使用 ``` ` ``` 或 ``` """ ``` 进行包覆。
116
117 除此之外,您还可以通过 `LoadOptions` 完全忽略行内注释:
118
119 ```go
120 cfg, err := LoadSources(LoadOptions{IgnoreInlineComment: true}, "app.ini"))
121 ```
116122
117123 ### 操作分区(Section)
118124
2323 "os"
2424 "regexp"
2525 "runtime"
26 "strconv"
2726 "strings"
2827 "sync"
29 "time"
3028 )
3129
3230 const (
3634
3735 // Maximum allowed depth when recursively substituing variable names.
3836 _DEPTH_VALUES = 99
39 _VERSION = "1.27.0"
37 _VERSION = "1.28.2"
4038 )
4139
4240 // Version returns current package version literal.
5957
6058 // Explicitly write DEFAULT section header
6159 DefaultHeader = false
60
61 // Indicate whether to put a line between sections
62 PrettySection = true
6263 )
6364
6465 func init() {
394395 return f.Reload()
395396 }
396397
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) {
401399 equalSign := "="
402400 if PrettyFormat {
403401 equalSign = " = "
411409 if sec.Comment[0] != '#' && sec.Comment[0] != ';' {
412410 sec.Comment = "; " + sec.Comment
413411 }
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
416414 }
417415 }
418416
419417 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
422420 }
423421 } else {
424422 // Write nothing if default section is empty
428426 }
429427
430428 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
433431 }
434432 continue
435433 }
465463 if key.Comment[0] != '#' && key.Comment[0] != ';' {
466464 key.Comment = "; " + key.Comment
467465 }
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
470468 }
471469 }
472470
484482 }
485483
486484 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
489487 }
490488
491489 if key.isBooleanType {
503501 // In case key value contains "\n", "`", "\"", "#" or ";"
504502 if strings.ContainsAny(val, "\n`") {
505503 val = `"""` + val + `"""`
506 } else if strings.ContainsAny(val, "#;") {
504 } else if !f.options.IgnoreInlineComment && strings.ContainsAny(val, "#;") {
507505 val = "`" + val + "`"
508506 }
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 }
521532 return buf.WriteTo(w)
522533 }
523534
530541 func (f *File) SaveToIndent(filename, indent string) error {
531542 // Note: Because we are truncating with os.Create,
532543 // 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)
537545 if err != nil {
538546 return err
539547 }
540548
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)
550550 }
551551
552552 // SaveTo writes content to file system.
214214
215215 So(cfg.Section("").Key("key1").String(), ShouldEqual, `value ;comment`)
216216 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 `)
217224 })
218225
219226 Convey("Load with boolean type keys", t, func() {
188188 // are quotes \" or \'.
189189 // It returns false if any other parts also contain same kind of quotes.
190190 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 &&
192192 strings.IndexByte(in[1:], quote) == len(in)-2
193193 }
194194
7777 var reflectTime = reflect.TypeOf(time.Now()).Kind()
7878
7979 // 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 {
8181 var strs []string
8282 if allowShadow {
8383 strs = key.StringsWithShadows(delim)
9191 }
9292
9393 var vals interface{}
94 var err error
9495
9596 sliceOf := field.Type().Elem().Kind()
9697 switch sliceOf {
9798 case reflect.String:
9899 vals = strs
99100 case reflect.Int:
100 vals, _ = key.parseInts(strs, true, false)
101 vals, err = key.parseInts(strs, true, false)
101102 case reflect.Int64:
102 vals, _ = key.parseInt64s(strs, true, false)
103 vals, err = key.parseInt64s(strs, true, false)
103104 case reflect.Uint:
104 vals, _ = key.parseUints(strs, true, false)
105 vals, err = key.parseUints(strs, true, false)
105106 case reflect.Uint64:
106 vals, _ = key.parseUint64s(strs, true, false)
107 vals, err = key.parseUint64s(strs, true, false)
107108 case reflect.Float64:
108 vals, _ = key.parseFloat64s(strs, true, false)
109 vals, err = key.parseFloat64s(strs, true, false)
109110 case reflectTime:
110 vals, _ = key.parseTimesFormat(time.RFC3339, strs, true, false)
111 vals, err = key.parseTimesFormat(time.RFC3339, strs, true, false)
111112 default:
112113 return fmt.Errorf("unsupported type '[]%s'", sliceOf)
114 }
115 if isStrict {
116 return err
113117 }
114118
115119 slice := reflect.MakeSlice(field.Type(), numVals, numVals)
135139 return nil
136140 }
137141
142 func wrapStrictError(err error, isStrict bool) error {
143 if isStrict {
144 return err
145 }
146 return nil
147 }
148
138149 // setWithProperType sets proper value to field based on its type,
139150 // but it does not return error for failing parsing,
140151 // 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 {
142153 switch t.Kind() {
143154 case reflect.String:
144155 if len(key.String()) == 0 {
148159 case reflect.Bool:
149160 boolVal, err := key.Bool()
150161 if err != nil {
151 return nil
162 return wrapStrictError(err, isStrict)
152163 }
153164 field.SetBool(boolVal)
154165 case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
160171 }
161172
162173 intVal, err := key.Int64()
163 if err != nil || intVal == 0 {
164 return nil
174 if err != nil {
175 return wrapStrictError(err, isStrict)
165176 }
166177 field.SetInt(intVal)
167178 // byte is an alias for uint8, so supporting uint8 breaks support for byte
175186
176187 uintVal, err := key.Uint64()
177188 if err != nil {
178 return nil
189 return wrapStrictError(err, isStrict)
179190 }
180191 field.SetUint(uintVal)
181192
182193 case reflect.Float32, reflect.Float64:
183194 floatVal, err := key.Float64()
184195 if err != nil {
185 return nil
196 return wrapStrictError(err, isStrict)
186197 }
187198 field.SetFloat(floatVal)
188199 case reflectTime:
189200 timeVal, err := key.Time()
190201 if err != nil {
191 return nil
202 return wrapStrictError(err, isStrict)
192203 }
193204 field.Set(reflect.ValueOf(timeVal))
194205 case reflect.Slice:
195 return setSliceWithProperType(key, field, delim, allowShadow)
206 return setSliceWithProperType(key, field, delim, allowShadow, isStrict)
196207 default:
197208 return fmt.Errorf("unsupported type '%s'", t)
198209 }
211222 return rawName, omitEmpty, allowShadow
212223 }
213224
214 func (s *Section) mapTo(val reflect.Value) error {
225 func (s *Section) mapTo(val reflect.Value, isStrict bool) error {
215226 if val.Kind() == reflect.Ptr {
216227 val = val.Elem()
217228 }
240251
241252 if isAnonymous || isStruct {
242253 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 {
244255 return fmt.Errorf("error mapping field(%s): %v", fieldName, err)
245256 }
246257 continue
249260
250261 if key, err := s.GetKey(fieldName); err == nil {
251262 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 {
253264 return fmt.Errorf("error mapping field(%s): %v", fieldName, err)
254265 }
255266 }
268279 return errors.New("cannot map to non-pointer struct")
269280 }
270281
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)
272298 }
273299
274300 // MapTo maps file to given struct.
275301 func (f *File) MapTo(v interface{}) error {
276302 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)
277309 }
278310
279311 // MapTo maps data sources to given struct with name mapper.
286318 return cfg.MapTo(v)
287319 }
288320
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
289332 // MapTo maps data sources to given struct.
290333 func MapTo(v, source interface{}, others ...interface{}) error {
291334 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...)
292341 }
293342
294343 // reflectSliceWithProperType does the opposite thing as setSliceWithProperType.
358407 return v.Uint() == 0
359408 case reflect.Float32, reflect.Float64:
360409 return v.Float() == 0
361 case reflectTime:
362 return v.Interface().(time.Time).IsZero()
363410 case reflect.Interface, reflect.Ptr:
364411 return v.IsNil()
412 case reflectTime:
413 t, ok := v.Interface().(time.Time)
414 return ok && t.IsZero()
365415 }
366416 return false
367417 }
226226 So(dv.Born.String(), ShouldEqual, t.String())
227227 So(strings.Join(dv.Cities, ","), ShouldEqual, "HangZhou,Boston")
228228 })
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)
229244 })
230245
231246 Convey("Reflect from struct", t, func() {