LCOV - code coverage report
Current view: top level - libs/utils/src - fs_ext.rs (source / functions) Coverage Total Hit
Test: 98683a8629f0f7f0031d02e04512998d589d76ea.info Lines: 98.0 % 99 97
Test Date: 2025-04-11 16:58:57 Functions: 78.6 % 28 22

            Line data    Source code
       1              : /// Extensions to `std::fs` types.
       2              : use std::{fs, io, path::Path};
       3              : 
       4              : use anyhow::Context;
       5              : 
       6              : #[cfg(feature = "rename_noreplace")]
       7              : mod rename_noreplace;
       8              : #[cfg(feature = "rename_noreplace")]
       9              : pub use rename_noreplace::rename_noreplace;
      10              : 
      11              : pub trait PathExt {
      12              :     /// Returns an error if `self` is not a directory.
      13              :     fn is_empty_dir(&self) -> io::Result<bool>;
      14              : }
      15              : 
      16              : impl<P> PathExt for P
      17              : where
      18              :     P: AsRef<Path>,
      19              : {
      20            3 :     fn is_empty_dir(&self) -> io::Result<bool> {
      21            3 :         Ok(fs::read_dir(self)?.next().is_none())
      22            3 :     }
      23              : }
      24              : 
      25            3 : pub async fn is_directory_empty(path: impl AsRef<Path>) -> anyhow::Result<bool> {
      26            3 :     let mut dir = tokio::fs::read_dir(&path)
      27            3 :         .await
      28            3 :         .context(format!("read_dir({})", path.as_ref().display()))?;
      29            1 :     Ok(dir.next_entry().await?.is_none())
      30            3 : }
      31              : 
      32            3 : pub async fn list_dir(path: impl AsRef<Path>) -> anyhow::Result<Vec<String>> {
      33            3 :     let mut dir = tokio::fs::read_dir(&path)
      34            3 :         .await
      35            3 :         .context(format!("read_dir({})", path.as_ref().display()))?;
      36              : 
      37            3 :     let mut content = vec![];
      38            6 :     while let Some(next) = dir.next_entry().await? {
      39            3 :         let file_name = next.file_name();
      40            3 :         content.push(file_name.to_string_lossy().to_string());
      41            3 :     }
      42              : 
      43            3 :     Ok(content)
      44            3 : }
      45              : 
      46           53 : pub fn ignore_not_found(e: io::Error) -> io::Result<()> {
      47           53 :     if e.kind() == io::ErrorKind::NotFound {
      48           53 :         Ok(())
      49              :     } else {
      50            0 :         Err(e)
      51              :     }
      52           53 : }
      53              : 
      54           14 : pub fn ignore_absent_files<F>(fs_operation: F) -> io::Result<()>
      55           14 : where
      56           14 :     F: Fn() -> io::Result<()>,
      57           14 : {
      58           14 :     fs_operation().or_else(ignore_not_found)
      59           14 : }
      60              : 
      61              : #[cfg(test)]
      62              : mod test {
      63              :     use super::ignore_absent_files;
      64              :     use crate::fs_ext::{is_directory_empty, list_dir};
      65              : 
      66              :     #[test]
      67            1 :     fn is_empty_dir() {
      68              :         use super::PathExt;
      69              : 
      70            1 :         let dir = camino_tempfile::tempdir().unwrap();
      71            1 :         let dir_path = dir.path();
      72            1 : 
      73            1 :         // test positive case
      74            1 :         assert!(
      75            1 :             dir_path.is_empty_dir().expect("test failure"),
      76            0 :             "new tempdir should be empty"
      77              :         );
      78              : 
      79              :         // invoke on a file to ensure it returns an error
      80            1 :         let file_path = dir_path.join("testfile");
      81            1 :         let f = std::fs::File::create(&file_path).unwrap();
      82            1 :         drop(f);
      83            1 :         assert!(file_path.is_empty_dir().is_err());
      84              : 
      85              :         // do it again on a path, we know to be nonexistent
      86            1 :         std::fs::remove_file(&file_path).unwrap();
      87            1 :         assert!(file_path.is_empty_dir().is_err());
      88            1 :     }
      89              : 
      90              :     #[tokio::test]
      91            1 :     async fn is_empty_dir_async() {
      92            1 :         let dir = camino_tempfile::tempdir().unwrap();
      93            1 :         let dir_path = dir.path();
      94            1 : 
      95            1 :         // test positive case
      96            1 :         assert!(
      97            1 :             is_directory_empty(dir_path).await.expect("test failure"),
      98            1 :             "new tempdir should be empty"
      99            1 :         );
     100            1 : 
     101            1 :         // invoke on a file to ensure it returns an error
     102            1 :         let file_path = dir_path.join("testfile");
     103            1 :         let f = std::fs::File::create(&file_path).unwrap();
     104            1 :         drop(f);
     105            1 :         assert!(is_directory_empty(&file_path).await.is_err());
     106            1 : 
     107            1 :         // do it again on a path, we know to be nonexistent
     108            1 :         std::fs::remove_file(&file_path).unwrap();
     109            1 :         assert!(is_directory_empty(file_path).await.is_err());
     110            1 :     }
     111              : 
     112              :     #[test]
     113            1 :     fn ignore_absent_files_works() {
     114            1 :         let dir = camino_tempfile::tempdir().unwrap();
     115            1 : 
     116            1 :         let file_path = dir.path().join("testfile");
     117            1 : 
     118            1 :         ignore_absent_files(|| std::fs::remove_file(&file_path)).expect("should execute normally");
     119            1 : 
     120            1 :         let f = std::fs::File::create(&file_path).unwrap();
     121            1 :         drop(f);
     122            1 : 
     123            1 :         ignore_absent_files(|| std::fs::remove_file(&file_path)).expect("should execute normally");
     124            1 : 
     125            1 :         assert!(!file_path.exists());
     126            1 :     }
     127              : 
     128              :     #[tokio::test]
     129            1 :     async fn list_dir_works() {
     130            1 :         let dir = camino_tempfile::tempdir().unwrap();
     131            1 :         let dir_path = dir.path();
     132            1 : 
     133            1 :         assert!(list_dir(dir_path).await.unwrap().is_empty());
     134            1 : 
     135            1 :         let file_path = dir_path.join("testfile");
     136            1 :         let _ = std::fs::File::create(&file_path).unwrap();
     137            1 : 
     138            1 :         assert_eq!(&list_dir(dir_path).await.unwrap(), &["testfile"]);
     139            1 : 
     140            1 :         let another_dir_path = dir_path.join("testdir");
     141            1 :         std::fs::create_dir(another_dir_path).unwrap();
     142            1 : 
     143            1 :         let expected = &["testdir", "testfile"];
     144            1 :         let mut actual = list_dir(dir_path).await.unwrap();
     145            1 :         actual.sort();
     146            1 :         assert_eq!(actual, expected);
     147            1 :     }
     148              : }
        

Generated by: LCOV version 2.1-beta