// High-level cached filtering pipeline for build systems and index consumers.
///|
pub enum PipelineError {
InvalidLimit
} derive(Debug, Eq)
///|
pub impl Show for PipelineError with fn output(self, logger) {
match self {
PipelineError::InvalidLimit => logger.write_string("InvalidLimit")
}
}
///|
pub(all) struct PipelineReport {
scanned : Int
included : Int
excluded : Int
returned : Int
truncated : Bool
results : Array[String]
cache_hits : Int
cache_misses : Int
} derive(Debug, Eq)
///|
pub(all) struct GlobPipeline {
cache : PatternCache
includes : Array[String]
excludes : Array[String]
options : GlobOptions
limit : Int?
} derive(Debug, Eq)
///|
/// Creates a pipeline with a bounded compiled-pattern cache.
pub fn GlobPipeline::new(
cache_capacity : Int,
) -> Result[GlobPipeline, CacheError] {
match PatternCache::new(cache_capacity) {
Err(err) => Err(err)
Ok(cache) =>
Ok({
cache,
includes: [],
excludes: [],
options: GlobOptions::default(),
limit: None,
})
}
}
///|
pub fn GlobPipeline::include_pattern(
self : GlobPipeline,
pattern : String,
) -> GlobPipeline {
let includes = self.includes.copy()
includes.push(pattern)
{ ..self, includes, }
}
///|
pub fn GlobPipeline::exclude_pattern(
self : GlobPipeline,
pattern : String,
) -> GlobPipeline {
let excludes = self.excludes.copy()
excludes.push(pattern)
{ ..self, excludes, }
}
///|
pub fn GlobPipeline::with_options(
self : GlobPipeline,
options : GlobOptions,
) -> GlobPipeline {
{ ..self, options, }
}
///|
pub fn GlobPipeline::sorted(self : GlobPipeline) -> GlobPipeline {
{ ..self, options: self.options.sorted() }
}
///|
pub fn GlobPipeline::without_hidden(self : GlobPipeline) -> GlobPipeline {
{ ..self, options: self.options.without_hidden() }
}
///|
pub fn GlobPipeline::with_limit(
self : GlobPipeline,
limit : Int,
) -> Result[GlobPipeline, PipelineError] {
if limit < 0 {
Err(PipelineError::InvalidLimit)
} else {
Ok({ ..self, limit: Some(limit) })
}
}
///|
pub fn GlobPipeline::include_count(self : GlobPipeline) -> Int {
self.includes.length()
}
///|
pub fn GlobPipeline::exclude_count(self : GlobPipeline) -> Int {
self.excludes.length()
}
///|
fn within_options(path : String, options : GlobOptions) -> Bool {
if !options.include_hidden && is_hidden_path(path) {
return false
}
match options.max_depth {
None => true
Some(max_depth) => path_depth(path) <= max_depth
}
}
///|
fn pipeline_matches(
cache : PatternCache,
patterns : Array[String],
path : String,
) -> Result[(PatternCache, Bool), GlobError] {
if patterns.is_empty() {
Ok((cache, true))
} else {
cache.match_any(patterns, [path])
}
}
///|
/// Runs include/exclude matching once over a candidate path array.
pub fn GlobPipeline::run_with_cache(
self : GlobPipeline,
paths : Array[String],
) -> Result[(GlobPipeline, PipelineReport), GlobError] {
let mut cache = self.cache
let results : Array[String] = []
let mut included = 0
let mut excluded = 0
let mut truncated = false
for raw_path in paths {
let path = normalize_path(raw_path)
if !within_options(path, self.options) {
continue
}
match pipeline_matches(cache, self.includes, path) {
Err(err) => return Err(err)
Ok((include_cache, included_match)) => {
cache = include_cache
if !included_match {
continue
}
match pipeline_matches(cache, self.excludes, path) {
Err(err) => return Err(err)
Ok((exclude_cache, excluded_match)) => {
cache = exclude_cache
if excluded_match && !self.excludes.is_empty() {
excluded = excluded + 1
continue
}
included = included + 1
match self.limit {
None => if !results.contains(path) { results.push(path) }
Some(limit) =>
if results.length() < limit {
if !results.contains(path) {
results.push(path)
}
} else {
truncated = true
}
}
}
}
}
}
}
if self.options.sort_results {
results.sort()
}
let stats = cache.stats()
let report = {
scanned: paths.length(),
included,
excluded,
returned: results.length(),
truncated,
results,
cache_hits: stats.hits,
cache_misses: stats.misses,
}
Ok(({ ..self, cache, }, report))
}
///|
/// Runs a pipeline and returns only the report for one-shot callers.
pub fn GlobPipeline::run(
self : GlobPipeline,
paths : Array[String],
) -> Result[PipelineReport, GlobError] {
match self.run_with_cache(paths) {
Err(err) => Err(err)
Ok((_, report)) => Ok(report)
}
}
///|
pub fn PipelineReport::summary(self : PipelineReport) -> String {
"scanned=" +
self.scanned.to_string() +
" included=" +
self.included.to_string() +
" excluded=" +
self.excluded.to_string() +
" returned=" +
self.returned.to_string() +
" truncated=" +
self.truncated.to_string() +
" cache_hits=" +
self.cache_hits.to_string()
}
///|
pub fn PipelineReport::has_results(self : PipelineReport) -> Bool {
!self.results.is_empty()
}
///|
pub fn PipelineReport::csv_header(_self : PipelineReport) -> String {
"scanned,included,excluded,returned,truncated,cache_hits,cache_misses"
}
///|
pub fn PipelineReport::csv_row(self : PipelineReport) -> String {
self.scanned.to_string() +
"," +
self.included.to_string() +
"," +
self.excluded.to_string() +
"," +
self.returned.to_string() +
"," +
self.truncated.to_string() +
"," +
self.cache_hits.to_string() +
"," +
self.cache_misses.to_string()
}