1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
// Source: https://github.com/assert-rs/dir-diff (Apache/MIT)
// Need to modify it so including it, will send PR and try to get it included
// upstream.

/// The various errors that can happen when diffing two directories
#[derive(Debug)]
pub enum DirDiffError {
    Io(std::io::Error),
    StripPrefix(std::path::StripPrefixError),
    WalkDir(walkdir::Error),
}

#[derive(Debug)]
pub enum DirDiff {
    ExpectedFileMissing {
        expected: std::path::PathBuf,
    },
    ExpectedFolderMissing {
        expected: std::path::PathBuf,
    },
    UnexpectedFileFound {
        found: std::path::PathBuf,
    },
    UnexpectedFolderFound {
        found: std::path::PathBuf,
    },
    FileTypeMismatch {
        file: std::path::PathBuf,
        expected: String,
        found: String,
    },
    ContentMismatch {
        file: std::path::PathBuf,
        expected: String,
        found: String,
    },
}

pub fn diff<A: AsRef<std::path::Path>, B: AsRef<std::path::Path>>(
    a_base: A,
    b_base: B,
) -> Result<Option<DirDiff>, DirDiffError> {
    let mut a_walker = walk_dir(a_base)?;
    let mut b_walker = walk_dir(b_base)?;

    loop {
        match (a_walker.next(), b_walker.next()) {
            (Some(a), Some(b)) => {
                // first lets check the depth:
                // a > b: UnexpectedFileFound or UnexpectedFolderFound else
                // b > a: ExpectedFileMissing or ExpectedFolderMissing

                // if file names dont match how to find if we got a new entry
                // on left or extra entry on right? how do people actually
                // calculate diff?

                // then check file type

                // finally check file content if its a file

                // TODO: this is dummy code to test stuff
                let a = a?;
                let b = b?;
                eprintln!("a: {:?}", a);
                eprintln!("b: {:?}", b);

                let found: std::path::PathBuf = b.path().into();
                return Ok(Some(if found.is_dir() {
                    DirDiff::UnexpectedFolderFound { found }
                } else {
                    DirDiff::UnexpectedFileFound { found }
                }));
            }
            (None, Some(b)) => {
                // we have something in b, but a is done, lets iterate over all
                // entries in b, and put them in UnexpectedFileFound and
                // UnexpectedFolderFound
                let found: std::path::PathBuf = b?.path().into();
                return Ok(Some(if found.is_dir() {
                    DirDiff::UnexpectedFolderFound { found }
                } else {
                    DirDiff::UnexpectedFileFound { found }
                }));
            }
            (Some(a), None) => {
                // we have something in a, but b is done, lets iterate over all
                // entries in a, and put them in ExpectedFileMissing and
                // ExpectedFolderMissing
                let expected: std::path::PathBuf = a?.path().into();
                return Ok(Some(if expected.is_dir() {
                    DirDiff::ExpectedFolderMissing { expected }
                } else {
                    DirDiff::ExpectedFileMissing { expected }
                }));
            }
            (None, None) => break,
        }
    }

    Ok(None)
}

fn walk_dir<P: AsRef<std::path::Path>>(path: P) -> Result<walkdir::IntoIter, std::io::Error> {
    let mut walkdir = walkdir::WalkDir::new(path)
        .sort_by(compare_by_file_name)
        .into_iter();
    if let Some(Err(e)) = walkdir.next() {
        Err(e.into())
    } else {
        Ok(walkdir)
    }
}

fn compare_by_file_name(a: &walkdir::DirEntry, b: &walkdir::DirEntry) -> std::cmp::Ordering {
    a.file_name().cmp(&b.file_name())
}

// fn read_to_vec<P: AsRef<Path>>(file: P) -> Result<Vec<u8>, std::io::Error> {
//     use std::fs::File;
//     use std::io::Read;
//
//     let mut data = Vec::new();
//     let mut file = File::open(file.as_ref())?;
//
//     file.read_to_end(&mut data)?;
//
//     Ok(data)
// }

impl From<std::io::Error> for DirDiffError {
    fn from(e: std::io::Error) -> DirDiffError {
        DirDiffError::Io(e)
    }
}

impl From<std::path::StripPrefixError> for DirDiffError {
    fn from(e: std::path::StripPrefixError) -> DirDiffError {
        DirDiffError::StripPrefix(e)
    }
}

impl From<walkdir::Error> for DirDiffError {
    fn from(e: walkdir::Error) -> DirDiffError {
        DirDiffError::WalkDir(e)
    }
}