Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 2 additions & 3 deletions data_source.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@ import (
"bytes"
"fmt"
"io"
"io/ioutil"
"os"
)

Expand Down Expand Up @@ -48,7 +47,7 @@ type sourceData struct {
}

func (s *sourceData) ReadCloser() (io.ReadCloser, error) {
return ioutil.NopCloser(bytes.NewReader(s.data)), nil
return io.NopCloser(bytes.NewReader(s.data)), nil
}

// sourceReadCloser represents an input stream with Close method.
Expand All @@ -69,7 +68,7 @@ func parseDataSource(source interface{}) (dataSource, error) {
case io.ReadCloser:
return &sourceReadCloser{s}, nil
case io.Reader:
return &sourceReadCloser{ioutil.NopCloser(s)}, nil
return &sourceReadCloser{io.NopCloser(s)}, nil
default:
return nil, fmt.Errorf("error parsing data source: unknown type %q", s)
}
Expand Down
3 changes: 1 addition & 2 deletions file.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@ import (
"errors"
"fmt"
"io"
"io/ioutil"
"os"
"strings"
"sync"
Expand Down Expand Up @@ -532,7 +531,7 @@ func (f *File) SaveToIndent(filename, indent string) error {
return err
}

return ioutil.WriteFile(filename, buf.Bytes(), 0666)
return os.WriteFile(filename, buf.Bytes(), 0666)
}

// SaveTo writes content to file system.
Expand Down
6 changes: 3 additions & 3 deletions file_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ package ini

