utoipauto_core/
file_utils.rs1use std::{
2 fs::{self, File},
3 io::{self, Read},
4 iter,
5 path::{Path, PathBuf},
6};
7
8pub fn parse_file<T: Into<PathBuf>>(filepath: T) -> Result<syn::File, io::Error> {
9 let pb: PathBuf = filepath.into();
10
11 if !pb.is_file() {
12 panic!("File not found: {:?}", pb);
13 }
14
15 let mut file = File::open(&pb)?;
16 let mut content = String::new();
17 file.read_to_string(&mut content)?;
18
19 Ok(syn::parse_file(&content).unwrap_or_else(move |_| panic!("Failed to parse file {:?}", pb)))
20}
21
22pub fn parse_files<T: Into<PathBuf>>(path: T) -> Result<Vec<(String, syn::File)>, io::Error> {
24 let mut files: Vec<(String, syn::File)> = vec![];
25
26 let pb: PathBuf = path.into();
27 if pb.is_file() {
28 if is_rust_file(&pb) {
30 files.push((pb.to_str().unwrap().to_string(), parse_file(pb)?));
31 }
32 } else {
33 for entry in fs::read_dir(pb)? {
34 let entry = entry?;
35 let path = entry.path();
36 if path.is_file() && is_rust_file(&path) {
37 files.push((path.to_str().unwrap().to_string(), parse_file(path)?));
38 } else {
39 files.append(&mut parse_files(path)?);
40 }
41 }
42 }
43 Ok(files)
44}
45
46fn is_rust_file(path: &Path) -> bool {
47 path.is_file()
48 && match path.extension() {
49 Some(ext) => match ext.to_str() {
50 Some(ext) => ext.eq("rs"),
51 None => false,
52 },
53 None => false,
54 }
55}
56
57pub fn extract_module_name_from_path(path: &str, crate_name: &str) -> String {
71 let path = path.replace('\\', "/");
72 let path = path
73 .trim_end_matches(".rs")
74 .trim_end_matches("/mod")
75 .trim_end_matches("/lib")
76 .trim_end_matches("/main")
77 .trim_start_matches("./");
78 let segments: Vec<_> = path.split('/').collect();
79
80 let segments_inside_crate = find_segment_and_skip(&segments, &["src", "tests"], 1);
86
87 let crate_name = crate_name.replace("-", "_");
91 let mut crate_segments = crate_name.split("::");
92 let first_crate_fragment = crate_segments.next().expect("Crate should not be empty");
93 let segments_inside_crate = match crate_segments.next() {
94 Some(crate_fragment) => find_segment_and_skip(segments_inside_crate, &[crate_fragment], 0),
95 None => segments_inside_crate,
96 };
97
98 let full_crate_path: Vec<_> = iter::once(first_crate_fragment)
99 .chain(segments_inside_crate.iter().copied())
100 .collect();
101 full_crate_path.join("::")
102}
103
104fn find_segment_and_skip<'a>(segments: &'a [&str], to_find: &[&str], to_skip: usize) -> &'a [&'a str] {
105 match segments.iter().rposition(|segment| to_find.contains(segment)) {
106 Some(idx) => &segments[(idx + to_skip)..],
107 None => segments,
108 }
109}
110
111#[cfg(test)]
112mod tests {
113 use super::*;
114
115 #[test]
116 fn test_extract_module_name_from_path() {
117 assert_eq!(
118 extract_module_name_from_path("./utoipa-auto-macro/tests/controllers/controller1.rs", "crate"),
119 "crate::controllers::controller1"
120 );
121 }
122
123 #[test]
124 fn test_extract_module_name_from_path_windows() {
125 assert_eq!(
126 extract_module_name_from_path(".\\utoipa-auto-macro\\tests\\controllers\\controller1.rs", "crate"),
127 "crate::controllers::controller1"
128 );
129 }
130
131 #[test]
132 fn test_extract_module_name_from_mod() {
133 assert_eq!(
134 extract_module_name_from_path("./utoipa-auto-macro/tests/controllers/mod.rs", "crate"),
135 "crate::controllers"
136 );
137 }
138
139 #[test]
140 fn test_extract_module_name_from_lib() {
141 assert_eq!(extract_module_name_from_path("./src/lib.rs", "crate"), "crate");
142 }
143
144 #[test]
145 fn test_extract_module_name_from_main() {
146 assert_eq!(extract_module_name_from_path("./src/main.rs", "crate"), "crate");
147 }
148
149 #[test]
150 fn test_extract_module_name_from_workspace() {
151 assert_eq!(
152 extract_module_name_from_path("./server/src/routes/asset.rs", "crate"),
153 "crate::routes::asset"
154 );
155 }
156
157 #[test]
158 fn test_extract_module_name_from_workspace_nested() {
159 assert_eq!(
160 extract_module_name_from_path("./crates/server/src/routes/asset.rs", "crate"),
161 "crate::routes::asset"
162 );
163 }
164
165 #[test]
166 fn test_extract_module_name_from_folders() {
167 assert_eq!(
168 extract_module_name_from_path("./src/routing/api/audio.rs", "crate"),
169 "crate::routing::api::audio"
170 );
171 }
172
173 #[test]
174 fn test_extract_module_name_from_folders_nested() {
175 assert_eq!(
176 extract_module_name_from_path("./src/applications/src/retail_api/controllers/mod.rs", "crate"),
177 "crate::retail_api::controllers"
178 );
179 }
180
181 #[test]
182 fn test_extract_module_name_from_folders_nested_external_crate() {
183 assert_eq!(
184 extract_module_name_from_path("./src/applications/src/retail_api/controllers/mod.rs", "other_crate"),
185 "other_crate::retail_api::controllers"
186 );
187 }
188
189 #[test]
190 fn test_extract_module_name_from_workspace_with_prefix_path() {
191 assert_eq!(
192 extract_module_name_from_path("./crates/server/src/routes_lib/routes/asset.rs", "crate::routes"),
193 "crate::routes::asset"
194 );
195 }
196
197 #[test]
198 fn test_extract_module_name_from_workspace_with_external_crate_and_underscore() {
199 assert_eq!(
200 extract_module_name_from_path("./src/applications/src/retail-api/controllers/mod.rs", "other-crate"),
201 "other_crate::retail-api::controllers"
202 );
203 }
204}