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 { 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 placement PlacementMode fieldSites map[string]importFieldSite } // 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 } type resolvedImportPolicy struct { required bool follow bool hidden bool recursive bool pattern []string depth int depthSet bool exclude []string destination Path placement PlacementMode } 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), } } 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...) } func validateImportDefaults(value ImportDefaults) []Diagnostic { for _, pattern := range value.Pattern { if _, err := path.Match(pattern, ""); err != nil { return []Diagnostic{optionDiagnostic(CodeOptionsInvalid, "import default pattern is invalid")} } } return nil } func resolveImportPolicy(spec importDeclarationSpec, defaults ImportDefaults) (resolvedImportPolicy, []Diagnostic) { policy := resolvedImportPolicy{ required: boolOr(spec.required, defaults.Required, false), 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 { policy.follow = boolOr(spec.follow, defaults.Follow, true) policy.hidden = boolOr(spec.hidden, defaults.Hidden, true) } if spec.selector == importSelectorDirectory { policy.recursive = boolOr(spec.recursive, defaults.Recursive, false) if spec.patternSet { policy.pattern = cloneStrings(spec.pattern) } else { policy.pattern = cloneStrings(defaults.Pattern) } } 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 } 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...) continue } specs = append(specs, spec) } 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), placement: PlacementModeMapping, 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": if diagnostic := parseImportBooleanAssignment(&spec, entry.Key, entry.Value, fieldPath); diagnostic != nil { return importDeclarationSpec{}, []Diagnostic{*diagnostic} } case "pattern": if diagnostic := parseImportWildcardAssignment(&spec, entry.Key, entry.Value, fieldPath); diagnostic != nil { return importDeclarationSpec{}, []Diagnostic{*diagnostic} } case "exclude": if diagnostic := parseImportWildcardAssignment(&spec, entry.Key, entry.Value, fieldPath); diagnostic != nil { return importDeclarationSpec{}, []Diagnostic{*diagnostic} } 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": placement, diagnostic := parseImportPlacement(entry.Value, fieldPath) if diagnostic != nil { return importDeclarationSpec{}, []Diagnostic{*diagnostic} } spec.placement = placement 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)} 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": spec.exclude = values } return nil } func parseImportPlacement(node *document.Node, fieldPath Path) (PlacementMode, *Diagnostic) { if node.Kind != document.String { diagnostic := nodeImportDiagnostic(CodeImportInvalid, fieldPath, "placement must be mapping or sequence-item", node) return PlacementModeMapping, &diagnostic } switch node.Text { case "mapping": return PlacementModeMapping, nil case "sequence-item": return PlacementModeSequenceItem, nil default: diagnostic := nodeImportDiagnostic(CodeImportInvalid, fieldPath, "placement must be mapping or sequence-item", node) return PlacementModeMapping, &diagnostic } } func parseImportSelectorValue(spec *importDeclarationSpec, field string, node *document.Node, selectorPath Path) *Diagnostic { switch spec.selector { case importSelectorPath, importSelectorDirectory: values, origins, ok, badIndex := parseStringSet(node) if !ok { diagnostic := stringSetDiagnostic(node, selectorPath, field, badIndex) return &diagnostic } for index, value := range values { if !validImportPath(value) { badPath, badNode := listMemberSite(node, selectorPath, origins[index]) diagnostic := nodeImportDiagnostic(CodeImportInvalid, badPath, field+" contains an invalid source path", badNode) return &diagnostic } badPath, badNode := listMemberSite(node, selectorPath, origins[index]) spec.selectorSites = append(spec.selectorSites, importFieldSite{path: badPath.clone(), location: publicLocation(badNode.Location)}) } spec.paths = values case importSelectorGlob: if node.Kind != document.String || !validImportPath(node.Text) { diagnostic := nodeImportDiagnostic(CodeImportInvalid, selectorPath, "glob must be a valid nonempty path pattern string", node) return &diagnostic } segments, ok := validGlobPattern(node.Text) if !ok { diagnostic := nodeImportDiagnostic(CodeImportInvalid, selectorPath, "glob pattern is invalid", node) return &diagnostic } spec.glob, spec.globSegments = node.Text, segments } return nil } 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 { value = maxInt continue // Saturate the bound, but validate every remaining character. } 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 }