syncdoc_core/
path_utils.rs1use crate::syncdoc_debug;
2use std::path::{Path, PathBuf};
3
4pub fn find_manifest_dir(start_path: &Path) -> Option<PathBuf> {
6 let mut current = start_path;
7
8 loop {
9 if current.join("Cargo.toml").exists() {
10 return Some(current.to_path_buf());
11 }
12
13 current = current.parent()?;
14 }
15}
16
17pub fn make_manifest_relative_path(doc_path: &str, call_site_file: &Path) -> String {
20 syncdoc_debug!("make_manifest_relative_path called:");
21 syncdoc_debug!(" doc_path: {}", doc_path);
22 syncdoc_debug!(" call_site_file: {}", call_site_file.display());
23
24 if doc_path.starts_with("../") || doc_path.starts_with("..\\") {
26 syncdoc_debug!(" Path already relative, returning as-is");
27 return doc_path.to_string();
28 }
29
30 let manifest_dir = match find_manifest_dir(call_site_file) {
32 Some(dir) => {
33 syncdoc_debug!(" manifest_dir: {}", dir.display());
34 dir
35 }
36 None => {
37 syncdoc_debug!(" manifest_dir: NOT FOUND (fallback)");
39 return doc_path.to_string();
40 }
41 };
42
43 let call_site_dir = call_site_file.parent().unwrap_or_else(|| Path::new("."));
45 syncdoc_debug!(" call_site_dir: {}", call_site_dir.display());
46
47 let rel_to_manifest =
49 path_relative_from(&manifest_dir, call_site_dir).unwrap_or_else(|| manifest_dir.clone());
50 syncdoc_debug!(" rel_to_manifest: {}", rel_to_manifest.display());
51
52 let full_path = rel_to_manifest.join(doc_path);
54 syncdoc_debug!(" full_path: {}", full_path.display());
55
56 let result = full_path.to_str().unwrap_or(doc_path).replace('\\', "/");
58 syncdoc_debug!(" result: {}", result);
59 result
60}
61
62fn path_relative_from(path: &Path, base: &Path) -> Option<PathBuf> {
73 use std::path::Component;
74
75 if path.is_absolute() != base.is_absolute() {
76 if path.is_absolute() {
77 Some(PathBuf::from(path))
78 } else {
79 None
80 }
81 } else {
82 let mut ita = path.components();
83 let mut itb = base.components();
84 let mut comps: Vec<Component> = vec![];
85 loop {
86 match (ita.next(), itb.next()) {
87 (None, None) => break,
88 (Some(a), None) => {
89 comps.push(a);
90 comps.extend(ita.by_ref());
91 break;
92 }
93 (None, _) => comps.push(Component::ParentDir),
94 (Some(a), Some(b)) if comps.is_empty() && a == b => {}
95 (Some(a), Some(_b)) => {
96 comps.push(Component::ParentDir);
97 for _ in itb {
98 comps.push(Component::ParentDir);
99 }
100 comps.push(a);
101 comps.extend(ita.by_ref());
102 break;
103 }
104 }
105 }
106 Some(comps.iter().map(|c| c.as_os_str()).collect())
107 }
108}
109
110pub fn extract_module_path(source_file: &str) -> String {
113 let source_path = Path::new(source_file);
114
115 if let Some(manifest_dir) = find_manifest_dir(source_path) {
116 if let Ok(rel) = source_path.strip_prefix(&manifest_dir) {
117 let rel_str = rel.to_string_lossy();
118 let without_src = rel_str
119 .strip_prefix("src/")
120 .or(rel_str.strip_prefix("src\\"))
121 .unwrap_or(&rel_str);
122
123 if without_src == "main.rs" || without_src == "lib.rs" {
124 return without_src.trim_end_matches(".rs").to_string();
125 } else if without_src.ends_with("/mod.rs") || without_src.ends_with("\\mod.rs") {
126 return without_src
127 .trim_end_matches("/mod.rs")
128 .trim_end_matches("\\mod.rs")
129 .replace('\\', "/");
130 } else if without_src.ends_with(".rs") {
131 return without_src.trim_end_matches(".rs").replace('\\', "/");
132 }
133 }
134 }
135
136 String::new()
137}
138
139pub fn apply_module_path(base_path: String) -> String {
140 syncdoc_debug!("apply_module_path called:");
141 syncdoc_debug!(" base_path: {}", base_path);
142
143 let call_site = proc_macro2::Span::call_site();
144 if let Some(source_path) = call_site.local_file() {
145 let source_file = source_path.to_string_lossy().to_string();
146 syncdoc_debug!(" source_file: {}", source_file);
147
148 let module_path = extract_module_path(&source_file);
149 syncdoc_debug!(" module_path: {}", module_path);
150
151 if module_path.is_empty() {
152 syncdoc_debug!(" result: {} (no module path)", base_path);
153 base_path
154 } else {
155 let result = format!("{}/{}", base_path, module_path);
156 syncdoc_debug!(" result: {}", result);
157 result
158 }
159 } else {
160 syncdoc_debug!(" result: {} (no source path)", base_path);
161 base_path
162 }
163}