From 39d2ece18772bc01c83bffd3365a3fffddc54571 Mon Sep 17 00:00:00 2001 From: Tanmay Patil Date: Sat, 20 Apr 2024 22:12:34 +0530 Subject: [PATCH 01/11] Handle directory-file and file-directory comparisons in the diff GNU diff treats `diff DIRECTORY FILE` as `diff DIRECTORY/FILE FILE` --- src/params.rs | 15 +++++++++++++++ tests/integration.rs | 31 +++++++++++++++++++++++++++++++ 2 files changed, 46 insertions(+) diff --git a/src/params.rs b/src/params.rs index 7e9cc78..40af9ea 100644 --- a/src/params.rs +++ b/src/params.rs @@ -1,4 +1,5 @@ use std::ffi::{OsStr, OsString}; +use std::path::PathBuf; use regex::Regex; @@ -178,6 +179,20 @@ pub fn parse_params>(opts: I) -> Result ", exe.to_string_lossy())); }; + + // diff DIRECTORY FILE => diff DIRECTORY/FILE FILE + // diff FILE DIRECTORY => diff FILE DIRECTORY/FILE + let mut from_path: PathBuf = PathBuf::from(¶ms.from); + let mut to_path: PathBuf = PathBuf::from(¶ms.to); + + if from_path.is_dir() && to_path.is_file() { + from_path.push(to_path.file_name().unwrap()); + params.from = from_path.into_os_string(); + } else if from_path.is_file() && to_path.is_dir() { + to_path.push(from_path.file_name().unwrap()); + params.to = to_path.into_os_string(); + } + params.format = format.unwrap_or(Format::default()); Ok(params) } diff --git a/tests/integration.rs b/tests/integration.rs index 853ba4d..3f49af3 100644 --- a/tests/integration.rs +++ b/tests/integration.rs @@ -6,6 +6,7 @@ use assert_cmd::cmd::Command; use diffutilslib::assert_diff_eq; use predicates::prelude::*; +use std::fs::File; use std::io::Write; use tempfile::NamedTempFile; @@ -234,3 +235,33 @@ fn read_from_stdin() -> Result<(), Box> { Ok(()) } + +#[test] +fn read_from_directory() -> Result<(), Box> { + let mut cmd = Command::cargo_bin("diffutils")?; + + let target = "target/integration"; + let _ = std::fs::create_dir(target); + let directory = &format!("{target}/d"); + let _ = std::fs::create_dir(directory); + let mut a = File::create(&format!("{target}/a")).unwrap(); + a.write_all(b"a\n").unwrap(); + let mut da = File::create(&format!("{directory}/a")).unwrap(); + da.write_all(b"da\n").unwrap(); + + cmd.arg("-u") + .arg(&format!("{target}/d")) + .arg(&format!("{target}/a")); + cmd.assert().code(predicate::eq(1)).failure(); + + let output = cmd.output().unwrap().stdout; + assert_diff_eq!( + output, + format!( + "--- {}/d/a\tTIMESTAMP\n+++ {}/a\tTIMESTAMP\n@@ -1 +1 @@\n-da\n+a\n", + target, target + ) + ); + + Ok(()) +} From 65993d6a13bbd36b74ae42b8fc3fcefc5793a0cf Mon Sep 17 00:00:00 2001 From: Tanmay Patil Date: Sat, 20 Apr 2024 22:44:17 +0530 Subject: [PATCH 02/11] Add tests for `diff FILE DIRECTORY` --- tests/integration.rs | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/tests/integration.rs b/tests/integration.rs index 3f49af3..c73e18e 100644 --- a/tests/integration.rs +++ b/tests/integration.rs @@ -238,8 +238,6 @@ fn read_from_stdin() -> Result<(), Box> { #[test] fn read_from_directory() -> Result<(), Box> { - let mut cmd = Command::cargo_bin("diffutils")?; - let target = "target/integration"; let _ = std::fs::create_dir(target); let directory = &format!("{target}/d"); @@ -249,6 +247,7 @@ fn read_from_directory() -> Result<(), Box> { let mut da = File::create(&format!("{directory}/a")).unwrap(); da.write_all(b"da\n").unwrap(); + let mut cmd = Command::cargo_bin("diffutils")?; cmd.arg("-u") .arg(&format!("{target}/d")) .arg(&format!("{target}/a")); @@ -263,5 +262,20 @@ fn read_from_directory() -> Result<(), Box> { ) ); + let mut cmd = Command::cargo_bin("diffutils")?; + cmd.arg("-u") + .arg(&format!("{target}/a")) + .arg(&format!("{target}/d")); + cmd.assert().code(predicate::eq(1)).failure(); + + let output = cmd.output().unwrap().stdout; + assert_diff_eq!( + output, + format!( + "--- {}/a\tTIMESTAMP\n+++ {}/d/a\tTIMESTAMP\n@@ -1 +1 @@\n-a\n+da\n", + target, target + ) + ); + Ok(()) } From 476e69ee206ad0f858fe7d96dfe73f6e2f486c19 Mon Sep 17 00:00:00 2001 From: Tanmay Patil Date: Sun, 21 Apr 2024 18:06:15 +0530 Subject: [PATCH 03/11] Windows: Fix tests --- tests/integration.rs | 36 ++++++++++++++++++++---------------- 1 file changed, 20 insertions(+), 16 deletions(-) diff --git a/tests/integration.rs b/tests/integration.rs index c73e18e..f7174fd 100644 --- a/tests/integration.rs +++ b/tests/integration.rs @@ -8,6 +8,7 @@ use diffutilslib::assert_diff_eq; use predicates::prelude::*; use std::fs::File; use std::io::Write; +use std::path::PathBuf; use tempfile::NamedTempFile; // Integration tests for the diffutils command @@ -238,42 +239,45 @@ fn read_from_stdin() -> Result<(), Box> { #[test] fn read_from_directory() -> Result<(), Box> { - let target = "target/integration"; - let _ = std::fs::create_dir(target); - let directory = &format!("{target}/d"); - let _ = std::fs::create_dir(directory); - let mut a = File::create(&format!("{target}/a")).unwrap(); + let target = PathBuf::from("target/integration"); + let _ = std::fs::create_dir(&target); + + let directory = target.join("d"); + let _ = std::fs::create_dir(&directory); + + let a_path = target.join("a"); + let mut a = File::create(&a_path).unwrap(); a.write_all(b"a\n").unwrap(); - let mut da = File::create(&format!("{directory}/a")).unwrap(); + + let da_path = directory.join("a"); + let mut da = File::create(&da_path).unwrap(); da.write_all(b"da\n").unwrap(); let mut cmd = Command::cargo_bin("diffutils")?; - cmd.arg("-u") - .arg(&format!("{target}/d")) - .arg(&format!("{target}/a")); + cmd.arg("-u").arg(&directory).arg(&a_path); cmd.assert().code(predicate::eq(1)).failure(); let output = cmd.output().unwrap().stdout; assert_diff_eq!( output, format!( - "--- {}/d/a\tTIMESTAMP\n+++ {}/a\tTIMESTAMP\n@@ -1 +1 @@\n-da\n+a\n", - target, target + "--- {}\tTIMESTAMP\n+++ {}\tTIMESTAMP\n@@ -1 +1 @@\n-da\n+a\n", + da_path.display(), + a_path.display() ) ); let mut cmd = Command::cargo_bin("diffutils")?; - cmd.arg("-u") - .arg(&format!("{target}/a")) - .arg(&format!("{target}/d")); + cmd.arg("-u").arg(&a_path).arg(&directory); cmd.assert().code(predicate::eq(1)).failure(); let output = cmd.output().unwrap().stdout; assert_diff_eq!( output, format!( - "--- {}/a\tTIMESTAMP\n+++ {}/d/a\tTIMESTAMP\n@@ -1 +1 @@\n-a\n+da\n", - target, target + "--- {}\tTIMESTAMP\n+++ {}\tTIMESTAMP\n@@ -1 +1 @@\n-a\n+da\n", + a_path.display(), + da_path.display() ) ); From fe28610f210b8addd5a497e7225111a67b308c51 Mon Sep 17 00:00:00 2001 From: Olivier Tilloy Date: Sat, 30 Mar 2024 16:42:07 +0100 Subject: [PATCH 04/11] Parse all valid arguments accepted by GNU diff to request a unified context (with an optional number of lines) --- src/params.rs | 131 +++++++++++++++++++++++++++++++++++++++----------- 1 file changed, 103 insertions(+), 28 deletions(-) diff --git a/src/params.rs b/src/params.rs index 7e9cc78..c0fdd32 100644 --- a/src/params.rs +++ b/src/params.rs @@ -50,7 +50,7 @@ impl Default for Params { } pub fn parse_params>(opts: I) -> Result { - let mut opts = opts.into_iter(); + let mut opts = opts.into_iter().peekable(); // parse CLI let Some(exe) = opts.next() else { @@ -60,7 +60,10 @@ pub fn parse_params>(opts: I) -> Result\d+)$").unwrap(); + let unified_re = + Regex::new(r"^(-[uU](?\d*)|--unified(=(?\d*))?|-(?\d+)u)$").unwrap(); while let Some(param) = opts.next() { if param == "--" { break; @@ -103,6 +106,40 @@ pub fn parse_params>(opts: I) -> Result().unwrap()); + } + if param == "-U" { + let next_param = opts.peek(); + if next_param.is_some() { + let next_value = next_param + .unwrap() + .to_string_lossy() + .as_ref() + .parse::(); + if next_value.is_ok() { + context_count = Some(next_value.unwrap()); + opts.next(); + } else { + return Err(format!( + "invalid context length '{}'", + next_param.unwrap().to_string_lossy() + )); + } + } + } + continue; + } let p = osstr_bytes(¶m); if p.first() == Some(&b'-') && p.get(1) != Some(&b'-') { let mut bit = p[1..].iter().copied().peekable(); @@ -111,10 +148,12 @@ pub fn parse_params>(opts: I) -> Result { - params.context_count = (b - b'0') as usize; + context_count = Some((b - b'0') as usize); while let Some(b'0'..=b'9') = bit.peek() { - params.context_count *= 10; - params.context_count += (bit.next().unwrap() - b'0') as usize; + context_count = Some(context_count.unwrap() * 10); + context_count = Some( + context_count.unwrap() + (bit.next().unwrap() - b'0') as usize, + ); } } b'c' => { @@ -129,30 +168,6 @@ pub fn parse_params>(opts: I) -> Result { - if format.is_some() && format != Some(Format::Unified) { - return Err("Conflicting output style options".to_string()); - } - format = Some(Format::Unified); - } - b'U' => { - if format.is_some() && format != Some(Format::Unified) { - return Err("Conflicting output style options".to_string()); - } - format = Some(Format::Unified); - let context_count_maybe = if bit.peek().is_some() { - String::from_utf8(bit.collect::>()).ok() - } else { - opts.next().map(|x| x.to_string_lossy().into_owned()) - }; - if let Some(context_count_maybe) = - context_count_maybe.and_then(|x| x.parse().ok()) - { - params.context_count = context_count_maybe; - break; - } - return Err("Invalid context count".to_string()); - } _ => return Err(format!("Unknown option: {}", String::from_utf8_lossy(&[b]))), } } @@ -179,6 +194,9 @@ pub fn parse_params>(opts: I) -> Result ", exe.to_string_lossy())); }; params.format = format.unwrap_or(Format::default()); + if context_count.is_some() { + params.context_count = context_count.unwrap(); + } Ok(params) } @@ -212,6 +230,63 @@ mod tests { ); } #[test] + fn unified_valid() { + for args in [vec!["-u"], vec!["--unified"], vec!["--unified="]] { + let mut params = vec!["diff"]; + params.extend(args); + params.extend(["foo", "bar"]); + assert_eq!( + Ok(Params { + from: os("foo"), + to: os("bar"), + format: Format::Unified, + ..Default::default() + }), + parse_params(params.iter().map(|x| os(x))) + ); + } + for args in [ + vec!["-u42"], + vec!["-U42"], + vec!["-U", "42"], + vec!["--unified=42"], + vec!["-42u"], + ] { + let mut params = vec!["diff"]; + params.extend(args); + params.extend(["foo", "bar"]); + assert_eq!( + Ok(Params { + from: os("foo"), + to: os("bar"), + format: Format::Unified, + context_count: 42, + ..Default::default() + }), + parse_params(params.iter().map(|x| os(x))) + ); + } + } + #[test] + fn unified_invalid() { + for args in [ + vec!["-u", "42"], + vec!["-u=42"], + vec!["-u="], + vec!["-U"], + vec!["-U=42"], + vec!["-U="], + vec!["--unified42"], + vec!["--unified", "42"], + vec!["-42U"], + ] { + let mut params = vec!["diff"]; + params.extend(args); + params.extend(["foo", "bar"]); + assert!(parse_params(params.iter().map(|x| os(x))).is_err()); + } + } + #[test] fn context_count() { assert_eq!( Ok(Params { From 22d973fce6daaa8f1368f4900acfee31eaa956fe Mon Sep 17 00:00:00 2001 From: Olivier Tilloy Date: Tue, 9 Apr 2024 19:12:40 +0200 Subject: [PATCH 05/11] Parse all valid arguments accepted by GNU diff to request a regular context (with an optional number of lines) --- src/params.rs | 110 ++++++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 93 insertions(+), 17 deletions(-) diff --git a/src/params.rs b/src/params.rs index c0fdd32..f5ac0ee 100644 --- a/src/params.rs +++ b/src/params.rs @@ -62,6 +62,8 @@ pub fn parse_params>(opts: I) -> Result\d+)$").unwrap(); + let context_re = + Regex::new(r"^(-[cC](?\d*)|--context(=(?\d*))?|-(?\d+)c)$").unwrap(); let unified_re = Regex::new(r"^(-[uU](?\d*)|--unified(=(?\d*))?|-(?\d+)u)$").unwrap(); while let Some(param) = opts.next() { @@ -106,6 +108,40 @@ pub fn parse_params>(opts: I) -> Result().unwrap()); + } + if param == "-C" { + let next_param = opts.peek(); + if next_param.is_some() { + let next_value = next_param + .unwrap() + .to_string_lossy() + .as_ref() + .parse::(); + if next_value.is_ok() { + context_count = Some(next_value.unwrap()); + opts.next(); + } else { + return Err(format!( + "invalid context length '{}'", + next_param.unwrap().to_string_lossy() + )); + } + } + } + continue; + } if unified_re.is_match(param.to_string_lossy().as_ref()) { if format.is_some() && format != Some(Format::Unified) { return Err("Conflicting output style options".to_string()); @@ -143,25 +179,8 @@ pub fn parse_params>(opts: I) -> Result { - context_count = Some((b - b'0') as usize); - while let Some(b'0'..=b'9') = bit.peek() { - context_count = Some(context_count.unwrap() * 10); - context_count = Some( - context_count.unwrap() + (bit.next().unwrap() - b'0') as usize, - ); - } - } - b'c' => { - if format.is_some() && format != Some(Format::Context) { - return Err("Conflicting output style options".to_string()); - } - format = Some(Format::Context); - } b'e' => { if format.is_some() && format != Some(Format::Ed) { return Err("Conflicting output style options".to_string()); @@ -230,6 +249,63 @@ mod tests { ); } #[test] + fn context_valid() { + for args in [vec!["-c"], vec!["--context"], vec!["--context="]] { + let mut params = vec!["diff"]; + params.extend(args); + params.extend(["foo", "bar"]); + assert_eq!( + Ok(Params { + from: os("foo"), + to: os("bar"), + format: Format::Context, + ..Default::default() + }), + parse_params(params.iter().map(|x| os(x))) + ); + } + for args in [ + vec!["-c42"], + vec!["-C42"], + vec!["-C", "42"], + vec!["--context=42"], + vec!["-42c"], + ] { + let mut params = vec!["diff"]; + params.extend(args); + params.extend(["foo", "bar"]); + assert_eq!( + Ok(Params { + from: os("foo"), + to: os("bar"), + format: Format::Context, + context_count: 42, + ..Default::default() + }), + parse_params(params.iter().map(|x| os(x))) + ); + } + } + #[test] + fn context_invalid() { + for args in [ + vec!["-c", "42"], + vec!["-c=42"], + vec!["-c="], + vec!["-C"], + vec!["-C=42"], + vec!["-C="], + vec!["--context42"], + vec!["--context", "42"], + vec!["-42C"], + ] { + let mut params = vec!["diff"]; + params.extend(args); + params.extend(["foo", "bar"]); + assert!(parse_params(params.iter().map(|x| os(x))).is_err()); + } + } + #[test] fn unified_valid() { for args in [vec!["-u"], vec!["--unified"], vec!["--unified="]] { let mut params = vec!["diff"]; From 37fe1ae8089bc347b1166d437d32efa904b33914 Mon Sep 17 00:00:00 2001 From: Olivier Tilloy Date: Tue, 9 Apr 2024 19:42:17 +0200 Subject: [PATCH 06/11] Handle --normal, -e and --ed options --- src/params.rs | 80 ++++++++++++++++++++++++++------------------ tests/integration.rs | 2 +- 2 files changed, 48 insertions(+), 34 deletions(-) diff --git a/src/params.rs b/src/params.rs index f5ac0ee..a3cdfe6 100644 --- a/src/params.rs +++ b/src/params.rs @@ -1,4 +1,4 @@ -use std::ffi::{OsStr, OsString}; +use std::ffi::OsString; use regex::Regex; @@ -11,17 +11,6 @@ pub enum Format { Ed, } -#[cfg(unix)] -fn osstr_bytes(osstr: &OsStr) -> &[u8] { - use std::os::unix::ffi::OsStrExt; - osstr.as_bytes() -} - -#[cfg(not(unix))] -fn osstr_bytes(osstr: &OsStr) -> Vec { - osstr.to_string_lossy().bytes().collect() -} - #[derive(Clone, Debug, Eq, PartialEq)] pub struct Params { pub from: OsString, @@ -92,6 +81,20 @@ pub fn parse_params>(opts: I) -> Result>(opts: I) -> Result { - if format.is_some() && format != Some(Format::Ed) { - return Err("Conflicting output style options".to_string()); - } - format = Some(Format::Ed); - } - _ => return Err(format!("Unknown option: {}", String::from_utf8_lossy(&[b]))), - } - } - } else if from.is_none() { + if param.to_string_lossy().starts_with('-') { + return Err(format!("Unknown option: {:?}", param)); + } + if from.is_none() { from = Some(param); } else if to.is_none() { to = Some(param); @@ -235,20 +227,34 @@ mod tests { }), parse_params([os("diff"), os("foo"), os("bar")].iter().cloned()) ); - } - #[test] - fn basics_ed() { assert_eq!( Ok(Params { from: os("foo"), to: os("bar"), - format: Format::Ed, ..Default::default() }), - parse_params([os("diff"), os("-e"), os("foo"), os("bar")].iter().cloned()) + parse_params( + [os("diff"), os("--normal"), os("foo"), os("bar")] + .iter() + .cloned() + ) ); } #[test] + fn basics_ed() { + for arg in ["-e", "--ed"] { + assert_eq!( + Ok(Params { + from: os("foo"), + to: os("bar"), + format: Format::Ed, + ..Default::default() + }), + parse_params([os("diff"), os(arg), os("foo"), os("bar")].iter().cloned()) + ); + } + } + #[test] fn context_valid() { for args in [vec!["-c"], vec!["--context"], vec!["--context="]] { let mut params = vec!["diff"]; @@ -655,7 +661,15 @@ mod tests { } #[test] fn conflicting_output_styles() { - for (arg1, arg2) in [("-u", "-c"), ("-u", "-e"), ("-c", "-u"), ("-c", "-U42")] { + for (arg1, arg2) in [ + ("-u", "-c"), + ("-u", "-e"), + ("-c", "-u"), + ("-c", "-U42"), + ("-u", "--normal"), + ("--normal", "-e"), + ("--context", "--normal"), + ] { assert!(parse_params( [os("diff"), os(arg1), os(arg2), os("foo"), os("bar")] .iter() diff --git a/tests/integration.rs b/tests/integration.rs index 853ba4d..8e4758e 100644 --- a/tests/integration.rs +++ b/tests/integration.rs @@ -18,7 +18,7 @@ fn unknown_param() -> Result<(), Box> { cmd.assert() .code(predicate::eq(2)) .failure() - .stderr(predicate::str::starts_with("Usage: ")); + .stderr(predicate::str::starts_with("Unknown option: \"--foobar\"")); Ok(()) } From b7261a43f47b26cbb4614aaf72a29cd692c53dda Mon Sep 17 00:00:00 2001 From: Olivier Tilloy Date: Thu, 11 Apr 2024 22:47:16 +0200 Subject: [PATCH 07/11] Break out the logic to match context/unified diff params into separate functions, for improved readability --- src/params.rs | 195 +++++++++++++++++++++++++++++++++----------------- 1 file changed, 130 insertions(+), 65 deletions(-) diff --git a/src/params.rs b/src/params.rs index a3cdfe6..88feb47 100644 --- a/src/params.rs +++ b/src/params.rs @@ -49,13 +49,10 @@ pub fn parse_params>(opts: I) -> Result\d+)$").unwrap(); - let context_re = - Regex::new(r"^(-[cC](?\d*)|--context(=(?\d*))?|-(?\d+)c)$").unwrap(); - let unified_re = - Regex::new(r"^(-[uU](?\d*)|--unified(=(?\d*))?|-(?\d+)u)$").unwrap(); while let Some(param) = opts.next() { + let next_param = opts.peek(); if param == "--" { break; } @@ -111,73 +108,43 @@ pub fn parse_params>(opts: I) -> Result().unwrap()); - } - if param == "-C" { - let next_param = opts.peek(); - if next_param.is_some() { - let next_value = next_param - .unwrap() - .to_string_lossy() - .as_ref() - .parse::(); - if next_value.is_ok() { - context_count = Some(next_value.unwrap()); + match match_context_diff_params(¶m, next_param, format) { + Ok(DiffStyleMatch { + is_match, + context_count, + next_param_consumed, + }) => { + if is_match { + format = Some(Format::Context); + if context_count.is_some() { + context = context_count; + } + if next_param_consumed { opts.next(); - } else { - return Err(format!( - "invalid context length '{}'", - next_param.unwrap().to_string_lossy() - )); } + continue; } } - continue; + Err(error) => return Err(error), } - if unified_re.is_match(param.to_string_lossy().as_ref()) { - if format.is_some() && format != Some(Format::Unified) { - return Err("Conflicting output style options".to_string()); - } - format = Some(Format::Unified); - let captures = unified_re.captures(param.to_str().unwrap()).unwrap(); - let num = captures - .name("num1") - .or(captures.name("num2")) - .or(captures.name("num3")); - if num.is_some() && !num.unwrap().as_str().is_empty() { - context_count = Some(num.unwrap().as_str().parse::().unwrap()); - } - if param == "-U" { - let next_param = opts.peek(); - if next_param.is_some() { - let next_value = next_param - .unwrap() - .to_string_lossy() - .as_ref() - .parse::(); - if next_value.is_ok() { - context_count = Some(next_value.unwrap()); + match match_unified_diff_params(¶m, next_param, format) { + Ok(DiffStyleMatch { + is_match, + context_count, + next_param_consumed, + }) => { + if is_match { + format = Some(Format::Unified); + if context_count.is_some() { + context = context_count; + } + if next_param_consumed { opts.next(); - } else { - return Err(format!( - "invalid context length '{}'", - next_param.unwrap().to_string_lossy() - )); } + continue; } } - continue; + Err(error) => return Err(error), } if param.to_string_lossy().starts_with('-') { return Err(format!("Unknown option: {:?}", param)); @@ -205,12 +172,110 @@ pub fn parse_params>(opts: I) -> Result ", exe.to_string_lossy())); }; params.format = format.unwrap_or(Format::default()); - if context_count.is_some() { - params.context_count = context_count.unwrap(); + if let Some(context_count) = context { + params.context_count = context_count; } Ok(params) } +struct DiffStyleMatch { + is_match: bool, + context_count: Option, + next_param_consumed: bool, +} + +fn match_context_diff_params( + param: &OsString, + next_param: Option<&OsString>, + format: Option, +) -> Result { + const CONTEXT_RE: &str = r"^(-[cC](?\d*)|--context(=(?\d*))?|-(?\d+)c)$"; + let regex = Regex::new(CONTEXT_RE).unwrap(); + let is_match = regex.is_match(param.to_string_lossy().as_ref()); + let mut context_count = None; + let mut next_param_consumed = false; + if is_match { + if format.is_some() && format != Some(Format::Context) { + return Err("Conflicting output style options".to_string()); + } + let captures = regex.captures(param.to_str().unwrap()).unwrap(); + let num = captures + .name("num1") + .or(captures.name("num2")) + .or(captures.name("num3")); + if let Some(numvalue) = num { + if !numvalue.as_str().is_empty() { + context_count = Some(numvalue.as_str().parse::().unwrap()); + } + } + if param == "-C" && next_param.is_some() { + match next_param.unwrap().to_string_lossy().parse::() { + Ok(context_size) => { + context_count = Some(context_size); + next_param_consumed = true; + } + Err(_) => { + return Err(format!( + "invalid context length '{}'", + next_param.unwrap().to_string_lossy() + )) + } + } + } + } + Ok(DiffStyleMatch { + is_match, + context_count, + next_param_consumed, + }) +} + +fn match_unified_diff_params( + param: &OsString, + next_param: Option<&OsString>, + format: Option, +) -> Result { + const UNIFIED_RE: &str = r"^(-[uU](?\d*)|--unified(=(?\d*))?|-(?\d+)u)$"; + let regex = Regex::new(UNIFIED_RE).unwrap(); + let is_match = regex.is_match(param.to_string_lossy().as_ref()); + let mut context_count = None; + let mut next_param_consumed = false; + if is_match { + if format.is_some() && format != Some(Format::Unified) { + return Err("Conflicting output style options".to_string()); + } + let captures = regex.captures(param.to_str().unwrap()).unwrap(); + let num = captures + .name("num1") + .or(captures.name("num2")) + .or(captures.name("num3")); + if let Some(numvalue) = num { + if !numvalue.as_str().is_empty() { + context_count = Some(numvalue.as_str().parse::().unwrap()); + } + } + if param == "-U" && next_param.is_some() { + match next_param.unwrap().to_string_lossy().parse::() { + Ok(context_size) => { + context_count = Some(context_size); + next_param_consumed = true; + } + Err(_) => { + return Err(format!( + "invalid context length '{}'", + next_param.unwrap().to_string_lossy() + )) + } + } + } + } + Ok(DiffStyleMatch { + is_match, + context_count, + next_param_consumed, + }) +} + #[cfg(test)] mod tests { use super::*; From 3dc3fdf5cd49dedde32a2faf778a3b987c8246b9 Mon Sep 17 00:00:00 2001 From: Olivier Tilloy Date: Tue, 23 Apr 2024 17:56:32 +0200 Subject: [PATCH 08/11] Un-hardcode a test filename in an integration test (fixes #61) --- tests/integration.rs | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/tests/integration.rs b/tests/integration.rs index 853ba4d..06f7fb6 100644 --- a/tests/integration.rs +++ b/tests/integration.rs @@ -26,22 +26,26 @@ fn unknown_param() -> Result<(), Box> { fn cannot_read_files() -> Result<(), Box> { let file = NamedTempFile::new()?; + let nofile = NamedTempFile::new()?; + let nopath = nofile.into_temp_path(); + std::fs::remove_file(&nopath)?; + let mut cmd = Command::cargo_bin("diffutils")?; - cmd.arg("foo.txt").arg(file.path()); + cmd.arg(&nopath).arg(file.path()); cmd.assert() .code(predicate::eq(2)) .failure() .stderr(predicate::str::starts_with("Failed to read from-file")); let mut cmd = Command::cargo_bin("diffutils")?; - cmd.arg(file.path()).arg("foo.txt"); + cmd.arg(file.path()).arg(&nopath); cmd.assert() .code(predicate::eq(2)) .failure() .stderr(predicate::str::starts_with("Failed to read to-file")); let mut cmd = Command::cargo_bin("diffutils")?; - cmd.arg("foo.txt").arg("foo.txt"); + cmd.arg(&nopath).arg(&nopath); cmd.assert() .code(predicate::eq(2)) .failure() From 0304391bc530c86c59dc9a8b03109ea67fa9d00d Mon Sep 17 00:00:00 2001 From: Tanmay Patil Date: Tue, 23 Apr 2024 22:44:06 +0530 Subject: [PATCH 09/11] Create test files in temporary directory --- tests/integration.rs | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/tests/integration.rs b/tests/integration.rs index f7174fd..3e14c3c 100644 --- a/tests/integration.rs +++ b/tests/integration.rs @@ -8,8 +8,7 @@ use diffutilslib::assert_diff_eq; use predicates::prelude::*; use std::fs::File; use std::io::Write; -use std::path::PathBuf; -use tempfile::NamedTempFile; +use tempfile::{tempdir, NamedTempFile}; // Integration tests for the diffutils command @@ -238,14 +237,13 @@ fn read_from_stdin() -> Result<(), Box> { } #[test] -fn read_from_directory() -> Result<(), Box> { - let target = PathBuf::from("target/integration"); - let _ = std::fs::create_dir(&target); +fn compare_file_to_directory() -> Result<(), Box> { + let tmp_dir = tempdir()?; - let directory = target.join("d"); + let directory = tmp_dir.path().join("d"); let _ = std::fs::create_dir(&directory); - let a_path = target.join("a"); + let a_path = tmp_dir.path().join("a"); let mut a = File::create(&a_path).unwrap(); a.write_all(b"a\n").unwrap(); From 99d4d029857dfeb55c810f571f8ca4105f7281fd Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Sat, 27 Apr 2024 13:12:16 +0200 Subject: [PATCH 10/11] add missing copyright --- LICENSE-APACHE | 3 +++ LICENSE-MIT | 3 +++ 2 files changed, 6 insertions(+) diff --git a/LICENSE-APACHE b/LICENSE-APACHE index 1b5ec8b..3d8493e 100644 --- a/LICENSE-APACHE +++ b/LICENSE-APACHE @@ -1,3 +1,6 @@ +Copyright (c) Michael Howell +Copyright (c) uutils developers + Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ diff --git a/LICENSE-MIT b/LICENSE-MIT index 31aa793..ba40932 100644 --- a/LICENSE-MIT +++ b/LICENSE-MIT @@ -1,3 +1,6 @@ +Copyright (c) Michael Howell +Copyright (c) uutils developers + Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the From d92132e72154a2f59bd053ee523175b7c235049c Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Sat, 27 Apr 2024 13:12:58 +0200 Subject: [PATCH 11/11] version 0.4.1 --- Cargo.lock | 2 +- Cargo.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index d70d307..2244a3b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -122,7 +122,7 @@ checksum = "6184e33543162437515c2e2b48714794e37845ec9851711914eec9d308f6ebe8" [[package]] name = "diffutils" -version = "0.4.0" +version = "0.4.1" dependencies = [ "assert_cmd", "chrono", diff --git a/Cargo.toml b/Cargo.toml index 0e8dab9..f219dbb 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "diffutils" -version = "0.4.0" +version = "0.4.1" edition = "2021" description = "A CLI app for generating diff files" license = "MIT OR Apache-2.0"