Skip to content

Commit

Permalink
Merge pull request #33 from TanmayPatil105/context-diff-modification-…
Browse files Browse the repository at this point in the history
…time

Display modification times of input files in context and unified diff
oSoMoN authored Apr 16, 2024
2 parents be66ff3 + aedd068 commit 1b311c6
Showing 9 changed files with 306 additions and 34 deletions.
159 changes: 157 additions & 2 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -15,6 +15,7 @@ name = "diffutils"
path = "src/main.rs"

[dependencies]
chrono = "0.4.35"
diff = "0.1.10"
regex = "1.10.3"
same-file = "1.0.6"
23 changes: 16 additions & 7 deletions src/context_diff.rs
Original file line number Diff line number Diff line change
@@ -8,6 +8,7 @@ use std::io::Write;

use crate::params::Params;
use crate::utils::do_write_line;
use crate::utils::get_modification_time;

#[derive(Debug, PartialEq)]
pub enum DiffLine {
@@ -267,10 +268,14 @@ fn make_diff(

#[must_use]
pub fn diff(expected: &[u8], actual: &[u8], params: &Params) -> Vec<u8> {
let from_modified_time = get_modification_time(&params.from.to_string_lossy());
let to_modified_time = get_modification_time(&params.to.to_string_lossy());
let mut output = format!(
"*** {0}\t\n--- {1}\t\n",
"*** {0}\t{1}\n--- {2}\t{3}\n",
params.from.to_string_lossy(),
params.to.to_string_lossy()
from_modified_time,
params.to.to_string_lossy(),
to_modified_time
)
.into_bytes();
let diff_results = make_diff(expected, actual, params.context_count, params.brief);
@@ -717,6 +722,8 @@ mod tests {

#[test]
fn test_stop_early() {
use crate::assert_diff_eq;

let from_filename = "foo";
let from = ["a", "b", "c", ""].join("\n");
let to_filename = "bar";
@@ -731,9 +738,10 @@ mod tests {
..Default::default()
},
);

let expected_full = [
"*** foo\t",
"--- bar\t",
"*** foo\tTIMESTAMP",
"--- bar\tTIMESTAMP",
"***************",
"*** 1,3 ****",
" a",
@@ -746,7 +754,7 @@ mod tests {
"",
]
.join("\n");
assert_eq!(diff_full, expected_full.as_bytes());
assert_diff_eq!(diff_full, expected_full);

let diff_brief = diff(
from.as_bytes(),
@@ -758,8 +766,9 @@ mod tests {
..Default::default()
},
);
let expected_brief = ["*** foo\t", "--- bar\t", ""].join("\n");
assert_eq!(diff_brief, expected_brief.as_bytes());

let expected_brief = ["*** foo\tTIMESTAMP", "--- bar\tTIMESTAMP", ""].join("\n");
assert_diff_eq!(diff_brief, expected_brief);

let nodiff_full = diff(
from.as_bytes(),
1 change: 1 addition & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
pub mod context_diff;
pub mod ed_diff;
pub mod macros;
pub mod normal_diff;
pub mod params;
pub mod unified_diff;
25 changes: 25 additions & 0 deletions src/macros.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
// asserts equality of the actual diff and expected diff
// considering datetime varitations
//
// It replaces the modification time in the actual diff
// with placeholer "TIMESTAMP" and then asserts the equality
//
// For eg.
// let brief = "*** fruits_old.txt\t2024-03-24 23:43:05.189597645 +0530\n
// --- fruits_new.txt\t2024-03-24 23:35:08.922581904 +0530\n";
//
// replaced = "*** fruits_old.txt\tTIMESTAMP\n
// --- fruits_new.txt\tTIMESTAMP\n";
#[macro_export]
macro_rules! assert_diff_eq {
($actual:expr, $expected:expr) => {{
use regex::Regex;
use std::str;

let diff = str::from_utf8(&$actual).unwrap();
let re = Regex::new(r"\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}\.\d+ [+-]\d{4}").unwrap();
let actual = re.replacen(diff, 2, "TIMESTAMP");

assert_eq!(actual, $expected);
}};
}
1 change: 1 addition & 0 deletions src/main.rs
Original file line number Diff line number Diff line change
@@ -12,6 +12,7 @@ use std::process::{exit, ExitCode};

mod context_diff;
mod ed_diff;
mod macros;
mod normal_diff;
mod params;
mod unified_diff;
23 changes: 16 additions & 7 deletions src/unified_diff.rs
Original file line number Diff line number Diff line change
@@ -8,6 +8,7 @@ use std::io::Write;

use crate::params::Params;
use crate::utils::do_write_line;
use crate::utils::get_modification_time;

#[derive(Debug, PartialEq)]
pub enum DiffLine {
@@ -238,10 +239,14 @@ fn make_diff(

#[must_use]
pub fn diff(expected: &[u8], actual: &[u8], params: &Params) -> Vec<u8> {
let from_modified_time = get_modification_time(&params.from.to_string_lossy());
let to_modified_time = get_modification_time(&params.to.to_string_lossy());
let mut output = format!(
"--- {0}\t\n+++ {1}\t\n",
"--- {0}\t{1}\n+++ {2}\t{3}\n",
params.from.to_string_lossy(),
params.to.to_string_lossy()
from_modified_time,
params.to.to_string_lossy(),
to_modified_time
)
.into_bytes();
let diff_results = make_diff(expected, actual, params.context_count, params.brief);
@@ -870,6 +875,8 @@ mod tests {

#[test]
fn test_stop_early() {
use crate::assert_diff_eq;

let from_filename = "foo";
let from = ["a", "b", "c", ""].join("\n");
let to_filename = "bar";
@@ -884,9 +891,10 @@ mod tests {
..Default::default()
},
);

let expected_full = [
"--- foo\t",
"+++ bar\t",
"--- foo\tTIMESTAMP",
"+++ bar\tTIMESTAMP",
"@@ -1,3 +1,3 @@",
" a",
"-b",
@@ -895,7 +903,7 @@ mod tests {
"",
]
.join("\n");
assert_eq!(diff_full, expected_full.as_bytes());
assert_diff_eq!(diff_full, expected_full);

let diff_brief = diff(
from.as_bytes(),
@@ -907,8 +915,9 @@ mod tests {
..Default::default()
},
);
let expected_brief = ["--- foo\t", "+++ bar\t", ""].join("\n");
assert_eq!(diff_brief, expected_brief.as_bytes());

let expected_brief = ["--- foo\tTIMESTAMP", "+++ bar\tTIMESTAMP", ""].join("\n");
assert_diff_eq!(diff_brief, expected_brief);

let nodiff_full = diff(
from.as_bytes(),
61 changes: 61 additions & 0 deletions src/utils.rs
Original file line number Diff line number Diff line change
@@ -52,6 +52,25 @@ pub fn do_write_line(
}
}

/// Retrieves the modification time of the input file specified by file path
/// If an error occurs, it returns the current system time
pub fn get_modification_time(file_path: &str) -> String {
use chrono::{DateTime, Local};
use std::fs;
use std::time::SystemTime;

let modification_time: SystemTime = fs::metadata(file_path)
.and_then(|m| m.modified())
.unwrap_or(SystemTime::now());

let modification_time: DateTime<Local> = modification_time.into();
let modification_time: String = modification_time
.format("%Y-%m-%d %H:%M:%S%.9f %z")
.to_string();

modification_time
}

#[cfg(test)]
mod tests {
use super::*;
@@ -115,4 +134,46 @@ mod tests {
assert_line_written("foo bar\tbaz", true, 8, "foo bar baz");
}
}

mod modification_time {
use super::*;

#[test]
fn set_time() {
use chrono::{DateTime, Local};
use std::time::SystemTime;
use tempfile::NamedTempFile;

let temp = NamedTempFile::new().unwrap();
// set file modification time equal to current time
let current = SystemTime::now();
let _ = temp.as_file().set_modified(current);

// format current time
let current: DateTime<Local> = current.into();
let current: String = current.format("%Y-%m-%d %H:%M:%S%.9f %z").to_string();

// verify
assert_eq!(
current,
get_modification_time(&temp.path().to_string_lossy())
);
}

#[test]
fn invalid_file() {
use chrono::{DateTime, Local};
use std::time::SystemTime;

let invalid_file = "target/utils/invalid-file";

// store current time before calling `get_modification_time`
// Because the file is invalid, it will return SystemTime::now()
// which will be greater than previously saved time
let current_time: DateTime<Local> = SystemTime::now().into();
let m_time: DateTime<Local> = get_modification_time(invalid_file).parse().unwrap();

assert!(m_time > current_time);
}
}
}
46 changes: 28 additions & 18 deletions tests/integration.rs
Original file line number Diff line number Diff line change
@@ -4,6 +4,7 @@
// files that was distributed with this source code.

use assert_cmd::cmd::Command;
use diffutilslib::assert_diff_eq;
use predicates::prelude::*;
use std::io::Write;
use tempfile::NamedTempFile;
@@ -178,26 +179,32 @@ fn read_from_stdin() -> Result<(), Box<dyn std::error::Error>> {
.arg(file1.path())
.arg("-")
.write_stdin("bar\n");
cmd.assert()
.code(predicate::eq(1))
.failure()
.stdout(predicate::eq(format!(
"--- {}\t\n+++ -\t\n@@ -1 +1 @@\n-foo\n+bar\n",
cmd.assert().code(predicate::eq(1)).failure();

let output = cmd.output().unwrap().stdout;
assert_diff_eq!(
output,
format!(
"--- {}\tTIMESTAMP\n+++ -\tTIMESTAMP\n@@ -1 +1 @@\n-foo\n+bar\n",
file1.path().to_string_lossy()
)));
)
);

let mut cmd = Command::cargo_bin("diffutils")?;
cmd.arg("-u")
.arg("-")
.arg(file2.path())
.write_stdin("foo\n");
cmd.assert()
.code(predicate::eq(1))
.failure()
.stdout(predicate::eq(format!(
"--- -\t\n+++ {}\t\n@@ -1 +1 @@\n-foo\n+bar\n",
cmd.assert().code(predicate::eq(1)).failure();

let output = cmd.output().unwrap().stdout;
assert_diff_eq!(
output,
format!(
"--- -\tTIMESTAMP\n+++ {}\tTIMESTAMP\n@@ -1 +1 @@\n-foo\n+bar\n",
file2.path().to_string_lossy()
)));
)
);

let mut cmd = Command::cargo_bin("diffutils")?;
cmd.arg("-u").arg("-").arg("-");
@@ -213,13 +220,16 @@ fn read_from_stdin() -> Result<(), Box<dyn std::error::Error>> {
.arg(file1.path())
.arg("/dev/stdin")
.write_stdin("bar\n");
cmd.assert()
.code(predicate::eq(1))
.failure()
.stdout(predicate::eq(format!(
"--- {}\t\n+++ /dev/stdin\t\n@@ -1 +1 @@\n-foo\n+bar\n",
cmd.assert().code(predicate::eq(1)).failure();

let output = cmd.output().unwrap().stdout;
assert_diff_eq!(
output,
format!(
"--- {}\tTIMESTAMP\n+++ /dev/stdin\tTIMESTAMP\n@@ -1 +1 @@\n-foo\n+bar\n",
file1.path().to_string_lossy()
)));
)
);
}

Ok(())

0 comments on commit 1b311c6

Please sign in to comment.