diff --git a/file_test.go b/file_test.go index ef8db6106dc4b16669318a9b8b2f78c4acc99054..51b3280cd8620db302bd751681289a764541cc5b 100644 --- a/file_test.go +++ b/file_test.go @@ -360,6 +360,7 @@ C: }) } + func addSample2() { e := &mockFile{} diff --git a/go.mod b/go.mod index f02d0d80c43c06b1a9b5ea63be5345a99c5f414d..dbbb1af07c6e9726c53a4ad358a5fa534c705314 100644 --- a/go.mod +++ b/go.mod @@ -9,7 +9,7 @@ require ( github.com/magiconair/properties v1.8.6 github.com/pelletier/go-toml/v2 v2.0.5 github.com/r3labs/diff/v3 v3.0.0 - github.com/stretchr/testify v1.8.0 + github.com/stretchr/testify v1.8.1 gitlab.schukai.com/oss/libraries/go/application/xflags v1.9.0 gitlab.schukai.com/oss/libraries/go/network/http-negotiation v1.3.0 gitlab.schukai.com/oss/libraries/go/utilities/pathfinder v0.3.1 @@ -21,6 +21,7 @@ require ( github.com/davecgh/go-spew v1.1.1 // indirect github.com/golang/protobuf v1.5.2 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect + github.com/stretchr/objx v0.5.0 // indirect github.com/vmihailenco/msgpack v4.0.4+incompatible // indirect golang.org/x/net v0.1.0 // indirect golang.org/x/sys v0.1.0 // indirect diff --git a/go.sum b/go.sum index c2d8f12f0264b0015e4ff4e73a35113601a8cc88..7b4782a92834903a711ae4ef5ebefdf267dc09c2 100644 --- a/go.sum +++ b/go.sum @@ -23,10 +23,14 @@ github.com/r3labs/diff/v3 v3.0.0 h1:ZhPwNxn9gW5WLPBV9GCYaVbMdLOSmJ0DeKdCiSbOLUI= github.com/r3labs/diff/v3 v3.0.0/go.mod h1:wCkTySAiDnZao1sZrVTDIzuzgLZ+cNPGn3LC8DlIg5g= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/objx v0.5.0 h1:1zr/of2m5FGMsad5YfcqgdqdWrIhu+EBEJRhR1U7z/c= +github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk= +github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/vmihailenco/msgpack v4.0.4+incompatible h1:dSLoQfGFAo3F6OoNhwUmLwVgaUXK79GlxNBwueZn0xI= github.com/vmihailenco/msgpack v4.0.4+incompatible/go.mod h1:fy3FlTQTDXWkZ7Bh6AcGMlsjHatGryHQYUTf1ShIgkk= gitlab.schukai.com/oss/libraries/go/application/xflags v1.9.0 h1:bSnwEV56JZQWBQCasMtCLXTkaSgdFhnoefJB4/2KN8c= diff --git a/import.go b/import.go index f53d9183f501156e2d4894cb770fb55ef1cd29ac..486f02b20da54e28e1c69387dc542e9ff8415d7f 100644 --- a/import.go +++ b/import.go @@ -6,6 +6,7 @@ package configuration import ( "bytes" "encoding/json" + "reflect" "github.com/imdario/mergo" "github.com/magiconair/properties" @@ -46,7 +47,7 @@ func importProperties[C any](config *C, reader io.Reader) error { } -func (s *Settings[C]) importStream(r reader) { +func (s *Settings[C]) importStream(r reader, f ...func(n *C)) { var c C var err error @@ -79,6 +80,12 @@ func (s *Settings[C]) importStream(r reader) { s.errors = append(s.errors, err) } + if f != nil { + for _, fn := range f { + fn(&c) + } + } + if err := mergo.Merge(&s.config, c); err != nil { s.errors = append(s.errors, err) } @@ -90,6 +97,42 @@ func (s *Settings[C]) importStreams() { } } +func replacePath(p string, c any) { + + if reflect.TypeOf(c).Kind() != reflect.Ptr { + panic("c must be a pointer") + } + + if reflect.TypeOf(c).Elem().Kind() != reflect.Struct { + panic("c must be a pointer to a struct") + } + + fields := reflect.VisibleFields(reflect.TypeOf(c).Elem()) + for _, field := range fields { + + r := reflect.ValueOf(c).Elem().FieldByName(field.Name) + if field.Type.Kind() == reflect.Struct { + if r.CanAddr() { + replacePath(p, r.Addr().Interface()) + } + continue + } + + _, ok := r.Interface().(pathInterface) + if ok { + + if r.CanSet() { + if !path.IsAbs(r.String()) { + r.SetString(path.Join(p, r.String())) + } + } + + } + + } + +} + func (s *Settings[C]) importFiles() { s.fileWatch.Lock() @@ -116,7 +159,9 @@ func (s *Settings[C]) importFiles() { } r := (io.Reader)(f) - s.importStream(reader{s.files.format, r}) + s.importStream(reader{s.files.format, r}, func(c *C) { + replacePath(d, c) + }) f.Close() } @@ -129,7 +174,10 @@ func (s *Settings[C]) importFiles() { continue } - s.importStream(reader{f.format, r}) + s.importStream(reader{f.format, r}, func(c *C) { + d := path.Dir(f.path) + replacePath(d, c) + }) r.Close() } diff --git a/issue-1_test.go b/issue-1_test.go new file mode 100644 index 0000000000000000000000000000000000000000..311fb3c51a1db932149e1d12da941e6fe75940ad --- /dev/null +++ b/issue-1_test.go @@ -0,0 +1,89 @@ +package configuration + +import ( + "bytes" + "github.com/stretchr/testify/assert" + "path" + "testing" + "time" +) + +type ConfigIssue1SubStruct struct { + DA PathValue `yaml:"DA"` +} + +type ConfigIssue1Struct struct { + A string `yaml:"A"` + B PathValue `yaml:"B"` + C PathValue `yaml:"C"` + D ConfigIssue1SubStruct `yaml:"D"` +} + +func addSampleIssue1() { + + e := &mockFile{} + + content := []byte( + `--- +A: "Hello!" +B: "/tmp" +C: "xyz.html" +D: + DA: "tmp.xyz" +... + +`) + + e.buffer = bytes.NewBuffer(content) + + e.FileInfo = mockFileInfo{ + name: "test", + size: int64(len(content)), + mode: 0, + mod: time.Now(), + dir: false, + sys: nil, + } + + fileSamples = append(fileSamples, fileSample{ + file: e, + }) + +} + +func TestIssue1(t *testing.T) { + + fileSamples = []fileSample{} + defer func() { fileSamples = nil }() + + addSampleIssue1() + + defaults := ConfigIssue1Struct{} + + mockFs := mockFS{} + + c := New(defaults) + c.SetMnemonic("test") + c.SetFileFormat(Yaml) + c.SetFilesystem(mockFs) + + // setDefaultdirectories can be returned errors + c.SetDefaultDirectories().ResetErrors() + c.Import() + + if c.HasErrors() { + t.Error("Expected not error", c.Errors()) + } + + assert.Equal(t, c.Config().A, "Hello!") + assert.Equal(t, c.Config().B, PathValue("/tmp")) + + if !path.IsAbs(string(c.Config().C)) { + t.Error("Expected absolute path got ", c.Config().C) + } + + if !path.IsAbs(string(c.Config().D.DA)) { + t.Error("Expected absolute path got ", c.Config().D.DA) + } + +} diff --git a/path-value.go b/path-value.go new file mode 100644 index 0000000000000000000000000000000000000000..56856fa0fd24efd759a2ecdd38fea031c4b0c445 --- /dev/null +++ b/path-value.go @@ -0,0 +1,28 @@ +package configuration + +import "path" + +type pathInterface interface { + String() string + Dir() string + Ext() string + Base() string +} + +type PathValue string + +func (p PathValue) Dir() string { + return path.Dir(string(p)) +} + +func (p PathValue) Ext() string { + return path.Ext(string(p)) +} + +func (p PathValue) String() string { + return string(p) +} + +func (p PathValue) Base() string { + return path.Base(string(p)) +} diff --git a/path-value_test.go b/path-value_test.go new file mode 100644 index 0000000000000000000000000000000000000000..87a67c761a954c915e9931548a0fb7e521431f03 --- /dev/null +++ b/path-value_test.go @@ -0,0 +1,58 @@ +package configuration + +import ( + "path" + "testing" +) + +func TestPathValue(t *testing.T) { + + data := []struct { + path string + expected string + base string + }{ + {"/a/b/c", "/a/b", "c"}, + {"/a/b/c/", "/a/b/c", "c"}, // trailing slash are ignored + {"/a/b/c/d.txt", "/a/b/c", "d.txt"}, + {"/a/b/c/d/", "/a/b/c/d", "d"}, + {"/a/b/c/d/e", "/a/b/c/d", "e"}, + {"/a/b/../c/d/e", "/a/c/d", "e"}, + } + + for _, d := range data { + t.Run("Dir-"+d.path, func(t *testing.T) { + p := PathValue(d.path) + if p.Dir() != d.expected { + t.Errorf("Expected %s, got %s", d.expected, p.Dir()) + } + }) + } + + for _, d := range data { + t.Run("Base-"+d.path, func(t *testing.T) { + p := PathValue(d.path) + if p.Base() != d.base { + t.Errorf("Expected %s, got %s", d.base, p.Base()) + } + }) + } + + for _, d := range data { + t.Run("String-"+d.path, func(t *testing.T) { + p := PathValue(d.path) + if p.String() != string(p) { + t.Errorf("Expected %s, got %s", string(p), p.String()) + } + }) + } + + for _, d := range data { + t.Run("Ext-"+d.path, func(t *testing.T) { + p := PathValue(d.path) + if p.Ext() != path.Ext(d.path) { + t.Errorf("Expected %s, got %s", path.Ext(d.path), p.Ext()) + } + }) + } +}