use crate::syncdoc_debug;
use std::path::{Path, PathBuf};
pub fn find_manifest_dir(start_path: &Path) -> Option<PathBuf> {
let mut current = start_path;
loop {
if current.join("Cargo.toml").exists() {
return Some(current.to_path_buf());
}
current = current.parent()?;
}
}
pub fn make_manifest_relative_path(doc_path: &str, call_site_file: &Path) -> String {
syncdoc_debug!("make_manifest_relative_path called:");
syncdoc_debug!(" doc_path: {}", doc_path);
syncdoc_debug!(" call_site_file: {}", call_site_file.display());
if doc_path.starts_with("../") || doc_path.starts_with("..\\") {
syncdoc_debug!(" Path already relative, returning as-is");
return doc_path.to_string();
}
let manifest_dir = match find_manifest_dir(call_site_file) {
Some(dir) => {
syncdoc_debug!(" manifest_dir: {}", dir.display());
dir
}
None => {
syncdoc_debug!(" manifest_dir: NOT FOUND (fallback)");
return doc_path.to_string();
}
};
let call_site_dir = call_site_file.parent().unwrap_or_else(|| Path::new("."));
syncdoc_debug!(" call_site_dir: {}", call_site_dir.display());
let rel_to_manifest =
path_relative_from(&manifest_dir, call_site_dir).unwrap_or_else(|| manifest_dir.clone());
syncdoc_debug!(" rel_to_manifest: {}", rel_to_manifest.display());
let full_path = rel_to_manifest.join(doc_path);
syncdoc_debug!(" full_path: {}", full_path.display());
let result = full_path.to_str().unwrap_or(doc_path).replace('\\', "/");
syncdoc_debug!(" result: {}", result);
result
}
fn path_relative_from(path: &Path, base: &Path) -> Option<PathBuf> {
use std::path::Component;
if path.is_absolute() != base.is_absolute() {
if path.is_absolute() {
Some(PathBuf::from(path))
} else {
None
}
} else {
let mut ita = path.components();
let mut itb = base.components();
let mut comps: Vec<Component> = vec![];
loop {
match (ita.next(), itb.next()) {
(None, None) => break,
(Some(a), None) => {
comps.push(a);
comps.extend(ita.by_ref());
break;
}
(None, _) => comps.push(Component::ParentDir),
(Some(a), Some(b)) if comps.is_empty() && a == b => {}
(Some(a), Some(_b)) => {
comps.push(Component::ParentDir);
for _ in itb {
comps.push(Component::ParentDir);
}
comps.push(a);
comps.extend(ita.by_ref());
break;
}
}
}
Some(comps.iter().map(|c| c.as_os_str()).collect())
}
}
pub fn extract_module_path(source_file: &str) -> String {
let source_path = Path::new(source_file);
if let Some(manifest_dir) = find_manifest_dir(source_path) {
if let Ok(rel) = source_path.strip_prefix(&manifest_dir) {
let rel_str = rel.to_string_lossy();
let without_src = rel_str
.strip_prefix("src/")
.or(rel_str.strip_prefix("src\\"))
.unwrap_or(&rel_str);
if without_src == "main.rs" || without_src == "lib.rs" {
return without_src.trim_end_matches(".rs").to_string();
} else if without_src.ends_with("/mod.rs") || without_src.ends_with("\\mod.rs") {
return without_src
.trim_end_matches("/mod.rs")
.trim_end_matches("\\mod.rs")
.replace('\\', "/");
} else if without_src.ends_with(".rs") {
return without_src.trim_end_matches(".rs").replace('\\', "/");
}
}
}
String::new()
}
pub fn apply_module_path(base_path: String) -> String {
syncdoc_debug!("apply_module_path called:");
syncdoc_debug!(" base_path: {}", base_path);
let call_site = proc_macro2::Span::call_site();
if let Some(source_path) = call_site.local_file() {
let source_file = source_path.to_string_lossy().to_string();
syncdoc_debug!(" source_file: {}", source_file);
let module_path = extract_module_path(&source_file);
syncdoc_debug!(" module_path: {}", module_path);
if module_path.is_empty() {
syncdoc_debug!(" result: {} (no module path)", base_path);
base_path
} else {
let result = format!("{}/{}", base_path, module_path);
syncdoc_debug!(" result: {}", result);
result
}
} else {
syncdoc_debug!(" result: {} (no source path)", base_path);
base_path
}
}