mirror of
https://github.com/uutils/diffutils.git
synced 2026-06-10 15:48:59 -07:00
Add side by side diff (partial)
Create the diff -y utility, this time introducing tests and changes focused
mainly on the construction of the utility and issues related to alignment
and response tabulation. New parameters were introduced such as the size
of the total width of the output in the parameters. A new calculation was
introduced to determine the size of the output columns and the maximum
total column size. The tab and spacing mechanism has the same behavior
as the original diff, with tabs and spaces formatted in the same way.
- Introducing tests for the diff 'main' function
- Introducing fuzzing for side diff utility
- Introducing tests for internal mechanisms
- Modular functions that allow consistent changes across the entire project
This commit is contained in:
+5
-1
@@ -47,4 +47,8 @@ path = "fuzz_targets/fuzz_ed.rs"
|
||||
test = false
|
||||
doc = false
|
||||
|
||||
|
||||
[[bin]]
|
||||
name = "fuzz_side"
|
||||
path = "fuzz_targets/fuzz_side.rs"
|
||||
test = false
|
||||
doc = false
|
||||
@@ -0,0 +1,42 @@
|
||||
#![no_main]
|
||||
#[macro_use]
|
||||
extern crate libfuzzer_sys;
|
||||
|
||||
use diffutilslib::side_diff;
|
||||
|
||||
use std::fs::File;
|
||||
use std::io::Write;
|
||||
use diffutilslib::params::Params;
|
||||
|
||||
fuzz_target!(|x: (Vec<u8>, Vec<u8>, /* usize, usize */ bool)| {
|
||||
let (original, new, /* width, tabsize, */ expand) = x;
|
||||
|
||||
// if width == 0 || tabsize == 0 {
|
||||
// return;
|
||||
// }
|
||||
|
||||
let params = Params {
|
||||
// width,
|
||||
// tabsize,
|
||||
expand_tabs: expand,
|
||||
..Default::default()
|
||||
};
|
||||
let mut output_buf = vec![];
|
||||
side_diff::diff(&original, &new, &mut output_buf, ¶ms);
|
||||
File::create("target/fuzz.file.original")
|
||||
.unwrap()
|
||||
.write_all(&original)
|
||||
.unwrap();
|
||||
File::create("target/fuzz.file.new")
|
||||
.unwrap()
|
||||
.write_all(&new)
|
||||
.unwrap();
|
||||
File::create("target/fuzz.file")
|
||||
.unwrap()
|
||||
.write_all(&original)
|
||||
.unwrap();
|
||||
File::create("target/fuzz.diff")
|
||||
.unwrap()
|
||||
.write_all(&output_buf)
|
||||
.unwrap();
|
||||
});
|
||||
+5
-2
@@ -9,7 +9,7 @@ use crate::{context_diff, ed_diff, normal_diff, side_diff, unified_diff};
|
||||
use std::env::ArgsOs;
|
||||
use std::ffi::OsString;
|
||||
use std::fs;
|
||||
use std::io::{self, Read, Write};
|
||||
use std::io::{self, stdout, Read, Write};
|
||||
use std::iter::Peekable;
|
||||
use std::process::{exit, ExitCode};
|
||||
|
||||
@@ -79,7 +79,10 @@ pub fn main(opts: Peekable<ArgsOs>) -> ExitCode {
|
||||
eprintln!("{error}");
|
||||
exit(2);
|
||||
}),
|
||||
Format::SideBySide => side_diff::diff(&from_content, &to_content),
|
||||
Format::SideBySide => {
|
||||
let mut output = stdout().lock();
|
||||
side_diff::diff(&from_content, &to_content, &mut output, ¶ms)
|
||||
}
|
||||
};
|
||||
if params.brief && !result.is_empty() {
|
||||
println!(
|
||||
|
||||
+34
-3
@@ -25,6 +25,7 @@ pub struct Params {
|
||||
pub brief: bool,
|
||||
pub expand_tabs: bool,
|
||||
pub tabsize: usize,
|
||||
pub width: usize,
|
||||
}
|
||||
|
||||
impl Default for Params {
|
||||
@@ -39,6 +40,7 @@ impl Default for Params {
|
||||
brief: false,
|
||||
expand_tabs: false,
|
||||
tabsize: 8,
|
||||
width: 130,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -58,6 +60,7 @@ pub fn parse_params<I: Iterator<Item = OsString>>(mut opts: Peekable<I>) -> Resu
|
||||
let mut format = None;
|
||||
let mut context = None;
|
||||
let tabsize_re = Regex::new(r"^--tabsize=(?<num>\d+)$").unwrap();
|
||||
let width_re = Regex::new(r"--width=(?P<long>\d+)$").unwrap();
|
||||
while let Some(param) = opts.next() {
|
||||
let next_param = opts.peek();
|
||||
if param == "--" {
|
||||
@@ -109,6 +112,27 @@ pub fn parse_params<I: Iterator<Item = OsString>>(mut opts: Peekable<I>) -> Resu
|
||||
format = Some(Format::SideBySide);
|
||||
continue;
|
||||
}
|
||||
if width_re.is_match(param.to_string_lossy().as_ref()) {
|
||||
let param = param.into_string().unwrap();
|
||||
let width_str: &str = width_re
|
||||
.captures(param.as_str())
|
||||
.unwrap()
|
||||
.name("long")
|
||||
.unwrap()
|
||||
.as_str();
|
||||
|
||||
params.width = match width_str.parse::<usize>() {
|
||||
Ok(num) => {
|
||||
if num == 0 {
|
||||
return Err("invalid width «0»".to_string());
|
||||
}
|
||||
|
||||
num
|
||||
}
|
||||
Err(_) => return Err(format!("invalid width «{width_str}»")),
|
||||
};
|
||||
continue;
|
||||
}
|
||||
if tabsize_re.is_match(param.to_string_lossy().as_ref()) {
|
||||
// Because param matches the regular expression,
|
||||
// it is safe to assume it is valid UTF-8.
|
||||
@@ -120,9 +144,16 @@ pub fn parse_params<I: Iterator<Item = OsString>>(mut opts: Peekable<I>) -> Resu
|
||||
.unwrap()
|
||||
.as_str();
|
||||
params.tabsize = match tabsize_str.parse::<usize>() {
|
||||
Ok(num) => num,
|
||||
Ok(num) => {
|
||||
if num == 0 {
|
||||
return Err("invalid tabsize «0»".to_string());
|
||||
}
|
||||
|
||||
num
|
||||
}
|
||||
Err(_) => return Err(format!("invalid tabsize «{tabsize_str}»")),
|
||||
};
|
||||
|
||||
continue;
|
||||
}
|
||||
match match_context_diff_params(¶m, next_param, format) {
|
||||
@@ -712,11 +743,11 @@ mod tests {
|
||||
executable: os("diff"),
|
||||
from: os("foo"),
|
||||
to: os("bar"),
|
||||
tabsize: 0,
|
||||
tabsize: 1,
|
||||
..Default::default()
|
||||
}),
|
||||
parse_params(
|
||||
[os("diff"), os("--tabsize=0"), os("foo"), os("bar")]
|
||||
[os("diff"), os("--tabsize=1"), os("foo"), os("bar")]
|
||||
.iter()
|
||||
.cloned()
|
||||
.peekable()
|
||||
|
||||
+1239
-59
File diff suppressed because it is too large
Load Diff
@@ -98,15 +98,6 @@ pub fn report_failure_to_read_input_file(
|
||||
);
|
||||
}
|
||||
|
||||
/// Limits a string at a certain limiter position. This can break the
|
||||
/// encoding of a specific char where it has been cut.
|
||||
#[must_use]
|
||||
pub fn limited_string(orig: &[u8], limiter: usize) -> &[u8] {
|
||||
// TODO: Verify if we broke the encoding of the char
|
||||
// when we cut it.
|
||||
&orig[..orig.len().min(limiter)]
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
@@ -213,64 +204,4 @@ mod tests {
|
||||
assert!(m_time > current_time);
|
||||
}
|
||||
}
|
||||
|
||||
mod limited_string {
|
||||
use super::*;
|
||||
use std::str;
|
||||
|
||||
#[test]
|
||||
fn empty_orig_returns_empty() {
|
||||
let orig: &[u8] = b"";
|
||||
let result = limited_string(&orig, 10);
|
||||
assert!(result.is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn zero_limit_returns_empty() {
|
||||
let orig: &[u8] = b"foo";
|
||||
let result = limited_string(&orig, 0);
|
||||
assert!(result.is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn limit_longer_than_orig_returns_full() {
|
||||
let orig: &[u8] = b"foo";
|
||||
let result = limited_string(&orig, 10);
|
||||
assert_eq!(result, orig);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ascii_limit_in_middle() {
|
||||
let orig: &[u8] = b"foobar";
|
||||
let result = limited_string(&orig, 3);
|
||||
assert_eq!(result, b"foo");
|
||||
assert!(str::from_utf8(&result).is_ok()); // All are ascii chars, we do not broke the enconding
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn utf8_multibyte_cut_invalidates() {
|
||||
let orig = "áéíóú".as_bytes();
|
||||
let result = limited_string(&orig, 1);
|
||||
// should contain only the first byte of mult-byte char
|
||||
assert_eq!(result, vec![0xC3]);
|
||||
assert!(str::from_utf8(&result).is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn utf8_limit_at_codepoint_boundary() {
|
||||
let orig = "áéí".as_bytes();
|
||||
let bytes = &orig;
|
||||
let result = limited_string(&orig, bytes.len());
|
||||
|
||||
assert_eq!(result, *bytes);
|
||||
assert!(str::from_utf8(&result).is_ok());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn works_with_byte_vec_input() {
|
||||
let orig_bytes = b"hello".to_vec();
|
||||
let result = limited_string(&orig_bytes, 3);
|
||||
assert_eq!(result, b"hel");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user