use std::path::{Component, Path, PathBuf};
use compact_str::CompactString;
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum CallOrigin {
SameLayer,
OtherLayer { signature: String },
OtherPackage { package: String },
ExternalPackage { package: String },
Stdlib,
UnresolvedMethod,
Macro,
}
impl CallOrigin {
pub fn encode(&self) -> CompactString {
match self {
Self::SameLayer => CompactString::new("_SAME_LAYER"),
Self::OtherLayer { signature } => {
let mut s = CompactString::new("_LAYER:");
s.push_str(signature);
s
}
Self::OtherPackage { package } => {
let mut s = CompactString::new("_PKG:");
s.push_str(package);
s
}
Self::ExternalPackage { package } => {
let mut s = CompactString::new("_EXT:");
s.push_str(package);
s
}
Self::Stdlib => CompactString::new("_STDLIB"),
Self::UnresolvedMethod => CompactString::new("_METHOD"),
Self::Macro => CompactString::new("_MACRO"),
}
}
}
pub fn layer_signature(caller_dir: &Path, callee_dir: &Path) -> Option<String> {
let caller_parts: Vec<&str> = path_components(caller_dir);
let callee_parts: Vec<&str> = path_components(callee_dir);
let common_len = caller_parts
.iter()
.zip(callee_parts.iter())
.take_while(|(a, b)| a == b)
.count();
if common_len == caller_parts.len() && common_len == callee_parts.len() {
return None;
}
let up_count = caller_parts.len() - common_len;
let mut out = String::new();
for _ in 0..up_count {
out.push_str("../");
}
if common_len < callee_parts.len() {
out.push_str(callee_parts[common_len]);
out.push('/');
}
Some(out)
}
fn path_components(p: &Path) -> Vec<&str> {
p.components()
.filter_map(|c| match c {
Component::Normal(s) => s.to_str(),
Component::CurDir => None,
_ => None,
})
.collect()
}
pub fn intra_package_origin(caller_dir: &Path, callee_dir: &Path) -> CallOrigin {
match layer_signature(caller_dir, callee_dir) {
None => CallOrigin::SameLayer,
Some(signature) => CallOrigin::OtherLayer { signature },
}
}
pub fn dir_of(file: &Path) -> PathBuf {
file.parent().map_or_else(PathBuf::new, Path::to_path_buf)
}
#[cfg(test)]
mod tests {
use super::*;
fn p(s: &str) -> PathBuf {
PathBuf::from(s)
}
#[test]
fn same_dir_returns_none_signature() {
assert_eq!(layer_signature(&p("src/commands"), &p("src/commands")), None);
}
#[test]
fn parent_dir_of_caller() {
assert_eq!(layer_signature(&p("src/commands"), &p("src")).as_deref(), Some("../"));
}
#[test]
fn sibling_layer_signature() {
assert_eq!(
layer_signature(&p("src/commands"), &p("src/loader")).as_deref(),
Some("../loader/"),
);
}
#[test]
fn deeper_callee_is_truncated_to_first_folder() {
assert_eq!(
layer_signature(&p("src/commands"), &p("src/loader/deep/inner")).as_deref(),
Some("../loader/"),
);
}
#[test]
fn cousin_at_two_levels_up() {
assert_eq!(
layer_signature(&p("src/commands/sub"), &p("src/anotherLayer")).as_deref(),
Some("../../anotherLayer/"),
);
}
#[test]
fn descendant_layer_no_up() {
assert_eq!(
layer_signature(&p("src/commands"), &p("src/commands/sub")).as_deref(),
Some("sub/"),
);
}
#[test]
fn intra_package_helper_returns_same_layer_when_equal() {
match intra_package_origin(&p("src/commands"), &p("src/commands")) {
CallOrigin::SameLayer => {}
other => panic!("expected SameLayer, got {other:?}"),
}
}
#[test]
fn intra_package_helper_returns_other_layer_when_diff() {
match intra_package_origin(&p("src/commands"), &p("src/loader")) {
CallOrigin::OtherLayer { signature } => assert_eq!(signature, "../loader/"),
other => panic!("expected OtherLayer, got {other:?}"),
}
}
#[test]
fn encode_same_layer() {
assert_eq!(CallOrigin::SameLayer.encode().as_str(), "_SAME_LAYER");
}
#[test]
fn encode_other_layer_includes_signature() {
let s = CallOrigin::OtherLayer { signature: "../loader/".into() }.encode();
assert_eq!(s.as_str(), "_LAYER:../loader/");
}
#[test]
fn encode_other_package() {
let s = CallOrigin::OtherPackage { package: "layer_conform_core".into() }.encode();
assert_eq!(s.as_str(), "_PKG:layer_conform_core");
}
#[test]
fn encode_external_package() {
let s = CallOrigin::ExternalPackage { package: "anyhow".into() }.encode();
assert_eq!(s.as_str(), "_EXT:anyhow");
}
#[test]
fn encode_stdlib_method_macro() {
assert_eq!(CallOrigin::Stdlib.encode().as_str(), "_STDLIB");
assert_eq!(CallOrigin::UnresolvedMethod.encode().as_str(), "_METHOD");
assert_eq!(CallOrigin::Macro.encode().as_str(), "_MACRO");
}
#[test]
fn dir_of_returns_parent() {
assert_eq!(dir_of(&p("src/commands/check.rs")), p("src/commands"));
}
#[test]
fn dir_of_returns_empty_for_bare_file() {
assert_eq!(dir_of(&p("check.rs")), PathBuf::new());
}
}