haz_query/engine/
filter.rs1use haz_dag::producer::anchor_to_workspace_absolute;
14use haz_domain::path::{InputSpec, OutputSpec, PathPattern, ProjectRoot};
15use haz_query_lang::expr::Expr;
16
17use crate::engine::candidate::CandidateTask;
18use crate::engine::spec::{QueryError, QuerySpec};
19use crate::expr::path;
20
21pub fn passes_non_relational(
38 candidate: &CandidateTask<'_>,
39 spec: &QuerySpec,
40 bearing_project_root: Option<&ProjectRoot>,
41) -> Result<bool, QueryError> {
42 if let Some(expr) = &spec.tags
43 && !expr.eval(|tag| candidate.project.tags.contains(tag))
44 {
45 return Ok(false);
46 }
47
48 if let Some(expr) = &spec.projects
49 && !expr.eval(|name| candidate.project_name == name)
50 {
51 return Ok(false);
52 }
53
54 if let Some(expr) = &spec.tasks
55 && !expr.eval(|name| candidate.task_name == name)
56 {
57 return Ok(false);
58 }
59
60 if let Some(expr) = &spec.inputs {
61 let task_canonical = canonicalise_task_patterns(
62 candidate.task.inputs.iter().map(InputSpec::pattern),
63 &candidate.project.root,
64 )?;
65 let matches = pattern_expr_matches(expr, &task_canonical, bearing_project_root)?;
66 if !matches {
67 return Ok(false);
68 }
69 }
70
71 if let Some(expr) = &spec.outputs {
72 let task_canonical = canonicalise_task_patterns(
73 candidate.task.outputs.iter().map(OutputSpec::pattern),
74 &candidate.project.root,
75 )?;
76 let matches = pattern_expr_matches(expr, &task_canonical, bearing_project_root)?;
77 if !matches {
78 return Ok(false);
79 }
80 }
81
82 for shortcut in &spec.shortcuts {
83 if !shortcut.matches(candidate.task) {
84 return Ok(false);
85 }
86 }
87
88 Ok(true)
89}
90
91fn canonicalise_task_patterns<'p>(
96 patterns: impl Iterator<Item = &'p PathPattern>,
97 project_root: &ProjectRoot,
98) -> Result<Vec<PathPattern>, QueryError> {
99 patterns
100 .map(|pattern| canonicalise(pattern, project_root))
101 .collect()
102}
103
104fn pattern_expr_matches(
109 expr: &Expr<PathPattern>,
110 task_canonical: &[PathPattern],
111 bearing_project_root: Option<&ProjectRoot>,
112) -> Result<bool, QueryError> {
113 expr.try_eval(|atom_pattern| {
114 let atom_canonical = canonicalise_user_atom(atom_pattern, bearing_project_root)?;
115 for task_pattern in task_canonical {
116 let hit = path::intersects(&atom_canonical, task_pattern)
117 .map_err(|source| QueryError::GlobIntersect { source })?;
118 if hit {
119 return Ok(true);
120 }
121 }
122 Ok(false)
123 })
124}
125
126fn canonicalise_user_atom(
131 atom: &PathPattern,
132 bearing_project_root: Option<&ProjectRoot>,
133) -> Result<PathPattern, QueryError> {
134 if matches!(
135 atom.anchor(),
136 haz_domain::path::PathAnchor::WorkspaceAbsolute
137 ) {
138 return Ok(atom.clone());
139 }
140 let Some(root) = bearing_project_root else {
141 return Err(QueryError::CanonicalisePattern {
142 canonical: atom.to_string(),
143 });
144 };
145 canonicalise(atom, root)
146}
147
148fn canonicalise(
150 pattern: &PathPattern,
151 project_root: &ProjectRoot,
152) -> Result<PathPattern, QueryError> {
153 let canonical_string = anchor_to_workspace_absolute(pattern, project_root);
154 PathPattern::parse(&canonical_string).map_err(|_| QueryError::CanonicalisePattern {
155 canonical: canonical_string,
156 })
157}