use slab::Slab;
use std::iter::Peekable;
use crate::id::matcher::matches::IntoIter;
use crate::id::matcher::Matches;
use crate::id::TryToId;
use super::condition::Condition;
use super::error::Result;
use super::Filter;
pub struct Candidates<'a> {
matches: Peekable<IntoIter>,
conditions: &'a Slab<Condition>,
negations: &'a [u32],
mapping: &'a [u32],
workset: Matches,
}
impl Filter {
#[inline]
pub fn candidates<T>(&self, id: &T) -> Result<Candidates<'_>>
where
T: TryToId,
{
let matches = self.matcher.matches(id)?;
Ok(Candidates {
matches: matches.into_iter().peekable(),
conditions: &self.conditions,
negations: &self.negations,
mapping: &self.mapping,
workset: Matches::new(),
})
}
}
impl Iterator for Candidates<'_> {
type Item = usize;
fn next(&mut self) -> Option<Self::Item> {
loop {
self.workset.clear();
let opt = self.matches.peek().copied();
let check = if let Some(start) = opt {
let index = self.mapping[start];
let opt = self.negations.first().copied();
opt.filter(|&first| first <= index).map_or(index, |first| {
self.negations = &self.negations[1..];
first
})
} else if let Some(&first) = self.negations.first() {
self.negations = &self.negations[1..];
first
} else {
return None;
};
if let Some(mut start) = opt {
while start > 0 && self.mapping[start - 1] == check {
start -= 1;
}
while let Some(index) =
self.matches.next_if(|&index| self.mapping[index] == check)
{
self.workset.add(index - start);
}
}
let index = check as usize;
if self.conditions[index].satisfies(&self.workset) {
return Some(index);
}
}
}
}
#[cfg(test)]
mod tests {
mod matches {
use crate::id::expression::Expression;
use crate::id::filter::{Filter, Result};
use crate::selector;
#[test]
fn handles_any() -> Result {
let mut builder = Filter::builder();
let _ = builder.insert(Expression::any(|expr| {
expr.with(selector!(location = "**/*.jpg")?)?
.with(selector!(location = "**/*.png")?)
})?);
let filter = builder.build()?;
for (id, check) in [
("zri:file:::docs:image.jpg:", vec![0]),
("zri:file:::docs:image.png:", vec![0]),
("zri:file:::docs:image.gif:", vec![]),
] {
assert_eq!(
filter.candidates(&id)?.collect::<Vec<_>>(), check
);
}
Ok(())
}
#[test]
fn handles_all() -> Result {
let mut builder = Filter::builder();
let _ = builder.insert(Expression::all(|expr| {
expr.with(selector!(location = "**/*.md")?)?
.with(selector!(provider = "file")?)
})?);
let filter = builder.build()?;
for (id, check) in [
("zri:file:::docs:index.md:", vec![0]),
("zri:file:::docs:image.png:", vec![]),
("zri:git:::docs:image.md:", vec![]),
] {
assert_eq!(
filter.candidates(&id)?.collect::<Vec<_>>(), check
);
}
Ok(())
}
#[test]
fn handles_not() -> Result {
let mut builder = Filter::builder();
let _ = builder.insert(Expression::not(|expr| {
expr.with(selector!(location = "**/*.jpg")?)?
.with(selector!(location = "**/*.png")?)
})?);
let filter = builder.build()?;
for (id, check) in [
("zri:file:::docs:index.md:", vec![0]),
("zri:file:::docs:image.jpg:", vec![]),
("zri:file:::docs:image.png:", vec![]),
] {
assert_eq!(
filter.candidates(&id)?.collect::<Vec<_>>(), check
);
}
Ok(())
}
#[test]
fn handles_all_any() -> Result {
let mut builder = Filter::builder();
let _ = builder.insert(Expression::all(|expr| {
expr.with(selector!(provider = "file")?)?
.with(Expression::any(|expr| {
expr.with(selector!(location = "**/*.jpg")?)?
.with(selector!(location = "**/*.png")?)
}))
})?);
let filter = builder.build()?;
for (id, check) in [
("zri:file:::docs:index.md:", vec![]),
("zri:file:::docs:image.jpg:", vec![0]),
("zri:file:::docs:image.png:", vec![0]),
("zri:file:::docs:image.gif:", vec![]),
("zri:git:::docs:image.jpg:", vec![]),
("zri:git:::docs:image.png:", vec![]),
] {
assert_eq!(
filter.candidates(&id)?.collect::<Vec<_>>(), check
);
}
Ok(())
}
#[test]
fn handles_all_any_not() -> Result {
let mut builder = Filter::builder();
let _ = builder.insert(Expression::all(|expr| {
expr.with(selector!(provider = "file")?)?
.with(Expression::any(|expr| {
expr.with(selector!(context = "docs")?)? .with(Expression::not(|expr| {
expr.with(selector!(location = "**/*.jpg")?)?
.with(selector!(location = "**/*.png")?)
}),
)
}))
})?);
let filter = builder.build()?;
for (id, check) in [
("zri:file:::docs:index.md:", vec![0]),
("zri:file:::docs:image.jpg:", vec![0]),
("zri:file:::docs:image.png:", vec![0]),
("zri:file:::docs:image.gif:", vec![0]),
("zri:git:::docs:image.jpg:", vec![]),
("zri:git:::docs:image.png:", vec![]),
] {
assert_eq!(
filter.candidates(&id)?.collect::<Vec<_>>(), check
);
}
Ok(())
}
}
}