use crate::config::{SkipCondition, WhenCondition};
use crate::error::Result;
use crate::variable::VariableStore;
pub fn should_skip(skip: Option<&SkipCondition>, vars: &VariableStore) -> Result<bool> {
match skip {
None => Ok(false),
Some(SkipCondition::Static(b)) => Ok(*b),
Some(SkipCondition::Variable(name)) => {
let val = vars.get_variable(name)?;
Ok(val.trim() == "true")
}
}
}
pub fn should_skip_due_to_when(
when: Option<&WhenCondition>,
vars: &VariableStore,
working_dir: Option<&std::path::Path>,
) -> Result<bool> {
let Some(cond) = when else {
return Ok(false);
};
let Some(pattern_raw) = cond.exists.as_deref() else {
return Ok(false);
};
let pattern = vars.resolve(pattern_raw)?;
let pattern_abs = if let Some(root) = working_dir {
let p = std::path::Path::new(&pattern);
if p.is_absolute() {
pattern
} else {
let escaped_root = glob::Pattern::escape(&root.to_string_lossy());
format!("{escaped_root}/{pattern}")
}
} else {
pattern
};
let mut any_match = false;
let mut had_io_error = false;
for entry in glob::glob(&pattern_abs).map_err(|e| {
crate::error::CruiseError::InvalidStepConfig(format!(
"when.exists glob is invalid after variable substitution: {e}"
))
})? {
match entry {
Ok(_) => {
any_match = true;
break;
}
Err(_) => {
had_io_error = true;
}
}
}
if had_io_error && !any_match {
eprintln!(
" warning: when.exists glob '{pattern_abs}' encountered I/O errors; treating as matched"
);
return Ok(false);
}
Ok(!any_match)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::variable::VariableStore;
fn empty_vars() -> VariableStore {
VariableStore::new(String::new())
}
#[test]
fn test_should_skip_none() -> crate::error::Result<()> {
assert!(!should_skip(None, &empty_vars())?);
Ok(())
}
#[test]
fn test_should_skip_false() -> crate::error::Result<()> {
assert!(!should_skip(
Some(&SkipCondition::Static(false)),
&empty_vars()
)?);
Ok(())
}
#[test]
fn test_should_skip_true() -> crate::error::Result<()> {
assert!(should_skip(
Some(&SkipCondition::Static(true)),
&empty_vars()
)?);
Ok(())
}
#[test]
fn test_should_skip_variable_true() -> crate::error::Result<()> {
let mut vars = VariableStore::new(String::new());
vars.set_prev_success(Some(true));
assert!(should_skip(
Some(&SkipCondition::Variable("prev.success".to_string())),
&vars
)?);
Ok(())
}
#[test]
fn test_should_skip_variable_false() -> crate::error::Result<()> {
let mut vars = VariableStore::new(String::new());
vars.set_prev_success(Some(false));
assert!(!should_skip(
Some(&SkipCondition::Variable("prev.success".to_string())),
&vars
)?);
Ok(())
}
#[test]
fn test_should_skip_variable_undefined() {
let vars = VariableStore::new(String::new());
let result = should_skip(
Some(&SkipCondition::Variable("prev.success".to_string())),
&vars,
);
assert!(result.is_err());
}
}
#[cfg(test)]
mod when_tests {
use super::*;
use crate::config::WhenCondition;
use crate::variable::VariableStore;
fn empty_vars() -> VariableStore {
VariableStore::new(String::new())
}
#[test]
fn test_when_none_returns_false() {
let result =
should_skip_due_to_when(None, &empty_vars(), None).unwrap_or_else(|e| panic!("{e:?}"));
assert!(!result);
}
#[test]
fn test_when_exists_field_is_none_returns_false() {
let when = WhenCondition { exists: None };
let result = should_skip_due_to_when(Some(&when), &empty_vars(), None)
.unwrap_or_else(|e| panic!("{e:?}"));
assert!(!result);
}
#[test]
fn test_when_exists_matching_file_returns_false() {
let dir = tempfile::tempdir().unwrap_or_else(|e| panic!("{e:?}"));
std::fs::write(dir.path().join("main.rs"), "fn main() {}")
.unwrap_or_else(|e| panic!("{e:?}"));
let when = WhenCondition {
exists: Some("*.rs".to_string()),
};
let result = should_skip_due_to_when(Some(&when), &empty_vars(), Some(dir.path()))
.unwrap_or_else(|e| panic!("{e:?}"));
assert!(!result, "file exists, step should not be skipped");
}
#[test]
fn test_when_exists_no_match_returns_true() {
let dir = tempfile::tempdir().unwrap_or_else(|e| panic!("{e:?}"));
let when = WhenCondition {
exists: Some("*.rs".to_string()),
};
let result = should_skip_due_to_when(Some(&when), &empty_vars(), Some(dir.path()))
.unwrap_or_else(|e| panic!("{e:?}"));
assert!(result, "no files, step should be skipped");
}
#[test]
fn test_when_exists_variable_in_glob_resolves() {
let dir = tempfile::tempdir().unwrap_or_else(|e| panic!("{e:?}"));
std::fs::write(dir.path().join("main.go"), "package main")
.unwrap_or_else(|e| panic!("{e:?}"));
let vars = VariableStore::new("go".to_string()); let when = WhenCondition {
exists: Some("*.{input}".to_string()),
};
let result = should_skip_due_to_when(Some(&when), &vars, Some(dir.path()))
.unwrap_or_else(|e| panic!("{e:?}"));
assert!(
!result,
"glob with variable substitution should match the file"
);
}
#[test]
fn test_when_exists_invalid_glob_returns_error() {
let when = WhenCondition {
exists: Some("[invalid".to_string()),
};
let result = should_skip_due_to_when(Some(&when), &empty_vars(), None);
assert!(result.is_err(), "invalid glob should return an error");
}
#[test]
fn test_when_exists_no_working_dir_uses_glob_as_is() {
let when = WhenCondition {
exists: Some("__nonexistent_test_file_xyz__.rs".to_string()),
};
let result = should_skip_due_to_when(Some(&when), &empty_vars(), None)
.unwrap_or_else(|e| panic!("{e:?}"));
assert!(result, "non-matching glob without working_dir should skip");
}
}