mirror of
https://github.com/go-ini/ini.git
synced 2026-06-19 21:46:45 +02:00
key: apply ValueMapper to substituted reference values (#382)
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.7
parent
5a14e1849d
commit
89efed6562
@@ -37,7 +37,7 @@ jobs:
|
|||||||
name: Test
|
name: Test
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
go-version: [1.24.x, 1.25.x]
|
go-version: [1.26.x]
|
||||||
platform: [ubuntu-latest, macos-latest, windows-latest]
|
platform: [ubuntu-latest, macos-latest, windows-latest]
|
||||||
runs-on: ${{ matrix.platform }}
|
runs-on: ${{ matrix.platform }}
|
||||||
steps:
|
steps:
|
||||||
|
|||||||
@@ -6,11 +6,6 @@ linters:
|
|||||||
- unconvert
|
- unconvert
|
||||||
- unparam
|
- unparam
|
||||||
settings:
|
settings:
|
||||||
govet:
|
|
||||||
disable:
|
|
||||||
# printf: non-constant format string in call to fmt.Errorf (govet)
|
|
||||||
# showing up since golangci-lint version 1.60.1
|
|
||||||
- printf
|
|
||||||
nakedret:
|
nakedret:
|
||||||
max-func-lines: 0 # Disallow any unnamed return statement
|
max-func-lines: 0 # Disallow any unnamed return statement
|
||||||
exclusions:
|
exclusions:
|
||||||
|
|||||||
+2
-3
@@ -18,7 +18,6 @@ import (
|
|||||||
"bytes"
|
"bytes"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"io/ioutil"
|
|
||||||
"os"
|
"os"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -48,7 +47,7 @@ type sourceData struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s *sourceData) ReadCloser() (io.ReadCloser, error) {
|
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.
|
// sourceReadCloser represents an input stream with Close method.
|
||||||
@@ -69,7 +68,7 @@ func parseDataSource(source interface{}) (dataSource, error) {
|
|||||||
case io.ReadCloser:
|
case io.ReadCloser:
|
||||||
return &sourceReadCloser{s}, nil
|
return &sourceReadCloser{s}, nil
|
||||||
case io.Reader:
|
case io.Reader:
|
||||||
return &sourceReadCloser{ioutil.NopCloser(s)}, nil
|
return &sourceReadCloser{io.NopCloser(s)}, nil
|
||||||
default:
|
default:
|
||||||
return nil, fmt.Errorf("error parsing data source: unknown type %q", s)
|
return nil, fmt.Errorf("error parsing data source: unknown type %q", s)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,7 +19,6 @@ import (
|
|||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"io/ioutil"
|
|
||||||
"os"
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
@@ -532,7 +531,7 @@ func (f *File) SaveToIndent(filename, indent string) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
return ioutil.WriteFile(filename, buf.Bytes(), 0666)
|
return os.WriteFile(filename, buf.Bytes(), 0666)
|
||||||
}
|
}
|
||||||
|
|
||||||
// SaveTo writes content to file system.
|
// SaveTo writes content to file system.
|
||||||
|
|||||||
+3
-3
@@ -16,7 +16,7 @@ package ini
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"io/ioutil"
|
"os"
|
||||||
"runtime"
|
"runtime"
|
||||||
"sort"
|
"sort"
|
||||||
"testing"
|
"testing"
|
||||||
@@ -421,10 +421,10 @@ func TestFile_WriteTo(t *testing.T) {
|
|||||||
|
|
||||||
golden := "testdata/TestFile_WriteTo.golden"
|
golden := "testdata/TestFile_WriteTo.golden"
|
||||||
if *update {
|
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)
|
require.NoError(t, err)
|
||||||
assert.Equal(t, string(expected), buf.String())
|
assert.Equal(t, string(expected), buf.String())
|
||||||
})
|
})
|
||||||
|
|||||||
+2
-2
@@ -17,7 +17,7 @@ package ini
|
|||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"flag"
|
"flag"
|
||||||
"io/ioutil"
|
"io"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"runtime"
|
"runtime"
|
||||||
"testing"
|
"testing"
|
||||||
@@ -58,7 +58,7 @@ func TestLoad(t *testing.T) {
|
|||||||
"testdata/minimal.ini",
|
"testdata/minimal.ini",
|
||||||
[]byte("NAME = ini\nIMPORT_PATH = gopkg.in/%(NAME)s.%(VERSION)s"),
|
[]byte("NAME = ini\nIMPORT_PATH = gopkg.in/%(NAME)s.%(VERSION)s"),
|
||||||
bytes.NewReader([]byte(`VERSION = v1`)),
|
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.NoError(t, err)
|
||||||
require.NotNil(t, f)
|
require.NotNil(t, f)
|
||||||
|
|||||||
@@ -170,7 +170,7 @@ func (k *Key) transformValue(val string) string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Substitute by new value and take off leading '%(' and trailing ')s'.
|
// Substitute by new value and take off leading '%(' and trailing ')s'.
|
||||||
val = strings.ReplaceAll(val, vr, nk.value)
|
val = strings.ReplaceAll(val, vr, nk.String())
|
||||||
}
|
}
|
||||||
return val
|
return val
|
||||||
}
|
}
|
||||||
|
|||||||
+19
@@ -183,6 +183,7 @@ func TestKey_Helpers(t *testing.T) {
|
|||||||
}
|
}
|
||||||
return in
|
return in
|
||||||
}
|
}
|
||||||
|
t.Cleanup(func() { f.ValueMapper = nil })
|
||||||
assert.Equal(t, "github.com/go-ini/ini", sec.Key("IMPORT_PATH").String())
|
assert.Equal(t, "github.com/go-ini/ini", sec.Key("IMPORT_PATH").String())
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
@@ -635,4 +636,22 @@ bar = %(missing)s
|
|||||||
|
|
||||||
assert.Equal(t, "%(missing)s", f.Section("foo").Key("bar").String())
|
assert.Equal(t, "%(missing)s", f.Section("foo").Key("bar").String())
|
||||||
})
|
})
|
||||||
|
|
||||||
|
t.Run("ValueMapper applies to substituted reference value", func(t *testing.T) {
|
||||||
|
f, err := Load([]byte(`
|
||||||
|
ACCOUNT_ID = ${LFSD_R2_ACCOUNT_ID}
|
||||||
|
ENDPOINT = https://%(ACCOUNT_ID)s.r2.cloudflarestorage.com
|
||||||
|
`))
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.NotNil(t, f)
|
||||||
|
|
||||||
|
f.ValueMapper = func(in string) string {
|
||||||
|
if in == "${LFSD_R2_ACCOUNT_ID}" {
|
||||||
|
return "abc123"
|
||||||
|
}
|
||||||
|
return in
|
||||||
|
}
|
||||||
|
|
||||||
|
assert.Equal(t, "https://abc123.r2.cloudflarestorage.com", f.Section("").Key("ENDPOINT").String())
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -156,7 +156,7 @@ func wrapStrictError(err error, isStrict bool) error {
|
|||||||
// because we want to use default value that is already assigned to struct.
|
// because we want to use default value that is already assigned to struct.
|
||||||
func setWithProperType(t reflect.Type, key *Key, field reflect.Value, delim string, allowShadow, isStrict bool) error {
|
func setWithProperType(t reflect.Type, key *Key, field reflect.Value, delim string, allowShadow, isStrict bool) error {
|
||||||
vt := t
|
vt := t
|
||||||
isPtr := t.Kind() == reflect.Ptr
|
isPtr := t.Kind() == reflect.Pointer
|
||||||
if isPtr {
|
if isPtr {
|
||||||
vt = t.Elem()
|
vt = t.Elem()
|
||||||
}
|
}
|
||||||
@@ -278,7 +278,7 @@ func parseTagOptions(tag string) (rawName string, omitEmpty bool, allowShadow bo
|
|||||||
// mapToField maps the given value to the matching field of the given section.
|
// 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.
|
// 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 {
|
func (s *Section) mapToField(val reflect.Value, isStrict bool, sectionIndex int, sectionName string) error {
|
||||||
if val.Kind() == reflect.Ptr {
|
if val.Kind() == reflect.Pointer {
|
||||||
val = val.Elem()
|
val = val.Elem()
|
||||||
}
|
}
|
||||||
typ := val.Type()
|
typ := val.Type()
|
||||||
@@ -299,8 +299,8 @@ func (s *Section) mapToField(val reflect.Value, isStrict bool, sectionIndex int,
|
|||||||
}
|
}
|
||||||
|
|
||||||
isStruct := tpField.Type.Kind() == reflect.Struct
|
isStruct := tpField.Type.Kind() == reflect.Struct
|
||||||
isStructPtr := tpField.Type.Kind() == reflect.Ptr && tpField.Type.Elem().Kind() == reflect.Struct
|
isStructPtr := tpField.Type.Kind() == reflect.Pointer && tpField.Type.Elem().Kind() == reflect.Struct
|
||||||
isAnonymousPtr := tpField.Type.Kind() == reflect.Ptr && tpField.Anonymous
|
isAnonymousPtr := tpField.Type.Kind() == reflect.Pointer && tpField.Anonymous
|
||||||
if isAnonymousPtr {
|
if isAnonymousPtr {
|
||||||
field.Set(reflect.New(tpField.Type.Elem()))
|
field.Set(reflect.New(tpField.Type.Elem()))
|
||||||
}
|
}
|
||||||
@@ -381,7 +381,7 @@ func (s *Section) mapToSlice(secName string, val reflect.Value, isStrict bool) (
|
|||||||
func (s *Section) mapTo(v interface{}, isStrict bool) error {
|
func (s *Section) mapTo(v interface{}, isStrict bool) error {
|
||||||
typ := reflect.TypeOf(v)
|
typ := reflect.TypeOf(v)
|
||||||
val := reflect.ValueOf(v)
|
val := reflect.ValueOf(v)
|
||||||
if typ.Kind() == reflect.Ptr {
|
if typ.Kind() == reflect.Pointer {
|
||||||
typ = typ.Elem()
|
typ = typ.Elem()
|
||||||
val = val.Elem()
|
val = val.Elem()
|
||||||
} else {
|
} else {
|
||||||
@@ -500,13 +500,13 @@ func reflectSliceWithProperType(key *Key, field reflect.Value, delim string, all
|
|||||||
case reflect.String:
|
case reflect.String:
|
||||||
buf.WriteString(slice.Index(i).String())
|
buf.WriteString(slice.Index(i).String())
|
||||||
case reflect.Int, reflect.Int64:
|
case reflect.Int, reflect.Int64:
|
||||||
buf.WriteString(fmt.Sprint(slice.Index(i).Int()))
|
fmt.Fprint(&buf, slice.Index(i).Int())
|
||||||
case reflect.Uint, reflect.Uint64:
|
case reflect.Uint, reflect.Uint64:
|
||||||
buf.WriteString(fmt.Sprint(slice.Index(i).Uint()))
|
fmt.Fprint(&buf, slice.Index(i).Uint())
|
||||||
case reflect.Float64:
|
case reflect.Float64:
|
||||||
buf.WriteString(fmt.Sprint(slice.Index(i).Float()))
|
fmt.Fprint(&buf, slice.Index(i).Float())
|
||||||
case reflect.Bool:
|
case reflect.Bool:
|
||||||
buf.WriteString(fmt.Sprint(slice.Index(i).Bool()))
|
fmt.Fprint(&buf, slice.Index(i).Bool())
|
||||||
case reflectTime:
|
case reflectTime:
|
||||||
buf.WriteString(slice.Index(i).Interface().(time.Time).Format(time.RFC3339))
|
buf.WriteString(slice.Index(i).Interface().(time.Time).Format(time.RFC3339))
|
||||||
default:
|
default:
|
||||||
@@ -535,7 +535,7 @@ func reflectWithProperType(t reflect.Type, key *Key, field reflect.Value, delim
|
|||||||
key.SetValue(fmt.Sprint(field.Interface().(time.Time).Format(time.RFC3339)))
|
key.SetValue(fmt.Sprint(field.Interface().(time.Time).Format(time.RFC3339)))
|
||||||
case reflect.Slice:
|
case reflect.Slice:
|
||||||
return reflectSliceWithProperType(key, field, delim, allowShadow)
|
return reflectSliceWithProperType(key, field, delim, allowShadow)
|
||||||
case reflect.Ptr:
|
case reflect.Pointer:
|
||||||
if !field.IsNil() {
|
if !field.IsNil() {
|
||||||
return reflectWithProperType(t.Elem(), key, field.Elem(), delim, allowShadow)
|
return reflectWithProperType(t.Elem(), key, field.Elem(), delim, allowShadow)
|
||||||
}
|
}
|
||||||
@@ -559,7 +559,7 @@ func isEmptyValue(v reflect.Value) bool {
|
|||||||
return v.Uint() == 0
|
return v.Uint() == 0
|
||||||
case reflect.Float32, reflect.Float64:
|
case reflect.Float32, reflect.Float64:
|
||||||
return v.Float() == 0
|
return v.Float() == 0
|
||||||
case reflect.Interface, reflect.Ptr:
|
case reflect.Interface, reflect.Pointer:
|
||||||
return v.IsNil()
|
return v.IsNil()
|
||||||
case reflectTime:
|
case reflectTime:
|
||||||
t, ok := v.Interface().(time.Time)
|
t, ok := v.Interface().(time.Time)
|
||||||
@@ -574,7 +574,7 @@ type StructReflector interface {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s *Section) reflectFrom(val reflect.Value) error {
|
func (s *Section) reflectFrom(val reflect.Value) error {
|
||||||
if val.Kind() == reflect.Ptr {
|
if val.Kind() == reflect.Pointer {
|
||||||
val = val.Elem()
|
val = val.Elem()
|
||||||
}
|
}
|
||||||
typ := val.Type()
|
typ := val.Type()
|
||||||
@@ -606,14 +606,14 @@ func (s *Section) reflectFrom(val reflect.Value) error {
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
if extends && tpField.Anonymous && (tpField.Type.Kind() == reflect.Ptr || tpField.Type.Kind() == reflect.Struct) {
|
if extends && tpField.Anonymous && (tpField.Type.Kind() == reflect.Pointer || tpField.Type.Kind() == reflect.Struct) {
|
||||||
if err := s.reflectFrom(field); err != nil {
|
if err := s.reflectFrom(field); err != nil {
|
||||||
return fmt.Errorf("reflect from field %q: %v", fieldName, err)
|
return fmt.Errorf("reflect from field %q: %v", fieldName, err)
|
||||||
}
|
}
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
if (tpField.Type.Kind() == reflect.Ptr && tpField.Type.Elem().Kind() == reflect.Struct) ||
|
if (tpField.Type.Kind() == reflect.Pointer && tpField.Type.Elem().Kind() == reflect.Struct) ||
|
||||||
(tpField.Type.Kind() == reflect.Struct && tpField.Type.Name() != "Time") {
|
(tpField.Type.Kind() == reflect.Struct && tpField.Type.Name() != "Time") {
|
||||||
// Note: The only error here is section doesn't exist.
|
// Note: The only error here is section doesn't exist.
|
||||||
sec, err := s.f.GetSection(fieldName)
|
sec, err := s.f.GetSection(fieldName)
|
||||||
@@ -641,7 +641,7 @@ func (s *Section) reflectFrom(val reflect.Value) error {
|
|||||||
sliceOf := field.Type().Elem().Kind()
|
sliceOf := field.Type().Elem().Kind()
|
||||||
|
|
||||||
for i := 0; i < field.Len(); i++ {
|
for i := 0; i < field.Len(); i++ {
|
||||||
if sliceOf != reflect.Struct && sliceOf != reflect.Ptr {
|
if sliceOf != reflect.Struct && sliceOf != reflect.Pointer {
|
||||||
return fmt.Errorf("field %q is not a slice of pointer or struct", fieldName)
|
return fmt.Errorf("field %q is not a slice of pointer or struct", fieldName)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -688,11 +688,11 @@ func (s *Section) ReflectFrom(v interface{}) error {
|
|||||||
val := reflect.ValueOf(v)
|
val := reflect.ValueOf(v)
|
||||||
|
|
||||||
if s.name != DefaultSection && s.f.options.AllowNonUniqueSections &&
|
if s.name != DefaultSection && s.f.options.AllowNonUniqueSections &&
|
||||||
(typ.Kind() == reflect.Slice || typ.Kind() == reflect.Ptr) {
|
(typ.Kind() == reflect.Slice || typ.Kind() == reflect.Pointer) {
|
||||||
// Clear sections to make sure none exists before adding the new ones
|
// Clear sections to make sure none exists before adding the new ones
|
||||||
s.f.DeleteSection(s.name)
|
s.f.DeleteSection(s.name)
|
||||||
|
|
||||||
if typ.Kind() == reflect.Ptr {
|
if typ.Kind() == reflect.Pointer {
|
||||||
sec, err := s.f.NewSection(s.name)
|
sec, err := s.f.NewSection(s.name)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -702,7 +702,7 @@ func (s *Section) ReflectFrom(v interface{}) error {
|
|||||||
|
|
||||||
slice := val.Slice(0, val.Len())
|
slice := val.Slice(0, val.Len())
|
||||||
sliceOf := val.Type().Elem().Kind()
|
sliceOf := val.Type().Elem().Kind()
|
||||||
if sliceOf != reflect.Ptr {
|
if sliceOf != reflect.Pointer {
|
||||||
return fmt.Errorf("not a slice of pointers")
|
return fmt.Errorf("not a slice of pointers")
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -721,7 +721,7 @@ func (s *Section) ReflectFrom(v interface{}) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
if typ.Kind() == reflect.Ptr {
|
if typ.Kind() == reflect.Pointer {
|
||||||
val = val.Elem()
|
val = val.Elem()
|
||||||
} else {
|
} else {
|
||||||
return errors.New("not a pointer to a struct")
|
return errors.New("not a pointer to a struct")
|
||||||
|
|||||||
Reference in New Issue
Block a user