import (
"bytes"
"io/ioutil"
"os"
"runtime"
"sort"
"testing"
Expand Down Expand Up @@ -421,10 +421,10 @@ func TestFile_WriteTo(t *testing.T) {

golden := "testdata/TestFile_WriteTo.golden"
if *update {
require.NoError(t, ioutil.WriteFile(golden, buf.Bytes(), 0644))
require.NoError(t, os.WriteFile(golden, buf.Bytes(), 0644))
}

expected, err := ioutil.ReadFile(golden)
expected, err := os.ReadFile(golden)
require.NoError(t, err)
assert.Equal(t, string(expected), buf.String())
})
Expand Down
4 changes: 2 additions & 2 deletions ini_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ package ini
import (
"bytes"
"flag"
"io/ioutil"
"io"
"path/filepath"
"runtime"
"testing"
Expand Down Expand Up @@ -58,7 +58,7 @@ func TestLoad(t *testing.T) {
"testdata/minimal.ini",
[]byte("NAME = ini\nIMPORT_PATH = gopkg.in/%(NAME)s.%(VERSION)s"),
bytes.NewReader([]byte(`VERSION = v1`)),
ioutil.NopCloser(bytes.NewReader([]byte("[author]\nNAME = Unknwon"))),
io.NopCloser(bytes.NewReader([]byte("[author]\nNAME = Unknwon"))),
)
require.NoError(t, err)
require.NotNil(t, f)
Expand Down
2 changes: 1 addition & 1 deletion key.go
Original file line number Diff line number Diff line change
Expand Up @@ -429,7 +429,7 @@ func (k *Key) InUint64(defaultVal uint64, candidates []uint64) uint64 {
func (k *Key) InTimeFormat(format string, defaultVal time.Time, candidates []time.Time) time.Time {
val := k.MustTimeFormat(format)
for _, cand := range candidates {
if val == cand {
if val.Equal(cand) {
return val
}
}
Expand Down
5 changes: 3 additions & 2 deletions parser.go
Original file line number Diff line number Diff line change
Expand Up @@ -130,13 +130,14 @@ func readKeyName(delimiters string, in []byte) (string, int, error) {

// Check if key name surrounded by quotes.
var keyQuote string
if line[0] == '"' {
switch line[0] {
case '"':
if len(line) > 6 && line[0:3] == `"""` {
keyQuote = `"""`
} else {
keyQuote = `"`
}
} else if line[0] == '`' {
case '`':
keyQuote = "`"
}

Expand Down
126 changes: 80 additions & 46 deletions struct.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import (
"errors"
"fmt"
"reflect"
"strconv"
"strings"
"time"
"unicode"
Expand Down Expand Up @@ -77,8 +78,15 @@ func parseDelim(actual string) string {

var reflectTime = reflect.TypeOf(time.Now()).Kind()

type typeStatus struct {
val reflect.Value
isPtr bool
addInvalid bool
returnOnInvalid bool
}

// setSliceWithProperType sets proper values to slice based on its type.
func setSliceWithProperType(key *Key, field reflect.Value, delim string, allowShadow, isStrict bool) error {
func setSliceWithProperType(key *Key, field reflect.Value, delim string, allowShadow, isStrict bool) (err error) {
var strs []string
if allowShadow {
strs = key.StringsWithShadows(delim)
Expand All @@ -88,57 +96,81 @@ func setSliceWithProperType(key *Key, field reflect.Value, delim string, allowSh

numVals := len(strs)
if numVals == 0 {
return nil
return
}

var vals interface{}
var err error
status := typeStatus{addInvalid: true}
elem := field.Type().Elem()
sliceType := elem.Kind()
switch sliceType {
case reflect.String:
field.Set(reflect.ValueOf(strs))
return
case reflect.Ptr:
sliceType = elem.Elem().Kind()
status.isPtr = true
}

sliceOf := field.Type().Elem().Kind()
switch sliceOf {
var setter func(str string) (reflect.Value, error)
switch sliceType {
case reflect.String:
vals = strs
setter = func(str string) (reflect.Value, error) {
return reflect.ValueOf(&str), nil
}
case reflect.Int:
vals, err = key.parseInts(strs, true, false)
setter = func(str string) (reflect.Value, error) {
parsed, err := strconv.ParseInt(str, 0, 64)
val := int(parsed)
return reflect.ValueOf(&val), err
}
case reflect.Int64:
vals, err = key.parseInt64s(strs, true, false)
setter = func(str string) (reflect.Value, error) {
val, err := strconv.ParseInt(str, 0, 64)
return reflect.ValueOf(&val), err
}
case reflect.Uint:
vals, err = key.parseUints(strs, true, false)
setter = func(str string) (reflect.Value, error) {
parsed, err := strconv.ParseUint(str, 0, 64)
val := uint(parsed)
return reflect.ValueOf(&val), err
}
case reflect.Uint64:
vals, err = key.parseUint64s(strs, true, false)
setter = func(str string) (reflect.Value, error) {
val, err := strconv.ParseUint(str, 0, 64)
return reflect.ValueOf(&val), err
}
case reflect.Float64:
vals, err = key.parseFloat64s(strs, true, false)
setter = func(str string) (reflect.Value, error) {
val, err := strconv.ParseFloat(str, 64)
return reflect.ValueOf(&val), err
}
case reflect.Bool:
vals, err = key.parseBools(strs, true, false)
setter = func(str string) (reflect.Value, error) {
val, err := parseBool(str)
return reflect.ValueOf(&val), err
}
case reflectTime:
vals, err = key.parseTimesFormat(time.RFC3339, strs, true, false)
setter = func(str string) (reflect.Value, error) {
val, err := time.Parse(time.RFC3339, str)
return reflect.ValueOf(&val), err
}
default:
return fmt.Errorf("unsupported type '[]%s'", sliceOf)
}
if err != nil && isStrict {
return err
return fmt.Errorf("unsupported type '[]%s'", sliceType)
}

slice := reflect.MakeSlice(field.Type(), numVals, numVals)
slice := reflect.MakeSlice(field.Type(), 0, numVals)
for i := 0; i < numVals; i++ {
switch sliceOf {
case reflect.String:
slice.Index(i).Set(reflect.ValueOf(vals.([]string)[i]))
case reflect.Int:
slice.Index(i).Set(reflect.ValueOf(vals.([]int)[i]))
case reflect.Int64:
slice.Index(i).Set(reflect.ValueOf(vals.([]int64)[i]))
case reflect.Uint:
slice.Index(i).Set(reflect.ValueOf(vals.([]uint)[i]))
case reflect.Uint64:
slice.Index(i).Set(reflect.ValueOf(vals.([]uint64)[i]))
case reflect.Float64:
slice.Index(i).Set(reflect.ValueOf(vals.([]float64)[i]))
case reflect.Bool:
slice.Index(i).Set(reflect.ValueOf(vals.([]bool)[i]))
case reflectTime:
slice.Index(i).Set(reflect.ValueOf(vals.([]time.Time)[i]))
if status.val, err = setter(strs[i]); err != nil {
if status.returnOnInvalid {
return
} else if !status.addInvalid {
continue
}
}
if !status.isPtr {
status.val = status.val.Elem()
}
slice = reflect.Append(slice, status.val)
}
field.Set(slice)
return nil
Expand Down Expand Up @@ -278,10 +310,15 @@ func parseTagOptions(tag string) (rawName string, omitEmpty bool, allowShadow bo
// mapToField maps the given value to the matching field of the given section.
// The sectionIndex is the index (if non unique sections are enabled) to which the value should be added.
func (s *Section) mapToField(val reflect.Value, isStrict bool, sectionIndex int, sectionName string) error {
typ := val.Type()
// Early normalization of structs
if val.Kind() == reflect.Ptr {
typ = val.Type().Elem()
if val.IsNil() {
val.Set(reflect.New(typ))
}
val = val.Elem()
}
typ := val.Type()

for i := 0; i < typ.NumField(); i++ {
field := val.Field(i)
Expand Down Expand Up @@ -324,11 +361,6 @@ func (s *Section) mapToField(val reflect.Value, isStrict bool, sectionIndex int,
if len(secs) <= sectionIndex {
return fmt.Errorf("there are not enough sections (%d <= %d) for the field %q", len(secs), sectionIndex, fieldName)
}
// Only set the field to non-nil struct value if we have a section for it.
// Otherwise, we end up with a non-nil struct ptr even though there is no data.
if isStructPtr && field.IsNil() {
field.Set(reflect.New(tpField.Type.Elem()))
}
if err = secs[sectionIndex].mapToField(field, isStrict, sectionIndex, fieldName); err != nil {
return fmt.Errorf("map to field %q: %v", fieldName, err)
}
Expand Down Expand Up @@ -367,12 +399,11 @@ func (s *Section) mapToSlice(secName string, val reflect.Value, isStrict bool) (

typ := val.Type().Elem()
for i, sec := range secs {
elem := reflect.New(typ)
val = reflect.Append(val, reflect.Zero(typ))
elem := val.Index(val.Len() - 1)
if err = sec.mapToField(elem, isStrict, i, sec.name); err != nil {
return reflect.Value{}, fmt.Errorf("map to field from section %q: %v", secName, err)
}

val = reflect.Append(val, elem.Elem())
}
return val, nil
}
Expand All @@ -381,7 +412,10 @@ func (s *Section) mapToSlice(secName string, val reflect.Value, isStrict bool) (
func (s *Section) mapTo(v interface{}, isStrict bool) error {
typ := reflect.TypeOf(v)
val := reflect.ValueOf(v)
if typ.Kind() == reflect.Ptr {
isPtr := typ.Kind() == reflect.Ptr
if isPtr && val.IsNil() {
return fmt.Errorf("cannot decode to nil value of %q", typ)
} else if isPtr {
typ = typ.Elem()
val = val.Elem()
} else {
Expand Down
Loading