Add default derive

Add Fast path parsing which catches the case of unescaped values with spaces.
This commit is contained in:
Till Wegmueller 2020-05-18 14:57:55 +02:00
parent f33df3d0ab
commit 441a47d384
2 changed files with 77 additions and 5 deletions

View file

@ -3,7 +3,7 @@
// MPL was not distributed with this file, You can // MPL was not distributed with this file, You can
// obtain one at https://mozilla.org/MPL/2.0/. // obtain one at https://mozilla.org/MPL/2.0/.
use regex::Regex; use regex::{Regex, Captures};
use std::collections::HashSet; use std::collections::HashSet;
use std::error; use std::error;
use std::fmt; use std::fmt;
@ -11,6 +11,7 @@ use std::fs::File;
use std::io::BufRead; use std::io::BufRead;
use std::io::BufReader; use std::io::BufReader;
#[derive(Debug, Default)]
pub struct Dir { pub struct Dir {
pub path: String, pub path: String,
pub group: String, pub group: String,
@ -18,6 +19,7 @@ pub struct Dir {
pub mode: String, //TODO implement as bitmask pub mode: String, //TODO implement as bitmask
} }
#[derive(Debug, Default)]
pub struct Attr { pub struct Attr {
pub key: String, pub key: String,
pub values: Vec<String>, pub values: Vec<String>,
@ -30,6 +32,7 @@ pub struct Property {
pub value: String, pub value: String,
} }
#[derive(Debug, Default)]
pub struct Manifest { pub struct Manifest {
pub attributes: Vec<Attr>, pub attributes: Vec<Attr>,
} }
@ -130,6 +133,49 @@ fn is_attr_action(line: &String) -> bool {
} }
pub fn parse_attr_action(line: String) -> Result<Attr, ManifestError> { pub fn parse_attr_action(line: String) -> Result<Attr, ManifestError> {
// Do a full line match to see if we can fast path this.
// This also catches values with spaces, that have not been properly escaped.
// Note: values with spaces must be properly escaped or the rest here will fail. Strings with
// unescaped spaces are never valid but sadly present in the wild.
// Fast path will fail if a value has multiple values or a '=' sign in the values
let full_line_regex = match Regex::new(r"^set name=([^ ]+) value=(.+)$") {
Ok(re) => re,
Err(e) => return Err(ManifestError::Regex(e)),
};
if full_line_regex.is_match(line.trim_start()) {
match full_line_regex.captures(line.trim_start()) {
Some(captures) => {
let mut fast_path_fail = false;
let mut val = String::from(&captures[2]);
if val.contains("=") {
fast_path_fail = true;
}
if val.contains("value=") {
fast_path_fail = true;
}
if val.contains("name=") {
fast_path_fail = true;
}
val = val.replace(&['"', '\\'][..], "");
if !fast_path_fail{
return Ok(Attr{
key: String::from(&captures[1]),
values: vec![val],
..Attr::default()
});
}
}
None => (),
};
}
//Todo move regex initialisation out of for loop into static area //Todo move regex initialisation out of for loop into static area
let name_regex = match Regex::new(r"name=([^ ]+) value=") { let name_regex = match Regex::new(r"name=([^ ]+) value=") {
Ok(re) => re, Ok(re) => re,

View file

@ -8,7 +8,7 @@ mod actions;
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use crate::actions::Manifest; use crate::actions::{Manifest, Property};
use crate::actions::{parse_manifest_string, Attr}; use crate::actions::{parse_manifest_string, Attr};
use std::collections::HashSet; use std::collections::HashSet;
@ -25,7 +25,15 @@ mod tests {
set name=info.source-url value=http://nginx.org/download/nginx-1.18.0.tar.gz set name=info.source-url value=http://nginx.org/download/nginx-1.18.0.tar.gz
set name=org.opensolaris.consolidation value=userland set name=org.opensolaris.consolidation value=userland
set name=com.oracle.info.version value=1.18.0 set name=com.oracle.info.version value=1.18.0
set name=variant.arch value=i386"); set name=pkg.summary value=\\\"provided mouse accessibility enhancements\\\"
set name=info.upstream value=X.Org Foundation
set name=pkg.description value=Latvian language support's extra files
set name=variant.arch value=i386 optional=testing optionalWithString=\"test ing\"");
let mut optional_hash = HashSet::new();
optional_hash.insert(Property{key: String::from("optional"), value:String::from("testing")});
optional_hash.insert(Property{key: String::from("optionalWithString"), value:String::from("test ing")});
let test_results = vec![ let test_results = vec![
Attr{ Attr{
key: String::from("pkg.fmri"), key: String::from("pkg.fmri"),
@ -82,10 +90,25 @@ mod tests {
values: vec![String::from("1.18.0")], values: vec![String::from("1.18.0")],
properties: HashSet::new(), properties: HashSet::new(),
}, },
Attr{
key: String::from("pkg.summary"),
values: vec![String::from("provided mouse accessibility enhancements")],
properties: HashSet::new(),
},
Attr{
key: String::from("info.upstream"),
values: vec![String::from("X.Org Foundation")],
properties: HashSet::new(),
},
Attr{
key: String::from("pkg.description"),
values: vec![String::from("Latvian language support's extra files")],
properties: HashSet::new(),
},
Attr{ Attr{
key: String::from("variant.arch"), key: String::from("variant.arch"),
values: vec![String::from("i386")], values: vec![String::from("i386")],
properties: HashSet::new(), properties: optional_hash,
} }
]; ];
@ -94,9 +117,12 @@ mod tests {
Ok(m) => manifest = m, Ok(m) => manifest = m,
Err(_) => assert!(false, "caught error"), Err(_) => assert!(false, "caught error"),
}; };
assert_eq!(manifest.attributes.len(), 12);
assert_eq!(manifest.attributes.len(), 15);
for (pos, attr) in manifest.attributes.iter().enumerate() { for (pos, attr) in manifest.attributes.iter().enumerate() {
assert_eq!(attr.key, test_results[pos].key); assert_eq!(attr.key, test_results[pos].key);
for (vpos, val) in attr.values.iter().enumerate() { for (vpos, val) in attr.values.iter().enumerate() {
assert_eq!(val, &test_results[pos].values[vpos]); assert_eq!(val, &test_results[pos].values[vpos]);
} }