Skip to content
Open
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -15,3 +15,4 @@ travis-ci = { repository = "steveklabnik/dir-diff" }

[dependencies]
walkdir = "2.0.1"
term-table = "0.1.5"
184 changes: 181 additions & 3 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,17 @@
//! assert!(dir_diff::is_different("dir/a", "dir/b").unwrap());
//! ```

extern crate term_table;
extern crate walkdir;

use std::cmp::Ordering;
use std::fs::File;
use std::io::prelude::*;
use std::io::{BufRead, BufReader};
use std::path::Path;
use std::cmp::Ordering;

use term_table::{
cell::{Alignment, Cell}, row::Row, Table, TableStyle,
};
use walkdir::{DirEntry, WalkDir};

/// The various errors that can happen when diffing two directories
Expand Down Expand Up @@ -45,7 +49,8 @@ pub fn is_different<A: AsRef<Path>, B: AsRef<Path>>(a_base: A, b_base: B) -> Res
let a = a?;
let b = b?;

if a.depth() != b.depth() || a.file_type() != b.file_type()
if a.depth() != b.depth()
|| a.file_type() != b.file_type()
|| a.file_name() != b.file_name()
|| (a.file_type().is_file() && read_to_vec(a.path())? != read_to_vec(b.path())?)
{
Expand All @@ -56,17 +61,190 @@ pub fn is_different<A: AsRef<Path>, B: AsRef<Path>>(a_base: A, b_base: B) -> Res
Ok(!a_walker.next().is_none() || !b_walker.next().is_none())
}

macro_rules! add_row {
($table:expr, $file_name:expr, $line_one:expr, $line_two:expr) => {
$table.add_row(Row::new(vec![
Cell::new($file_name, 1),
Cell::new($line_one, 1),
Cell::new($line_two, 1),
]));
};
}

/// Prints any differences between content of two directories to stdout.
///
/// # Examples
///
/// ```no_run
/// extern crate dir_diff;
///
/// assert_eq!(dir_diff::see_difference("main/dir1", "main/dir1").unwrap(), ());
/// ```
pub fn see_difference<A: AsRef<Path>, B: AsRef<Path>>(a_base: A, b_base: B) -> Result<(), Error> {
let mut table = Table::new();
table.max_column_width = 400;

table.style = TableStyle::extended();

let filename_a = &a_base.as_ref().to_string_lossy();
let filename_b = &b_base.as_ref().to_string_lossy();

table.add_row(Row::new(vec![Cell::new_with_alignment(
"DIFFERENCES",
3,
Alignment::Center,
)]));

table.add_row(Row::new(vec![
Cell::new("Filename", 1),
Cell::new(filename_a, 1),
Cell::new(filename_b, 1),
]));

let zipped_file_names = pair_files_to_same_name(
&walk_dir_and_get_only_files(&a_base),
&mut walk_dir_and_get_only_files(&b_base),
);

for (a, b) in zipped_file_names.into_iter() {
match (a, b) {
(Some(i), None) => {
add_row!(table, i, "FILE EXISTS", "DOESN'T EXIST");
}

(None, Some(i)) => {
add_row!(table, i, "DOESN'T EXIST", "FILE EXISTS");
}

(Some(file_1), Some(file_2)) => {
let mut buffreader_a =
BufReader::new(File::open(format!("{}/{}", filename_a, &file_1))?).lines();
let mut buffreader_b =
BufReader::new(File::open(format!("{}/{}", filename_b, &file_2))?).lines();

let mut line_number = 1;

loop {
match (&buffreader_a.next(), &buffreader_b.next()) {
(None, None) => break,

(Some(line_a), Some(line_b)) => {
match (line_a, line_b) {
(Ok(content_a), Ok(content_b)) => if content_a != content_b {
add_row!(
table,
format!("\"{}\":{}", &file_1, line_number),
&content_a,
&content_b
);
},
(Ok(content_a), Err(_)) => {
add_row!(
table,
format!("\"{}\":{}", &file_1, line_number),
&content_a,
""
);
}
(Err(_), Ok(content_b)) => {
add_row!(
table,
format!("\"{}\":{}", &file_1, line_number),
"",
&content_b
);
}
_ => {}
};
}

(Some(line_a), None) => match line_a {
Ok(line_content) => add_row!(
table,
format!("\"{}\":{}", &file_1, line_number),
&line_content,
""
),

Err(_) => {
add_row!(table, format!("\"{}\":{}", &file_1, line_number), "", "")
}
},

(None, Some(line_b)) => match line_b {
Ok(line_content) => add_row!(
table,
format!("\"{}\":{}", &file_2, line_number),
"",
&line_content
),
Err(_) => {
add_row!(table, format!("\"{}\":{}", &file_2, line_number), "", "")
}
},
};

line_number += 1;
}
}
_ => {}
}
}

println!("{}", table.as_string());
Ok(())
}

fn walk_dir<P: AsRef<Path>>(path: P) -> std::iter::Skip<walkdir::IntoIter> {
WalkDir::new(path)
.sort_by(compare_by_file_name)
.into_iter()
.skip(1)
}

/// Iterated through a directory, and collects only the file paths (excluding dir path).
fn walk_dir_and_get_only_files<P: AsRef<Path>>(path: P) -> Vec<String> {
let base_path: &str = &path.as_ref().to_string_lossy().to_string();

WalkDir::new(&path)
.into_iter()
.filter_map(Result::ok)
.filter(|a| a.file_type().is_file())
.into_iter()
.map(|e| {
let file_path = e.path().to_string_lossy().to_string();
String::from(file_path).replace(base_path, "")
})
.collect()
}

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

fn pair_files_to_same_name<'a>(
dir1: &[String],
dir2: &mut Vec<String>,
) -> Vec<(Option<String>, Option<String>)> {
let matched_data = dir1.iter().fold(
Vec::<(Option<String>, Option<String>)>::new(),
|mut previous, current| {
match dir2.into_iter().position(|x| x == current) {
Some(i) => previous.push((Some(current.to_string()), Some(dir2.remove(i)))),
None => previous.push((Some(current.to_string()), None)),
};

return previous;
},
);

dir2.into_iter()
.fold(matched_data, |mut previous, current| {
previous.push((None, Some(current.to_string())));
previous
})
}

fn read_to_vec<P: AsRef<Path>>(file: P) -> Result<Vec<u8>, std::io::Error> {
let mut data = Vec::new();
let mut file = File::open(file.as_ref())?;
Expand Down
2 changes: 1 addition & 1 deletion tests/smoke.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
extern crate dir_diff;

use std::path::Path;
use std::fs::create_dir;
use std::io::ErrorKind;
use std::path::Path;

#[test]
fn easy_good() {
Expand Down