use crate::error::{FlecheError, Result};
use crate::registry::{ArchivedFilter, JobRecord, JobStatus, Registry, build_job_filter_pattern};
use regex::{Regex, RegexBuilder};
#[derive(Clone, Copy, Default)]
pub struct JobFilters<'a> {
pub statuses: &'a [String],
pub name: Option<&'a str>,
pub tags: &'a [(String, String)],
pub note: Option<&'a str>,
}
impl JobFilters<'_> {
pub fn is_empty(&self) -> bool {
self.statuses.is_empty()
&& self.name.is_none()
&& self.tags.is_empty()
&& self.note.is_none()
}
}
pub fn parse_statuses(statuses: &[String]) -> Result<Vec<JobStatus>> {
statuses.iter().map(|s| s.parse()).collect()
}
pub fn list_matching(
registry: &Registry,
filters: JobFilters<'_>,
limit: usize,
) -> Result<Vec<JobRecord>> {
list_matching_archived(registry, filters, ArchivedFilter::ExcludeArchived, limit)
}
pub fn list_matching_archived(
registry: &Registry,
filters: JobFilters<'_>,
archived: ArchivedFilter,
limit: usize,
) -> Result<Vec<JobRecord>> {
let statuses = parse_statuses(filters.statuses)?;
registry.list_jobs(
None,
&statuses,
filters.name,
filters.note,
filters.tags,
archived,
limit,
)
}
pub fn resolve_job(
registry: &Registry,
job_id: Option<&str>,
filters: JobFilters<'_>,
) -> Result<JobRecord> {
if let Some(id) = job_id {
registry.get_job(id)
} else {
list_matching(registry, filters, 1)?
.into_iter()
.next()
.ok_or(FlecheError::NoRecentJob)
}
}
pub struct CompiledFilters {
statuses: Vec<JobStatus>,
name: Option<Regex>,
note: Option<Regex>,
tags: Vec<(String, String)>,
}
impl CompiledFilters {
pub fn compile(filters: JobFilters<'_>) -> Result<Self> {
let name = filters
.name
.map(|p| {
Regex::new(&build_job_filter_pattern(p))
.map_err(|e| FlecheError::InvalidRegexPattern(format!("name '{p}': {e}")))
})
.transpose()?;
let note = filters
.note
.map(|p| {
RegexBuilder::new(&build_job_filter_pattern(p))
.case_insensitive(true)
.build()
.map_err(|e| FlecheError::InvalidRegexPattern(format!("note '{p}': {e}")))
})
.transpose()?;
Ok(Self {
statuses: parse_statuses(filters.statuses)?,
name,
note,
tags: filters.tags.to_vec(),
})
}
pub fn matches(&self, job: &JobRecord) -> bool {
(self.statuses.is_empty() || self.statuses.contains(&job.status))
&& self.name.as_ref().is_none_or(|re| re.is_match(&job.id))
&& self
.note
.as_ref()
.is_none_or(|re| job.note.as_ref().is_some_and(|n| re.is_match(n)))
&& self.tags.iter().all(|(k, v)| job.tags.get(k) == Some(v))
}
}