Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 8105420

Browse files
committed
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
1 parent 978390c commit 8105420

File tree

6 files changed

+169
-3
lines changed

6 files changed

+169
-3
lines changed

src/diff.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55

66
use crate::params::{parse_params, Format};
77
use crate::utils::report_failure_to_read_input_file;
8-
use crate::{context_diff, ed_diff, normal_diff, unified_diff};
8+
use crate::{context_diff, ed_diff, normal_diff, side_diff, unified_diff};
99
use std::env::ArgsOs;
1010
use std::ffi::OsString;
1111
use std::fs;
@@ -79,6 +79,7 @@ pub fn main(opts: Peekable<ArgsOs>) -> ExitCode {
7979
eprintln!("{error}");
8080
exit(2);
8181
}),
82+
Format::SideBySide => side_diff::diff(&from_content, &to_content),
8283
};
8384
if params.brief && !result.is_empty() {
8485
println!(

src/lib.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,13 @@ pub mod ed_diff;
44
pub mod macros;
55
pub mod normal_diff;
66
pub mod params;
7+
pub mod side_diff;
78
pub mod unified_diff;
89
pub mod utils;
910

1011
// Re-export the public functions/types you need
1112
pub use context_diff::diff as context_diff;
1213
pub use ed_diff::diff as ed_diff;
1314
pub use normal_diff::diff as normal_diff;
15+
pub use side_diff::diff as side_by_side_diff;
1416
pub use unified_diff::diff as unified_diff;

src/main.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ mod ed_diff;
1818
mod macros;
1919
mod normal_diff;
2020
mod params;
21+
mod side_diff;
2122
mod unified_diff;
2223
mod utils;
2324

src/params.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ pub enum Format {
1111
Unified,
1212
Context,
1313
Ed,
14+
SideBySide,
1415
}
1516

1617
#[derive(Clone, Debug, Eq, PartialEq)]
@@ -101,6 +102,13 @@ pub fn parse_params<I: Iterator<Item = OsString>>(mut opts: Peekable<I>) -> Resu
101102
format = Some(Format::Ed);
102103
continue;
103104
}
105+
if param == "-y" || param == "--side-by-side" {
106+
if format.is_some() && format != Some(Format::SideBySide) {
107+
return Err("Conflicting output style option".to_string());
108+
}
109+
format = Some(Format::SideBySide);
110+
continue;
111+
}
104112
if tabsize_re.is_match(param.to_string_lossy().as_ref()) {
105113
// Because param matches the regular expression,
106114
// it is safe to assume it is valid UTF-8.

src/side_diff.rs

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
// This file is part of the uutils diffutils package.
2+
//
3+
// For the full copyright and license information, please view the LICENSE-*
4+
// files that was distributed with this source code.
5+
6+
use crate::utils::limited_string;
7+
use diff::Result;
8+
use std::{
9+
io::{stdout, StdoutLock, Write},
10+
vec,
11+
};
12+
13+
fn push_output(
14+
output: &mut StdoutLock,
15+
left_ln: &[u8],
16+
right_ln: &[u8],
17+
symbol: &[u8],
18+
tab_size: usize,
19+
) -> std::io::Result<()> {
20+
// The reason why this function exists, is that we cannot
21+
// assume a enconding for our left or right line, and the
22+
// writeln!() macro obligattes us to do it.
23+
24+
// side-by-side diff usually prints the output like:
25+
// {left_line}{tab}{space_char}{symbol(|, < or >)}{space_char}{right_line}{EOL}
26+
27+
// recalculate how many spaces are nescessary, cause we need to take into
28+
// consideration the lenght of the word before print it.
29+
let tab_size = (tab_size as isize - left_ln.len() as isize).max(0);
30+
let ident = vec![b' '; tab_size as usize];
31+
output.write_all(left_ln)?; // {left_line}
32+
output.write_all(&ident)?; // {tab}
33+
output.write_all(b" ")?; // {space_char}
34+
output.write_all(symbol)?; // {symbol}
35+
output.write_all(b" ")?; // {space_char}
36+
output.write_all(right_ln)?; // {right_line}
37+
38+
writeln!(output)?; // {EOL}
39+
40+
Ok(())
41+
}
42+
43+
pub fn diff(from_file: &[u8], to_file: &[u8]) -> Vec<u8> {
44+
// ^ The left file ^ The right file
45+
46+
let mut output = stdout().lock();
47+
let left_lines: Vec<&[u8]> = from_file.split(|&c| c == b'\n').collect();
48+
let right_lines: Vec<&[u8]> = to_file.split(|&c| c == b'\n').collect();
49+
let tab_size = 61; // for some reason the tab spaces are 61 not 60
50+
for result in diff::slice(&left_lines, &right_lines) {
51+
match result {
52+
Result::Left(left_ln) => {
53+
push_output(
54+
&mut output,
55+
limited_string(left_ln, tab_size),
56+
&[],
57+
b"<",
58+
tab_size,
59+
)
60+
.unwrap();
61+
}
62+
Result::Right(right_ln) => {
63+
push_output(
64+
&mut output,
65+
&[],
66+
limited_string(right_ln, tab_size),
67+
b">",
68+
tab_size,
69+
)
70+
.unwrap();
71+
}
72+
Result::Both(left_ln, right_ln) => {
73+
push_output(
74+
&mut output,
75+
limited_string(left_ln, tab_size),
76+
limited_string(right_ln, tab_size),
77+
b" ",
78+
tab_size,
79+
)
80+
.unwrap();
81+
}
82+
}
83+
}
84+
85+
vec![]
86+
}

src/utils.rs

Lines changed: 70 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,8 @@
33
// For the full copyright and license information, please view the LICENSE-*
44
// files that was distributed with this source code.
55

6-
use std::{ffi::OsString, io::Write};
7-
86
use regex::Regex;
7+
use std::{ffi::OsString, io::Write};
98
use unicode_width::UnicodeWidthStr;
109

1110
/// Replace tabs by spaces in the input line.
@@ -99,6 +98,15 @@ pub fn report_failure_to_read_input_file(
9998
);
10099
}
101100

101+
/// Limits a string at a certain limiter position. This can break the
102+
/// encoding of a specific char where it has been cut.
103+
#[must_use]
104+
pub fn limited_string(orig: &[u8], limiter: usize) -> &[u8] {
105+
// TODO: Verify if we broke the encoding of the char
106+
// when we cut it.
107+
&orig[..orig.len().min(limiter)]
108+
}
109+
102110
#[cfg(test)]
103111
mod tests {
104112
use super::*;
@@ -205,4 +213,64 @@ mod tests {
205213
assert!(m_time > current_time);
206214
}
207215
}
216+
217+
mod limited_string {
218+
use super::*;
219+
use std::str;
220+
221+
#[test]
222+
fn empty_orig_returns_empty() {
223+
let orig: &[u8] = b"";
224+
let result = limited_string(&orig, 10);
225+
assert!(result.is_empty());
226+
}
227+
228+
#[test]
229+
fn zero_limit_returns_empty() {
230+
let orig: &[u8] = b"foo";
231+
let result = limited_string(&orig, 0);
232+
assert!(result.is_empty());
233+
}
234+
235+
#[test]
236+
fn limit_longer_than_orig_returns_full() {
237+
let orig: &[u8] = b"foo";
238+
let result = limited_string(&orig, 10);
239+
assert_eq!(result, orig);
240+
}
241+
242+
#[test]
243+
fn ascii_limit_in_middle() {
244+
let orig: &[u8] = b"foobar";
245+
let result = limited_string(&orig, 3);
246+
assert_eq!(result, b"foo");
247+
assert!(str::from_utf8(&result).is_ok()); // All are ascii chars, we do not broke the enconding
248+
}
249+
250+
#[test]
251+
fn utf8_multibyte_cut_invalidates() {
252+
let orig = "áéíóú".as_bytes();
253+
let result = limited_string(&orig, 1);
254+
// should contain only the first byte of mult-byte char
255+
assert_eq!(result, vec![0xC3]);
256+
assert!(str::from_utf8(&result).is_err());
257+
}
258+
259+
#[test]
260+
fn utf8_limit_at_codepoint_boundary() {
261+
let orig = "áéí".as_bytes();
262+
let bytes = &orig;
263+
let result = limited_string(&orig, bytes.len());
264+
265+
assert_eq!(result, *bytes);
266+
assert!(str::from_utf8(&result).is_ok());
267+
}
268+
269+
#[test]
270+
fn works_with_byte_vec_input() {
271+
let orig_bytes = b"hello".to_vec();
272+
let result = limited_string(&orig_bytes, 3);
273+
assert_eq!(result, b"hel");
274+
}
275+
}
208276
}

0 commit comments

Comments
 (0)