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-23 14:01:39 +00:00
type rootDirectiveContext uint8
const (
rootDirectiveContextImportsOnly rootDirectiveContext = iota + 1
rootDirectiveContextSource
rootDirectiveContextOverlay
2026-10-23 19:49:12 +00:00
rootDirectiveContextLayered
2026-10-23 14:01:39 +00:00
)
type rootDirectiveExtraction struct {
body * document . Node
imports [ ] importDeclarationSpec
sequences [ ] sequenceDeclarationSpec
importsPresent bool
sequencesPresent bool
}
2026-10-19 20:50:47 +00:00
func splitImportDirectives ( root * document . Node ) ( * document . Node , [ ] importDeclarationSpec , [ ] Diagnostic ) {
2026-10-23 14:01:39 +00:00
extracted , diagnostics := extractRootDirectives ( root , rootDirectiveContextImportsOnly )
if len ( diagnostics ) != 0 {
return nil , nil , diagnostics
}
return extracted . body , extracted . imports , nil
}
func extractRootDirectives ( root * document . Node , context rootDirectiveContext ) ( rootDirectiveExtraction , [ ] Diagnostic ) {
if root == nil {
return rootDirectiveExtraction { } , nil
}
2026-09-12 15:58:06 +00:00
body := document . Clone ( root )
directiveIndex := - 1
for index , entry := range body . Mapping {
if entry . Key != directiveKey {
continue
}
directiveIndex = index
2026-10-23 14:01:39 +00:00
extracted , diagnostics := parseRootDirectiveBlock ( entry . Value , Keys ( directiveKey ) , context )
2026-09-12 15:58:06 +00:00
if len ( diagnostics ) != 0 {
2026-10-23 14:01:39 +00:00
return rootDirectiveExtraction { } , diagnostics
2026-09-12 15:58:06 +00:00
}
2026-10-23 14:01:39 +00:00
extracted . body = body
if directiveIndex >= 0 {
extracted . body . Mapping = append ( extracted . body . Mapping [ : directiveIndex ] , extracted . body . Mapping [ directiveIndex + 1 : ] ... )
}
if diagnostics := nestedDirectiveDiagnostics ( extracted . body , Path { } ) ; len ( diagnostics ) != 0 {
return rootDirectiveExtraction { } , diagnostics
}
return extracted , nil
2026-09-12 15:58:06 +00:00
}
if diagnostics := nestedDirectiveDiagnostics ( body , Path { } ) ; len ( diagnostics ) != 0 {
2026-10-23 14:01:39 +00:00
return rootDirectiveExtraction { } , diagnostics
}
return rootDirectiveExtraction { body : body } , nil
}
func parseRootDirectiveBlock ( node * document . Node , directivePath Path , context rootDirectiveContext ) ( rootDirectiveExtraction , [ ] Diagnostic ) {
if node == nil || node . Kind != document . Mapping {
return rootDirectiveExtraction { } , [ ] Diagnostic { nodeImportDiagnostic ( CodeImportInvalid , directivePath , "configuration directive must be a mapping" , node ) }
}
var extracted rootDirectiveExtraction
var diagnostics [ ] Diagnostic
for _ , entry := range node . Mapping {
fieldPath := directivePath . Key ( entry . Key )
switch entry . Key {
case "imports" :
extracted . importsPresent = true
if context == rootDirectiveContextOverlay {
diagnostics = append ( diagnostics , nodeImportDiagnostic ( CodeOverlayDirective , fieldPath , "overlays cannot contain imports" , entry . Value ) )
continue
}
2026-10-23 19:49:12 +00:00
if context != rootDirectiveContextImportsOnly && context != rootDirectiveContextSource && context != rootDirectiveContextLayered {
2026-10-23 14:01:39 +00:00
diagnostics = append ( diagnostics , nodeImportDiagnostic ( CodeImportInvalid , fieldPath , "directive extraction context is invalid" , entry . Value ) )
continue
}
parsed , issues := parseImportDeclarations ( entry . Value , fieldPath )
extracted . imports = append ( extracted . imports , parsed ... )
diagnostics = append ( diagnostics , issues ... )
case "sequences" :
extracted . sequencesPresent = true
if context == rootDirectiveContextImportsOnly {
diagnostics = append ( diagnostics , sequenceDeclarationDiagnostic ( fieldPath , "sequence declarations are not enabled in this extraction context" , entry . Value ) )
continue
}
2026-10-23 19:49:12 +00:00
if context != rootDirectiveContextSource && context != rootDirectiveContextOverlay && context != rootDirectiveContextLayered {
2026-10-23 14:01:39 +00:00
diagnostics = append ( diagnostics , sequenceDeclarationDiagnostic ( fieldPath , "directive extraction context is invalid" , entry . Value ) )
continue
}
parsed , issues := parseSequenceDeclarationDirective ( entry . Value , fieldPath )
extracted . sequences = append ( extracted . sequences , parsed ... )
diagnostics = append ( diagnostics , issues ... )
default :
diagnostics = append ( diagnostics , nodeImportDiagnostic ( CodeImportInvalid , fieldPath , "configuration directive field is not recognized" , entry . Value ) )
}
}
if len ( diagnostics ) != 0 {
return rootDirectiveExtraction { } , canonicalDiagnostics ( diagnostics )
2026-09-12 15:58:06 +00:00
}
2026-10-23 14:01:39 +00:00
return extracted , nil
2026-09-12 15:58:06 +00:00
}
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-23 18:07:15 +00:00
projected , ok := assigned . inputForOutput ( index )
if ! ok {
2026-10-16 19:11:08 +00:00
return nil
}
2026-10-23 18:07:15 +00:00
index = projected
2026-10-16 19:11:08 +00:00
}
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-10-23 19:30:48 +00:00
func overlayDirectiveDiagnostics ( node * document . Node , current Path , allowSequences ... bool ) [ ] Diagnostic {
sequencesAllowed := len ( allowSequences ) != 0 && allowSequences [ 0 ]
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 {
2026-10-23 19:30:48 +00:00
if sequencesAllowed && len ( current . segments ) == 0 && entry . Value != nil && entry . Value . Kind == document . Mapping && len ( entry . Value . Mapping ) == 1 && entry . Value . Mapping [ 0 ] . Key == "sequences" {
continue
}
2026-09-12 15:58:06 +00:00
location := publicLocation ( entry . KeyLocation )
return [ ] Diagnostic { {
2026-09-23 18:27:29 +00:00
Code : CodeOverlayDirective , Severity : SeverityError , Path : childPath ,
2026-10-23 19:30:48 +00:00
Message : "overlays may contain only root collection sequence declarations" , Primary : & location ,
2026-09-12 15:58:06 +00:00
} }
}
2026-10-23 19:30:48 +00:00
if diagnostics := overlayDirectiveDiagnostics ( entry . Value , childPath , sequencesAllowed ) ; 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-10-23 19:30:48 +00:00
if diagnostics := overlayDirectiveDiagnostics ( child , current . Index ( index ) , sequencesAllowed ) ; 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
}
2026-10-23 19:12:39 +00:00
func disabledRootImportDiagnostics ( node * document . Node ) [ ] Diagnostic {
if node == nil || node . Kind != document . Mapping {
return nil
}
var rootDirective * document . Node
var rootLocation document . Location
var foundRootDirective bool
for index := range node . Mapping {
if node . Mapping [ index ] . Key == directiveKey {
rootDirective = node . Mapping [ index ] . Value
rootLocation = node . Mapping [ index ] . KeyLocation
foundRootDirective = true
break
}
}
if foundRootDirective {
if rootDirective == nil || rootDirective . Kind != document . Mapping {
return disabledDirectiveDiagnostic ( rootLocation )
}
for _ , directive := range rootDirective . Mapping {
if directive . Key == "imports" {
return disabledDirectiveDiagnostic ( rootLocation )
}
}
}
for _ , entry := range node . Mapping {
if entry . Key == directiveKey {
continue
}
if diagnostic := disabledNestedDirectiveDiagnostics ( entry . Value , Path { } . Key ( entry . Key ) ) ; len ( diagnostic ) != 0 {
return diagnostic
}
}
return nil
}
func disabledNestedDirectiveDiagnostics ( node * document . Node , path Path ) [ ] 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 }
return [ ] Diagnostic { {
Code : CodeImportDisabled , Severity : SeverityError , Path : childPath ,
Message : "configuration directives require imports to be enabled" , Primary : & location ,
} }
}
if diagnostics := disabledNestedDirectiveDiagnostics ( entry . Value , childPath ) ; len ( diagnostics ) != 0 {
return diagnostics
}
}
} else if node . Kind == document . Sequence {
for index , child := range node . Sequence {
if diagnostics := disabledNestedDirectiveDiagnostics ( child , path . Index ( index ) ) ; len ( diagnostics ) != 0 {
return diagnostics
}
}
}
return nil
}
func disabledDirectiveDiagnostic ( location document . Location ) [ ] Diagnostic {
public := Location { SourceID : location . SourceID , Line : location . Line , Column : location . Column }
return [ ] Diagnostic { {
Code : CodeImportDisabled , Severity : SeverityError , Path : Keys ( directiveKey ) ,
Message : "configuration directives require imports to be enabled" , Primary : & public ,
} }
}