2026-09-12 15:58:06 +00:00
package engine
import (
"path"
"strings"
"gitea.maximumdirect.net/eric/ConfigKit/internal/document"
)
const directiveKey = "configkit"
2026-10-19 20:50:47 +00:00
func splitImportDirectives ( root * document . Node ) ( * document . Node , [ ] importDeclarationSpec , [ ] Diagnostic ) {
2026-09-12 15:58:06 +00:00
body := document . Clone ( root )
2026-10-19 20:50:47 +00:00
var declarations [ ] importDeclarationSpec
2026-09-12 15:58:06 +00:00
directiveIndex := - 1
for index , entry := range body . Mapping {
if entry . Key != directiveKey {
continue
}
directiveIndex = index
2026-10-19 20:50:47 +00:00
parsed , diagnostics := parseImportSpecificationDirective ( entry . Value , Keys ( directiveKey ) )
2026-09-12 15:58:06 +00:00
if len ( diagnostics ) != 0 {
return nil , nil , diagnostics
}
declarations = parsed
break
}
if directiveIndex >= 0 {
body . Mapping = append ( body . Mapping [ : directiveIndex ] , body . Mapping [ directiveIndex + 1 : ] ... )
}
if diagnostics := nestedDirectiveDiagnostics ( body , Path { } ) ; len ( diagnostics ) != 0 {
return nil , nil , diagnostics
}
return body , declarations , nil
}
func nestedDirectiveDiagnostics ( node * document . Node , current Path ) [ ] Diagnostic {
if node . Kind == document . Mapping {
for _ , entry := range node . Mapping {
childPath := current . Key ( entry . Key )
if entry . Key == directiveKey {
location := publicLocation ( entry . KeyLocation )
return [ ] Diagnostic { { Code : CodeImportInvalid , Severity : SeverityError , Path : childPath , Message : "configuration directives are permitted only at the document root" , Primary : & location } }
}
if diagnostics := nestedDirectiveDiagnostics ( entry . Value , childPath ) ; len ( diagnostics ) != 0 {
return diagnostics
}
}
} else if node . Kind == document . Sequence {
for index , child := range node . Sequence {
if diagnostics := nestedDirectiveDiagnostics ( child , current . Index ( index ) ) ; len ( diagnostics ) != 0 {
return diagnostics
}
}
}
return nil
}
func fragmentVersionClaim ( root * document . Node , rule * VersionRule ) * document . Node {
2026-10-16 19:11:08 +00:00
return fragmentVersionClaimProjected ( root , rule , - 1 , nil )
}
func fragmentVersionClaimProjected ( root * document . Node , rule * VersionRule , sourceIndex int , ranges [ ] sequenceRange ) * document . Node {
2026-10-17 04:52:20 +00:00
return fragmentVersionClaimWithIndex ( root , rule , sourceIndex , indexSequenceRanges ( ranges ) )
}
func fragmentVersionClaimWithIndex ( root * document . Node , rule * VersionRule , sourceIndex int , ranges sequenceRangeIndex ) * document . Node {
2026-09-12 15:58:06 +00:00
if rule == nil {
return nil
}
current := root
2026-10-16 19:11:08 +00:00
currentPath := Path { }
2026-09-12 15:58:06 +00:00
if len ( rule . Path . segments ) == 0 {
return current
}
for _ , segment := range rule . Path . segments {
2026-10-16 19:11:08 +00:00
switch current . Kind {
case document . Mapping :
if segment . kind == pathSegmentIndex {
return current
}
index := documentEntryIndex ( current . Mapping , segment . key )
if index < 0 {
return nil
}
current = current . Mapping [ index ] . Value
currentPath = currentPath . Key ( segment . key )
case document . Sequence :
2026-10-17 04:46:53 +00:00
index , valid := schemaIndex ( segment )
if ! valid {
2026-10-16 19:11:08 +00:00
return current
}
2026-10-17 04:52:20 +00:00
if assigned , found := ranges [ sequenceRangeKey { sourceIndex : sourceIndex , path : currentPath . String ( ) } ] ; found {
2026-10-16 19:11:08 +00:00
if index < assigned . start || index >= assigned . end {
return nil
}
index -= assigned . start
}
if index < 0 || index >= len ( current . Sequence ) {
return nil
}
current = current . Sequence [ index ]
currentPath = currentPath . Index ( index )
default :
// A scalar or null at an ancestor replaces the protected subtree.
2026-09-12 15:58:06 +00:00
return current
}
}
return current
}
func validImportPath ( value string ) bool {
if value == "" || strings . TrimSpace ( value ) != value || path . IsAbs ( value ) || strings . HasPrefix ( value , "//" ) {
return false
}
return ! hasImportScheme ( value )
}
func hasImportScheme ( value string ) bool {
colon := strings . IndexByte ( value , ':' )
slash := strings . IndexByte ( value , '/' )
if colon <= 0 || ( slash >= 0 && slash < colon ) {
return false
}
for index := 0 ; index < colon ; index ++ {
character := value [ index ]
if index == 0 {
if ( character < 'a' || character > 'z' ) && ( character < 'A' || character > 'Z' ) {
return false
}
continue
}
if ( character < 'a' || character > 'z' ) && ( character < 'A' || character > 'Z' ) &&
( character < '0' || character > '9' ) && character != '+' && character != '-' && character != '.' {
return false
}
}
return true
}
func nodeImportDiagnostic ( code Code , path Path , message string , node * document . Node ) Diagnostic {
location := publicLocation ( node . Location )
return Diagnostic { Code : code , Severity : SeverityError , Path : path , Message : message , Primary : & location }
}
2026-09-23 18:27:29 +00:00
func overlayDirectiveDiagnostics ( node * document . Node , current Path ) [ ] Diagnostic {
2026-09-12 15:58:06 +00:00
if node . Kind == document . Mapping {
for _ , entry := range node . Mapping {
childPath := current . Key ( entry . Key )
if entry . Key == directiveKey {
location := publicLocation ( entry . KeyLocation )
return [ ] Diagnostic { {
2026-09-23 18:27:29 +00:00
Code : CodeOverlayDirective , Severity : SeverityError , Path : childPath ,
Message : "overlays cannot contain configuration directives" , Primary : & location ,
2026-09-12 15:58:06 +00:00
} }
}
2026-09-23 18:27:29 +00:00
if diagnostics := overlayDirectiveDiagnostics ( entry . Value , childPath ) ; len ( diagnostics ) != 0 {
2026-09-12 15:58:06 +00:00
return diagnostics
}
}
} else if node . Kind == document . Sequence {
for index , child := range node . Sequence {
2026-09-23 18:27:29 +00:00
if diagnostics := overlayDirectiveDiagnostics ( child , current . Index ( index ) ) ; len ( diagnostics ) != 0 {
2026-09-12 15:58:06 +00:00
return diagnostics
}
}
}
return nil
}
func disabledDirectiveDiagnostics ( node * document . Node , path Path ) [ ] Diagnostic {
var diagnostics [ ] Diagnostic
if node . Kind == document . Mapping {
for _ , entry := range node . Mapping {
childPath := path . Key ( entry . Key )
if entry . Key == directiveKey {
location := Location { SourceID : entry . KeyLocation . SourceID , Line : entry . KeyLocation . Line , Column : entry . KeyLocation . Column }
diagnostics = append ( diagnostics , Diagnostic {
Code : CodeImportDisabled , Severity : SeverityError , Path : childPath ,
Message : "configuration directives require imports to be enabled" , Primary : & location ,
} )
}
diagnostics = append ( diagnostics , disabledDirectiveDiagnostics ( entry . Value , childPath ) ... )
}
} else if node . Kind == document . Sequence {
for index , child := range node . Sequence {
diagnostics = append ( diagnostics , disabledDirectiveDiagnostics ( child , path . Index ( index ) ) ... )
}
}
return diagnostics
}