1use std::borrow::Borrow;
2use std::ffi::OsStr;
3use std::fmt;
4use std::ops::Deref;
5use std::path::{Component, Path, PathBuf};
6
7use crate::consts::PYTHON_MODE;
8use crate::env::erg_pkgs_path;
9use crate::traits::Immutable;
10use crate::vfs::VFS;
11use crate::{cheap_canonicalize_path, normalize_path, Str};
12
13#[derive(Debug, Clone, PartialEq, Eq, Hash, Default, PartialOrd, Ord)]
19pub struct NormalizedPathBuf(PathBuf);
20
21impl Immutable for NormalizedPathBuf {}
22
23impl fmt::Display for NormalizedPathBuf {
24 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
25 write!(f, "{}", self.display())
26 }
27}
28
29impl<P: Into<PathBuf>> From<P> for NormalizedPathBuf {
30 fn from(path: P) -> Self {
31 NormalizedPathBuf::new(path.into())
32 }
33}
34
35impl AsRef<Path> for NormalizedPathBuf {
36 fn as_ref(&self) -> &Path {
37 self.0.as_path()
38 }
39}
40
41impl Borrow<PathBuf> for NormalizedPathBuf {
42 fn borrow(&self) -> &PathBuf {
43 &self.0
44 }
45}
46
47impl Borrow<Path> for NormalizedPathBuf {
48 fn borrow(&self) -> &Path {
49 self.0.as_path()
50 }
51}
52
53impl Deref for NormalizedPathBuf {
54 type Target = Path;
55
56 fn deref(&self) -> &Self::Target {
57 self.0.as_path()
58 }
59}
60
61impl NormalizedPathBuf {
62 pub fn new(path: PathBuf) -> Self {
63 NormalizedPathBuf(normalize_path(cheap_canonicalize_path(&path)))
64 }
65
66 pub fn as_path(&self) -> &Path {
67 self.0.as_path()
68 }
69
70 pub fn to_path_buf(&self) -> PathBuf {
71 self.0.clone()
72 }
73
74 pub fn try_read(&self) -> std::io::Result<String> {
75 VFS.read(self)
76 }
77}
78
79#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
80pub enum DirKind {
81 ErgModule,
82 PyModule,
83 Other,
84 NotDir,
85}
86
87impl From<&Path> for DirKind {
88 fn from(path: &Path) -> Self {
89 let Ok(dir) = path.read_dir() else {
90 return DirKind::NotDir;
91 };
92 for ent in dir {
93 let Ok(ent) = ent else {
94 continue;
95 };
96 if ent.path().file_name() == Some(OsStr::new("__init__.er")) {
97 return DirKind::ErgModule;
98 } else if ent.path().file_name() == Some(OsStr::new("__init__.py")) {
99 return DirKind::PyModule;
100 }
101 }
102 DirKind::Other
103 }
104}
105
106impl DirKind {
107 pub const fn is_erg_module(&self) -> bool {
108 matches!(self, DirKind::ErgModule)
109 }
110}
111
112#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
113pub enum FileKind {
114 InitEr,
115 InitPy,
116 Er,
117 Py,
118 Other,
119 NotFile,
120}
121
122impl From<&Path> for FileKind {
123 fn from(path: &Path) -> Self {
124 if path.is_file() {
125 match path.file_name() {
126 Some(name) if name == OsStr::new("__init__.er") => FileKind::InitEr,
127 Some(name) if name == OsStr::new("__init__.py") => FileKind::InitPy,
128 Some(name) if name.to_string_lossy().ends_with(".er") => FileKind::Er,
129 Some(name) if name.to_string_lossy().ends_with(".py") => FileKind::Py,
130 _ => FileKind::Other,
131 }
132 } else {
133 FileKind::NotFile
134 }
135 }
136}
137
138impl FileKind {
139 pub const fn is_init_er(&self) -> bool {
140 matches!(self, FileKind::InitEr)
141 }
142 pub const fn is_simple_erg_file(&self) -> bool {
143 matches!(self, FileKind::Er)
144 }
145}
146
147pub fn is_cur_dir<P: AsRef<Path>>(path: P) -> bool {
148 path.as_ref().components().next() == Some(Component::CurDir)
149}
150
151pub fn add_postfix_foreach<P: AsRef<Path>, Q: AsRef<Path>>(path: P, postfix: Q) -> PathBuf {
162 let mut result = PathBuf::new();
163 for c in path.as_ref().components() {
164 match c {
165 Component::Prefix(_) => result.push(c),
166 Component::RootDir => result.push(c),
167 Component::CurDir => result.push(c),
168 Component::ParentDir => result.push(c),
169 Component::Normal(os_str) => {
170 let mut os_string = os_str.to_os_string();
171 os_string.push(postfix.as_ref().as_os_str());
172 result.push(PathBuf::from(os_string));
173 }
174 }
175 }
176 result
177}
178
179pub fn remove_postfix_foreach<P: AsRef<Path>>(path: P, extension: &str) -> PathBuf {
180 let mut result = PathBuf::new();
181 for c in path.as_ref().components() {
182 match c {
183 Component::Prefix(_) => result.push(c),
184 Component::RootDir => result.push(c),
185 Component::CurDir => result.push(c),
186 Component::ParentDir => result.push(c),
187 Component::Normal(os_str) => {
188 let string = os_str.to_string_lossy();
189 result.push(string.trim_end_matches(extension));
190 }
191 }
192 }
193 result
194}
195
196pub fn remove_postfix<P: AsRef<Path>>(path: P, extension: &str) -> PathBuf {
207 let string = path.as_ref().to_string_lossy();
208 PathBuf::from(string.trim_end_matches(extension))
209}
210
211pub fn squash(path: PathBuf) -> PathBuf {
223 let mut result = PathBuf::new();
224 for c in path.components() {
225 match c {
226 Component::Prefix(_) => result.push(c),
227 Component::RootDir => result.push(c),
228 Component::CurDir => {}
229 Component::ParentDir => {
230 result.pop();
231 }
232 Component::Normal(os_str) => {
233 result.push(os_str);
234 }
235 }
236 }
237 result
238}
239
240pub fn remove_verbatim(path: &Path) -> String {
241 path.to_string_lossy().replace("\\\\?\\", "")
242}
243
244pub fn mod_name(path: &Path) -> Str {
256 let path = match path.strip_prefix(erg_pkgs_path()) {
257 Ok(path) => {
258 let mod_root = path
260 .components()
261 .nth(1)
262 .unwrap()
263 .as_os_str()
264 .to_string_lossy();
265 let sub = path
266 .components()
267 .skip(4)
268 .map(|c| {
269 c.as_os_str()
270 .to_string_lossy()
271 .trim_end_matches("lib.d.er")
272 .trim_end_matches(".d.er")
273 .trim_end_matches(".d")
274 .trim_end_matches(".py")
275 .to_string()
276 })
277 .collect::<Vec<_>>()
278 .join("/");
279 return Str::rc(format!("{mod_root}/{sub}").trim_end_matches('/'));
280 }
281 Err(_) if path.display().to_string().split("/src/").count() > 1 => {
283 let path = path.display().to_string();
285 let mod_root = path
286 .split("/src/")
287 .next()
288 .unwrap()
289 .split('/')
290 .next_back()
291 .unwrap();
292 let sub = path
293 .split("/src/")
294 .nth(1)
295 .unwrap()
296 .split('/')
297 .map(|c| {
298 c.trim_end_matches("lib.d.er")
299 .trim_end_matches(".d.er")
300 .trim_end_matches(".d")
301 .trim_end_matches(".py")
302 .to_string()
303 })
304 .collect::<Vec<_>>()
305 .join("/");
306 return Str::rc(format!("{mod_root}/{sub}").trim_end_matches('/'));
307 }
308 Err(_) => path,
309 };
310 let mut name = path
311 .file_name()
312 .unwrap()
313 .to_string_lossy()
314 .trim_end_matches(".d.er")
315 .trim_end_matches(".py")
316 .to_string();
317 let mut parents = path.components().rev().skip(1);
318 while let Some(parent) = parents.next() {
319 let parent = parent.as_os_str().to_string_lossy();
320 if parent == "__pycache__" {
321 if name == "__init__" {
322 let p = parents
323 .next()
324 .unwrap()
325 .as_os_str()
326 .to_string_lossy()
327 .trim_end_matches(".d")
328 .to_string();
329 name = p;
330 }
331 break;
332 } else if parent.ends_with(".d") {
333 let p = parent.trim_end_matches(".d").to_string();
334 if name == "__init__" {
335 name = p;
336 } else {
337 name = p + "." + &name;
338 }
339 } else {
340 break;
341 }
342 }
343 Str::from(name)
344}
345
346fn erg_project_entry_dir_of(path: &Path) -> Option<PathBuf> {
347 if path.is_dir() && path.join("package.er").exists() {
348 if path.join("src").exists() {
349 return Some(path.join("src"));
350 } else {
351 return Some(path.to_path_buf());
352 }
353 }
354 let mut path = path.to_path_buf();
355 while let Some(parent) = path.parent() {
356 if parent.join("package.er").exists() {
357 if parent.join("src").exists() {
358 return Some(parent.join("src"));
359 } else {
360 return Some(parent.to_path_buf());
361 }
362 }
363 path = parent.to_path_buf();
364 }
365 None
366}
367
368fn py_project_entry_dir_of(path: &Path) -> Option<PathBuf> {
369 let dir_name = path.file_name()?;
370 if path.is_dir() && path.join("pyproject.toml").exists() {
371 if path.join(dir_name).exists() {
372 return Some(path.join(dir_name));
373 } else if path.join("src").join(dir_name).exists() {
374 return Some(path.join("src").join(dir_name));
375 }
376 }
377 let mut path = path.to_path_buf();
378 while let Some(parent) = path.parent() {
379 let dir_name = parent.file_name()?;
380 if parent.join("pyproject.toml").exists() {
381 if parent.join(dir_name).exists() {
382 return Some(parent.join(dir_name));
383 } else if parent.join("src").join(dir_name).exists() {
384 return Some(parent.join("src").join(dir_name));
385 }
386 }
387 path = parent.to_path_buf();
388 }
389 None
390}
391
392pub fn project_entry_dir_of(path: &Path) -> Option<PathBuf> {
393 if PYTHON_MODE {
394 py_project_entry_dir_of(path)
395 } else {
396 erg_project_entry_dir_of(path)
397 }
398}
399
400fn erg_project_entry_file_of(path: &Path) -> Option<PathBuf> {
401 let entry = erg_project_entry_dir_of(path)?;
402 if entry.join("lib.er").exists() {
403 Some(entry.join("lib.er"))
404 } else if entry.join("main.er").exists() {
405 Some(entry.join("main.er"))
406 } else if entry.join("lib.d.er").exists() {
407 Some(entry.join("lib.d.er"))
408 } else {
409 None
410 }
411}
412
413fn py_project_entry_file_of(path: &Path) -> Option<PathBuf> {
414 let entry = py_project_entry_dir_of(path)?;
415 if entry.join("__init__.py").exists() {
416 Some(entry.join("__init__.py"))
417 } else {
418 None
419 }
420}
421
422pub fn project_entry_file_of(path: &Path) -> Option<PathBuf> {
423 if PYTHON_MODE {
424 py_project_entry_file_of(path)
425 } else {
426 erg_project_entry_file_of(path)
427 }
428}