2026-10-19 20:02:05 +00:00
package engine
import (
"path"
"gitea.maximumdirect.net/eric/ConfigKit/internal/document"
)
type importSelector uint8
const (
importSelectorPath importSelector = iota + 1
importSelectorDirectory
importSelectorGlob
)
type importFieldSite struct {
path Path
location Location
}
// importDeclarationSpec preserves authored presence until discovery policy is resolved.
type importDeclarationSpec struct {
2026-10-19 20:13:33 +00:00
path Path
location Location
selector importSelector
paths [ ] string
selectorSites [ ] importFieldSite
glob string
globSegments [ ] string
required * bool
recursive * bool
follow * bool
hidden * bool
pattern [ ] string
patternSet bool
exclude [ ] string
depth int
depthSet bool
destination Path
2026-10-26 00:18:12 +00:00
placement PlacementMode
2026-10-19 20:13:33 +00:00
fieldSites map [ string ] importFieldSite
2026-10-19 20:02:05 +00:00
}
2026-10-19 21:06:31 +00:00
// ImportDefaults transports root-owned load defaults across the facade
// boundary. Load options snapshot this value before source acquisition.
type ImportDefaults struct {
// Required controls absence handling when non-nil.
Required * bool
// Follow controls symlink following when non-nil.
Follow * bool
// Hidden controls hidden-entry discovery when non-nil.
Hidden * bool
// Recursive controls descendant directory traversal when non-nil.
Recursive * bool
// Pattern filters directory-search filenames; nil and empty differ.
Pattern [ ] string
}
2026-10-19 20:02:05 +00:00
type resolvedImportPolicy struct {
required bool
follow bool
hidden bool
recursive bool
pattern [ ] string
depth int
depthSet bool
exclude [ ] string
destination Path
2026-10-26 00:18:12 +00:00
placement PlacementMode
2026-10-19 20:02:05 +00:00
}
2026-10-26 00:10:07 +00:00
func snapshotImportDefaults ( value ImportDefaults ) ImportDefaults {
return ImportDefaults {
Required : cloneBool ( value . Required ) ,
Follow : cloneBool ( value . Follow ) ,
Hidden : cloneBool ( value . Hidden ) ,
Recursive : cloneBool ( value . Recursive ) ,
Pattern : cloneStrings ( value . Pattern ) ,
2026-10-19 20:02:05 +00:00
}
}
func cloneBool ( value * bool ) * bool {
if value == nil {
return nil
}
cloned := * value
return & cloned
}
func cloneStrings ( values [ ] string ) [ ] string {
if values == nil {
return nil
}
return append ( make ( [ ] string , 0 , len ( values ) ) , values ... )
}
2026-10-26 00:10:07 +00:00
func validateImportDefaults ( value ImportDefaults ) [ ] Diagnostic {
for _ , pattern := range value . Pattern {
2026-10-19 20:02:05 +00:00
if _ , err := path . Match ( pattern , "" ) ; err != nil {
return [ ] Diagnostic { optionDiagnostic ( CodeOptionsInvalid , "import default pattern is invalid" ) }
}
}
return nil
}
2026-10-26 00:10:07 +00:00
func resolveImportPolicy ( spec importDeclarationSpec , defaults ImportDefaults ) ( resolvedImportPolicy , [ ] Diagnostic ) {
2026-10-19 20:02:05 +00:00
policy := resolvedImportPolicy {
2026-10-26 00:10:07 +00:00
required : boolOr ( spec . required , defaults . Required , false ) ,
2026-10-19 20:02:05 +00:00
follow : true ,
hidden : true ,
recursive : false ,
destination : spec . destination . clone ( ) ,
placement : spec . placement ,
depth : spec . depth ,
depthSet : spec . depthSet ,
exclude : cloneStrings ( spec . exclude ) ,
}
if spec . selector == importSelectorDirectory || spec . selector == importSelectorGlob {
2026-10-26 00:10:07 +00:00
policy . follow = boolOr ( spec . follow , defaults . Follow , true )
policy . hidden = boolOr ( spec . hidden , defaults . Hidden , true )
2026-10-19 20:02:05 +00:00
}
if spec . selector == importSelectorDirectory {
2026-10-26 00:10:07 +00:00
policy . recursive = boolOr ( spec . recursive , defaults . Recursive , false )
2026-10-19 20:02:05 +00:00
if spec . patternSet {
policy . pattern = cloneStrings ( spec . pattern )
} else {
2026-10-26 00:10:07 +00:00
policy . pattern = cloneStrings ( defaults . Pattern )
2026-10-19 20:02:05 +00:00
}
}
if spec . depthSet && ! policy . recursive {
site := spec . fieldSites [ "depth" ]
location := site . location
return resolvedImportPolicy { } , [ ] Diagnostic { {
Code : CodeImportInvalid , Severity : SeverityError ,
Path : site . path . clone ( ) , Message : "depth requires effective recursive discovery" ,
Primary : & location ,
} }
}
return policy , nil
}
func boolOr ( explicit , application * bool , fallback bool ) bool {
if explicit != nil {
return * explicit
}
if application != nil {
return * application
}
return fallback
}
2026-10-23 14:01:39 +00:00
func parseImportDeclarations ( node * document . Node , declarationPath Path ) ( [ ] importDeclarationSpec , [ ] Diagnostic ) {
if node == nil || node . Kind != document . Sequence {
return nil , [ ] Diagnostic { nodeImportDiagnostic ( CodeImportInvalid , declarationPath , "imports must be a sequence" , node ) }
}
var specs [ ] importDeclarationSpec
var diagnostics [ ] Diagnostic
for index , declarationNode := range node . Sequence {
spec , issues := parseImportDeclarationSpec ( declarationNode , declarationPath . Index ( index ) )
if len ( issues ) != 0 {
diagnostics = append ( diagnostics , issues ... )
2026-10-19 20:02:05 +00:00
continue
}
2026-10-23 14:01:39 +00:00
specs = append ( specs , spec )
2026-10-19 20:02:05 +00:00
}
return specs , canonicalDiagnostics ( diagnostics )
}
func parseImportDeclarationSpec ( node * document . Node , declarationPath Path ) ( importDeclarationSpec , [ ] Diagnostic ) {
if node . Kind != document . Mapping {
return importDeclarationSpec { } , [ ] Diagnostic { nodeImportDiagnostic ( CodeImportInvalid , declarationPath , "import declaration must be a mapping" , node ) }
}
spec := importDeclarationSpec {
path : declarationPath . clone ( ) ,
location : publicLocation ( node . Location ) ,
2026-10-26 00:18:12 +00:00
placement : PlacementModeMapping ,
2026-10-19 20:02:05 +00:00
fieldSites : make ( map [ string ] importFieldSite , len ( node . Mapping ) ) ,
}
selectors := 0
selectorIndex := - 1
for index , entry := range node . Mapping {
if selectorForField ( entry . Key ) != 0 {
selectors ++
selectorIndex = index
}
}
if selectors != 1 {
return importDeclarationSpec { } , [ ] Diagnostic { nodeImportDiagnostic ( CodeImportInvalid , declarationPath , "import declaration requires exactly one path, dir, or glob selector" , node ) }
}
selectorEntry := node . Mapping [ selectorIndex ]
spec . selector = selectorForField ( selectorEntry . Key )
for _ , entry := range node . Mapping {
fieldPath := declarationPath . Key ( entry . Key )
spec . fieldSites [ entry . Key ] = importFieldSite { path : fieldPath . clone ( ) , location : publicLocation ( entry . Value . Location ) }
if selectorForField ( entry . Key ) != 0 {
continue
}
if ! importFieldApplies ( entry . Key , spec . selector ) {
return importDeclarationSpec { } , [ ] Diagnostic { nodeImportDiagnostic ( CodeImportInvalid , fieldPath , "import declaration field is not supported by this selector" , entry . Value ) }
}
switch entry . Key {
case "required" , "recursive" , "follow" , "hidden" :
2026-10-25 13:23:09 +00:00
if diagnostic := parseImportBooleanAssignment ( & spec , entry . Key , entry . Value , fieldPath ) ; diagnostic != nil {
return importDeclarationSpec { } , [ ] Diagnostic { * diagnostic }
2026-10-19 20:02:05 +00:00
}
case "pattern" :
2026-10-25 13:23:09 +00:00
if diagnostic := parseImportWildcardAssignment ( & spec , entry . Key , entry . Value , fieldPath ) ; diagnostic != nil {
return importDeclarationSpec { } , [ ] Diagnostic { * diagnostic }
2026-10-19 20:02:05 +00:00
}
case "exclude" :
2026-10-25 13:23:09 +00:00
if diagnostic := parseImportWildcardAssignment ( & spec , entry . Key , entry . Value , fieldPath ) ; diagnostic != nil {
return importDeclarationSpec { } , [ ] Diagnostic { * diagnostic }
2026-10-19 20:02:05 +00:00
}
case "depth" :
depth , ok := parseImportDepth ( entry . Value )
if ! ok {
return importDeclarationSpec { } , [ ] Diagnostic { nodeImportDiagnostic ( CodeImportInvalid , fieldPath , "depth must be a nonnegative integer" , entry . Value ) }
}
spec . depth , spec . depthSet = depth , true
case "destination" :
destination , diagnostic := parseImportDestination ( entry . Value , fieldPath )
if diagnostic != nil {
return importDeclarationSpec { } , [ ] Diagnostic { * diagnostic }
}
spec . destination = destination
case "placement" :
2026-10-25 13:23:09 +00:00
placement , diagnostic := parseImportPlacement ( entry . Value , fieldPath )
if diagnostic != nil {
return importDeclarationSpec { } , [ ] Diagnostic { * diagnostic }
2026-10-19 20:02:05 +00:00
}
2026-10-25 13:23:09 +00:00
spec . placement = placement
2026-10-19 20:02:05 +00:00
default :
return importDeclarationSpec { } , [ ] Diagnostic { nodeImportDiagnostic ( CodeImportInvalid , fieldPath , "import declaration field is not recognized" , entry . Value ) }
}
}
selectorPath := declarationPath . Key ( selectorEntry . Key )
spec . fieldSites [ selectorEntry . Key ] = importFieldSite { path : selectorPath . clone ( ) , location : publicLocation ( selectorEntry . Value . Location ) }
2026-10-25 13:23:09 +00:00
if diagnostic := parseImportSelectorValue ( & spec , selectorEntry . Key , selectorEntry . Value , selectorPath ) ; diagnostic != nil {
return importDeclarationSpec { } , [ ] Diagnostic { * diagnostic }
}
return spec , nil
}
func parseImportBooleanAssignment ( spec * importDeclarationSpec , field string , node * document . Node , fieldPath Path ) * Diagnostic {
if node . Kind != document . Boolean {
diagnostic := nodeImportDiagnostic ( CodeImportInvalid , fieldPath , field + " must be a boolean" , node )
return & diagnostic
}
value := node . Text == "true"
switch field {
case "required" :
spec . required = & value
case "recursive" :
spec . recursive = & value
case "follow" :
spec . follow = & value
case "hidden" :
spec . hidden = & value
}
return nil
}
func parseImportWildcardAssignment ( spec * importDeclarationSpec , field string , node * document . Node , fieldPath Path ) * Diagnostic {
values , origins , ok , badIndex := parseStringSet ( node )
if ! ok {
diagnostic := stringSetDiagnostic ( node , fieldPath , field , badIndex )
return & diagnostic
}
if badIndex , ok := invalidWildcard ( values ) ; ok {
badPath , badNode := listMemberSite ( node , fieldPath , origins [ badIndex ] )
diagnostic := nodeImportDiagnostic ( CodeImportInvalid , badPath , field + " contains an invalid wildcard expression" , badNode )
return & diagnostic
}
switch field {
case "pattern" :
spec . pattern , spec . patternSet = values , true
case "exclude" :
2026-10-25 22:18:12 +00:00
spec . exclude = values
2026-10-25 13:23:09 +00:00
}
return nil
}
2026-10-26 00:18:12 +00:00
func parseImportPlacement ( node * document . Node , fieldPath Path ) ( PlacementMode , * Diagnostic ) {
2026-10-25 13:23:09 +00:00
if node . Kind != document . String {
diagnostic := nodeImportDiagnostic ( CodeImportInvalid , fieldPath , "placement must be mapping or sequence-item" , node )
2026-10-26 00:18:12 +00:00
return PlacementModeMapping , & diagnostic
2026-10-25 13:23:09 +00:00
}
switch node . Text {
case "mapping" :
2026-10-26 00:18:12 +00:00
return PlacementModeMapping , nil
2026-10-25 13:23:09 +00:00
case "sequence-item" :
2026-10-26 00:18:12 +00:00
return PlacementModeSequenceItem , nil
2026-10-25 13:23:09 +00:00
default :
diagnostic := nodeImportDiagnostic ( CodeImportInvalid , fieldPath , "placement must be mapping or sequence-item" , node )
2026-10-26 00:18:12 +00:00
return PlacementModeMapping , & diagnostic
2026-10-25 13:23:09 +00:00
}
}
func parseImportSelectorValue ( spec * importDeclarationSpec , field string , node * document . Node , selectorPath Path ) * Diagnostic {
2026-10-19 20:02:05 +00:00
switch spec . selector {
case importSelectorPath , importSelectorDirectory :
2026-10-25 13:23:09 +00:00
values , origins , ok , badIndex := parseStringSet ( node )
2026-10-19 20:02:05 +00:00
if ! ok {
2026-10-25 13:23:09 +00:00
diagnostic := stringSetDiagnostic ( node , selectorPath , field , badIndex )
return & diagnostic
2026-10-19 20:02:05 +00:00
}
for index , value := range values {
if ! validImportPath ( value ) {
2026-10-25 13:23:09 +00:00
badPath , badNode := listMemberSite ( node , selectorPath , origins [ index ] )
diagnostic := nodeImportDiagnostic ( CodeImportInvalid , badPath , field + " contains an invalid source path" , badNode )
return & diagnostic
2026-10-19 20:02:05 +00:00
}
2026-10-25 13:23:09 +00:00
badPath , badNode := listMemberSite ( node , selectorPath , origins [ index ] )
2026-10-19 20:13:33 +00:00
spec . selectorSites = append ( spec . selectorSites , importFieldSite { path : badPath . clone ( ) , location : publicLocation ( badNode . Location ) } )
2026-10-19 20:02:05 +00:00
}
spec . paths = values
case importSelectorGlob :
2026-10-25 13:23:09 +00:00
if node . Kind != document . String || ! validImportPath ( node . Text ) {
diagnostic := nodeImportDiagnostic ( CodeImportInvalid , selectorPath , "glob must be a valid nonempty path pattern string" , node )
return & diagnostic
2026-10-19 20:02:05 +00:00
}
2026-10-25 13:23:09 +00:00
segments , ok := validGlobPattern ( node . Text )
2026-10-19 20:02:05 +00:00
if ! ok {
2026-10-25 13:23:09 +00:00
diagnostic := nodeImportDiagnostic ( CodeImportInvalid , selectorPath , "glob pattern is invalid" , node )
return & diagnostic
2026-10-19 20:02:05 +00:00
}
2026-10-25 13:23:09 +00:00
spec . glob , spec . globSegments = node . Text , segments
2026-10-19 20:02:05 +00:00
}
2026-10-25 13:23:09 +00:00
return nil
2026-10-19 20:02:05 +00:00
}
func selectorForField ( field string ) importSelector {
switch field {
case "path" :
return importSelectorPath
case "dir" :
return importSelectorDirectory
case "glob" :
return importSelectorGlob
default :
return 0
}
}
func importFieldApplies ( field string , selector importSelector ) bool {
switch field {
case "required" , "exclude" , "destination" , "placement" :
return true
case "pattern" , "recursive" , "depth" :
return selector == importSelectorDirectory
case "follow" , "hidden" :
return selector == importSelectorDirectory || selector == importSelectorGlob
default :
return false
}
}
func parseStringSet ( node * document . Node ) ( [ ] string , [ ] int , bool , int ) {
if node . Kind == document . String {
return [ ] string { node . Text } , [ ] int { - 1 } , true , - 1
}
if node . Kind != document . Sequence {
return nil , nil , false , - 1
}
values := make ( [ ] string , 0 , len ( node . Sequence ) )
origins := make ( [ ] int , 0 , len ( node . Sequence ) )
seen := make ( map [ string ] struct { } , len ( node . Sequence ) )
for index , item := range node . Sequence {
if item . Kind != document . String {
return nil , nil , false , index
}
if _ , exists := seen [ item . Text ] ; exists {
continue
}
seen [ item . Text ] = struct { } { }
values = append ( values , item . Text )
origins = append ( origins , index )
}
return values , origins , true , - 1
}
func stringSetDiagnostic ( node * document . Node , fieldPath Path , field string , badIndex int ) Diagnostic {
if badIndex >= 0 {
badPath , item := listMemberSite ( node , fieldPath , badIndex )
return nodeImportDiagnostic ( CodeImportInvalid , badPath , field + " members must be strings" , item )
}
return nodeImportDiagnostic ( CodeImportInvalid , fieldPath , field + " must be a string or a flat list of strings" , node )
}
func invalidWildcard ( values [ ] string ) ( int , bool ) {
for index , value := range values {
if _ , err := path . Match ( value , "" ) ; err != nil {
return index , true
}
}
return - 1 , false
}
func listMemberSite ( node * document . Node , fieldPath Path , index int ) ( Path , * document . Node ) {
if node . Kind != document . Sequence || index < 0 {
return fieldPath , node
}
return fieldPath . Index ( index ) , node . Sequence [ index ]
}
func parseImportDepth ( node * document . Node ) ( int , bool ) {
if node . Kind != document . Number || node . Text == "" {
return 0 , false
}
if node . Text == "-0" {
return 0 , true
}
var value uint64
maxInt := uint64 ( ^ uint ( 0 ) >> 1 )
for index := 0 ; index < len ( node . Text ) ; index ++ {
character := node . Text [ index ]
if character < '0' || character > '9' {
return 0 , false
}
digit := uint64 ( character - '0' )
if value > ( maxInt - digit ) / 10 {
2026-10-20 00:27:55 +00:00
value = maxInt
continue // Saturate the bound, but validate every remaining character.
2026-10-19 20:02:05 +00:00
}
value = value * 10 + digit
}
return int ( value ) , true
}
func parseImportDestination ( node * document . Node , fieldPath Path ) ( Path , * Diagnostic ) {
if node . Kind != document . Sequence {
diagnostic := nodeImportDiagnostic ( CodeImportInvalid , fieldPath , "destination must be a sequence of strings" , node )
return Path { } , & diagnostic
}
relative := Path { segments : make ( [ ] pathSegment , 0 , len ( node . Sequence ) ) }
for index , item := range node . Sequence {
memberPath := fieldPath . Index ( index )
if item . Kind != document . String {
diagnostic := nodeImportDiagnostic ( CodeImportInvalid , memberPath , "destination members must be strings" , item )
return Path { } , & diagnostic
}
member := Path { segments : [ ] pathSegment { { kind : pathSegmentKey , key : item . Text } } }
if _ , err := normalizeDestination ( member ) ; err != nil {
diagnostic := nodeImportDiagnostic ( CodeImportInvalid , memberPath , "destination members must be non-reserved mapping keys" , item )
return Path { } , & diagnostic
}
relative . segments = append ( relative . segments , pathSegment { kind : pathSegmentKey , key : item . Text } )
}
normalized , err := normalizeDestination ( relative )
if err != nil {
diagnostic := nodeImportDiagnostic ( CodeImportInvalid , fieldPath , "destination must contain only non-reserved mapping keys" , node )
return Path { } , & diagnostic
}
return normalized , nil
}