From f727e454881b58ab3b493b4773baf882cb694bb6 Mon Sep 17 00:00:00 2001 From: Tochukwu Nkemdilim Date: Wed, 1 Aug 2018 17:30:10 +0100 Subject: [PATCH 1/7] Chore(Stage): Some Improvements --- Cargo.toml | 1 + src/lib.rs | 69 +++++++++++++++++++++++++++++++++++++++++++++++--- tests/smoke.rs | 2 +- 3 files changed, 68 insertions(+), 4 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index bd83954..34ea6d7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -15,3 +15,4 @@ travis-ci = { repository = "steveklabnik/dir-diff" } [dependencies] walkdir = "2.0.1" +term-table = "0.1.5" diff --git a/src/lib.rs b/src/lib.rs index 1cce8bd..9c969d0 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -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 @@ -45,7 +49,8 @@ pub fn is_different, B: AsRef>(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())?) { @@ -56,6 +61,64 @@ pub fn is_different, B: AsRef>(a_base: A, b_base: B) -> Res Ok(!a_walker.next().is_none() || !b_walker.next().is_none()) } +macro_rules! add_row { + (&mut $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), + ])); + }; +} + +pub fn see_difference, B: AsRef>(a_base: A, b_base: B) -> Result<(), Error> { + let mut a_walker = walk_dir(a_base); + let mut b_walker = walk_dir(b_base); + + let mut table = Table::new(); + table.max_column_width = 40; + + table.style = TableStyle::extended(); + + table.add_row(Row::new(vec![Cell::new_with_alignment( + "Differences", + 3, + Alignment::Center, + )])); + + for (a, b) in (&mut a_walker).zip(&mut b_walker) { + let a = a?; + let b = b?; + + let lines = BufReader::new(File::open(b.path())?) + .lines() + .zip(BufReader::new(File::open(a.path())?).lines()); + + for (line_for_a, line_for_b) in lines { + match (line_for_a, line_for_b) { + (Ok(content_a), Ok(content_b)) => if content_a != content_b { + add_row!( + &mut table, + a.path().to_string_lossy(), + &content_a, + &content_b + ); + }, + (Ok(content_a), Err(_)) => { + add_row!(&mut table, a.path().to_string_lossy(), &content_a, ""); + } + (Err(_), Ok(content_b)) => { + add_row!(&mut table, a.path().to_string_lossy(), "", &content_b); + } + _ => {} + }; + } + } + + println!("{}", table.as_string()); + Ok(()) +} + fn walk_dir>(path: P) -> std::iter::Skip { WalkDir::new(path) .sort_by(compare_by_file_name) diff --git a/tests/smoke.rs b/tests/smoke.rs index ce4d338..dd4a22d 100644 --- a/tests/smoke.rs +++ b/tests/smoke.rs @@ -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() { From 47df031cdc3c32bb0e664375d3cbf260068f17bd Mon Sep 17 00:00:00 2001 From: Tochukwu Nkemdilim Date: Wed, 1 Aug 2018 17:37:17 +0100 Subject: [PATCH 2/7] Chore(Stage): Some Improvements --- src/main.rs | 194 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 194 insertions(+) create mode 100644 src/main.rs diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..04f5374 --- /dev/null +++ b/src/main.rs @@ -0,0 +1,194 @@ +//! Determine if two directories have different contents. +//! +//! For now, only one function exists: are they different, or not? In the future, +//! more functionality to actually determine the difference may be added. +//! +//! # Examples +//! +//! ```no_run +//! extern crate dir_diff; +//! +//! 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::iter::FilterMap; +use std::path::Path; +use term_table::{ + cell::{Alignment, Cell}, row::Row, Table, TableStyle, +}; +use walkdir::{DirEntry, WalkDir}; + +/// The various errors that can happen when diffing two directories +#[derive(Debug)] +pub enum Error { + Io(std::io::Error), + StripPrefix(std::path::StripPrefixError), + WalkDir(walkdir::Error), +} + +/// Are the contents of two directories different? +/// +/// # Examples +/// +/// ```no_run +/// extern crate dir_diff; +/// +/// assert!(dir_diff::is_different("dir/a", "dir/b").unwrap()); +/// ``` +pub fn is_different, B: AsRef>(a_base: A, b_base: B) -> Result { + let mut a_walker = walk_dir(a_base); + let mut b_walker = walk_dir(b_base); + + for (a, b) in (&mut a_walker).zip(&mut b_walker) { + let a = a?; + let b = b?; + + 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())?) + { + return Ok(true); + } + } + + Ok(!a_walker.next().is_none() || !b_walker.next().is_none()) +} + +macro_rules! add_row { + (&mut $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), + ])); + }; +} + +pub fn see_difference, B: AsRef>(a_base: A, b_base: B) -> Result<(), Error> { + let mut a_walker = walk_dir(a_base); + let mut b_walker = walk_dir(b_base); + + let mut table = Table::new(); + table.max_column_width = 40; + + table.style = TableStyle::extended(); + + table.add_row(Row::new(vec![Cell::new_with_alignment( + "Differences", + 3, + Alignment::Center, + )])); + + // println!("{:?}", a_walker.next()); + // println!("{:?}", b_walker.next()); + for (a, b) in (&mut a_walker).zip(&mut b_walker) { + let a = a?; + let b = b?; + + let lines = BufReader::new(File::open(b.path())?) + .lines() + .zip(BufReader::new(File::open(a.path())?).lines()); + + for (line_for_a, line_for_b) in lines { + match (line_for_a, line_for_b) { + (Ok(content_a), Ok(content_b)) => if content_a != content_b { + add_row!( + &mut table, + a.path().to_string_lossy(), + &content_a, + &content_b + ); + }, + (Ok(content_a), Err(_)) => { + add_row!(&mut table, a.path().to_string_lossy(), &content_a, ""); + } + (Err(_), Ok(content_b)) => { + add_row!(&mut table, a.path().to_string_lossy(), "", &content_b); + } + _ => {} + }; + } + } + + println!("{}", table.as_string()); + Ok(()) +} + +fn walk_dir>(path: P) -> std::iter::Skip { + WalkDir::new(path) + .sort_by(compare_by_file_name) + .into_iter() + .skip(1) +} + +fn compare_by_file_name(a: &DirEntry, b: &DirEntry) -> Ordering { + a.file_name().cmp(b.file_name()) +} + +fn read_to_vec>(file: P) -> Result, std::io::Error> { + let mut data = Vec::new(); + let mut file = File::open(file.as_ref())?; + + file.read_to_end(&mut data)?; + + Ok(data) +} + +impl From for Error { + fn from(e: std::io::Error) -> Error { + Error::Io(e) + } +} + +impl From for Error { + fn from(e: std::path::StripPrefixError) -> Error { + Error::StripPrefix(e) + } +} + +impl From for Error { + fn from(e: walkdir::Error) -> Error { + Error::WalkDir(e) + } +} + +// fn walk_dir_and_get_files() -> walkdir::IntoIter { +// WalkDir::new("tests/easy").into_iter().filter_map(|e| { +// if let Ok(a) = e { +// if !a.file_type().is_dir() { +// return Some(a); +// } +// } +// None +// }) +// // .collect() +// } + +fn walk_dir_and_get_files() -> impl IntoIterator { + WalkDir::new("tests/easy") + .into_iter() + .filter_map(Result::ok) + .filter(|a| a.file_type().is_file()) +} + +fn main() { + println!("{:?}", see_difference("tests/easy/bad/", "tests/easy/bad/")); + // for entry in WalkDir::new("tests/easy").into_iter().filter_map(|e| { + // if let Ok(a) = e { + // if !a.file_type().is_dir() { + // return Some(a); + // } + // } + // None + // }) { + // println!("{:?}", entry.path().display()); + // } +} From 0ad3fe61fac97b4936bc8ff8cdd74700a90e9b42 Mon Sep 17 00:00:00 2001 From: Tochukwu Nkemdilim Date: Mon, 6 Aug 2018 19:48:12 +0100 Subject: [PATCH 3/7] Feat(Dir/Difference): See Difference Between Dir Files & Content --- src/lib.rs | 160 +++++++++++++++++++++++++++++++++++-------- src/main.rs | 194 ---------------------------------------------------- 2 files changed, 131 insertions(+), 223 deletions(-) delete mode 100644 src/main.rs diff --git a/src/lib.rs b/src/lib.rs index 9c969d0..0b03d29 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -62,7 +62,7 @@ pub fn is_different, B: AsRef>(a_base: A, b_base: B) -> Res } macro_rules! add_row { - (&mut $table:expr, $file_name:expr, $line_one:expr, $line_two:expr) => { + ($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), @@ -72,46 +72,114 @@ macro_rules! add_row { } pub fn see_difference, B: AsRef>(a_base: A, b_base: B) -> Result<(), Error> { - let mut a_walker = walk_dir(a_base); - let mut b_walker = walk_dir(b_base); - let mut table = Table::new(); table.max_column_width = 40; table.style = TableStyle::extended(); + let filename_a = &a_base.as_ref().to_str().unwrap(); + let filename_b = &b_base.as_ref().to_str().unwrap(); + table.add_row(Row::new(vec![Cell::new_with_alignment( - "Differences", + "DIFFERENCES", 3, Alignment::Center, )])); - for (a, b) in (&mut a_walker).zip(&mut b_walker) { - let a = a?; - let b = b?; + table.add_row(Row::new(vec![ + Cell::new("Filename", 1), + Cell::new(filename_a, 1), + Cell::new(filename_b, 1), + ])); - let lines = BufReader::new(File::open(b.path())?) - .lines() - .zip(BufReader::new(File::open(a.path())?).lines()); - - for (line_for_a, line_for_b) in lines { - match (line_for_a, line_for_b) { - (Ok(content_a), Ok(content_b)) => if content_a != content_b { - add_row!( - &mut table, - a.path().to_string_lossy(), - &content_a, - &content_b - ); - }, - (Ok(content_a), Err(_)) => { - add_row!(&mut table, a.path().to_string_lossy(), &content_a, ""); - } - (Err(_), Ok(content_b)) => { - add_row!(&mut table, a.path().to_string_lossy(), "", &content_b); + let zipped_file_names = zip_dir_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; } - _ => {} - }; + } + _ => {} } } @@ -126,10 +194,44 @@ fn walk_dir>(path: P) -> std::iter::Skip { .skip(1) } +fn walk_dir_and_get_only_files>(path: P) -> Vec { + WalkDir::new(&path) + .into_iter() + .filter_map(Result::ok) + .filter(|a| a.file_type().is_file()) + .into_iter() + .map(|e| { + String::from(e.path().to_str().unwrap()).replace(path.as_ref().to_str().unwrap(), "") + }) + .collect() +} + fn compare_by_file_name(a: &DirEntry, b: &DirEntry) -> Ordering { a.file_name().cmp(b.file_name()) } +fn zip_dir_files_to_same_name<'a>( + el1: &[String], + el2: &mut Vec, +) -> Vec<(Option, Option)> { + let matched_data = el1.iter().fold( + Vec::<(Option, Option)>::new(), + |mut previous, current| { + match el2.into_iter().position(|x| x == current) { + Some(i) => previous.push((Some(current.to_string()), Some(el2.remove(i)))), + None => previous.push((Some(current.to_string()), None)), + }; + + return previous; + }, + ); + + el2.into_iter().fold(matched_data, |mut previous, current| { + previous.push((None, Some(current.to_string()))); + previous + }) +} + fn read_to_vec>(file: P) -> Result, std::io::Error> { let mut data = Vec::new(); let mut file = File::open(file.as_ref())?; diff --git a/src/main.rs b/src/main.rs deleted file mode 100644 index 04f5374..0000000 --- a/src/main.rs +++ /dev/null @@ -1,194 +0,0 @@ -//! Determine if two directories have different contents. -//! -//! For now, only one function exists: are they different, or not? In the future, -//! more functionality to actually determine the difference may be added. -//! -//! # Examples -//! -//! ```no_run -//! extern crate dir_diff; -//! -//! 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::iter::FilterMap; -use std::path::Path; -use term_table::{ - cell::{Alignment, Cell}, row::Row, Table, TableStyle, -}; -use walkdir::{DirEntry, WalkDir}; - -/// The various errors that can happen when diffing two directories -#[derive(Debug)] -pub enum Error { - Io(std::io::Error), - StripPrefix(std::path::StripPrefixError), - WalkDir(walkdir::Error), -} - -/// Are the contents of two directories different? -/// -/// # Examples -/// -/// ```no_run -/// extern crate dir_diff; -/// -/// assert!(dir_diff::is_different("dir/a", "dir/b").unwrap()); -/// ``` -pub fn is_different, B: AsRef>(a_base: A, b_base: B) -> Result { - let mut a_walker = walk_dir(a_base); - let mut b_walker = walk_dir(b_base); - - for (a, b) in (&mut a_walker).zip(&mut b_walker) { - let a = a?; - let b = b?; - - 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())?) - { - return Ok(true); - } - } - - Ok(!a_walker.next().is_none() || !b_walker.next().is_none()) -} - -macro_rules! add_row { - (&mut $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), - ])); - }; -} - -pub fn see_difference, B: AsRef>(a_base: A, b_base: B) -> Result<(), Error> { - let mut a_walker = walk_dir(a_base); - let mut b_walker = walk_dir(b_base); - - let mut table = Table::new(); - table.max_column_width = 40; - - table.style = TableStyle::extended(); - - table.add_row(Row::new(vec![Cell::new_with_alignment( - "Differences", - 3, - Alignment::Center, - )])); - - // println!("{:?}", a_walker.next()); - // println!("{:?}", b_walker.next()); - for (a, b) in (&mut a_walker).zip(&mut b_walker) { - let a = a?; - let b = b?; - - let lines = BufReader::new(File::open(b.path())?) - .lines() - .zip(BufReader::new(File::open(a.path())?).lines()); - - for (line_for_a, line_for_b) in lines { - match (line_for_a, line_for_b) { - (Ok(content_a), Ok(content_b)) => if content_a != content_b { - add_row!( - &mut table, - a.path().to_string_lossy(), - &content_a, - &content_b - ); - }, - (Ok(content_a), Err(_)) => { - add_row!(&mut table, a.path().to_string_lossy(), &content_a, ""); - } - (Err(_), Ok(content_b)) => { - add_row!(&mut table, a.path().to_string_lossy(), "", &content_b); - } - _ => {} - }; - } - } - - println!("{}", table.as_string()); - Ok(()) -} - -fn walk_dir>(path: P) -> std::iter::Skip { - WalkDir::new(path) - .sort_by(compare_by_file_name) - .into_iter() - .skip(1) -} - -fn compare_by_file_name(a: &DirEntry, b: &DirEntry) -> Ordering { - a.file_name().cmp(b.file_name()) -} - -fn read_to_vec>(file: P) -> Result, std::io::Error> { - let mut data = Vec::new(); - let mut file = File::open(file.as_ref())?; - - file.read_to_end(&mut data)?; - - Ok(data) -} - -impl From for Error { - fn from(e: std::io::Error) -> Error { - Error::Io(e) - } -} - -impl From for Error { - fn from(e: std::path::StripPrefixError) -> Error { - Error::StripPrefix(e) - } -} - -impl From for Error { - fn from(e: walkdir::Error) -> Error { - Error::WalkDir(e) - } -} - -// fn walk_dir_and_get_files() -> walkdir::IntoIter { -// WalkDir::new("tests/easy").into_iter().filter_map(|e| { -// if let Ok(a) = e { -// if !a.file_type().is_dir() { -// return Some(a); -// } -// } -// None -// }) -// // .collect() -// } - -fn walk_dir_and_get_files() -> impl IntoIterator { - WalkDir::new("tests/easy") - .into_iter() - .filter_map(Result::ok) - .filter(|a| a.file_type().is_file()) -} - -fn main() { - println!("{:?}", see_difference("tests/easy/bad/", "tests/easy/bad/")); - // for entry in WalkDir::new("tests/easy").into_iter().filter_map(|e| { - // if let Ok(a) = e { - // if !a.file_type().is_dir() { - // return Some(a); - // } - // } - // None - // }) { - // println!("{:?}", entry.path().display()); - // } -} From f3c03aa8e692123d676fb92cdce28bb3a2ae2e15 Mon Sep 17 00:00:00 2001 From: Tochukwu Nkemdilim Date: Tue, 7 Aug 2018 01:57:27 +0100 Subject: [PATCH 4/7] Chore(Docs): Add Missing Documentation --- src/lib.rs | 45 +++++++++++++++++++++++++++++---------------- 1 file changed, 29 insertions(+), 16 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 0b03d29..85ccd7b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -71,14 +71,23 @@ macro_rules! add_row { }; } +/// 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, B: AsRef>(a_base: A, b_base: B) -> Result<(), Error> { let mut table = Table::new(); - table.max_column_width = 40; + table.max_column_width = 400; table.style = TableStyle::extended(); - let filename_a = &a_base.as_ref().to_str().unwrap(); - let filename_b = &b_base.as_ref().to_str().unwrap(); + 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", @@ -92,7 +101,7 @@ pub fn see_difference, B: AsRef>(a_base: A, b_base: B) -> R Cell::new(filename_b, 1), ])); - let zipped_file_names = zip_dir_files_to_same_name( + 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), ); @@ -110,7 +119,6 @@ pub fn see_difference, B: AsRef>(a_base: A, b_base: B) -> R (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(); @@ -194,14 +202,18 @@ fn walk_dir>(path: P) -> std::iter::Skip { .skip(1) } +/// Iterated through a directory, and collects only the file paths (excluding dir path). fn walk_dir_and_get_only_files>(path: P) -> Vec { + 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| { - String::from(e.path().to_str().unwrap()).replace(path.as_ref().to_str().unwrap(), "") + let file_path = e.path().to_string_lossy().to_string(); + String::from(file_path).replace(base_path, "") }) .collect() } @@ -210,15 +222,15 @@ fn compare_by_file_name(a: &DirEntry, b: &DirEntry) -> Ordering { a.file_name().cmp(b.file_name()) } -fn zip_dir_files_to_same_name<'a>( - el1: &[String], - el2: &mut Vec, +fn pair_files_to_same_name<'a>( + dir1: &[String], + dir2: &mut Vec, ) -> Vec<(Option, Option)> { - let matched_data = el1.iter().fold( + let matched_data = dir1.iter().fold( Vec::<(Option, Option)>::new(), |mut previous, current| { - match el2.into_iter().position(|x| x == current) { - Some(i) => previous.push((Some(current.to_string()), Some(el2.remove(i)))), + 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)), }; @@ -226,10 +238,11 @@ fn zip_dir_files_to_same_name<'a>( }, ); - el2.into_iter().fold(matched_data, |mut previous, current| { - previous.push((None, Some(current.to_string()))); - previous - }) + dir2.into_iter() + .fold(matched_data, |mut previous, current| { + previous.push((None, Some(current.to_string()))); + previous + }) } fn read_to_vec>(file: P) -> Result, std::io::Error> { From 2704e025c3b72588daab8999bebede6d832333b5 Mon Sep 17 00:00:00 2001 From: Tochukwu Nkemdilim Date: Sat, 18 Aug 2018 11:34:49 +0100 Subject: [PATCH 5/7] Feat(SeeDifference): Return Structured Differences --- src/lib.rs | 229 +++++++++++++++++++---------------------------------- 1 file changed, 80 insertions(+), 149 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 85ccd7b..3f256e7 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -9,19 +9,15 @@ //! extern crate dir_diff; //! //! 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::io::prelude::Read; use std::path::Path; -use term_table::{ - cell::{Alignment, Cell}, row::Row, Table, TableStyle, -}; +use std::path::PathBuf; +use std::{fs, fs::File}; use walkdir::{DirEntry, WalkDir}; /// The various errors that can happen when diffing two directories @@ -30,6 +26,22 @@ pub enum Error { Io(std::io::Error), StripPrefix(std::path::StripPrefixError), WalkDir(walkdir::Error), + /// One directory has more or less files than the other. + MissingFiles, + /// File name doesn't match. + FileNameMismatch(PathBuf, PathBuf), + /// Binary contetn doesn't match. + BinaryContentMismatch(PathBuf, PathBuf), + /// One file has more or less lines than the other. + FileLengthMismatch(PathBuf, PathBuf), + /// The content of a file doesn't match. + ContentMismatch { + line_number: usize, + a_path: PathBuf, + b_path: PathBuf, + a_content: String, + b_content: String, + }, } /// Are the contents of two directories different? @@ -61,17 +73,7 @@ pub fn is_different, B: AsRef>(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. +/// Identify the differences between two directories. /// /// # Examples /// @@ -81,117 +83,70 @@ macro_rules! add_row { /// assert_eq!(dir_diff::see_difference("main/dir1", "main/dir1").unwrap(), ()); /// ``` pub fn see_difference, B: AsRef>(a_base: A, b_base: B) -> Result<(), Error> { - let mut table = Table::new(); - table.max_column_width = 400; + let mut files_a = walk_dir_and_strip_prefix(&a_base) + .into_iter() + .collect::>(); + let mut files_b = walk_dir_and_strip_prefix(&b_base) + .into_iter() + .collect::>(); - table.style = TableStyle::extended(); + if files_a.len() != files_b.len() { + return Err(Error::MissingFiles); + } - let filename_a = &a_base.as_ref().to_string_lossy(); - let filename_b = &b_base.as_ref().to_string_lossy(); + files_a.sort(); + files_b.sort(); - table.add_row(Row::new(vec![Cell::new_with_alignment( - "DIFFERENCES", - 3, - Alignment::Center, - )])); + for (a, b) in files_a.into_iter().zip(files_b.into_iter()).into_iter() { + if a != b { + return Err(Error::FileNameMismatch(a, b)); + } - table.add_row(Row::new(vec![ - Cell::new("Filename", 1), - Cell::new(filename_a, 1), - Cell::new(filename_b, 1), - ])); + let full_path_a = &a_base.as_ref().join(&a); + let full_path_b = &b_base.as_ref().join(&b); - 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), - ); + if full_path_a.is_dir() || full_path_b.is_dir() { + continue; + } - for (a, b) in zipped_file_names.into_iter() { - match (a, b) { - (Some(i), None) => { - add_row!(table, i, "FILE EXISTS", "DOESN'T EXIST"); - } + let content_of_a = fs::read(full_path_a)?; + let content_of_b = fs::read(full_path_b)?; - (None, Some(i)) => { - add_row!(table, i, "DOESN'T EXIST", "FILE EXISTS"); + match ( + String::from_utf8(content_of_a), + String::from_utf8(content_of_b), + ) { + (Err(content_of_a), Err(content_of_b)) => { + if content_of_a.as_bytes() != content_of_b.as_bytes() { + return Err(Error::BinaryContentMismatch(a, b)); + } } + (Ok(content_of_a), Ok(content_of_b)) => { + let mut a_lines = content_of_a.lines().collect::>(); + let mut b_lines = content_of_b.lines().collect::>(); - (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), "", "") - } - }, - }; + if a_lines.len() != b_lines.len() { + return Err(Error::FileLengthMismatch(a, b)); + } - line_number += 1; + for (line_number, (line_a, line_b)) in + a_lines.into_iter().zip(b_lines.into_iter()).enumerate() + { + if line_a != line_b { + return Err(Error::ContentMismatch { + a_path: a, + b_path: b, + a_content: line_a.to_string(), + b_content: line_b.to_string(), + line_number, + }); + } } } - _ => {} + _ => return Err(Error::BinaryContentMismatch(a, b)), } } - println!("{}", table.as_string()); Ok(()) } @@ -202,49 +157,25 @@ fn walk_dir>(path: P) -> std::iter::Skip { .skip(1) } -/// Iterated through a directory, and collects only the file paths (excluding dir path). -fn walk_dir_and_get_only_files>(path: P) -> Vec { - let base_path: &str = &path.as_ref().to_string_lossy().to_string(); - - WalkDir::new(&path) +/// Iterated through a directory, and strip t +fn walk_dir_and_strip_prefix<'a, P>(path: P) -> impl Iterator +where + P: AsRef + Copy, +{ + 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, "") + // .filter(|a| a.to_owned().file_type().is_file()) + .filter_map(move |e| { + let new_path = e.path(); + new_path.strip_prefix(&path).map(|e| e.to_owned()).ok() }) - .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, -) -> Vec<(Option, Option)> { - let matched_data = dir1.iter().fold( - Vec::<(Option, Option)>::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>(file: P) -> Result, std::io::Error> { let mut data = Vec::new(); let mut file = File::open(file.as_ref())?; From fde981c424da5f0ed0348ddd7d8fbe0c53df9ffa Mon Sep 17 00:00:00 2001 From: Tochukwu Nkemdilim Date: Sat, 18 Aug 2018 11:35:55 +0100 Subject: [PATCH 6/7] Chore(Tests): Add Tests For Structured Diffences --- Cargo.toml | 2 +- tests/content_mismatch/dir1/test.txt | 1 + tests/content_mismatch/dir2/test.txt | 1 + tests/dir_name_mismatch/dir1/a.txt | 1 + tests/dir_name_mismatch/dir2/a.txt | 1 + tests/file_length_mismatch/dir1/a.txt | 1 + tests/file_length_mismatch/dir2/a.txt | 2 + tests/file_name_mismatch/dir1/b.txt | 1 + tests/file_name_mismatch/dir2/a.txt | 1 + tests/missing_dir/dir1/a.txt | 1 + tests/missing_dir/dir2/a.txt | 1 + tests/missing_file/dir1/a.txt | 1 + tests/missing_file/dir1/b.txt | 1 + tests/missing_file/dir2/a.txt | 1 + tests/smoke.rs | 127 +++++++++++++++++++++++++- 15 files changed, 141 insertions(+), 2 deletions(-) create mode 100644 tests/content_mismatch/dir1/test.txt create mode 100644 tests/content_mismatch/dir2/test.txt create mode 100644 tests/dir_name_mismatch/dir1/a.txt create mode 100644 tests/dir_name_mismatch/dir2/a.txt create mode 100644 tests/file_length_mismatch/dir1/a.txt create mode 100644 tests/file_length_mismatch/dir2/a.txt create mode 100644 tests/file_name_mismatch/dir1/b.txt create mode 100644 tests/file_name_mismatch/dir2/a.txt create mode 100644 tests/missing_dir/dir1/a.txt create mode 100644 tests/missing_dir/dir2/a.txt create mode 100644 tests/missing_file/dir1/a.txt create mode 100644 tests/missing_file/dir1/b.txt create mode 100644 tests/missing_file/dir2/a.txt diff --git a/Cargo.toml b/Cargo.toml index 34ea6d7..14be61d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -15,4 +15,4 @@ travis-ci = { repository = "steveklabnik/dir-diff" } [dependencies] walkdir = "2.0.1" -term-table = "0.1.5" +matches = "0.1.7" diff --git a/tests/content_mismatch/dir1/test.txt b/tests/content_mismatch/dir1/test.txt new file mode 100644 index 0000000..bad6cf1 --- /dev/null +++ b/tests/content_mismatch/dir1/test.txt @@ -0,0 +1 @@ +testing testing \ No newline at end of file diff --git a/tests/content_mismatch/dir2/test.txt b/tests/content_mismatch/dir2/test.txt new file mode 100644 index 0000000..8882ebd --- /dev/null +++ b/tests/content_mismatch/dir2/test.txt @@ -0,0 +1 @@ +oh no! \ No newline at end of file diff --git a/tests/dir_name_mismatch/dir1/a.txt b/tests/dir_name_mismatch/dir1/a.txt new file mode 100644 index 0000000..f3a3485 --- /dev/null +++ b/tests/dir_name_mismatch/dir1/a.txt @@ -0,0 +1 @@ +text \ No newline at end of file diff --git a/tests/dir_name_mismatch/dir2/a.txt b/tests/dir_name_mismatch/dir2/a.txt new file mode 100644 index 0000000..f3a3485 --- /dev/null +++ b/tests/dir_name_mismatch/dir2/a.txt @@ -0,0 +1 @@ +text \ No newline at end of file diff --git a/tests/file_length_mismatch/dir1/a.txt b/tests/file_length_mismatch/dir1/a.txt new file mode 100644 index 0000000..b6fc4c6 --- /dev/null +++ b/tests/file_length_mismatch/dir1/a.txt @@ -0,0 +1 @@ +hello \ No newline at end of file diff --git a/tests/file_length_mismatch/dir2/a.txt b/tests/file_length_mismatch/dir2/a.txt new file mode 100644 index 0000000..6d5645c --- /dev/null +++ b/tests/file_length_mismatch/dir2/a.txt @@ -0,0 +1,2 @@ +hello +master \ No newline at end of file diff --git a/tests/file_name_mismatch/dir1/b.txt b/tests/file_name_mismatch/dir1/b.txt new file mode 100644 index 0000000..f3a3485 --- /dev/null +++ b/tests/file_name_mismatch/dir1/b.txt @@ -0,0 +1 @@ +text \ No newline at end of file diff --git a/tests/file_name_mismatch/dir2/a.txt b/tests/file_name_mismatch/dir2/a.txt new file mode 100644 index 0000000..f3a3485 --- /dev/null +++ b/tests/file_name_mismatch/dir2/a.txt @@ -0,0 +1 @@ +text \ No newline at end of file diff --git a/tests/missing_dir/dir1/a.txt b/tests/missing_dir/dir1/a.txt new file mode 100644 index 0000000..9233c1a --- /dev/null +++ b/tests/missing_dir/dir1/a.txt @@ -0,0 +1 @@ +dd \ No newline at end of file diff --git a/tests/missing_dir/dir2/a.txt b/tests/missing_dir/dir2/a.txt new file mode 100644 index 0000000..f3a3485 --- /dev/null +++ b/tests/missing_dir/dir2/a.txt @@ -0,0 +1 @@ +text \ No newline at end of file diff --git a/tests/missing_file/dir1/a.txt b/tests/missing_file/dir1/a.txt new file mode 100644 index 0000000..c844b92 --- /dev/null +++ b/tests/missing_file/dir1/a.txt @@ -0,0 +1 @@ +some text from dir1 \ No newline at end of file diff --git a/tests/missing_file/dir1/b.txt b/tests/missing_file/dir1/b.txt new file mode 100644 index 0000000..b649a9b --- /dev/null +++ b/tests/missing_file/dir1/b.txt @@ -0,0 +1 @@ +some text \ No newline at end of file diff --git a/tests/missing_file/dir2/a.txt b/tests/missing_file/dir2/a.txt new file mode 100644 index 0000000..b649a9b --- /dev/null +++ b/tests/missing_file/dir2/a.txt @@ -0,0 +1 @@ +some text \ No newline at end of file diff --git a/tests/smoke.rs b/tests/smoke.rs index dd4a22d..d120509 100644 --- a/tests/smoke.rs +++ b/tests/smoke.rs @@ -1,8 +1,11 @@ extern crate dir_diff; +#[macro_use] +extern crate matches; +use dir_diff::Error::*; use std::fs::create_dir; use std::io::ErrorKind; -use std::path::Path; +use std::path::{Path, PathBuf}; #[test] fn easy_good() { @@ -63,3 +66,125 @@ fn filedepth() { dir_diff::is_different("tests/filedepth/desc/dir1", "tests/filedepth/desc/dir2").unwrap() ); } + +#[test] +fn missing_file() { + assert_matches!( + dir_diff::see_difference("tests/missing_file/dir1", "tests/missing_file/dir2"), + Err(MissingFiles) + ); + + assert_matches!( + dir_diff::see_difference("tests/missing_dir/dir1", "tests/missing_dir/dir2"), + Err(MissingFiles) + ); +} + +#[test] +fn file_length_mismatch() { + assert_matches!( + dir_diff::see_difference( + "tests/file_length_mismatch/dir1", + "tests/file_length_mismatch/dir2", + ), + Err(FileLengthMismatch(_, _)) + ); +} + +#[test] +fn binary_content_mismatch() { + let expected_binary_filename_a = PathBuf::from("rust-logo.png"); + let expected_binary_filename_b = PathBuf::from("rust-logo.png"); + + let result = dir_diff::see_difference("tests/binary/bad/dir1", "tests/binary/bad/dir2"); + assert_matches!(result, Err(BinaryContentMismatch(_, _))); + + let result = result.unwrap_err(); + if let BinaryContentMismatch(a, b) = &result { + if *a != expected_binary_filename_a || *b != expected_binary_filename_b { + let expected = FileNameMismatch(expected_binary_filename_a, expected_binary_filename_b); + panic!("{:?} doesn't match {:?}", &result, expected); + } + }; +} + +#[test] +fn dir_name_mismatch() { + let expected_dir_a = PathBuf::from("dirA"); + let expected_dir_b = PathBuf::from("dirB"); + + let result = dir_diff::see_difference( + "tests/dir_name_mismatch/dir1", + "tests/dir_name_mismatch/dir2", + ); + assert_matches!(result, Err(FileNameMismatch(_, _))); + + let result = result.unwrap_err(); + if let FileNameMismatch(a, b) = &result { + if *a != expected_dir_a || *b != expected_dir_b { + let expected = FileNameMismatch(expected_dir_a, expected_dir_b); + panic!("{:?} doesn't match {:?}", &result, expected); + } + }; +} + +#[test] +fn file_name_mismatch() { + let expected_file_a = PathBuf::from("b.txt"); + let expected_file_b = PathBuf::from("a.txt"); + + let result = dir_diff::see_difference( + "tests/file_name_mismatch/dir1", + "tests/file_name_mismatch/dir2", + ); + assert_matches!(result, Err(FileNameMismatch(_, _))); + + let result = result.unwrap_err(); + if let FileNameMismatch(a, b) = &result { + if *a != expected_file_a || *b != expected_file_b { + let expected = FileNameMismatch(expected_file_a, expected_file_b); + panic!("{:?} doesn't match {:?}", &result, expected); + } + }; +} + +#[test] +fn content_misatch() { + let expected_a_path = PathBuf::from("test.txt"); + let expected_b_path = PathBuf::from("test.txt"); + let expected_a_content = String::from("testing testing"); + let expected_b_content = String::from("oh no!"); + + let result = + dir_diff::see_difference("tests/content_mismatch/dir1", "tests/content_mismatch/dir2"); + + assert_matches!(result, Err(ContentMismatch { .. })); + let result = result.unwrap_err(); + + // Match the ContentMismatch result with th expected values. + if let ContentMismatch { + line_number, + a_path, + b_path, + a_content, + b_content, + } = &result + { + if *line_number != 0 + || *a_path != expected_a_path + || *b_path != expected_b_path + || *a_content != expected_a_content + || *b_content != expected_b_content + { + let expected = ContentMismatch { + line_number: 0, + a_path: expected_a_path, + b_path: expected_b_path, + a_content: expected_a_content, + b_content: expected_b_content, + }; + + panic!("{:?} doesn't match {:?}", &result, expected); + } + } +} From aaa2cace8c34a7ca13fd87c35b6e340cd3ab4bcf Mon Sep 17 00:00:00 2001 From: Tochukwu Nkemdilim Date: Sat, 18 Aug 2018 11:37:35 +0100 Subject: [PATCH 7/7] Chore(Doc): Improve Documentation --- src/lib.rs | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 3f256e7..e600da8 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -157,18 +157,17 @@ fn walk_dir>(path: P) -> std::iter::Skip { .skip(1) } -/// Iterated through a directory, and strip t -fn walk_dir_and_strip_prefix<'a, P>(path: P) -> impl Iterator +/// Iterated through a directory, and strips the prefix of each path. +fn walk_dir_and_strip_prefix<'a, P>(prefix: P) -> impl Iterator where P: AsRef + Copy, { - WalkDir::new(path) + WalkDir::new(prefix) .into_iter() .filter_map(Result::ok) - // .filter(|a| a.to_owned().file_type().is_file()) .filter_map(move |e| { let new_path = e.path(); - new_path.strip_prefix(&path).map(|e| e.to_owned()).ok() + new_path.strip_prefix(&prefix).map(|e| e.to_owned()).ok() }) }