1pub mod deps;
2pub mod fs;
3
4use std::io::ErrorKind;
5
6pub use tree_type_proc_macro::{dir_type, file_type, tree_type};
8
9pub enum GenericPath {
10 File(GenericFile),
11 Dir(GenericDir),
12}
13
14impl TryFrom<std::fs::DirEntry> for GenericPath {
15 type Error = std::io::Error;
16
17 fn try_from(value: std::fs::DirEntry) -> Result<Self, Self::Error> {
18 let path: &std::path::Path = &value.path();
19 GenericPath::try_from(path)
20 }
21}
22
23#[cfg(feature = "walk")]
24impl TryFrom<crate::deps::walk::DirEntry> for GenericPath {
25 type Error = std::io::Error;
26
27 fn try_from(value: crate::deps::walk::DirEntry) -> Result<Self, Self::Error> {
28 GenericPath::try_from(value.path())
29 }
30}
31
32#[cfg(feature = "enhanced-errors")]
33impl TryFrom<crate::deps::enhanced_errors::DirEntry> for GenericPath {
34 type Error = std::io::Error; fn try_from(value: crate::deps::enhanced_errors::DirEntry) -> Result<Self, Self::Error> {
37 let path: &std::path::Path = &value.path();
38 GenericPath::try_from(path)
39 }
40}
41
42impl TryFrom<&std::path::Path> for GenericPath {
43 type Error = std::io::Error;
44
45 fn try_from(path: &std::path::Path) -> Result<Self, Self::Error> {
46 let path = resolve_path(path)?;
47 if path.is_file() {
48 Ok(GenericPath::File(GenericFile(path)))
49 } else if path.is_dir() {
50 Ok(GenericPath::Dir(GenericDir(path)))
51 } else {
52 Err(std::io::Error::other("unsupported path type"))
53 }
54 }
55}
56
57impl std::fmt::Display for GenericPath {
58 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
59 match self {
60 GenericPath::File(file) => write!(f, "{}", file.0.display()),
61 GenericPath::Dir(dir) => write!(f, "{}", dir.0.display()),
62 }
63 }
64}
65
66impl std::fmt::Debug for GenericPath {
67 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
68 match self {
69 GenericPath::File(file) => write!(f, "GenericPath::File({})", file.0.display()),
70 GenericPath::Dir(dir) => write!(f, "GenericPath::Dir({})", dir.0.display()),
71 }
72 }
73}
74
75fn resolve_path(path: &std::path::Path) -> Result<std::path::PathBuf, std::io::Error> {
76 use std::collections::HashSet;
77 let mut p = path.to_path_buf();
78 let mut seen = HashSet::new();
79 seen.insert(p.clone());
80 while p.is_symlink() {
81 p = p.read_link()?;
82 if seen.contains(&p) {
83 return Err(std::io::Error::other("symlink loop"));
84 }
85 }
86 Ok(p)
87}
88
89#[allow(unexpected_cfgs)]
91#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
92#[derive(Clone, PartialEq, Eq, PartialOrd, Ord)]
93pub struct GenericFile(std::path::PathBuf);
94
95impl GenericFile {
96 pub fn new(path: impl Into<std::path::PathBuf>) -> std::io::Result<Self> {
102 let path_buf = path.into();
103 let file_name = path_buf.file_name();
104 if file_name.is_none() {
105 return Err(std::io::Error::from(ErrorKind::InvalidFilename));
106 }
107 Ok(Self(path_buf))
108 }
109
110 #[must_use]
111 pub fn as_path(&self) -> &std::path::Path {
112 &self.0
113 }
114
115 #[must_use]
116 pub fn exists(&self) -> bool {
117 self.0.exists()
118 }
119
120 #[must_use]
121 pub fn as_generic(&self) -> GenericFile {
122 self.clone()
123 }
124
125 pub fn read(&self) -> std::io::Result<Vec<u8>> {
131 fs::read(&self.0)
132 }
133
134 pub fn read_to_string(&self) -> std::io::Result<String> {
140 fs::read_to_string(&self.0)
141 }
142
143 pub fn write<C: AsRef<[u8]>>(&self, contents: C) -> std::io::Result<()> {
149 if let Some(parent) = self.0.parent() {
150 if !parent.exists() {
151 fs::create_dir_all(parent)?;
152 }
153 }
154 fs::write(&self.0, contents)
155 }
156
157 pub fn create(&self) -> std::io::Result<()> {
163 if let Some(parent) = self.0.parent() {
164 if !parent.exists() {
165 fs::create_dir_all(parent)?;
166 }
167 }
168 fs::create_file(&self.0)
169 }
170
171 pub fn remove(&self) -> std::io::Result<()> {
177 fs::remove_file(&self.0)
178 }
179
180 pub fn fs_metadata(&self) -> std::io::Result<fs::Metadata> {
186 fs::metadata(&self.0)
187 }
188
189 #[must_use]
193 #[allow(clippy::missing_panics_doc)]
194 pub fn file_name(&self) -> String {
195 self.0
196 .file_name()
197 .expect("validated in new")
198 .to_string_lossy()
199 .to_string()
200 }
201
202 pub fn rename(
226 self,
227 new_path: impl AsRef<std::path::Path>,
228 ) -> Result<Self, (std::io::Error, Self)> {
229 let new_path = new_path.as_ref();
230
231 if let Some(parent) = new_path.parent() {
233 if !parent.exists() {
234 if let Err(e) = fs::create_dir_all(parent) {
235 return Err((e, self));
236 }
237 }
238 }
239
240 match fs::rename(&self.0, new_path) {
242 Ok(()) => Self::new(new_path).map_err(|e| (e, self)),
243 Err(e) => Err((e, self)),
244 }
245 }
246
247 #[cfg(unix)]
262 pub fn secure(&self) -> std::io::Result<()> {
263 use std::os::unix::fs::PermissionsExt;
264 let permissions = std::fs::Permissions::from_mode(0o600);
265 fs::set_permissions(&self.0, permissions)
266 }
267
268 #[must_use]
271 #[allow(clippy::missing_panics_doc)]
272 pub fn parent(&self) -> GenericDir {
273 let parent_path = self.0.parent().expect("Files must have a parent directory");
274 GenericDir::new(parent_path).expect("Parent path should be valid")
275 }
276}
277
278impl AsRef<std::path::Path> for GenericFile {
279 fn as_ref(&self) -> &std::path::Path {
280 &self.0
281 }
282}
283
284impl std::fmt::Display for GenericFile {
285 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
286 write!(f, "{}", self.0.display())
287 }
288}
289
290impl std::fmt::Debug for GenericFile {
291 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
292 write!(f, "GenericFile({})", self.0.display())
293 }
294}
295
296#[allow(unexpected_cfgs)]
314#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
315#[derive(Clone, PartialEq, Eq, PartialOrd, Ord)]
316pub struct GenericDir(std::path::PathBuf);
317
318impl GenericDir {
319 pub fn new(path: impl Into<std::path::PathBuf>) -> std::io::Result<Self> {
325 let path_buf = path.into();
326 if path_buf.as_os_str().is_empty() {
329 return Err(std::io::Error::from(ErrorKind::InvalidFilename));
330 }
331 Ok(Self(path_buf))
332 }
333
334 #[must_use]
335 pub fn as_path(&self) -> &std::path::Path {
336 &self.0
337 }
338
339 #[must_use]
340 pub fn exists(&self) -> bool {
341 self.0.exists()
342 }
343
344 #[must_use]
346 pub fn as_generic(&self) -> GenericDir {
347 self.clone()
348 }
349
350 pub fn create(&self) -> std::io::Result<()> {
356 fs::create_dir(&self.0)
357 }
358
359 pub fn create_all(&self) -> std::io::Result<()> {
365 fs::create_dir_all(&self.0)
366 }
367
368 pub fn remove(&self) -> std::io::Result<()> {
374 fs::remove_dir(&self.0)
375 }
376
377 pub fn remove_all(&self) -> std::io::Result<()> {
383 fs::remove_dir_all(&self.0)
384 }
385
386 #[cfg(feature = "enhanced-errors")]
392 pub fn read_dir(&self) -> std::io::Result<impl Iterator<Item = std::io::Result<GenericPath>>> {
393 crate::deps::enhanced_errors::read_dir(&self.0)
394 .map(|read_dir| read_dir.map(|result| result.and_then(GenericPath::try_from)))
395 }
396
397 #[cfg(not(feature = "enhanced-errors"))]
403 pub fn read_dir(&self) -> std::io::Result<impl Iterator<Item = std::io::Result<GenericPath>>> {
404 std::fs::read_dir(&self.0)
405 .map(|read_dir| read_dir.map(|result| result.and_then(GenericPath::try_from)))
406 }
407
408 pub fn fs_metadata(&self) -> std::io::Result<std::fs::Metadata> {
414 fs::metadata(&self.0)
415 }
416
417 #[cfg(feature = "walk")]
418 pub fn walk_dir(&self) -> crate::deps::walk::WalkDir {
419 crate::deps::walk::WalkDir::new(&self.0)
420 }
421
422 #[cfg(feature = "walk")]
423 pub fn walk(&self) -> impl Iterator<Item = std::io::Result<GenericPath>> {
424 crate::deps::walk::WalkDir::new(&self.0)
425 .into_iter()
426 .map(|r| r.map_err(|e| e.into()).and_then(GenericPath::try_from))
427 }
428
429 #[cfg(feature = "walk")]
430 pub fn size_in_bytes(&self) -> std::io::Result<u64> {
432 let mut total = 0u64;
433 for entry in crate::deps::walk::WalkDir::new(&self.0) {
434 let entry = entry?;
435 if entry.file_type().is_file() {
436 total += entry.metadata()?.len();
437 }
438 }
439 Ok(total)
440 }
441
442 #[cfg(feature = "walk")]
443 pub fn lsl(&self) -> std::io::Result<Vec<(std::path::PathBuf, std::fs::Metadata)>> {
445 let mut results = Vec::new();
446 for entry in crate::deps::walk::WalkDir::new(&self.0) {
447 let entry = entry?;
448 let metadata = entry.metadata()?;
449 results.push((entry.path().to_path_buf(), metadata));
450 }
451 Ok(results)
452 }
453
454 #[must_use]
458 pub fn file_name(&self) -> String {
459 self.0
460 .file_name()
461 .map(|name| name.to_string_lossy().to_string())
462 .unwrap_or_default()
463 }
464
465 pub fn rename(
490 self,
491 new_path: impl AsRef<std::path::Path>,
492 ) -> Result<Self, (std::io::Error, Self)> {
493 let new_path = new_path.as_ref();
494
495 if let Some(parent) = new_path.parent() {
497 if !parent.exists() {
498 if let Err(e) = std::fs::create_dir_all(parent) {
499 return Err((e, self));
500 }
501 }
502 }
503
504 match fs::rename(&self.0, new_path) {
506 Ok(()) => Self::new(new_path).map_err(|e| (e, self)),
507 Err(e) => Err((e, self)),
508 }
509 }
510
511 #[cfg(unix)]
526 pub fn secure(&self) -> std::io::Result<()> {
527 use std::os::unix::fs::PermissionsExt;
528 let permissions = std::fs::Permissions::from_mode(0o700);
529 fs::set_permissions(&self.0, permissions)
530 }
531
532 #[must_use]
535 pub fn parent(&self) -> Option<GenericDir> {
536 self.0
537 .parent()
538 .and_then(|parent_path| GenericDir::new(parent_path).ok())
539 }
540}
541
542impl AsRef<std::path::Path> for GenericDir {
543 fn as_ref(&self) -> &std::path::Path {
544 &self.0
545 }
546}
547
548impl std::fmt::Display for GenericDir {
549 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
550 write!(f, "{}", self.0.display())
551 }
552}
553
554impl std::fmt::Debug for GenericDir {
555 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
556 write!(f, "GenericDir({})", self.0.display())
557 }
558}
559
560#[derive(Debug, Clone, Copy, PartialEq, Eq)]
562pub enum CreateDefaultOutcome {
563 Created,
565 AlreadyExists,
567}
568
569#[derive(Debug)]
571pub enum BuildError {
572 Directory(std::path::PathBuf, std::io::Error),
574 File(std::path::PathBuf, Box<dyn std::error::Error>),
576}
577
578#[derive(Debug, Clone, Copy, PartialEq, Eq)]
580pub enum Recursive {
581 No,
583 Yes,
585}
586
587#[derive(Debug, Clone, PartialEq, Eq)]
589pub struct ValidationError {
590 pub path: std::path::PathBuf,
592 pub message: String,
594}
595
596#[derive(Debug, Clone, PartialEq, Eq)]
598pub struct ValidationWarning {
599 pub path: std::path::PathBuf,
601 pub message: String,
603}
604
605#[derive(Debug, Clone, PartialEq, Eq)]
607pub struct ValidationReport {
608 pub errors: Vec<ValidationError>,
610 pub warnings: Vec<ValidationWarning>,
612}
613
614impl ValidationReport {
615 #[must_use]
617 pub fn new() -> Self {
618 Self {
619 errors: Vec::new(),
620 warnings: Vec::new(),
621 }
622 }
623
624 #[must_use]
626 pub fn is_ok(&self) -> bool {
627 self.errors.is_empty()
628 }
629
630 pub fn merge(&mut self, other: ValidationReport) {
632 self.errors.extend(other.errors);
633 self.warnings.extend(other.warnings);
634 }
635}
636
637impl Default for ValidationReport {
638 fn default() -> Self {
639 Self::new()
640 }
641}
642
643#[derive(Debug, Default, Clone, PartialEq, Eq)]
645pub struct ValidatorResult {
646 pub errors: Vec<String>,
648 pub warnings: Vec<String>,
650}