mirror of
https://github.com/uutils/diffutils.git
synced 2026-06-10 15:48:59 -07:00
Create the side-by-side option (-y) feature for the diff command (Incomplete).
- Create the function, in the utils package, limited_string that allows you to truncate a string based on a delimiter (May break the encoding of the character where it was cut) - Create tests for limited_string function - Add support for -y and --side-by-side flags that enables diff output for side-by-side mode - Create implementation of the diff -y (SideBySide) command, base command for sdiff, using the crate diff as engine. Currently it does not fully represent GNU diff -y, some flags (|, (, ), , /) could not be developed due to the limitation of the engine we currently use (crate diff), which did not allow perform logic around it. Only the use of '<' and '>' were enabled. - Create tests for SideBySide implementation
This commit is contained in:
committed by
Sami Daniel (Tsoi)
parent
978390c14d
commit
8105420bb4
+2
-1
@@ -5,7 +5,7 @@
|
||||
|
||||
use crate::params::{parse_params, Format};
|
||||
use crate::utils::report_failure_to_read_input_file;
|
||||
use crate::{context_diff, ed_diff, normal_diff, unified_diff};
|
||||
use crate::{context_diff, ed_diff, normal_diff, side_diff, unified_diff};
|
||||
use std::env::ArgsOs;
|
||||
use std::ffi::OsString;
|
||||
use std::fs;
|
||||
@@ -79,6 +79,7 @@ pub fn main(opts: Peekable<ArgsOs>) -> ExitCode {
|
||||
eprintln!("{error}");
|
||||
exit(2);
|
||||
}),
|
||||
Format::SideBySide => side_diff::diff(&from_content, &to_content),
|
||||
};
|
||||
if params.brief && !result.is_empty() {
|
||||
println!(
|
||||
|
||||
@@ -4,6 +4,7 @@ pub mod ed_diff;
|
||||
pub mod macros;
|
||||
pub mod normal_diff;
|
||||
pub mod params;
|
||||
pub mod side_diff;
|
||||
pub mod unified_diff;
|
||||
pub mod utils;
|
||||
|
||||
@@ -11,4 +12,5 @@ pub mod utils;
|
||||
pub use context_diff::diff as context_diff;
|
||||
pub use ed_diff::diff as ed_diff;
|
||||
pub use normal_diff::diff as normal_diff;
|
||||
pub use side_diff::diff as side_by_side_diff;
|
||||
pub use unified_diff::diff as unified_diff;
|
||||
|
||||
@@ -18,6 +18,7 @@ mod ed_diff;
|
||||
mod macros;
|
||||
mod normal_diff;
|
||||
mod params;
|
||||
mod side_diff;
|
||||
mod unified_diff;
|
||||
mod utils;
|
||||
|
||||
|
||||
@@ -11,6 +11,7 @@ pub enum Format {
|
||||
Unified,
|
||||
Context,
|
||||
Ed,
|
||||
SideBySide,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||
@@ -101,6 +102,13 @@ pub fn parse_params<I: Iterator<Item = OsString>>(mut opts: Peekable<I>) -> Resu
|
||||
format = Some(Format::Ed);
|
||||
continue;
|
||||
}
|
||||
if param == "-y" || param == "--side-by-side" {
|
||||
if format.is_some() && format != Some(Format::SideBySide) {
|
||||
return Err("Conflicting output style option".to_string());
|
||||
}
|
||||
format = Some(Format::SideBySide);
|
||||
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.
|
||||
|
||||
@@ -0,0 +1,86 @@
|
||||
// This file is part of the uutils diffutils package.
|
||||
//
|
||||
// For the full copyright and license information, please view the LICENSE-*
|
||||
// files that was distributed with this source code.
|
||||
|
||||
use crate::utils::limited_string;
|
||||
use diff::Result;
|
||||
use std::{
|
||||
io::{stdout, StdoutLock, Write},
|
||||
vec,
|
||||
};
|
||||
|
||||
fn push_output(
|
||||
output: &mut StdoutLock,
|
||||
left_ln: &[u8],
|
||||
right_ln: &[u8],
|
||||
symbol: &[u8],
|
||||
tab_size: usize,
|
||||
) -> std::io::Result<()> {
|
||||
// The reason why this function exists, is that we cannot
|
||||
// assume a enconding for our left or right line, and the
|
||||
// writeln!() macro obligattes us to do it.
|
||||
|
||||
// side-by-side diff usually prints the output like:
|
||||
// {left_line}{tab}{space_char}{symbol(|, < or >)}{space_char}{right_line}{EOL}
|
||||
|
||||
// recalculate how many spaces are nescessary, cause we need to take into
|
||||
// consideration the lenght of the word before print it.
|
||||
let tab_size = (tab_size as isize - left_ln.len() as isize).max(0);
|
||||
let ident = vec![b' '; tab_size as usize];
|
||||
output.write_all(left_ln)?; // {left_line}
|
||||
output.write_all(&ident)?; // {tab}
|
||||
output.write_all(b" ")?; // {space_char}
|
||||
output.write_all(symbol)?; // {symbol}
|
||||
output.write_all(b" ")?; // {space_char}
|
||||
output.write_all(right_ln)?; // {right_line}
|
||||
|
||||
writeln!(output)?; // {EOL}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn diff(from_file: &[u8], to_file: &[u8]) -> Vec<u8> {
|
||||
// ^ The left file ^ The right file
|
||||
|
||||
let mut output = stdout().lock();
|
||||
let left_lines: Vec<&[u8]> = from_file.split(|&c| c == b'\n').collect();
|
||||
let right_lines: Vec<&[u8]> = to_file.split(|&c| c == b'\n').collect();
|
||||
let tab_size = 61; // for some reason the tab spaces are 61 not 60
|
||||
for result in diff::slice(&left_lines, &right_lines) {
|
||||
match result {
|
||||
Result::Left(left_ln) => {
|
||||
push_output(
|
||||
&mut output,
|
||||
limited_string(left_ln, tab_size),
|
||||
&[],
|
||||
b"<",
|
||||
tab_size,
|
||||
)
|
||||
.unwrap();
|
||||
}
|
||||
Result::Right(right_ln) => {
|
||||
push_output(
|
||||
&mut output,
|
||||
&[],
|
||||
limited_string(right_ln, tab_size),
|
||||
b">",
|
||||
tab_size,
|
||||
)
|
||||
.unwrap();
|
||||
}
|
||||
Result::Both(left_ln, right_ln) => {
|
||||
push_output(
|
||||
&mut output,
|
||||
limited_string(left_ln, tab_size),
|
||||
limited_string(right_ln, tab_size),
|
||||
b" ",
|
||||
tab_size,
|
||||
)
|
||||
.unwrap();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
vec![]
|
||||
}
|
||||
+70
-2
@@ -3,9 +3,8 @@
|
||||
// For the full copyright and license information, please view the LICENSE-*
|
||||
// files that was distributed with this source code.
|
||||
|
||||
use std::{ffi::OsString, io::Write};
|
||||
|
||||
use regex::Regex;
|
||||
use std::{ffi::OsString, io::Write};
|
||||
use unicode_width::UnicodeWidthStr;
|
||||
|
||||
/// Replace tabs by spaces in the input line.
|
||||
@@ -99,6 +98,15 @@ 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::*;
|
||||
@@ -205,4 +213,64 @@ 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