Allow absent inputs to use import contexts
This commit is contained in:
@@ -78,9 +78,14 @@ non-root destination. Unknown modes, invalid destinations, and invalid or
|
|||||||
conflicting sequence registrations fail before source acquisition. Overlay and
|
conflicting sequence registrations fail before source acquisition. Overlay and
|
||||||
CLI coordinates remain rooted at the application root. Ordinary `Load` recognizes
|
CLI coordinates remain rooted at the application root. Ordinary `Load` recognizes
|
||||||
source-root collection declarations whether or not imports are enabled; an
|
source-root collection declarations whether or not imports are enabled; an
|
||||||
`imports` declaration still requires imports to be enabled. `NoSource` rejects
|
`imports` declaration still requires imports to be enabled. `NoSource` may enable
|
||||||
imports, active overlays, and filesystem context as invalid combinations but
|
imports, but it has no main declarations to traverse. It accepts an explicit
|
||||||
accepts overrides and remains an absent source rather than an empty document.
|
filesystem context: a host context requires an absolute existing `Root` and may
|
||||||
|
omit `BaseDir` to use that root as its internal import base; a native context
|
||||||
|
requires its namespace, `BaseDir`, and `Root`. Supplied context is validated even
|
||||||
|
when imports are disabled. Active ordinary overlays remain invalid for
|
||||||
|
`NoSource`. It accepts overrides and remains absent rather than contributing an
|
||||||
|
empty document.
|
||||||
|
|
||||||
Every source, options, schema, version, defaults, decode, resolution, or validation
|
Every source, options, schema, version, defaults, decode, resolution, or validation
|
||||||
failure returns a nil result and a `*LoadError`. Successful results contain caller-owned `Config`, detached
|
failure returns a nil result and a `*LoadError`. Successful results contain caller-owned `Config`, detached
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
# Configuration Discovery Implementation Plan
|
# Configuration Discovery Implementation Plan
|
||||||
|
|
||||||
Status: in progress. Stages 1, 2, and 3 are complete.
|
Status: in progress. Stages 1, 2, 3, and 4 are complete.
|
||||||
|
|
||||||
Implement the target behavior in [the discovery roadmap](discovery.md). That
|
Implement the target behavior in [the discovery roadmap](discovery.md). That
|
||||||
document owns the selected conventions and OD-1 through OD-5; this plan owns the
|
document owns the selected conventions and OD-1 through OD-5; this plan owns the
|
||||||
@@ -271,7 +271,7 @@ running defaults/resolution. A selected source's later read failure is not a
|
|||||||
fallback signal. Run focused discovery/source tests and the everyday check;
|
fallback signal. Run focused discovery/source tests and the everyday check;
|
||||||
mark and commit.
|
mark and commit.
|
||||||
|
|
||||||
## Stage 4 — Unify Absent Input Acquisition And Context Validation
|
## Stage 4 — Unify Absent Input Acquisition And Context Validation (Complete)
|
||||||
|
|
||||||
This stage changes `NoSource()` deliberately, before attaching discovery to loads.
|
This stage changes `NoSource()` deliberately, before attaching discovery to loads.
|
||||||
|
|
||||||
|
|||||||
@@ -9,6 +9,9 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func validateFilesystemContext(source Source, context *FilesystemContext, main parsedSource) *Diagnostic {
|
func validateFilesystemContext(source Source, context *FilesystemContext, main parsedSource) *Diagnostic {
|
||||||
|
if source.kind == sourceNone && context.FS == nil {
|
||||||
|
return validateAbsentHostFilesystemContext(context)
|
||||||
|
}
|
||||||
if nativeContext(source, context) {
|
if nativeContext(source, context) {
|
||||||
access, basePath, mainPath, diagnostic := nativeFilesystemAccess(source, context)
|
access, basePath, mainPath, diagnostic := nativeFilesystemAccess(source, context)
|
||||||
if diagnostic != nil {
|
if diagnostic != nil {
|
||||||
@@ -33,10 +36,54 @@ func validateFilesystemContext(source Source, context *FilesystemContext, main p
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func validateAbsentHostFilesystemContext(context *FilesystemContext) *Diagnostic {
|
||||||
|
rootPath, err := canonicalExistingPath(context.Root)
|
||||||
|
if err != nil {
|
||||||
|
value := importDiagnostic(CodeImportInvalid, "configuration root could not be canonicalized", nil)
|
||||||
|
return &value
|
||||||
|
}
|
||||||
|
basePath := rootPath
|
||||||
|
if context.BaseDir != "" {
|
||||||
|
basePath, err = canonicalExistingPath(context.BaseDir)
|
||||||
|
if err != nil {
|
||||||
|
value := importDiagnostic(CodeImportInvalid, "import base directory could not be canonicalized", nil)
|
||||||
|
return &value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !containedPath(rootPath, basePath) {
|
||||||
|
value := importDiagnostic(CodeImportOutsideRoot, "import base must stay inside the configuration root", nil)
|
||||||
|
return &value
|
||||||
|
}
|
||||||
|
for _, candidate := range []string{rootPath, basePath} {
|
||||||
|
info, err := os.Stat(candidate)
|
||||||
|
if err != nil || !info.IsDir() {
|
||||||
|
value := importDiagnostic(CodeImportInvalid, "configuration root and import base must be directories", nil)
|
||||||
|
return &value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
root, err := os.OpenRoot(rootPath)
|
||||||
|
if err != nil {
|
||||||
|
value := globFilesystemDiagnostic(err, "configuration root could not be opened", Location{})
|
||||||
|
value.Primary = nil
|
||||||
|
return &value
|
||||||
|
}
|
||||||
|
if err := root.Close(); err != nil {
|
||||||
|
value := importDiagnostic(CodeImportRead, "configuration root could not be closed", nil)
|
||||||
|
return &value
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func filesystemOptionDiagnostics(source Source, context *FilesystemContext) []Diagnostic {
|
func filesystemOptionDiagnostics(source Source, context *FilesystemContext) []Diagnostic {
|
||||||
switch source.kind {
|
switch source.kind {
|
||||||
case sourceNone:
|
case sourceNone:
|
||||||
return []Diagnostic{optionDiagnostic(CodeOptionsInvalid, "NoSource cannot use filesystem context")}
|
if context.FS != nil {
|
||||||
|
if context.FS.namespace == nil || nilFilesystem(context.FS.namespace.filesystem) || context.BaseDir == "" || context.Root == "" || !fs.ValidPath(context.BaseDir) || !fs.ValidPath(context.Root) {
|
||||||
|
return []Diagnostic{optionDiagnostic(CodeOptionsInvalid, "native filesystem context requires a namespace, base, and root directory")}
|
||||||
|
}
|
||||||
|
} else if context.Root == "" || !filepath.IsAbs(context.Root) || context.BaseDir != "" && !filepath.IsAbs(context.BaseDir) {
|
||||||
|
return []Diagnostic{optionDiagnostic(CodeOptionsInvalid, "absent-input filesystem root and base directories must be absolute host paths")}
|
||||||
|
}
|
||||||
case sourceFile:
|
case sourceFile:
|
||||||
if context.FS != nil {
|
if context.FS != nil {
|
||||||
return []Diagnostic{optionDiagnostic(CodeOptionsInvalid, "host file sources cannot use a native filesystem context")}
|
return []Diagnostic{optionDiagnostic(CodeOptionsInvalid, "host file sources cannot use a native filesystem context")}
|
||||||
|
|||||||
@@ -268,6 +268,48 @@ func TestFilesystemContextIsValidatedWithoutImportsOrActiveOverlay(t *testing.T)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestNoSourceImportsAndFilesystemContext(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
directory := t.TempDir()
|
||||||
|
for _, imports := range []bool{false, true} {
|
||||||
|
name := "imports disabled"
|
||||||
|
if imports {
|
||||||
|
name = "imports enabled"
|
||||||
|
}
|
||||||
|
t.Run(name, func(t *testing.T) {
|
||||||
|
result, err := testLoad[importTestConfig](NoSource(), Options[importTestConfig]{
|
||||||
|
Imports: imports, Filesystem: &FilesystemContext{Root: directory},
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if sources := result.Sources(); len(sources) != 0 {
|
||||||
|
t.Fatalf("NoSource sources = %#v", sources)
|
||||||
|
}
|
||||||
|
if _, ok := result.Metadata().Lookup(Keys("items", "root")); ok {
|
||||||
|
t.Fatal("NoSource contributed explicit input")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
outside := t.TempDir()
|
||||||
|
for _, test := range []struct {
|
||||||
|
name string
|
||||||
|
context FilesystemContext
|
||||||
|
code Code
|
||||||
|
}{
|
||||||
|
{name: "missing root", context: FilesystemContext{BaseDir: directory}, code: CodeOptionsInvalid},
|
||||||
|
{name: "relative root", context: FilesystemContext{Root: "."}, code: CodeOptionsInvalid},
|
||||||
|
{name: "missing root directory", context: FilesystemContext{Root: filepath.Join(directory, "missing")}, code: CodeImportInvalid},
|
||||||
|
{name: "base outside root", context: FilesystemContext{BaseDir: outside, Root: directory}, code: CodeImportOutsideRoot},
|
||||||
|
} {
|
||||||
|
t.Run(test.name, func(t *testing.T) {
|
||||||
|
requireLoadCode(t, NoSource(), Options[importTestConfig]{Filesystem: &test.context}, test.code)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func writeGlobRoot(t *testing.T, directory, pattern string, required bool) string {
|
func writeGlobRoot(t *testing.T, directory, pattern string, required bool) string {
|
||||||
t.Helper()
|
t.Helper()
|
||||||
requiredLine := ""
|
requiredLine := ""
|
||||||
|
|||||||
@@ -398,7 +398,7 @@ label: helper
|
|||||||
childPath := writeImportFile(t, directory, "records/children/child.yml", "value: nested\n")
|
childPath := writeImportFile(t, directory, "records/children/child.yml", "value: nested\n")
|
||||||
|
|
||||||
loaded, err := LoadLayered(
|
loaded, err := LoadLayered(
|
||||||
Input{Source: NoSource()}, testSecretBinding(),
|
Input{Source: NoSource(), Imports: true}, testSecretBinding(),
|
||||||
LayeredOptions[config]{Underlays: []Underlay{{Inputs: []Input{{
|
LayeredOptions[config]{Underlays: []Underlay{{Inputs: []Input{{
|
||||||
Source: File(itemPath), Imports: true, Destination: Keys("catalog", "entries"), placementMode: placementModeSequenceItem,
|
Source: File(itemPath), Imports: true, Destination: Keys("catalog", "entries"), placementMode: placementModeSequenceItem,
|
||||||
}}}}},
|
}}}}},
|
||||||
|
|||||||
@@ -21,8 +21,9 @@ type Definition[T any] struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Options contains the application definition and per-load input selections.
|
// Options contains the application definition and per-load input selections.
|
||||||
// Imports enables explicit import declarations; Overlay selects one fixed overlay;
|
// Imports enables traversal of declarations in a selected main document; an
|
||||||
// Overrides contains only values explicitly supplied by an application CLI.
|
// absent source has no main declarations to traverse. Overlay selects one fixed
|
||||||
|
// overlay. Overrides contain only values explicitly supplied by an application CLI.
|
||||||
type Options[T any] struct {
|
type Options[T any] struct {
|
||||||
Definition Definition[T]
|
Definition Definition[T]
|
||||||
Imports bool
|
Imports bool
|
||||||
@@ -39,7 +40,9 @@ type Options[T any] struct {
|
|||||||
importDefaultsValidated bool
|
importDefaultsValidated bool
|
||||||
}
|
}
|
||||||
|
|
||||||
// FilesystemContext supplies filesystem roots for imports and overlays.
|
// FilesystemContext supplies filesystem roots for imports and overlays. For an
|
||||||
|
// absent host input, Root is required and BaseDir may be omitted to use Root as
|
||||||
|
// the import base. Native contexts still require both BaseDir and Root.
|
||||||
type FilesystemContext struct {
|
type FilesystemContext struct {
|
||||||
BaseDir string
|
BaseDir string
|
||||||
Root string
|
Root string
|
||||||
|
|||||||
@@ -200,7 +200,7 @@ func assembleBaseInputTrackedForRole[T any](source Source, options Options[T], d
|
|||||||
versionState.mainProcessing = &processed
|
versionState.mainProcessing = &processed
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if options.Imports {
|
if options.Imports && source.kind != sourceNone {
|
||||||
if options.deferAssembly {
|
if options.deferAssembly {
|
||||||
input.composition, input.sources, diagnostics = loadImportedDocumentsTrackedWithProcessingDeferred(source, options.Filesystem, parsed, destination, options.Definition.Version, limits, accounting, visited, hostToken, versionState, versionProcessing, options.sequencePolicy, true)
|
input.composition, input.sources, diagnostics = loadImportedDocumentsTrackedWithProcessingDeferred(source, options.Filesystem, parsed, destination, options.Definition.Version, limits, accounting, visited, hostToken, versionState, versionProcessing, options.sequencePolicy, true)
|
||||||
} else {
|
} else {
|
||||||
@@ -218,7 +218,7 @@ func assembleBaseInputTrackedForRole[T any](source Source, options Options[T], d
|
|||||||
if versionProcessing != nil {
|
if versionProcessing != nil {
|
||||||
input.document = versionProcessing.payload
|
input.document = versionProcessing.payload
|
||||||
}
|
}
|
||||||
if options.Filesystem != nil && !activeOverlay(options.Overlay) {
|
if options.Filesystem != nil && (source.kind == sourceNone || !activeOverlay(options.Overlay)) {
|
||||||
if diagnostic := validateFilesystemContext(source, options.Filesystem, parsed); diagnostic != nil {
|
if diagnostic := validateFilesystemContext(source, options.Filesystem, parsed); diagnostic != nil {
|
||||||
return input, []Diagnostic{*diagnostic}
|
return input, []Diagnostic{*diagnostic}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -31,9 +31,6 @@ func selectOptions[T any](source Source, supplied []Options[T]) (Options[T], []D
|
|||||||
if options.Filesystem != nil {
|
if options.Filesystem != nil {
|
||||||
diagnostics = append(diagnostics, filesystemOptionDiagnostics(source, options.Filesystem)...)
|
diagnostics = append(diagnostics, filesystemOptionDiagnostics(source, options.Filesystem)...)
|
||||||
}
|
}
|
||||||
if source.kind == sourceNone && options.Imports {
|
|
||||||
diagnostics = append(diagnostics, optionDiagnostic(CodeOptionsInvalid, "NoSource cannot enable imports"))
|
|
||||||
}
|
|
||||||
if source.kind == sourceNone && options.Overlay != "" && options.Overlay != "default" {
|
if source.kind == sourceNone && options.Overlay != "" && options.Overlay != "default" {
|
||||||
diagnostics = append(diagnostics, optionDiagnostic(CodeOptionsInvalid, "NoSource cannot select an active overlay"))
|
diagnostics = append(diagnostics, optionDiagnostic(CodeOptionsInvalid, "NoSource cannot select an active overlay"))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -130,6 +130,7 @@ func TestNoSourceOverridesReachResolutionAfterDefaults(t *testing.T) {
|
|||||||
t.Parallel()
|
t.Parallel()
|
||||||
type config struct {
|
type config struct {
|
||||||
Count int `yaml:"count"`
|
Count int `yaml:"count"`
|
||||||
|
Enabled bool `yaml:"enabled"`
|
||||||
Derived int `yaml:"derived"`
|
Derived int `yaml:"derived"`
|
||||||
}
|
}
|
||||||
calls := 0
|
calls := 0
|
||||||
@@ -137,27 +138,33 @@ func TestNoSourceOverridesReachResolutionAfterDefaults(t *testing.T) {
|
|||||||
Definition: Definition[config]{
|
Definition: Definition[config]{
|
||||||
Defaults: func() config {
|
Defaults: func() config {
|
||||||
calls++
|
calls++
|
||||||
return config{Count: 2}
|
return config{Count: 2, Enabled: true}
|
||||||
},
|
},
|
||||||
Resolve: func(value *config, resolution *Resolution) error {
|
Resolve: func(value *config, resolution *Resolution) error {
|
||||||
info, ok := resolution.InputInfo(Keys("count"))
|
for _, key := range []string{"count", "enabled"} {
|
||||||
|
info, ok := resolution.InputInfo(Keys(key))
|
||||||
if !ok || !info.Explicit || info.Origin.Layer != LayerCLI {
|
if !ok || !info.Explicit || info.Origin.Layer != LayerCLI {
|
||||||
t.Errorf("resolution count info = %#v, %v", info, ok)
|
t.Errorf("resolution %s info = %#v, %v", key, info, ok)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Default(resolution, Keys("count"), &value.Count, 10, "preserve CLI value")
|
Default(resolution, Keys("count"), &value.Count, 10, "preserve CLI value")
|
||||||
|
Default(resolution, Keys("enabled"), &value.Enabled, true, "preserve CLI value")
|
||||||
Default(resolution, Keys("derived"), &value.Derived, value.Count*3, "derive from CLI count")
|
Default(resolution, Keys("derived"), &value.Derived, value.Count*3, "derive from CLI count")
|
||||||
return nil
|
return nil
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Overrides: []Override{{Path: Keys("count"), Value: 4, Flag: "--count"}},
|
Overrides: []Override{
|
||||||
|
{Path: Keys("count"), Value: 0, Flag: "--count"},
|
||||||
|
{Path: Keys("enabled"), Value: false, Flag: "--enabled"},
|
||||||
|
},
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
if calls != 1 || result.Config.Count != 4 || result.Config.Derived != 12 {
|
if calls != 1 || result.Config.Count != 0 || result.Config.Enabled || result.Config.Derived != 0 {
|
||||||
t.Fatalf("defaults-only CLI result = %#v, defaults calls %d", result.Config, calls)
|
t.Fatalf("defaults-only CLI result = %#v, defaults calls %d", result.Config, calls)
|
||||||
}
|
}
|
||||||
if sources := result.Sources(); len(sources) != 1 || sources[0].Layer != LayerCLI {
|
if sources := result.Sources(); len(sources) != 2 || sources[0].Layer != LayerCLI || sources[1].Layer != LayerCLI {
|
||||||
t.Fatalf("defaults-only CLI sources = %#v", sources)
|
t.Fatalf("defaults-only CLI sources = %#v", sources)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -174,7 +174,10 @@ func Mapping(label string, values map[string]any) Source {
|
|||||||
return source
|
return source
|
||||||
}
|
}
|
||||||
|
|
||||||
// NoSource explicitly selects a defaults-only input. It creates no source record.
|
// NoSource selects an absent input for defaults and resolution. It creates no
|
||||||
|
// source record and has no main declarations to traverse when imports are enabled.
|
||||||
|
// An explicit filesystem context may be supplied; active overlays are not
|
||||||
|
// available for absent inputs.
|
||||||
func NoSource() Source {
|
func NoSource() Source {
|
||||||
return Source{kind: sourceNone}
|
return Source{kind: sourceNone}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -539,6 +539,41 @@ func TestSourceVersionNoSourceMainRetainsAbsentCallback(t *testing.T) {
|
|||||||
if !called || len(result.diagnostics) != 0 || result.evidence != nil || result.payload != nil {
|
if !called || len(result.diagnostics) != 0 || result.evidence != nil || result.payload != nil {
|
||||||
t.Fatalf("NoSource result = %#v, callback called = %v", result, called)
|
t.Fatalf("NoSource result = %#v, callback called = %v", result, called)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type config struct{}
|
||||||
|
for _, reject := range []bool{false, true} {
|
||||||
|
called = false
|
||||||
|
definition := Definition[config]{Version: &VersionRule{
|
||||||
|
Path: Keys("version"),
|
||||||
|
Check: func(version Version, _ *Validation) error {
|
||||||
|
called = true
|
||||||
|
if version != (Version{}) {
|
||||||
|
t.Errorf("NoSource load version = %#v", version)
|
||||||
|
}
|
||||||
|
if reject {
|
||||||
|
return errors.New("rejected absent main")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
loaded, loadErr := testLoad(NoSource(), Options[config]{
|
||||||
|
Definition: definition, Imports: true, SourceVersionMode: test.mode,
|
||||||
|
})
|
||||||
|
if !called {
|
||||||
|
t.Fatal("NoSource load did not call the source-version checker")
|
||||||
|
}
|
||||||
|
if reject {
|
||||||
|
var failure *LoadError
|
||||||
|
if !errors.As(loadErr, &failure) {
|
||||||
|
t.Fatalf("rejected NoSource load = %v, want *LoadError", loadErr)
|
||||||
|
}
|
||||||
|
requireSourceVersionDiagnostic(t, failure.Diagnostics(), CodeVersionHookFailed, "/version")
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if loadErr != nil || loaded == nil || len(loaded.Sources()) != 0 {
|
||||||
|
t.Fatalf("accepted NoSource load = %#v, %v", loaded, loadErr)
|
||||||
|
}
|
||||||
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
13
lifecycle.go
13
lifecycle.go
@@ -83,8 +83,9 @@ type ImportDefaults struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Options contains the application definition and per-load input selections.
|
// Options contains the application definition and per-load input selections.
|
||||||
// Imports enables explicit import declarations; Overlay selects one fixed overlay;
|
// Imports enables traversal of declarations in a selected main document; an
|
||||||
// Overrides contains only values explicitly supplied by an application CLI.
|
// absent source has no main declarations to traverse. Overlay selects one fixed
|
||||||
|
// overlay. Overrides contain only values explicitly supplied by an application CLI.
|
||||||
// Enabled source-version modes interpret Definition.Version.Path in each
|
// Enabled source-version modes interpret Definition.Version.Path in each
|
||||||
// document's local coordinates. Placement projects checks and main protection
|
// document's local coordinates. Placement projects checks and main protection
|
||||||
// through the input destination; item placement adds the source's item
|
// through the input destination; item placement adds the source's item
|
||||||
@@ -109,9 +110,11 @@ type Options[T any] struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// FilesystemContext supplies roots for imports and overlays. With FS nil,
|
// FilesystemContext supplies roots for imports and overlays. With FS nil,
|
||||||
// BaseDir and Root use absolute host paths. With FS set, they use slash-relative
|
// BaseDir and Root use absolute host paths. For NoSource, Root is required and
|
||||||
// paths in that namespace ("." denotes its root). File sources derive BaseDir
|
// BaseDir may be omitted to use Root as the import base. With FS set, BaseDir
|
||||||
// from the selected file; a native file's explicit Root limits its scope.
|
// and Root use slash-relative paths in that namespace ("." denotes its root).
|
||||||
|
// File sources derive BaseDir from the selected file; a native file's explicit
|
||||||
|
// Root limits its scope.
|
||||||
type FilesystemContext struct {
|
type FilesystemContext struct {
|
||||||
BaseDir string
|
BaseDir string
|
||||||
Root string
|
Root string
|
||||||
|
|||||||
@@ -47,7 +47,10 @@ func Mapping(label string, values map[string]any) Source {
|
|||||||
return Source{value: engine.Mapping(label, values)}
|
return Source{value: engine.Mapping(label, values)}
|
||||||
}
|
}
|
||||||
|
|
||||||
// NoSource explicitly selects a defaults-only input. It creates no source record.
|
// NoSource selects an absent input for defaults and resolution. It creates no
|
||||||
|
// source record and has no main declarations to traverse when imports are enabled.
|
||||||
|
// An explicit filesystem context may be supplied; active overlays are not
|
||||||
|
// available for absent inputs.
|
||||||
func NoSource() Source { return Source{value: engine.NoSource()} }
|
func NoSource() Source { return Source{value: engine.NoSource()} }
|
||||||
|
|
||||||
func (s Source) toEngine() engine.Source { return s.value }
|
func (s Source) toEngine() engine.Source { return s.value }
|
||||||
|
|||||||
Reference in New Issue
Block a user