use std::collections::BTreeSet;
use anyhow::{Result, bail};
use globset::{Glob, GlobSet, GlobSetBuilder};
use crate::components::ComponentMap;
use crate::config::Config;
#[derive(Default)]
pub struct Scope {
components: BTreeSet<String>,
paths: Option<GlobSet>,
}
impl Scope {
pub fn new(config: &Config, components: &[String], paths: &[String]) -> Result<Self> {
for name in components {
if !config.components.contains_key(name) {
bail!("--component names unknown component `{name}`");
}
}
let paths = if paths.is_empty() {
None
} else {
let mut builder = GlobSetBuilder::new();
for path in paths {
let path = path.trim_start_matches("./").trim_end_matches('/');
for glob in [path.to_string(), format!("{path}/**")] {
builder.add(Glob::new(&glob)?);
}
}
Some(builder.build()?)
};
Ok(Self {
components: components.iter().cloned().collect(),
paths,
})
}
pub fn contains(&self, map: &ComponentMap, file: &str) -> bool {
(self.components.is_empty()
|| map
.component_of(file)
.is_some_and(|c| self.components.contains(c)))
&& self.paths.as_ref().is_none_or(|p| p.is_match(file))
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn restricts_by_component_and_path() {
let config: Config = serde_norway::from_str(
"version: 1
components:
web: { in: web }
api: { in: api }
",
)
.unwrap();
let map = ComponentMap::new(&config).unwrap();
let all = Scope::default();
assert!(all.contains(&map, "anything/x.ts"));
let web = Scope::new(&config, &["web".into()], &[]).unwrap();
assert!(web.contains(&map, "web/a.ts"));
assert!(!web.contains(&map, "api/a.ts"));
assert!(!web.contains(&map, "other/a.ts"));
let path = Scope::new(&config, &[], &["web/pages".into()]).unwrap();
assert!(path.contains(&map, "web/pages/a.ts"));
assert!(!path.contains(&map, "web/other/a.ts"));
let both = Scope::new(&config, &["web".into()], &["api".into()]).unwrap();
assert!(!both.contains(&map, "web/a.ts"));
let err = Scope::new(&config, &["ghost".into()], &[]).err().unwrap();
assert!(err.to_string().contains("ghost"));
}
}