Skip to content

Commit 463f5e2

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 463f5e2

File tree

6 files changed

+164
-3
lines changed

6 files changed

+164
-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, unified_diff, side_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
@@ -6,9 +6,11 @@ pub mod normal_diff;
66
pub mod params;
77
pub mod unified_diff;
88
pub mod utils;
9+
pub mod side_diff;
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;
1415
pub use unified_diff::diff as unified_diff;
16+
pub use side_diff::diff as side_by_syde_diff;

src/main.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ mod macros;
1919
mod normal_diff;
2020
mod params;
2121
mod unified_diff;
22+
mod side_diff;
2223
mod utils;
2324

2425
/// # Panics

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

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<'a>(orig: &'a [u8], limiter: usize) -> &'a [u8] {
105+
// TODO: Verify if we broke the enconding 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)