Skip to content

Commit

Permalink
Parse all valid arguments accepted by GNU diff to request a unified c…
Browse files Browse the repository at this point in the history
…ontext (with an optional number of lines)
  • Loading branch information
oSoMoN committed Apr 1, 2024
1 parent b135b6f commit 40ba4ad
Showing 1 changed file with 208 additions and 28 deletions.
236 changes: 208 additions & 28 deletions src/params.rs
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ impl Default for Params {
}

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

let Some(exe) = opts.next() else {
Expand All @@ -60,7 +60,10 @@ pub fn parse_params<I: IntoIterator<Item = OsString>>(opts: I) -> Result<Params,
let mut from = None;
let mut to = None;
let mut format = None;
let mut context_count = None;
let tabsize_re = Regex::new(r"^--tabsize=(?<num>\d+)$").unwrap();
let unified_re =
Regex::new(r"^(-[uU](?<num1>\d*)|--unified(=(?<num2>\d*))?|-(?<num3>\d+)u)$").unwrap();
while let Some(param) = opts.next() {
if param == "--" {
break;
Expand Down Expand Up @@ -103,6 +106,40 @@ pub fn parse_params<I: IntoIterator<Item = OsString>>(opts: I) -> Result<Params,
};
continue;
}
if unified_re.is_match(param.to_string_lossy().as_ref()) {
if format.is_some() && format != Some(Format::Unified) {
return Err("Conflicting output style options".to_string());
}
format = Some(Format::Unified);
let captures = unified_re.captures(param.to_str().unwrap()).unwrap();
let num = captures
.name("num1")
.or(captures.name("num2"))
.or(captures.name("num3"));
if num.is_some() && !num.unwrap().as_str().is_empty() {
context_count = Some(num.unwrap().as_str().parse::<usize>().unwrap());
}
if param == "-U" {
let next_param = opts.peek();
if next_param.is_some() {
let next_value = next_param
.unwrap()
.to_string_lossy()
.as_ref()
.parse::<usize>();
if next_value.is_ok() {
context_count = Some(next_value.unwrap());
opts.next();
} else {
return Err(format!(
"invalid context length '{}'",
next_param.unwrap().to_string_lossy()
));
}
}
}
continue;
}
let p = osstr_bytes(&param);
if p.first() == Some(&b'-') && p.get(1) != Some(&b'-') {
let mut bit = p[1..].iter().copied().peekable();
Expand All @@ -111,10 +148,12 @@ pub fn parse_params<I: IntoIterator<Item = OsString>>(opts: I) -> Result<Params,
while let Some(b) = bit.next() {
match b {
b'0'..=b'9' => {
params.context_count = (b - b'0') as usize;
context_count = Some((b - b'0') as usize);
while let Some(b'0'..=b'9') = bit.peek() {
params.context_count *= 10;
params.context_count += (bit.next().unwrap() - b'0') as usize;
context_count = Some(context_count.unwrap() * 10);
context_count = Some(
context_count.unwrap() + (bit.next().unwrap() - b'0') as usize,
);
}
}
b'c' => {
Expand All @@ -129,30 +168,6 @@ pub fn parse_params<I: IntoIterator<Item = OsString>>(opts: I) -> Result<Params,
}
format = Some(Format::Ed);
}
b'u' => {
if format.is_some() && format != Some(Format::Unified) {
return Err("Conflicting output style options".to_string());
}
format = Some(Format::Unified);
}
b'U' => {
if format.is_some() && format != Some(Format::Unified) {
return Err("Conflicting output style options".to_string());
}
format = Some(Format::Unified);
let context_count_maybe = if bit.peek().is_some() {
String::from_utf8(bit.collect::<Vec<u8>>()).ok()
} else {
opts.next().map(|x| x.to_string_lossy().into_owned())
};
if let Some(context_count_maybe) =
context_count_maybe.and_then(|x| x.parse().ok())
{
params.context_count = context_count_maybe;
break;
}
return Err("Invalid context count".to_string());
}
_ => return Err(format!("Unknown option: {}", String::from_utf8_lossy(&[b]))),
}
}
Expand All @@ -179,6 +194,9 @@ pub fn parse_params<I: IntoIterator<Item = OsString>>(opts: I) -> Result<Params,
return Err(format!("Usage: {} <from> <to>", exe.to_string_lossy()));
};
params.format = format.unwrap_or(Format::default());
if context_count.is_some() {
params.context_count = context_count.unwrap();
}
Ok(params)
}

