package engine import ( "path" "strings" "gitea.maximumdirect.net/eric/ConfigKit/internal/document" ) const directiveKey = "configkit" func splitImportDirectives(root *document.Node) (*document.Node, []importDeclarationSpec, []Diagnostic) { body := document.Clone(root) var declarations []importDeclarationSpec directiveIndex := -1 for index, entry := range body.Mapping { if entry.Key != directiveKey { continue } directiveIndex = index parsed, diagnostics := parseImportSpecificationDirective(entry.Value, Keys(directiveKey)) 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 { return fragmentVersionClaimProjected(root, rule, -1, nil) } func fragmentVersionClaimProjected(root *document.Node, rule *VersionRule, sourceIndex int, ranges []sequenceRange) *document.Node { return fragmentVersionClaimWithIndex(root, rule, sourceIndex, indexSequenceRanges(ranges)) } func fragmentVersionClaimWithIndex(root *document.Node, rule *VersionRule, sourceIndex int, ranges sequenceRangeIndex) *document.Node { if rule == nil { return nil } current := root currentPath := Path{} if len(rule.Path.segments) == 0 { return current } for _, segment := range rule.Path.segments { 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: index, valid := schemaIndex(segment) if !valid { return current } if assigned, found := ranges[sequenceRangeKey{sourceIndex: sourceIndex, path: currentPath.String()}]; found { 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. 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} } func overlayDirectiveDiagnostics(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: CodeOverlayDirective, Severity: SeverityError, Path: childPath, Message: "overlays cannot contain configuration directives", Primary: &location, }} } if diagnostics := overlayDirectiveDiagnostics(entry.Value, childPath); len(diagnostics) != 0 { return diagnostics } } } else if node.Kind == document.Sequence { for index, child := range node.Sequence { if diagnostics := overlayDirectiveDiagnostics(child, current.Index(index)); len(diagnostics) != 0 { 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 }