use std::str::FromStr;
use super::convert::TryToId;
mod builder;
mod component;
mod error;
pub mod matches;
pub mod selector;
pub use builder::Builder;
use component::Component;
pub use error::{Error, Result};
pub use matches::Matches;
#[derive(Debug, Default)]
pub struct Matcher {
provider: Component,
resource: Component,
variant: Component,
context: Component,
location: Component,
fragment: Component,
}
impl Matcher {
#[inline]
pub fn is_match<T>(&self, id: &T) -> Result<bool>
where
T: TryToId,
{
self.matches(id).map(|matches| !matches.is_empty())
}
#[allow(clippy::missing_panics_doc)]
pub fn matches<T>(&self, id: &T) -> Result<Matches>
where
T: TryToId,
{
let id = id.try_to_id()?;
let mut opt: Option<Matches> = None;
for (component, value) in [
(&self.location, Some(id.location())),
(&self.context, Some(id.context())),
(&self.provider, Some(id.provider())),
(&self.resource, id.resource()),
(&self.fragment, id.fragment()),
(&self.variant, id.variant()),
] {
let path = value.as_deref().unwrap_or("\u{FFFE}");
let matches = component.matches(path);
if let Some(tracked) = &mut opt {
tracked.intersect(&matches);
} else {
opt = Some(matches);
}
}
Ok(opt.expect("invariant"))
}
}
impl FromStr for Matcher {
type Err = Error;
fn from_str(value: &str) -> Result<Self> {
Matcher::builder().with(&value)?.build()
}
}
#[cfg(test)]
mod tests {
mod is_match {
use crate::id::matcher::{Matcher, Result};
#[test]
fn handles_selectors() -> Result {
for selector in &[
"zrs:file:::docs:index.md:",
"zrs::::docs:index.md:",
"zrs:::::index.md:",
"zrs::::::",
] {
let matcher: Matcher = selector.parse()?;
assert!(matcher.is_match(&"zri:file:::docs:index.md:")?);
}
Ok(())
}
#[test]
fn handles_wildcards() -> Result {
for selector in &[
"zrs:file:::docs:*.md:",
"zrs:::::*.md:",
"zrs:*::::*.md:",
"zrs:*:*:*:*:*:",
] {
let matcher: Matcher = selector.parse()?;
assert!(matcher.is_match(&"zri:file:::docs:index.md:")?);
}
Ok(())
}
#[test]
fn handles_optionals() -> Result {
for selector in &[
"zrs:{git,file}:::{docs}:index.md:",
"zrs::::docs:{index,about}.md:",
"zrs:::::index.{md,rst}:",
"zrs:::::{*}:",
] {
let matcher: Matcher = selector.parse()?;
assert!(matcher.is_match(&"zri:file:::docs:index.md:")?);
}
Ok(())
}
#[test]
fn handles_non_matches() -> Result {
for selector in &[
"zrs:file:::{docs}:index.md:anchor",
"zrs:{git,file}:master::::",
"zrs:::::about.md:",
"zrs::::::anchor",
] {
let matcher: Matcher = selector.parse()?;
assert!(!matcher.is_match(&"zri:file:::docs:index.md:")?);
}
Ok(())
}
}
mod matches {
use crate::id::matcher::{Matcher, Matches, Result};
#[test]
fn handles_selectors() -> Result {
for selector in &[
"zrs:file:::docs:index.md:",
"zrs::::docs:index.md:",
"zrs:::::index.md:",
"zrs::::::",
] {
let matcher: Matcher = selector.parse()?;
assert_eq!(
matcher.matches(&"zri:file:::docs:index.md:")?,
Matches::from_iter([0])
);
}
Ok(())
}
#[test]
fn handles_wildcards() -> Result {
for selector in &[
"zrs:file:::docs:*.md:",
"zrs:::::*.md:",
"zrs:*::::*.md:",
"zrs:*:*:*:*:*:",
] {
let matcher: Matcher = selector.parse()?;
assert_eq!(
matcher.matches(&"zri:file:::docs:index.md:")?,
Matches::from_iter([0])
);
}
Ok(())
}
#[test]
fn handles_optionals() -> Result {
for selector in &[
"zrs:{git,file}:::{docs}:index.md:",
"zrs::::docs:{index,about}.md:",
"zrs:::::index.{md,rst}:",
"zrs:::::{*}:",
] {
let matcher: Matcher = selector.parse()?;
assert_eq!(
matcher.matches(&"zri:file:::docs:index.md:")?,
Matches::from_iter([0])
);
}
Ok(())
}
#[test]
fn handles_non_matches() -> Result {
for selector in &[
"zrs:file:::{docs}:index.md:anchor",
"zrs:{git,file}:master::::",
"zrs:::::about.md:",
"zrs::::::anchor",
] {
let matcher: Matcher = selector.parse()?;
assert_eq!(
matcher.matches(&"zri:file:::docs:index.md:")?,
Matches::default()
);
}
Ok(())
}
}
}