use std::collections::HashMap;
use std::path::PathBuf;
use super::extras::Language;
use super::output::{OutputConfig, OutputTemplate};
use super::raw_crate::RawCrateConfig;
pub(crate) fn resolve_output_paths(
krate: &RawCrateConfig,
template: &OutputTemplate,
languages: &[Language],
multi_crate: bool,
) -> HashMap<String, PathBuf> {
let mut paths = HashMap::new();
for lang in languages {
let lang_str = lang.to_string();
let explicit = per_crate_explicit_output(&krate.output, lang);
let path = explicit
.map(PathBuf::from)
.unwrap_or_else(|| template.resolve(&krate.name, &lang_str, multi_crate));
paths.insert(lang_str, path);
}
paths
}
pub(crate) fn per_crate_explicit_output(output: &OutputConfig, lang: &Language) -> Option<String> {
let path = match lang {
Language::Python => output.python.as_ref(),
Language::Node => output.node.as_ref(),
Language::Ruby => output.ruby.as_ref(),
Language::Php => output.php.as_ref(),
Language::Elixir => output.elixir.as_ref(),
Language::Wasm => output.wasm.as_ref(),
Language::Ffi => output.ffi.as_ref(),
Language::Gleam => output.gleam.as_ref(),
Language::Go => output.go.as_ref(),
Language::Java => output.java.as_ref(),
Language::Kotlin => output.kotlin.as_ref(),
Language::Dart => output.dart.as_ref(),
Language::Swift => output.swift.as_ref(),
Language::Csharp => output.csharp.as_ref(),
Language::R => output.r.as_ref(),
Language::Zig => output.zig.as_ref(),
Language::Rust | Language::C => None,
};
path.map(|p| p.to_string_lossy().into_owned())
}
pub(crate) fn merge_map<V: Clone>(
workspace: &HashMap<String, V>,
per_crate: &HashMap<String, V>,
) -> HashMap<String, V> {
let mut merged = workspace.clone();
for (k, v) in per_crate {
merged.insert(k.clone(), v.clone());
}
merged
}
pub fn resolve_output_dir(config_path: Option<&PathBuf>, crate_name: &str, default: &str) -> String {
config_path
.map(|p| p.to_string_lossy().replace("{name}", crate_name))
.unwrap_or_else(|| default.replace("{name}", crate_name))
}
pub fn detect_serde_available(output_dir: &str) -> bool {
let src_path = std::path::Path::new(output_dir);
let mut dir = src_path;
loop {
let cargo_toml = dir.join("Cargo.toml");
if cargo_toml.exists() {
return cargo_toml_has_serde(&cargo_toml);
}
match dir.parent() {
Some(parent) if !parent.as_os_str().is_empty() => dir = parent,
_ => break,
}
}
false
}
fn cargo_toml_has_serde(path: &std::path::Path) -> bool {
let content = match std::fs::read_to_string(path) {
Ok(c) => c,
Err(_) => return false,
};
let has_serde_json = content.contains("serde_json");
let has_serde_dep = content.lines().any(|line| {
let trimmed = line.trim();
trimmed.starts_with("serde ")
|| trimmed.starts_with("serde=")
|| trimmed.starts_with("serde.")
|| trimmed == "[dependencies.serde]"
});
has_serde_json && has_serde_dep
}
pub(crate) fn find_after_crates_prefix(path: &str) -> Option<&str> {
if let Some(pos) = path.find("/crates/") {
return Some(&path[pos + "/crates/".len()..]);
}
if let Some(stripped) = path.strip_prefix("crates/") {
return Some(stripped);
}
None
}