Demonstrate main-relative companion loading
This commit is contained in:
@@ -196,6 +196,8 @@ coverage, including existing namespace and repeated-source cases.
|
|||||||
|
|
||||||
## Stage 6 — Exercise the combined workflow
|
## Stage 6 — Exercise the combined workflow
|
||||||
|
|
||||||
|
**Status:** Complete. The executable consumer example and strict-peer regression pass.
|
||||||
|
|
||||||
Add a maintained executable example in `examples/consumers` for a single
|
Add a maintained executable example in `examples/consumers` for a single
|
||||||
layered load using `SearchConfig`, a mapping companion with optional named
|
layered load using `SearchConfig`, a mapping companion with optional named
|
||||||
files and recursive directory imports, an independently located embedded
|
files and recursive directory imports, an independently located embedded
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ import (
|
|||||||
configkit "go.maximumdirect.net/configkit"
|
configkit "go.maximumdirect.net/configkit"
|
||||||
)
|
)
|
||||||
|
|
||||||
//go:embed testdata/native-source testdata/layered-bundle testdata/collection-bundle testdata/ordinary-imports testdata/source-version
|
//go:embed testdata/native-source testdata/layered-bundle testdata/collection-bundle testdata/ordinary-imports testdata/source-version testdata/main-relative-bundle
|
||||||
var bundledFiles embed.FS
|
var bundledFiles embed.FS
|
||||||
|
|
||||||
// BundledConfig is a small configuration loaded directly from an embedded file.
|
// BundledConfig is a small configuration loaded directly from an embedded file.
|
||||||
|
|||||||
47
examples/consumers/main_relative.go
Normal file
47
examples/consumers/main_relative.go
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
package consumers
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io/fs"
|
||||||
|
"path/filepath"
|
||||||
|
|
||||||
|
configkit "go.maximumdirect.net/configkit"
|
||||||
|
)
|
||||||
|
|
||||||
|
// CompanionConfig demonstrates independently bundled and operator prompt tiers.
|
||||||
|
type CompanionConfig struct {
|
||||||
|
Mode string `yaml:"mode"`
|
||||||
|
Prompts []CompanionPrompt `yaml:"prompts"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type CompanionPrompt struct {
|
||||||
|
ID string `yaml:"id"`
|
||||||
|
Text string `yaml:"text"`
|
||||||
|
Note string `yaml:"note"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// LoadCompanionConfig discovers a main file and binds operator imports and the
|
||||||
|
// selected overlay to its directory while retaining the embedded catalog tier.
|
||||||
|
func LoadCompanionConfig(mainPath, overlay string) (*configkit.Result[CompanionConfig], error) {
|
||||||
|
files, err := fs.Sub(bundledFiles, "testdata/main-relative-bundle")
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
bundle := configkit.NewFilesystem("bundled catalog", files)
|
||||||
|
absolute, err := filepath.Abs(mainPath)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
declarations := map[string]any{"config": map[string]any{"imports": []any{
|
||||||
|
map[string]any{"path": "prompts.yml", "destination": []any{"prompts"}, "placement": "sequence-item"},
|
||||||
|
map[string]any{"dir": "operator", "recursive": true, "pattern": []any{"*.yml", "*.yaml"}, "destination": []any{"prompts"}, "placement": "sequence-item"},
|
||||||
|
}}}
|
||||||
|
main := configkit.SearchConfig("", configkit.Searches(configkit.SearchPaths(absolute)), configkit.SearchOptions{Required: true})
|
||||||
|
return configkit.LoadLayered(configkit.Input{Source: main}, configkit.LayeredOptions[CompanionConfig]{
|
||||||
|
Underlays: []configkit.Underlay{
|
||||||
|
{Inputs: []configkit.Input{{Source: bundle.File("catalog.yml")}}},
|
||||||
|
{Inputs: []configkit.Input{{Source: configkit.Mapping("operator imports", declarations), Imports: true, MainRelative: configkit.MainRelative()}}},
|
||||||
|
},
|
||||||
|
Overlay: overlay,
|
||||||
|
Definition: configkit.Definition[CompanionConfig]{SequenceIdentities: []configkit.SequenceIdentity{configkit.SequenceIdentityAt(configkit.Keys("prompts"), "id")}},
|
||||||
|
})
|
||||||
|
}
|
||||||
46
examples/consumers/main_relative_test.go
Normal file
46
examples/consumers/main_relative_test.go
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
package consumers_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
configkit "go.maximumdirect.net/configkit"
|
||||||
|
"go.maximumdirect.net/configkit/examples/consumers"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Example_mainRelativeCompanions() {
|
||||||
|
result, err := consumers.LoadCompanionConfig(fixture("main-relative", "main.yml"), "live")
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
fmt.Println(result.Config.Mode, result.Config.Prompts)
|
||||||
|
// Output: overlay [{shared operator replacement } {bundled bundled text } {local local text transitive detail}]
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMainRelativeStrictPeers(t *testing.T) {
|
||||||
|
root := t.TempDir()
|
||||||
|
for name, contents := range map[string]string{
|
||||||
|
"main.yml": "mode: main\n", "prompts.yml": "id: duplicate\ntext: first\n", "operator/second.yml": "id: duplicate\ntext: second\n",
|
||||||
|
} {
|
||||||
|
file := filepath.Join(root, name)
|
||||||
|
if err := os.MkdirAll(filepath.Dir(file), 0700); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if err := os.WriteFile(file, []byte(contents), 0600); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_, err := consumers.LoadCompanionConfig(filepath.Join(root, "main.yml"), "")
|
||||||
|
if err == nil {
|
||||||
|
t.Fatal("duplicate peer identities succeeded")
|
||||||
|
}
|
||||||
|
for _, d := range err.(*configkit.LoadError).Diagnostics() {
|
||||||
|
if d.Code == configkit.CodeComposeConflict {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
t.Fatalf("diagnostics = %#v", err.(*configkit.LoadError).Diagnostics())
|
||||||
|
}
|
||||||
7
examples/consumers/testdata/main-relative-bundle/catalog.yml
vendored
Normal file
7
examples/consumers/testdata/main-relative-bundle/catalog.yml
vendored
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
mode: embedded
|
||||||
|
prompts:
|
||||||
|
- id: shared
|
||||||
|
text: embedded text
|
||||||
|
note: replaced
|
||||||
|
- id: bundled
|
||||||
|
text: bundled text
|
||||||
1
examples/consumers/testdata/main-relative/main.yml
vendored
Normal file
1
examples/consumers/testdata/main-relative/main.yml
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
mode: main
|
||||||
5
examples/consumers/testdata/main-relative/operator/nested/one.yaml
vendored
Normal file
5
examples/consumers/testdata/main-relative/operator/nested/one.yaml
vendored
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
config:
|
||||||
|
imports:
|
||||||
|
- path: ../../shared/extra.yml
|
||||||
|
id: local
|
||||||
|
text: local text
|
||||||
1
examples/consumers/testdata/main-relative/overlays/live.yml
vendored
Normal file
1
examples/consumers/testdata/main-relative/overlays/live.yml
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
mode: overlay
|
||||||
2
examples/consumers/testdata/main-relative/prompts.yml
vendored
Normal file
2
examples/consumers/testdata/main-relative/prompts.yml
vendored
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
id: shared
|
||||||
|
text: operator replacement
|
||||||
1
examples/consumers/testdata/main-relative/shared/extra.yml
vendored
Normal file
1
examples/consumers/testdata/main-relative/shared/extra.yml
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
note: transitive detail
|
||||||
Reference in New Issue
Block a user