Skip to content

Commit

Permalink
treefile: Error if missing terminating quote in pkgs
Browse files Browse the repository at this point in the history
split_whitespace_unless_quoted() now returns Result type; returns Err() if missing a terminating quote in element. Unit tests are adjusted accordingly.
  • Loading branch information
kelvinfan001 committed Jul 2, 2020
1 parent 1b12369 commit 3bf8e02
Showing 1 changed file with 47 additions and 44 deletions.
91 changes: 47 additions & 44 deletions rust/src/treefile.rs
Original file line number Diff line number Diff line change
Expand Up @@ -102,13 +102,13 @@ fn treefile_parse_stream<R: io::Read>(
let mut pkgs: Vec<String> = vec![];
{
if let Some(base_pkgs) = treefile.packages.take() {
pkgs.extend_from_slice(&whitespace_split_packages(&base_pkgs));
pkgs.extend_from_slice(&whitespace_split_packages(&base_pkgs)?);
}
if let Some(bootstrap_pkgs) = treefile.bootstrap_packages.take() {
pkgs.extend_from_slice(&whitespace_split_packages(&bootstrap_pkgs));
pkgs.extend_from_slice(&whitespace_split_packages(&bootstrap_pkgs)?);
}
if let Some(archful_pkgs) = archful_pkgs.take() {
pkgs.extend_from_slice(&whitespace_split_packages(&archful_pkgs));
pkgs.extend_from_slice(&whitespace_split_packages(&archful_pkgs)?);
}
}

Expand Down Expand Up @@ -574,42 +574,46 @@ impl TreefileExternals {

/// For increased readability in YAML/JSON, we support whitespace in individual
/// array elements.
fn whitespace_split_packages(pkgs: &[String]) -> Vec<String> {
pkgs.iter()
.flat_map(|element| split_whitespace_unless_quoted(element).map(String::from))
.collect()
fn whitespace_split_packages(pkgs: &[String]) -> Result<Vec<String>> {
let mut ret = vec![];
for element in pkgs.iter() {
ret.extend(split_whitespace_unless_quoted(element)?.map(String::from));
}

Ok(ret)
}

// Helper for whitespace_split_packages().
// Splits a String by whitespace unless substring is wrapped between single quotes
// and returns split Strings in an Iterator, with quoted packages first.
fn split_whitespace_unless_quoted(element: &String) -> impl Iterator<Item = String> {
let mut quoted_pkgs: Vec<String> = vec![];
let mut to_whitespace_split: Vec<String> = vec![];
// and returns split &str in an Iterator, with quoted packages first.
fn split_whitespace_unless_quoted(element: &str) -> Result<impl Iterator<Item = &str>> {
let mut ret = vec![];
let mut to_whitespace_split = vec![];
let mut start_index = 0;
let mut looping_over_quoted_pkg = false;
for (i, c) in element.chars().enumerate() {
if c == '\'' {
if looping_over_quoted_pkg {
quoted_pkgs.push(String::from(&element[start_index..i]));
ret.push(&element[start_index..i]);
looping_over_quoted_pkg = false;
} else {
to_whitespace_split.push(String::from(&element[start_index..i]));
to_whitespace_split.push(&element[start_index..i]);
looping_over_quoted_pkg = true;
}
start_index = i + 1;
}
if i == element.len() - 1 {
to_whitespace_split.push(String::from(&element[start_index..]));
if looping_over_quoted_pkg {
bail!("Missing terminating quote: {}", element);
}
to_whitespace_split.push(&element[start_index..]);
}
}
let mut ret: Vec<String> = vec![];
ret.extend(quoted_pkgs);
for item in to_whitespace_split.iter() {
ret.extend(item.split_whitespace().map(String::from));
ret.extend(item.split_whitespace());
}

ret.into_iter()
Ok(ret.into_iter())
}

#[derive(Serialize, Deserialize, Debug, PartialEq)]
Expand Down Expand Up @@ -1261,63 +1265,62 @@ etc-group-members:
}

#[test]
fn test_split_whitespace_unless_quoted() {
fn test_split_whitespace_unless_quoted() -> Result<()> {
// test single quoted package
let single_quoted_pkg = String::from("'foobar >= 1.0'");
let pkgs: Vec<String> = split_whitespace_unless_quoted(&single_quoted_pkg).collect();
let single_quoted_pkg = "'foobar >= 1.0'";
let pkgs: Vec<&str> = split_whitespace_unless_quoted(&single_quoted_pkg)?.collect();
assert_eq!("foobar >= 1.0", pkgs[0]);

// test multiple quoted packages
let mult_quoted_pkg = String::from("'foobar >= 1.0' 'quuz < 0.5' 'corge > 2'");
let pkgs: Vec<String> = split_whitespace_unless_quoted(&mult_quoted_pkg).collect();
let mult_quoted_pkg = "'foobar >= 1.0' 'quuz < 0.5' 'corge > 2'";
let pkgs: Vec<&str> = split_whitespace_unless_quoted(&mult_quoted_pkg)?.collect();
assert_eq!("foobar >= 1.0", pkgs[0]);
assert_eq!("quuz < 0.5", pkgs[1]);
assert_eq!("corge > 2", pkgs[2]);

// test single unquoted package
let single_unquoted_pkg = String::from("foobar");
let pkgs: Vec<String> = split_whitespace_unless_quoted(&single_unquoted_pkg).collect();
let single_unquoted_pkg = "foobar";
let pkgs: Vec<&str> = split_whitespace_unless_quoted(&single_unquoted_pkg)?.collect();
assert_eq!("foobar", pkgs[0]);

// test multiple unquoted packages
let mult_unquoted_pkg = String::from("foobar quuz corge");
let pkgs: Vec<String> = split_whitespace_unless_quoted(&mult_unquoted_pkg).collect();
let mult_unquoted_pkg = "foobar quuz corge";
let pkgs: Vec<&str> = split_whitespace_unless_quoted(&mult_unquoted_pkg)?.collect();
assert_eq!("foobar", pkgs[0]);
assert_eq!("quuz", pkgs[1]);
assert_eq!("corge", pkgs[2]);

// test different orderings of mixed quoted and unquoted packages
let mix_quoted_unquoted_pkgs = String::from("'foobar >= 1.1' baz-package 'corge < 0.5'");
let pkgs: Vec<String> = split_whitespace_unless_quoted(&mix_quoted_unquoted_pkgs).collect();
let mix_quoted_unquoted_pkgs = "'foobar >= 1.1' baz-package 'corge < 0.5'";
let pkgs: Vec<&str> = split_whitespace_unless_quoted(&mix_quoted_unquoted_pkgs)?.collect();
assert_eq!("foobar >= 1.1", pkgs[0]);
assert_eq!("corge < 0.5", pkgs[1]);
assert_eq!("baz-package", pkgs[2]);
let mix_quoted_unquoted_pkgs = String::from("corge 'foobar >= 1.1' baz-package");
let pkgs: Vec<String> = split_whitespace_unless_quoted(&mix_quoted_unquoted_pkgs).collect();
let mix_quoted_unquoted_pkgs = "corge 'foobar >= 1.1' baz-package";
let pkgs: Vec<&str> = split_whitespace_unless_quoted(&mix_quoted_unquoted_pkgs)?.collect();
assert_eq!("foobar >= 1.1", pkgs[0]);
assert_eq!("corge", pkgs[1]);
assert_eq!("baz-package", pkgs[2]);
let mix_quoted_unquoted_pkgs =
String::from("corge 'foobar >= 1.1' baz-package 'quuz < 0.0.1'");
let pkgs: Vec<String> = split_whitespace_unless_quoted(&mix_quoted_unquoted_pkgs).collect();
let mix_quoted_unquoted_pkgs = "corge 'foobar >= 1.1' baz-package 'quuz < 0.0.1'";
let pkgs: Vec<&str> = split_whitespace_unless_quoted(&mix_quoted_unquoted_pkgs)?.collect();
assert_eq!("foobar >= 1.1", pkgs[0]);
assert_eq!("quuz < 0.0.1", pkgs[1]);
assert_eq!("corge", pkgs[2]);
assert_eq!("baz-package", pkgs[3]);

// test missing quotes around packages using version qualifiers
let missing_quotes = String::from("foobar >= 1.0 quuz");
let pkgs: Vec<String> = split_whitespace_unless_quoted(&missing_quotes).collect();
assert_ne!("foobar >= 1.0", pkgs[0]);
assert_eq!(">=", pkgs[1]);
let missing_leading_quote = String::from("foobar >= 1.0'");
let pkgs: Vec<String> = split_whitespace_unless_quoted(&missing_leading_quote).collect();
assert_ne!("foobar >= 1.0", pkgs[0]);
assert_eq!(">=", pkgs[1]);
let missing_trailing_quote = String::from("'foobar >= 1.0 baz-package");
let pkgs: Vec<String> = split_whitespace_unless_quoted(&missing_trailing_quote).collect();
let missing_quotes = "foobar >= 1.0 quuz";
let pkgs: Vec<&str> = split_whitespace_unless_quoted(&missing_quotes)?.collect();
assert_ne!("foobar >= 1.0", pkgs[0]);
assert_eq!(">=", pkgs[1]);
let missing_leading_quote = "foobar >= 1.0'";
assert!(split_whitespace_unless_quoted(&missing_leading_quote).is_err());
let missing_trailing_quote = "'foobar >= 1.0 baz-package";
assert!(split_whitespace_unless_quoted(&missing_trailing_quote).is_err());
let stray_quote = "'foobar >= 1.0' quuz' corge";
assert!(split_whitespace_unless_quoted(&stray_quote).is_err());

Ok(())
}
}

Expand Down

0 comments on commit 3bf8e02

Please sign in to comment.