use std::path::{Path, PathBuf};
use crate::{SourceRef, SourceRefKind};
pub fn resolve_source_ref_targets(
source_path: &Path,
source_ref: &SourceRef,
roots: &[String],
root_base: &Path,
) -> Option<PathBuf> {
source_ref_candidate_paths(source_path, source_ref, roots, root_base)
.into_iter()
.find(|path| path.is_file())
}
pub fn source_ref_candidate_paths(
source_path: &Path,
source_ref: &SourceRef,
roots: &[String],
root_base: &Path,
) -> Vec<PathBuf> {
let candidate = match &source_ref.kind {
SourceRefKind::Literal(candidate) | SourceRefKind::Directive(candidate) => {
candidate.as_str()
}
SourceRefKind::DirectiveDevNull
| SourceRefKind::Dynamic
| SourceRefKind::SingleVariableStaticTail { .. } => return Vec::new(),
};
candidate_paths(source_path, candidate, roots, root_base)
}
pub fn resolve_candidate_targets(
source_path: &Path,
candidate: &str,
roots: &[String],
root_base: &Path,
) -> Option<PathBuf> {
candidate_paths(source_path, candidate, roots, root_base)
.into_iter()
.find(|path| path.is_file())
}
fn candidate_paths(
source_path: &Path,
candidate: &str,
roots: &[String],
root_base: &Path,
) -> Vec<PathBuf> {
let candidate_path = PathBuf::from(candidate);
if candidate_path.is_absolute() {
return vec![candidate_path];
}
let mut candidates = Vec::new();
if let Some(base_dir) = source_path.parent() {
candidates.push(base_dir.join(&candidate_path));
}
candidates.extend(candidate_paths_against_roots(
source_path,
candidate,
roots,
root_base,
));
candidates.dedup();
candidates
}
pub fn resolve_candidate_against_roots(
source_path: &Path,
candidate: &str,
roots: &[String],
root_base: &Path,
) -> Vec<PathBuf> {
candidate_paths_against_roots(source_path, candidate, roots, root_base)
.into_iter()
.filter(|path| path.is_file())
.collect()
}
fn candidate_paths_against_roots(
source_path: &Path,
candidate: &str,
roots: &[String],
root_base: &Path,
) -> Vec<PathBuf> {
let candidate_path = Path::new(candidate);
let mut candidates = Vec::new();
for root in roots {
let root_path = if root == "SCRIPTDIR" {
source_path.parent().unwrap_or(Path::new("")).to_path_buf()
} else {
let root_path = PathBuf::from(root);
if root_path.is_absolute() {
root_path
} else {
root_base.join(root_path)
}
};
let joined = root_path.join(candidate_path);
candidates.push(joined);
}
candidates
}
#[cfg(test)]
mod tests {
use std::fs;
use super::*;
#[test]
fn candidate_next_to_annotating_file_shadows_root_matches() {
let tempdir = tempfile::tempdir().unwrap();
let base = tempdir.path();
fs::create_dir_all(base.join("scripts")).unwrap();
fs::create_dir_all(base.join("lib")).unwrap();
fs::write(base.join("scripts/util.sh"), "").unwrap();
fs::write(base.join("lib/util.sh"), "").unwrap();
let resolved = resolve_candidate_targets(
&base.join("scripts/main.sh"),
"util.sh",
&["lib".to_owned()],
base,
);
assert_eq!(
resolved,
Some(base.join("scripts/util.sh")),
"the annotating file's own directory wins over configured roots"
);
}
#[test]
fn earlier_configured_root_shadows_later_ones() {
let tempdir = tempfile::tempdir().unwrap();
let base = tempdir.path();
fs::create_dir_all(base.join("first")).unwrap();
fs::create_dir_all(base.join("second")).unwrap();
fs::write(base.join("first/util.sh"), "").unwrap();
fs::write(base.join("second/util.sh"), "").unwrap();
let resolved = resolve_candidate_targets(
&base.join("scripts/main.sh"),
"util.sh",
&["first".to_owned(), "second".to_owned()],
base,
);
assert_eq!(
resolved,
Some(base.join("first/util.sh")),
"roots are searched in configured order, first match wins"
);
}
#[test]
fn unresolvable_candidate_yields_none() {
let tempdir = tempfile::tempdir().unwrap();
let base = tempdir.path();
let resolved = resolve_candidate_targets(
&base.join("scripts/main.sh"),
"missing.sh",
&["lib".to_owned()],
base,
);
assert_eq!(resolved, None);
}
#[test]
fn source_ref_candidates_include_missing_paths_before_the_winner() {
let tempdir = tempfile::tempdir().unwrap();
let base = tempdir.path();
fs::create_dir_all(base.join("scripts")).unwrap();
fs::create_dir_all(base.join("lib")).unwrap();
fs::write(base.join("lib/util.sh"), "").unwrap();
let source_ref = SourceRef {
kind: SourceRefKind::Directive("util.sh".into()),
span: Default::default(),
path_span: Default::default(),
resolution: crate::SourceRefResolution::Unchecked,
explicitly_provided: false,
directive: None,
diagnostic_class: crate::SourceRefDiagnosticClass::UntrackedFile,
};
assert_eq!(
source_ref_candidate_paths(
&base.join("scripts/main.sh"),
&source_ref,
&["lib".to_owned()],
base,
),
vec![base.join("scripts/util.sh"), base.join("lib/util.sh")]
);
}
}