Skip to content

Commit ad5706b

Browse files
zxvfcsylvestre
authored andcommitted
Add sdiff initiall implementation
1 parent db337cb commit ad5706b

1 file changed

Lines changed: 221 additions & 0 deletions

File tree

src/sdiff.rs

Lines changed: 221 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,221 @@
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 regex::Regex;
7+
8+
// use crate::params::parse_params;
9+
use crate::side_diff;
10+
use crate::utils;
11+
use std::env::ArgsOs;
12+
use std::ffi::OsString;
13+
use std::io::{self, stdout, Write};
14+
use std::iter::Peekable;
15+
use std::process::{exit, ExitCode};
16+
17+
#[derive(Eq, PartialEq, Debug)]
18+
pub struct Params {
19+
pub executable: OsString,
20+
pub from: OsString,
21+
pub to: OsString,
22+
pub expand_tabs: bool,
23+
pub tabsize: usize,
24+
pub width: usize,
25+
}
26+
27+
impl Default for Params {
28+
fn default() -> Self {
29+
Self {
30+
executable: OsString::default(),
31+
from: OsString::default(),
32+
to: OsString::default(),
33+
expand_tabs: false,
34+
tabsize: 8,
35+
width: 130,
36+
}
37+
}
38+
}
39+
40+
pub fn parse_params<I: Iterator<Item = OsString>>(mut opts: Peekable<I>) -> Result<Params, String> {
41+
let Some(executable) = opts.next() else {
42+
return Err("Usage: <exe> <from> <to>".to_string());
43+
};
44+
45+
let mut params = Params {
46+
executable,
47+
..Default::default()
48+
};
49+
50+
let mut from = None;
51+
let mut to = None;
52+
let tabsize_re = Regex::new(r"^--tabsize=(?<num>\d+)$").unwrap();
53+
let width_re = Regex::new(r"--width=(?P<long>\d+)$").unwrap();
54+
55+
while let Some(param) = opts.next() {
56+
if param == "-" {
57+
if from.is_none() {
58+
from = Some(param);
59+
} else if to.is_none() {
60+
to = Some(param);
61+
} else {
62+
return Err(format!(
63+
"Usage: {} <from> <to>",
64+
params.executable.to_string_lossy()
65+
));
66+
}
67+
continue;
68+
}
69+
70+
if param == "-t" || param == "--expand-tabs" {
71+
params.expand_tabs = true;
72+
continue;
73+
}
74+
75+
if tabsize_re.is_match(param.to_string_lossy().as_ref()) {
76+
// Because param matches the regular expression,
77+
// it is safe to assume it is valid UTF-8.
78+
let param = param.into_string().unwrap();
79+
let tabsize_str = tabsize_re
80+
.captures(param.as_str())
81+
.unwrap()
82+
.name("num")
83+
.unwrap()
84+
.as_str();
85+
params.tabsize = match tabsize_str.parse::<usize>() {
86+
Ok(num) => {
87+
if num == 0 {
88+
return Err("invalid tabsize «0»".to_string());
89+
}
90+
91+
num
92+
}
93+
Err(_) => return Err(format!("invalid tabsize «{tabsize_str}»")),
94+
};
95+
96+
continue;
97+
}
98+
99+
if width_re.is_match(param.to_string_lossy().as_ref()) {
100+
let param = param.into_string().unwrap();
101+
let width_str: &str = width_re
102+
.captures(param.as_str())
103+
.unwrap()
104+
.name("long")
105+
.unwrap()
106+
.as_str();
107+
108+
params.width = match width_str.parse::<usize>() {
109+
Ok(num) => {
110+
if num == 0 {
111+
return Err("invalid width «0»".to_string());
112+
}
113+
114+
num
115+
}
116+
Err(_) => return Err(format!("invalid width «{width_str}»")),
117+
};
118+
continue;
119+
}
120+
121+
if from.is_none() {
122+
from = Some(param);
123+
} else if to.is_none() {
124+
to = Some(param);
125+
} else {
126+
return Err(format!(
127+
"Usage: {} <from> <to>",
128+
params.executable.to_string_lossy()
129+
));
130+
}
131+
}
132+
133+
params.from = if let Some(from) = from {
134+
from
135+
} else if let Some(param) = opts.next() {
136+
param
137+
} else {
138+
return Err(format!("Err"));
139+
};
140+
141+
params.to = if let Some(to) = to {
142+
to
143+
} else if let Some(param) = opts.next() {
144+
param
145+
} else {
146+
return Err(format!("Err"));
147+
};
148+
149+
Ok(params)
150+
}
151+
152+
pub fn main(opts: Peekable<ArgsOs>) -> ExitCode {
153+
let params = parse_params(opts).unwrap_or_else(|error| {
154+
eprintln!("{error}");
155+
exit(2);
156+
});
157+
158+
if params.from == "-" && params.to == "-"
159+
|| same_file::is_same_file(&params.from, &params.to).unwrap_or(false)
160+
{
161+
return ExitCode::SUCCESS;
162+
}
163+
164+
let (from_content, to_content) = match utils::read_both_files(&params.from, &params.to) {
165+
Ok(contents) => contents,
166+
Err((filepath, error)) => {
167+
eprintln!(
168+
"{}",
169+
utils::format_failure_to_read_input_file(&params.executable, &filepath, &error)
170+
);
171+
return ExitCode::from(2);
172+
}
173+
};
174+
175+
// run diff
176+
let mut output = stdout().lock();
177+
let result = side_diff::diff(
178+
&from_content,
179+
&to_content,
180+
&mut output,
181+
&side_diff::Params {
182+
tabsize: params.tabsize,
183+
width: params.width,
184+
expand_tabs: params.expand_tabs,
185+
},
186+
);
187+
188+
io::stdout().write_all(&result).unwrap();
189+
if result.is_empty() {
190+
ExitCode::SUCCESS
191+
} else {
192+
ExitCode::from(1)
193+
}
194+
}
195+
196+
#[cfg(test)]
197+
mod tests {
198+
use super::*;
199+
200+
fn os(s: &str) -> OsString {
201+
OsString::from(s)
202+
}
203+
204+
#[test]
205+
fn sdiff_params() {
206+
assert_eq!(
207+
Ok(Params {
208+
executable: os("sdiff"),
209+
from: os("foo"),
210+
to: os("bar"),
211+
..Default::default()
212+
}),
213+
parse_params(
214+
[os("sdiff"), os("foo"), os("bar")]
215+
.iter()
216+
.cloned()
217+
.peekable()
218+
)
219+
)
220+
}
221+
}

0 commit comments

Comments
 (0)