use std::path::{Path, PathBuf};
use fallow_engine::write_guard::{self, WriteScope, WriteTarget};
use crate::{Cli, Command};
pub fn write_path_error(cli: &Cli, root: &Path) -> Option<String> {
let targets = write_targets(cli, root);
let cwd = match std::env::current_dir() {
Ok(cwd) => cwd,
Err(err) => {
let (first_flag, _, _) = targets.first()?;
return Some(format!(
"{first_flag} cannot be checked, because the working directory cannot be read ({err}). Run fallow from an existing directory."
));
}
};
let shared = write_guard::shared_dirs();
let scopes = WriteScope::new(root, &cwd, shared.clone()).and_then(|scope| {
WriteScope::new(root, root, shared).map(|config_scope| (scope, config_scope))
});
let (scope, config_scope) = match scopes {
Ok(scopes) => scopes,
Err(message) => {
let (first_flag, _, _) = targets.first()?;
return Some(format!("{first_flag} cannot be checked: {message}"));
}
};
let error = targets
.iter()
.find_map(|(flag, path, target)| match target {
WriteTarget::DiscoveredConfig => config_scope.check(flag, path, &cwd),
WriteTarget::Path => scope.check(flag, path, &cwd),
});
if error.is_none() {
write_guard::confine(cwd, scope, config_scope);
}
error
}
pub fn default_cache_dir_note(cli: &Cli, root: &Path) -> Option<String> {
if cli.no_cache || crate::runtime_support::resolve_cache_dir_env().is_some() {
return None;
}
let cache_dir = root.join(".fallow");
cache_dir.symlink_metadata().ok()?;
let cwd = std::env::current_dir().ok()?;
let resolved = write_guard::resolve(&cache_dir);
let inside = WriteScope::new(root, &cwd, write_guard::shared_dirs())
.is_ok_and(|scope| scope.contains(&resolved));
(!inside).then(|| {
format!(
"note: {} resolves to {}, which is outside the project, so it is not used for the cache in this run. Remove the link, or set FALLOW_CACHE_DIR or cache.dir to relocate the cache.",
cache_dir.display(),
resolved.display()
)
})
}
fn write_targets(cli: &Cli, root: &Path) -> Vec<(&'static str, PathBuf, WriteTarget)> {
let mut targets = if matches!(
cli.command,
None | Some(Command::Check { .. } | Command::Dupes { .. } | Command::Health { .. })
) {
save_targets(cli, root)
} else {
Vec::new()
};
let writes_sarif = matches!(
cli.command,
None | Some(
Command::Check { .. }
| Command::Security {
subcommand: None,
..
}
)
);
for (flag, path) in [
("--output-file", cli.output_file.as_deref()),
(
"--sarif-file",
cli.sarif_file.as_deref().filter(|_| writes_sarif),
),
] {
if let Some(path) = path.filter(|path| !path.as_os_str().is_empty()) {
targets.push((flag, path.to_path_buf(), WriteTarget::Path));
}
}
targets
}
fn save_targets(cli: &Cli, root: &Path) -> Vec<(&'static str, PathBuf, WriteTarget)> {
let mut targets = Vec::new();
if let Some(path) = cli.save_baseline.as_deref() {
targets.push(("--save-baseline", path.to_path_buf(), WriteTarget::Path));
}
if let Some(value) = cli.save_regression_baseline.as_ref() {
match value.as_deref().filter(|path| !path.is_empty()) {
Some(path) => targets.push((
"--save-regression-baseline",
PathBuf::from(path),
WriteTarget::Path,
)),
None => targets.push((
"--save-regression-baseline",
crate::regression::regression_config_target(cli.config.as_deref(), root),
if cli.config.is_some() {
WriteTarget::Path
} else {
WriteTarget::DiscoveredConfig
},
)),
}
}
let health_snapshot = match cli.command.as_ref() {
Some(Command::Health { save_snapshot, .. }) => save_snapshot.as_ref(),
_ => None,
};
for snapshot in [cli.save_snapshot.as_ref(), health_snapshot]
.into_iter()
.flatten()
{
match snapshot.as_deref().filter(|path| !path.is_empty()) {
Some(path) => targets.push(("--save-snapshot", PathBuf::from(path), WriteTarget::Path)),
None => targets.push((
"--save-snapshot",
root.join(".fallow").join("snapshots").join("snapshot.json"),
WriteTarget::Path,
)),
}
}
targets.retain(|(_, path, _)| !path.as_os_str().is_empty());
targets
}