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:
ᴊᴏᴇ ᴄʜᴇɴ
2026-05-02 21:44:05 -04:00
committed by GitHub
co-authored by Claude Opus 4.7
parent 5a14e1849d
commit 89efed6562
9 changed files with 48 additions and 36 deletions
+1 -1
View File
@@ -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:
-5
View File
@@ -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
View File
@@ -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)
} }
+1 -2
View File
@@ -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
View File
@@ -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
View File
@@ -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)
+1 -1
View File
@@ -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
View File
@@ -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())
})
} }
+19 -19
View File
@@ -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")