///|
/// A validated, deduplicated exclusion scope. Build with parse_scope.
pub struct ExclusionScope {
priv patterns : Array[String]
priv compiled : Array[PathPattern]
}
///|
/// Validate every pattern; the whole scope fails on the first invalid one.
/// Patterns are deduplicated by exact spelling and kept in ordinal order so the
/// delivered report is independent of config and command-line ordering.
pub fn parse_scope(
patterns : Array[String],
) -> Result[ExclusionScope, InputError] {
let unique : Map[String, Bool] = Map([])
for pattern in patterns {
unique[pattern] = true
}
let keys = sorted_keys(unique)
let compiled : Array[PathPattern] = []
for pattern in keys {
match parse_pattern(pattern) {
Err(error) => return Err(error)
Ok(compiled_pattern) => compiled.push(compiled_pattern)
}
}
Ok({ patterns: keys, compiled, })
}
///|
/// The normalized scope recorded in reports: unique patterns in ordinal order.
pub fn effective_scope_patterns(scope : ExclusionScope) -> Array[String] {
scope.patterns.copy()
}
///|
/// True when any pattern matches the entry. Structurally invalid paths never
/// match (pattern_matches guards on path structure), so they stay in scope.
pub fn entry_excluded(scope : ExclusionScope, entry : PathEntry) -> Bool {
for pattern in scope.compiled {
if pattern_matches(pattern, entry) {
return true
}
}
false
}
///|
/// Explicit directories plus the implicit directories implied by every entry
/// prefix become directories in this delivery tree.
fn directory_set(entries : Array[PathEntry]) -> Map[String, Bool] {
let directories : Map[String, Bool] = Map([])
for entry in entries {
guard check_path(entry.path, 1, []) is Some(parts) else { continue }
if entry.kind == Directory {
directories[entry.path] = true
}
let mut prefix = ""
for i in 0..<(parts.length() - 1) {
prefix = if i == 0 { parts[0] } else { prefix + "/" + parts[i] }
directories[prefix] = true
}
}
directories
}
///|
/// Directories whose whole subtree is excluded, in ordinal order. Only the
/// top-most excluded directory of each path is reported: a real scan prunes at
/// the first matching ancestor and never visits deeper directories, so a check
/// over the equivalent manifest must agree by reporting the same minimal set.
/// Computed over the full (pre-filter) input so an excluded subtree's own
/// implicit directory is still represented. Descendant counts are never
/// extrapolated.
fn pruned_directories(
scope : ExclusionScope,
entries : Array[PathEntry],
) -> Array[String] {
let directories = directory_set(entries)
let matching : Map[String, Bool] = Map([])
for directory in sorted_keys(directories) {
if entry_excluded(scope, { path: directory, kind: Directory, }) {
matching[directory] = true
}
}
let pruned : Array[String] = []
for directory in sorted_keys(matching) {
if !has_pruned_ancestor(directory, matching) {
pruned.push(directory)
}
}
pruned
}
///|
/// Whether a proper ancestor of this directory is itself fully excluded.
fn has_pruned_ancestor(path : String, matching : Map[String, Bool]) -> Bool {
guard check_path(path, 1, []) is Some(parts) else { return false }
if parts.length() <= 1 {
return false
}
let mut prefix = parts[0]
for i in 1.. DetailedReport {
let retained : Array[PathEntry] = []
let mut excluded = 0
for entry in entries {
if entry_excluded(scope, entry) {
excluded += 1
} else {
retained.push(entry)
}
}
let base = audit_with_options(retained, options)
{
schema_version: base.schema_version,
profile: base.profile,
source: base.source,
complete: base.complete,
summary: base.summary,
diagnostics: base.diagnostics,
limitations: base.limitations,
scan_issues: base.scan_issues,
scope: scope.patterns.copy(),
excluded_entries: excluded,
pruned_directories: pruned_directories(scope, entries),
}
}