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
strategy:
matrix:
go-version: [1.24.x, 1.25.x]
go-version: [1.26.x]
platform: [ubuntu-latest, macos-latest, windows-latest]
runs-on: ${{ matrix.platform }}
steps:
-5
View File
@@ -6,11 +6,6 @@ linters:
- unconvert
- unparam
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:
max-func-lines: 0 # Disallow any unnamed return statement
exclusions:
+2 -3
View File
@@ -18,7 +18,6 @@ import (
"bytes"
"fmt"
"io"
"io/ioutil"
"os"
)
@@ -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.
@@ -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)
}
+1 -2
View File
@@ -19,7 +19,6 @@ import (
"errors"
"fmt"
"io"
"io/ioutil"
"os"
"strings"
"sync"
@@ -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.
+3 -3
View File
@@ -16,7 +16,7 @@ package ini
import (
"bytes"
"io/ioutil"
"os"
"runtime"
"sort"
"testing"
@@ -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())
})
+2 -2
View File
@@ -17,7 +17,7 @@ package ini
import (
"bytes"
"flag"
"io/ioutil"
"io"
"path/filepath"
"runtime"
"testing"
@@ -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)
+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'.
val = strings.ReplaceAll(val, vr, nk.value)
val = strings.ReplaceAll(val, vr, nk.String())
}
return val
}
+19
View File
@@ -183,6 +183,7 @@ func TestKey_Helpers(t *testing.T) {
}
return in
}
t.Cleanup(func() { f.ValueMapper = nil })
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())
})
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.
func setWithProperType(t reflect.Type, key *Key, field reflect.Value, delim string, allowShadow, isStrict bool) error {
vt := t
isPtr := t.Kind() == reflect.Ptr
isPtr := t.Kind() == reflect.Pointer
if isPtr {
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.
// 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 {
if val.Kind() == reflect.Ptr {
if val.Kind() == reflect.Pointer {
val = val.Elem()
}
typ := val.Type()
@@ -299,8 +299,8 @@ func (s *Section) mapToField(val reflect.Value, isStrict bool, sectionIndex int,
}
isStruct := tpField.Type.Kind() == reflect.Struct
isStructPtr := tpField.Type.Kind() == reflect.Ptr && tpField.Type.Elem().Kind() == reflect.Struct
isAnonymousPtr := tpField.Type.Kind() == reflect.Ptr && tpField.Anonymous
isStructPtr := tpField.Type.Kind() == reflect.Pointer && tpField.Type.Elem().Kind() == reflect.Struct
isAnonymousPtr := tpField.Type.Kind() == reflect.Pointer && tpField.Anonymous
if isAnonymousPtr {
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 {
typ := reflect.TypeOf(v)
val := reflect.ValueOf(v)
if typ.Kind() == reflect.Ptr {
if typ.Kind() == reflect.Pointer {
typ = typ.Elem()
val = val.Elem()
} else {
@@ -500,13 +500,13 @@ func reflectSliceWithProperType(key *Key, field reflect.Value, delim string, all
case reflect.String:
buf.WriteString(slice.Index(i).String())
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:
buf.WriteString(fmt.Sprint(slice.Index(i).Uint()))
fmt.Fprint(&buf, slice.Index(i).Uint())
case reflect.Float64:
buf.WriteString(fmt.Sprint(slice.Index(i).Float()))
fmt.Fprint(&buf, slice.Index(i).Float())
case reflect.Bool:
buf.WriteString(fmt.Sprint(slice.Index(i).Bool()))
fmt.Fprint(&buf, slice.Index(i).Bool())
case reflectTime:
buf.WriteString(slice.Index(i).Interface().(time.Time).Format(time.RFC3339))
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)))
case reflect.Slice:
return reflectSliceWithProperType(key, field, delim, allowShadow)
case reflect.Ptr:
case reflect.Pointer:
if !field.IsNil() {
return reflectWithProperType(t.Elem(), key, field.Elem(), delim, allowShadow)
}
@@ -559,7 +559,7 @@ func isEmptyValue(v reflect.Value) bool {
return v.Uint() == 0
case reflect.Float32, reflect.Float64:
return v.Float() == 0
case reflect.Interface, reflect.Ptr:
case reflect.Interface, reflect.Pointer:
return v.IsNil()
case reflectTime:
t, ok := v.Interface().(time.Time)
@@ -574,7 +574,7 @@ type StructReflector interface {
}
func (s *Section) reflectFrom(val reflect.Value) error {
if val.Kind() == reflect.Ptr {
if val.Kind() == reflect.Pointer {
val = val.Elem()
}
typ := val.Type()
@@ -606,14 +606,14 @@ func (s *Section) reflectFrom(val reflect.Value) error {
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 {
return fmt.Errorf("reflect from field %q: %v", fieldName, err)
}
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") {
// Note: The only error here is section doesn't exist.
sec, err := s.f.GetSection(fieldName)
@@ -641,7 +641,7 @@ func (s *Section) reflectFrom(val reflect.Value) error {
sliceOf := field.Type().Elem().Kind()
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)
}
@@ -688,11 +688,11 @@ func (s *Section) ReflectFrom(v interface{}) error {
val := reflect.ValueOf(v)
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
s.f.DeleteSection(s.name)
if typ.Kind() == reflect.Ptr {
if typ.Kind() == reflect.Pointer {
sec, err := s.f.NewSection(s.name)
if err != nil {
return err
@@ -702,7 +702,7 @@ func (s *Section) ReflectFrom(v interface{}) error {
slice := val.Slice(0, val.Len())
sliceOf := val.Type().Elem().Kind()
if sliceOf != reflect.Ptr {
if sliceOf != reflect.Pointer {
return fmt.Errorf("not a slice of pointers")
}
@@ -721,7 +721,7 @@ func (s *Section) ReflectFrom(v interface{}) error {
return nil
}
if typ.Kind() == reflect.Ptr {
if typ.Kind() == reflect.Pointer {
val = val.Elem()
} else {
return errors.New("not a pointer to a struct")