use std::sync::Arc;
use ignore::types::{Types, TypesBuilder};
use thiserror::Error;
#[derive(Debug, Error)]
pub enum FileTypeError {
#[error("unknown file type '{0}' (see --ftype-list)")]
UnknownType(String),
#[error("file-type filter error: {0}")]
Build(String),
}
pub fn build_file_types(
select: &[String],
negate: &[String],
) -> Result<Option<Arc<Types>>, FileTypeError> {
if select.is_empty() && negate.is_empty() {
return Ok(None);
}
let mut builder = TypesBuilder::new();
builder.add_defaults();
for name in select {
builder.select(name);
}
for name in negate {
builder.negate(name);
}
match builder.build() {
Ok(types) => Ok(Some(Arc::new(types))),
Err(ignore::Error::UnrecognizedFileType(name)) => Err(FileTypeError::UnknownType(name)),
Err(other) => Err(FileTypeError::Build(other.to_string())),
}
}
pub fn list_file_types() -> Vec<(String, Vec<String>)> {
let mut builder = TypesBuilder::new();
builder.add_defaults();
builder
.definitions()
.into_iter()
.map(|def| (def.name().to_string(), def.globs().to_vec()))
.collect()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn empty_lists_mean_no_filter() {
assert!(build_file_types(&[], &[]).expect("ok").is_none());
}
#[test]
fn select_known_type_builds() {
let types = build_file_types(&["rust".to_string()], &[]).expect("ok");
assert!(types.is_some());
}
#[test]
fn negate_known_type_builds() {
let types = build_file_types(&[], &["rust".to_string()]).expect("ok");
assert!(types.is_some());
}
#[test]
fn unknown_type_is_loud() {
let err = build_file_types(&["definitely-not-a-type".to_string()], &[])
.expect_err("unknown type must error");
assert!(
matches!(err, FileTypeError::UnknownType(ref name) if name == "definitely-not-a-type"),
"got {err:?}",
);
}
#[test]
fn list_is_nonempty_and_maps_rust_to_rs() {
let defs = list_file_types();
assert!(!defs.is_empty());
let (_, globs) = defs
.iter()
.find(|(name, _)| name == "rust")
.expect("rust type must be defined");
assert!(globs.iter().any(|g| g == "*.rs"), "rust globs: {globs:?}");
}
}