use std::path::{Path, PathBuf};
use super::ResolvedCrateConfig;
use crate::core::config::extras::Language;
use crate::core::config::resolve_helpers::{find_after_crates_prefix, relative_slash_path};
impl ResolvedCrateConfig {
pub fn core_import_name(&self) -> String {
self.core_import.clone().unwrap_or_else(|| self.name.replace('-', "_"))
}
pub fn error_type_name(&self) -> String {
self.error_type.clone().unwrap_or_else(|| "Error".to_string())
}
pub fn error_constructor_expr(&self) -> String {
self.error_constructor
.clone()
.unwrap_or_else(|| format!("{}::{}::from({{msg}})", self.core_import_name(), self.error_type_name()))
}
pub fn core_crate_dir(&self) -> String {
if let Some(first_source) = self.sources.first() {
let path = std::path::Path::new(first_source);
let mut current = path.parent();
while let Some(dir) = current {
if dir.file_name().is_some_and(|n| n == "src") {
if let Some(crate_dir) = dir.parent()
&& let Some(dir_name) = crate_dir.file_name()
{
return dir_name.to_string_lossy().into_owned();
}
break;
}
current = dir.parent();
}
}
self.name.clone()
}
pub fn core_crate_root(&self) -> PathBuf {
if let Some(first_source) = self.sources.first() {
let path = Path::new(first_source);
let mut current = path.parent();
while let Some(dir) = current {
if dir.file_name().is_some_and(|n| n == "src") {
return dir.parent().map(Path::to_path_buf).unwrap_or_default();
}
current = dir.parent();
}
}
PathBuf::from("crates").join(&self.name)
}
pub fn core_crate_dep_path(&self, binding_root: &Path) -> String {
relative_slash_path(binding_root, &self.core_crate_root())
}
pub fn core_crate_dep_path_for_language(&self, binding_root: &Path, lang: Language) -> String {
let override_name = match lang {
Language::Wasm => self.wasm.as_ref().and_then(|c| c.core_crate_override.as_deref()),
Language::Dart => self.dart.as_ref().and_then(|c| c.core_crate_override.as_deref()),
Language::Swift => self.swift.as_ref().and_then(|c| c.core_crate_override.as_deref()),
_ => None,
};
match override_name {
Some(name) => {
let sibling_root = binding_root
.parent()
.map(Path::to_path_buf)
.unwrap_or_else(|| binding_root.to_path_buf())
.join(name);
relative_slash_path(binding_root, &sibling_root)
}
None => self.core_crate_dep_path(binding_root),
}
}
pub fn core_crate_for_language(&self, lang: Language) -> String {
let override_name = match lang {
Language::Wasm => self.wasm.as_ref().and_then(|c| c.core_crate_override.as_deref()),
Language::Dart => self.dart.as_ref().and_then(|c| c.core_crate_override.as_deref()),
Language::Swift => self.swift.as_ref().and_then(|c| c.core_crate_override.as_deref()),
_ => None,
};
match override_name {
Some(name) => name.to_string(),
None => self.core_crate_dir(),
}
}
pub fn core_import_for_language(&self, lang: Language) -> String {
let override_name = match lang {
Language::Wasm => self.wasm.as_ref().and_then(|c| c.core_crate_override.as_deref()),
Language::Dart => self.dart.as_ref().and_then(|c| c.core_crate_override.as_deref()),
Language::Swift => self.swift.as_ref().and_then(|c| c.core_crate_override.as_deref()),
_ => None,
};
match override_name {
Some(name) => name.replace('-', "_"),
None => self.core_import_name(),
}
}
pub fn effective_path_mappings(&self) -> std::collections::HashMap<String, String> {
let mut mappings = std::collections::HashMap::new();
if self.auto_path_mappings {
let core_import = self.core_import_name();
for source in &self.sources {
let source_str = source.to_string_lossy();
if let Some(after_crates) = find_after_crates_prefix(&source_str)
&& let Some(slash_pos) = after_crates.find('/')
{
let crate_dir = &after_crates[..slash_pos];
let crate_ident = crate_dir.replace('-', "_");
if crate_ident != core_import && !mappings.contains_key(&crate_ident) {
mappings.insert(crate_ident, core_import.clone());
}
}
}
}
for (from, to) in &self.path_mappings {
mappings.insert(from.clone(), to.clone());
}
mappings
}
}
#[cfg(test)]
mod tests {
use crate::core::config::new_config::NewAlefConfig;
fn resolved_one(toml: &str) -> super::super::ResolvedCrateConfig {
let cfg: NewAlefConfig = toml::from_str(toml).unwrap();
cfg.resolve().unwrap().remove(0)
}
fn minimal() -> super::super::ResolvedCrateConfig {
resolved_one(
r#"
[workspace]
languages = ["python"]
[[crates]]
name = "test-lib"
sources = ["src/lib.rs"]
"#,
)
}
#[test]
fn core_import_name_defaults_to_snake_case_name() {
let r = minimal();
assert_eq!(r.core_import_name(), "test_lib");
}
#[test]
fn core_import_name_explicit_wins() {
let r = resolved_one(
r#"
[workspace]
languages = ["python"]
[[crates]]
name = "test-lib"
sources = ["src/lib.rs"]
core_import = "custom_core"
"#,
);
assert_eq!(r.core_import_name(), "custom_core");
}
#[test]
fn error_type_name_defaults_to_error() {
let r = minimal();
assert_eq!(r.error_type_name(), "Error");
}
#[test]
fn error_type_name_explicit_wins() {
let r = resolved_one(
r#"
[workspace]
languages = ["python"]
[[crates]]
name = "test-lib"
sources = ["src/lib.rs"]
error_type = "MyError"
"#,
);
assert_eq!(r.error_type_name(), "MyError");
}
#[test]
fn error_constructor_expr_defaults_to_from_pattern() {
let r = minimal();
assert_eq!(r.error_constructor_expr(), "test_lib::Error::from({msg})");
}
#[test]
fn error_constructor_expr_explicit_wins() {
let r = resolved_one(
r#"
[workspace]
languages = ["python"]
[[crates]]
name = "test-lib"
sources = ["src/lib.rs"]
error_constructor = "MyError::new({msg})"
"#,
);
assert_eq!(r.error_constructor_expr(), "MyError::new({msg})");
}
#[test]
fn core_crate_dir_from_source_path() {
let r = resolved_one(
r#"
[workspace]
languages = ["python"]
[[crates]]
name = "test-lib"
sources = ["crates/my-core/src/lib.rs"]
"#,
);
assert_eq!(r.core_crate_dir(), "my-core");
}
#[test]
fn core_crate_dir_falls_back_to_name() {
let r = minimal();
assert_eq!(r.core_crate_dir(), "test-lib");
}
#[test]
fn core_crate_root_is_empty_for_a_root_flat_core_crate() {
let r = minimal();
assert_eq!(r.core_crate_root(), super::PathBuf::new());
}
#[test]
fn core_crate_root_is_the_crates_sibling_for_a_workspace_core_crate() {
let r = resolved_one(
r#"
[workspace]
languages = ["python"]
[[crates]]
name = "test-lib"
sources = ["crates/my-core/src/lib.rs"]
"#,
);
assert_eq!(r.core_crate_root(), super::PathBuf::from("crates/my-core"));
}
#[test]
fn core_crate_dep_path_covers_both_layouts_from_the_default_binding_crate_root() {
let root_flat = minimal();
assert_eq!(
root_flat.core_crate_dep_path(std::path::Path::new("crates/test-lib-ffi")),
"../..",
"root-flat: binding crate is two levels below the core crate's own Cargo.toml"
);
let workspace = resolved_one(
r#"
[workspace]
languages = ["python"]
[[crates]]
name = "test-lib"
sources = ["crates/my-core/src/lib.rs"]
"#,
);
assert_eq!(
workspace.core_crate_dep_path(std::path::Path::new("crates/test-lib-ffi")),
"../my-core",
"workspace: core crate is a `crates/` sibling of the binding crate"
);
}
#[test]
fn core_crate_dep_path_for_language_targets_the_override_sibling_not_sources() {
use crate::core::config::extras::Language;
let r = resolved_one(
r#"
[workspace]
languages = ["wasm"]
[[crates]]
name = "mylib"
sources = ["src/lib.rs"]
[crates.wasm]
core_crate_override = "mylib-core"
"#,
);
assert_eq!(
r.core_crate_dep_path_for_language(std::path::Path::new("crates/mylib-wasm"), Language::Wasm),
"../mylib-core"
);
}
#[test]
fn core_crate_dep_path_for_language_falls_back_without_an_override() {
use crate::core::config::extras::Language;
let r = minimal();
assert_eq!(
r.core_crate_dep_path_for_language(std::path::Path::new("crates/test-lib-wasm"), Language::Wasm),
r.core_crate_dep_path(std::path::Path::new("crates/test-lib-wasm")),
);
}
#[test]
fn core_crate_for_language_uses_wasm_override() {
use crate::core::config::extras::Language;
let r = resolved_one(
r#"
[workspace]
languages = ["wasm"]
[[crates]]
name = "test-lib"
sources = ["src/lib.rs"]
[crates.wasm]
core_crate_override = "test-lib-wasm-core"
"#,
);
assert_eq!(r.core_crate_for_language(Language::Wasm), "test-lib-wasm-core");
}
#[test]
fn core_import_for_language_normalizes_override_hyphens() {
use crate::core::config::extras::Language;
let r = resolved_one(
r#"
[workspace]
languages = ["wasm"]
[[crates]]
name = "test-lib"
sources = ["src/lib.rs"]
[crates.wasm]
core_crate_override = "test-lib-wasm-core"
"#,
);
assert_eq!(r.core_import_for_language(Language::Wasm), "test_lib_wasm_core");
}
#[test]
fn resolved_path_mappings_per_crate_only() {
let r = resolved_one(
r#"
[workspace]
languages = ["python"]
[[crates]]
name = "test-lib"
sources = ["src/lib.rs"]
path_mappings = { "old_mod" = "new_mod" }
"#,
);
let mappings = r.effective_path_mappings();
assert_eq!(mappings.get("old_mod").map(|s| s.as_str()), Some("new_mod"));
}
#[test]
fn effective_path_mappings_auto_derives_from_sources() {
let r = resolved_one(
r#"
[workspace]
languages = ["python"]
[[crates]]
name = "my-lib"
sources = ["crates/my-dep/src/lib.rs", "crates/my-lib/src/lib.rs"]
core_import = "my_lib"
auto_path_mappings = true
"#,
);
let mappings = r.effective_path_mappings();
assert_eq!(mappings.get("my_dep").map(|s| s.as_str()), Some("my_lib"));
assert!(!mappings.contains_key("my_lib"));
}
}