Skip to content

Commit fe28610

Browse files
committed
Parse all valid arguments accepted by GNU diff to request a unified context (with an optional number of lines)
1 parent 3a8eddf commit fe28610

File tree

1 file changed

+103
-28
lines changed

1 file changed

+103
-28
lines changed

src/params.rs

Lines changed: 103 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ impl Default for Params {
5050
}
5151

5252
pub fn parse_params<I: IntoIterator<Item = OsString>>(opts: I) -> Result<Params, String> {
53-
let mut opts = opts.into_iter();
53+
let mut opts = opts.into_iter().peekable();
5454
// parse CLI
5555

5656
let Some(exe) = opts.next() else {
@@ -60,7 +60,10 @@ pub fn parse_params<I: IntoIterator<Item = OsString>>(opts: I) -> Result<Params,
6060
let mut from = None;
6161
let mut to = None;
6262
let mut format = None;
63+
let mut context_count = None;
6364
let tabsize_re = Regex::new(r"^--tabsize=(?<num>\d+)$").unwrap();
65+
let unified_re =
66+
Regex::new(r"^(-[uU](?<num1>\d*)|--unified(=(?<num2>\d*))?|-(?<num3>\d+)u)$").unwrap();
6467
while let Some(param) = opts.next() {
6568
if param == "--" {
6669
break;
@@ -103,6 +106,40 @@ pub fn parse_params<I: IntoIterator<Item = OsString>>(opts: I) -> Result<Params,
103106
};
104107
continue;
105108
}
109+
if unified_re.is_match(param.to_string_lossy().as_ref()) {
110+
if format.is_some() && format != Some(Format::Unified) {
111+
return Err("Conflicting output style options".to_string());
112+
}
113+
format = Some(Format::Unified);
114+
let captures = unified_re.captures(param.to_str().unwrap()).unwrap();
115+
let num = captures
116+
.name("num1")
117+
.or(captures.name("num2"))
118+
.or(captures.name("num3"));
119+
if num.is_some() && !num.unwrap().as_str().is_empty() {
120+
context_count = Some(num.unwrap().as_str().parse::<usize>().unwrap());
121+
}
122+
if param == "-U" {
123+
let next_param = opts.peek();
124+
if next_param.is_some() {
125+
let next_value = next_param
126+
.unwrap()
127+
.to_string_lossy()
128+
.as_ref()
129+
.parse::<usize>();
130+
if next_value.is_ok() {
131+
context_count = Some(next_value.unwrap());
132+
opts.next();
133+
} else {
134+
return Err(format!(
135+
"invalid context length '{}'",
136+
next_param.unwrap().to_string_lossy()
137+
));
138+
}
139+
}
140+
}
141+
continue;
142+
}
106143
let p = osstr_bytes(&param);
107144
if p.first() == Some(&b'-') && p.get(1) != Some(&b'-') {
108145
let mut bit = p[1..].iter().copied().peekable();
@@ -111,10 +148,12 @@ pub fn parse_params<I: IntoIterator<Item = OsString>>(opts: I) -> Result<Params,
111148
while let Some(b) = bit.next() {
112149
match b {
113150
b'0'..=b'9' => {
114-
params.context_count = (b - b'0') as usize;
151+
context_count = Some((b - b'0') as usize);
115152
while let Some(b'0'..=b'9') = bit.peek() {
116-
params.context_count *= 10;
117-
params.context_count += (bit.next().unwrap() - b'0') as usize;
153+
context_count = Some(context_count.unwrap() * 10);
154+
context_count = Some(
155+
context_count.unwrap() + (bit.next().unwrap() - b'0') as usize,
156+
);
118157
}
119158
}
120159
b'c' => {
@@ -129,30 +168,6 @@ pub fn parse_params<I: IntoIterator<Item = OsString>>(opts: I) -> Result<Params,
129168
}
130169
format = Some(Format::Ed);
131170
}
132-
b'u' => {
133-
if format.is_some() && format != Some(Format::Unified) {
134-
return Err("Conflicting output style options".to_string());
135-
}
136-
format = Some(Format::Unified);
137-
}
138-
b'U' => {
139-
if format.is_some() && format != Some(Format::Unified) {
140-
return Err("Conflicting output style options".to_string());
141-
}
142-
format = Some(Format::Unified);
143-
let context_count_maybe = if bit.peek().is_some() {
144-
String::from_utf8(bit.collect::<Vec<u8>>()).ok()
145-
} else {
146-
opts.next().map(|x| x.to_string_lossy().into_owned())
147-
};
148-
if let Some(context_count_maybe) =
149-
context_count_maybe.and_then(|x| x.parse().ok())
150-
{
151-
params.context_count = context_count_maybe;
152-
break;
153-
}
154-
return Err("Invalid context count".to_string());
155-
}
156171
_ => return Err(format!("Unknown option: {}", String::from_utf8_lossy(&[b]))),
157172
}
158173
}
@@ -179,6 +194,9 @@ pub fn parse_params<I: IntoIterator<Item = OsString>>(opts: I) -> Result<Params,
179194
return Err(format!("Usage: {} <from> <to>", exe.to_string_lossy()));
180195
};
181196
params.format = format.unwrap_or(Format::default());
197+
if context_count.is_some() {
198+
params.context_count = context_count.unwrap();
199+
}
182200
Ok(params)
183201
}
184202

@@ -212,6 +230,63 @@ mod tests {
212230
);
213231
}
214232
#[test]
233+
fn unified_valid() {
234+
for args in [vec!["-u"], vec!["--unified"], vec!["--unified="]] {
235+
let mut params = vec!["diff"];
236+
params.extend(args);
237+
params.extend(["foo", "bar"]);
238+
assert_eq!(
239+
Ok(Params {
240+
from: os("foo"),
241+
to: os("bar"),
242+
format: Format::Unified,
243+
..Default::default()
244+
}),
245+
parse_params(params.iter().map(|x| os(x)))
246+
);
247+
}
248+
for args in [
249+
vec!["-u42"],
250+
vec!["-U42"],
251+
vec!["-U", "42"],
252+
vec!["--unified=42"],
253+
vec!["-42u"],
254+
] {
255+
let mut params = vec!["diff"];
256+
params.extend(args);
257+
params.extend(["foo", "bar"]);
258+
assert_eq!(
259+
Ok(Params {
260+
from: os("foo"),
261+
to: os("bar"),
262+
format: Format::Unified,
263+
context_count: 42,
264+
..Default::default()
265+
}),
266+
parse_params(params.iter().map(|x| os(x)))
267+
);
268+
}
269+
}
270+
#[test]
271+
fn unified_invalid() {
272+
for args in [
273+
vec!["-u", "42"],
274+
vec!["-u=42"],
275+
vec!["-u="],
276+
vec!["-U"],
277+
vec!["-U=42"],
278+
vec!["-U="],
279+
vec!["--unified42"],
280+
vec!["--unified", "42"],
281+
vec!["-42U"],
282+
] {
283+
let mut params = vec!["diff"];
284+
params.extend(args);
285+
params.extend(["foo", "bar"]);
286+
assert!(parse_params(params.iter().map(|x| os(x))).is_err());
287+
}
288+
}
289+
#[test]
215290
fn context_count() {
216291
assert_eq!(
217292
Ok(Params {

0 commit comments

Comments
 (0)