Expand Down Expand Up @@ -212,6 +230,168 @@ mod tests {
);
}
#[test]
fn unified_valid() {
assert_eq!(
Ok(Params {
from: os("foo"),
to: os("bar"),
format: Format::Unified,
..Default::default()
}),
parse_params([os("diff"), os("-u"), os("foo"), os("bar")].iter().cloned())
);
assert_eq!(
Ok(Params {
from: os("foo"),
to: os("bar"),
format: Format::Unified,
context_count: 42,
..Default::default()
}),
parse_params(
[os("diff"), os("-u42"), os("foo"), os("bar")]
.iter()
.cloned()
)
);
assert_eq!(
Ok(Params {
from: os("foo"),
to: os("bar"),
format: Format::Unified,
context_count: 42,
..Default::default()
}),
parse_params(
[os("diff"), os("-U42"), os("foo"), os("bar")]
.iter()
.cloned()
)
);
assert_eq!(
Ok(Params {
from: os("foo"),
to: os("bar"),
format: Format::Unified,
context_count: 42,
..Default::default()
}),
parse_params(
[os("diff"), os("-U"), os("42"), os("foo"), os("bar")]
.iter()
.cloned()
)
);
assert_eq!(
Ok(Params {
from: os("foo"),
to: os("bar"),
format: Format::Unified,
..Default::default()
}),
parse_params(
[os("diff"), os("--unified"), os("foo"), os("bar")]
.iter()
.cloned()
)
);
assert_eq!(
Ok(Params {
from: os("foo"),
to: os("bar"),
format: Format::Unified,
..Default::default()
}),
parse_params(
[os("diff"), os("--unified="), os("foo"), os("bar")]
.iter()
.cloned()
)
);
assert_eq!(
Ok(Params {
from: os("foo"),
to: os("bar"),
format: Format::Unified,
context_count: 42,
..Default::default()
}),
parse_params(
[os("diff"), os("--unified=42"), os("foo"), os("bar")]
.iter()
.cloned()
)
);
assert_eq!(
Ok(Params {
from: os("foo"),
to: os("bar"),
format: Format::Unified,
context_count: 42,
..Default::default()
}),
parse_params(
[os("diff"), os("-42u"), os("foo"), os("bar")]
.iter()
.cloned()
)
);
}
#[test]
fn unified_invalid() {
assert!(parse_params(
[os("diff"), os("-u"), os("42"), os("foo"), os("bar")]
.iter()
.cloned()
)
.is_err());
assert!(parse_params(
[os("diff"), os("-u=42"), os("foo"), os("bar")]
.iter()
.cloned()
)
.is_err());
assert!(parse_params(
[os("diff"), os("-u="), os("foo"), os("bar")]
.iter()
.cloned()
)
.is_err());
assert!(
parse_params([os("diff"), os("-U"), os("foo"), os("bar")].iter().cloned()).is_err()
);
assert!(parse_params(
[os("diff"), os("-U=42"), os("foo"), os("bar")]
.iter()
.cloned()
)
.is_err());
assert!(parse_params(
[os("diff"), os("-U="), os("foo"), os("bar")]
.iter()
.cloned()
)
.is_err());
assert!(parse_params(
[os("diff"), os("--unified42"), os("foo"), os("bar")]
.iter()
.cloned()
)
.is_err());
assert!(parse_params(
[os("diff"), os("--unified"), os("42"), os("foo"), os("bar")]
.iter()
.cloned()
)
.is_err());
assert!(parse_params(
[os("diff"), os("-42U"), os("foo"), os("bar")]
.iter()
.cloned()
)
.is_err());
}
#[test]
fn context_count() {
assert_eq!(
Ok(Params {
Expand Down

0 comments on commit 40ba4ad

Please sign in to comment.