Skip to content

Commit

Permalink
Merge pull request #1 from Jakur/master
Browse files Browse the repository at this point in the history
Reduce string allocations
  • Loading branch information
SergChr authored Jul 18, 2020
2 parents f22e927 + b00dec0 commit 48598d5
Show file tree
Hide file tree
Showing 2 changed files with 95 additions and 72 deletions.
64 changes: 32 additions & 32 deletions src/parser/mod.rs
Original file line number Diff line number Diff line change
@@ -1,19 +1,17 @@
mod structs;
mod tests;

use regex::{Captures, Regex};
use std::fs;
use std::io::prelude::*;
use structs::Resume;
use regex::{Regex, Captures};

pub fn extract_resume(path: &str) -> Resume {
let mut file = fs::File::open(path)
.expect("Unable to read the JSON file");
let mut file = fs::File::open(path).expect("Unable to read the JSON file");
let mut content = String::new();
file.read_to_string(&mut content)
.expect("Unable to read the JSON file");
let resume: Resume = serde_json::from_str(&content)
.expect("Invalid JSON format");
let resume: Resume = serde_json::from_str(&content).expect("Invalid JSON format");

resume
}
Expand All @@ -23,66 +21,68 @@ pub fn replace_html_vars(html: &str, resume: Resume) -> String {
let array_vars_re = Regex::new(r"\{!(?:\s?+)(.+)(?:\s?+)(.[^!]+)!\}").unwrap();
let array_var_primitive_re = Regex::new(r"\{\s*(\w+)\s*\}").unwrap();

let resume_in_json: serde_json::Value = serde_json::from_str(
&serde_json::to_string(&resume).unwrap()
).expect("Cannot parse JSON");
// Could we use the initial parsed JSON instead?
let resume_in_json: serde_json::Value =
serde_json::from_str(&serde_json::to_string(&resume).unwrap()).expect("Cannot parse JSON");

let primitives_replaced = primitive_vars_re.replace_all(html, |caps: &Captures| {
let value = json_get(&resume_in_json, caps[1].to_string());
let value = json_get(&resume_in_json, caps.get(1).unwrap().as_str());

remove_quotes(&value.to_string()[..])
remove_quotes(&value.as_str().expect("Primitive"))
});

let result = array_vars_re.replace_all(&primitives_replaced, |caps: &Captures| {
let primary_var_name = caps[1].to_string();
let html = caps[2].to_string();
let primary_var_name = caps.get(1).unwrap().as_str();
let html = caps.get(2).unwrap().as_str();
let values = json_get(&resume_in_json, primary_var_name);
let mut replaced_html = String::new();
for value in values.as_array().unwrap().iter() {
let replaced = array_var_primitive_re.replace_all(&html, |c: &Captures| {
let key = c[1].to_string();
let res = value[key].to_string();
remove_quotes(&res)
let key = c.get(1).unwrap().as_str();
let res = value.get(key).unwrap().as_str().expect("Primitive");
remove_quotes(res)
});
replaced_html.push_str(&replaced);
}

replaced_html
});

result.to_string()
result.into_owned()
}

// Helps to get nested value
pub fn json_get(json: &serde_json::Value, key_str: String) -> serde_json::Value {
let keys: Vec<String> = key_str.split(".").map(|s| s.to_string()).collect();
let get_err_msg = |key: &String| {
format!("Invalid key used in the HTML template: \"{}\"; full path: \"{}\"", key, key_str)
pub fn json_get(json: &serde_json::Value, key_str: &str) -> serde_json::Value {
use serde_json::Value;
let keys: Vec<&str> = key_str.split(".").collect();
eprintln!("{:?}", keys);
let get_err_msg = |key: &str| {
format!(
"Invalid key used in the HTML template: \"{}\"; full path: \"{}\"",
key, key_str
)
};
let mut result: &serde_json::Value = json.get(&keys[0])
.expect(&get_err_msg(&keys[0]));
let mut result: &serde_json::Value = json.get(keys[0]).expect(&get_err_msg(keys[0]));

for key in &keys {
if key == &keys[0] {
continue;
}
for key in keys.iter().skip(1) {
let value = result.get(key);
match value {
Some(v) => {
if v.is_string() || v.is_object() || v.is_number() {
Some(v) => match v {
Value::String(_) | Value::Object(_) | Value::Number(_) => {
result = v;
} else {
}
_ => {
result = v;
break;
}
}
},
None => panic!(get_err_msg(key)),
}
}

result.to_owned()
}

pub fn remove_quotes(str: &str) -> String {
str.replace("\"", "")
pub fn remove_quotes(s: &str) -> String {
s.replace("\"", "")
}
103 changes: 63 additions & 40 deletions src/parser/tests.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
#[cfg(test)]
mod test {
use crate::parser::{remove_quotes, json_get, replace_html_vars};
use crate::parser::structs::Resume;
use crate::parser::{json_get, remove_quotes, replace_html_vars};
use serde_json::json;

#[test]
Expand All @@ -14,7 +14,8 @@ mod test {
}

fn get_resume_json() -> Resume {
let json: Resume = match serde_json::from_str(r#"{
let json: Resume = match serde_json::from_str(
r#"{
"basics": {
"name": "John Doe",
"label": "Programmer",
Expand Down Expand Up @@ -74,18 +75,19 @@ mod test {
"level": "Fluent"
}
]
}"#) {
Ok(result) => result,
Err(e) => panic!(e),
};

json
}"#,
) {
Ok(result) => result,
Err(e) => panic!(e),
};

json
}

fn json_to_value(json: Resume) -> serde_json::value::Value {
let result: serde_json::Value = serde_json::from_str(
&serde_json::to_string(&json).unwrap()
).expect("Cannot parse JSON");
let result: serde_json::Value =
serde_json::from_str(&serde_json::to_string(&json).unwrap())
.expect("Cannot parse JSON");
result
}

Expand All @@ -94,16 +96,19 @@ mod test {
let json: Resume = get_resume_json();
let value = json_to_value(json);

let res_1 = json_get(&value, "basics".to_string());
let res_1 = json_get(&value, "basics");
assert_eq!(res_1.is_object(), true);
assert_eq!(res_1.get("name").unwrap(), "John Doe");
assert_eq!(res_1.get("profiles").unwrap().is_array(), true);

let res_2 = json_get(&value, "skills".to_string());
let res_2 = json_get(&value, "skills");
assert_eq!(res_2.is_array(), true);
assert_eq!(res_2.get(0).unwrap().get("name").unwrap(), "Web Development");
assert_eq!(
res_2.get(0).unwrap().get("name").unwrap(),
"Web Development"
);

let res_3 = json_get(&value, "languages".to_string());
let res_3 = json_get(&value, "languages");
assert_eq!(res_3.is_array(), true);
assert_eq!(res_3.get(0).unwrap().get("language").unwrap(), "English");
assert_eq!(res_3.get(1).unwrap().get("language").unwrap(), "Spanish");
Expand All @@ -113,7 +118,7 @@ mod test {
fn json_get_nested() {
let json: Resume = get_resume_json();
let value = json_to_value(json);
let res = json_get(&value, "basics.location.city".to_string());
let res = json_get(&value, "basics.location.city");
assert_eq!(remove_quotes(&res.to_string()), "San Francisco");
}

Expand All @@ -127,24 +132,24 @@ mod test {
"obj": {
"int": 2
}
}
}
});
let res_1 = json_get(&json, "int".to_string());
let res_1 = json_get(&json, "int");
assert_eq!(res_1, 1);

let res_2 = json_get(&json, "bool".to_string());
let res_2 = json_get(&json, "bool");
assert_eq!(res_2, true);

let res_3 = json_get(&json, "nested".to_string());
let res_3 = json_get(&json, "nested");
assert_eq!(res_3.is_object(), true);

let res_4 = json_get(&json, "nested.obj.int".to_string());
let res_4 = json_get(&json, "nested.obj.int");
assert_eq!(res_4, 2);

let res_5 = json_get(&json, "nested.obj".to_string());
let res_5 = json_get(&json, "nested.obj");
assert_eq!(res_5.get("int").unwrap(), 2);

let res_6 = json_get(&json, "nested.arr".to_string());
let res_6 = json_get(&json, "nested.arr");
assert_eq!(res_6.get(0).unwrap(), "str1");
assert_eq!(res_6.get(1).unwrap(), "str2");
}
Expand All @@ -153,7 +158,7 @@ mod test {
#[should_panic]
fn json_get_unexisted_prop() {
let json = json!({});
json_get(&json, "prop".to_string());
json_get(&json, "prop");
}

#[test]
Expand All @@ -162,55 +167,71 @@ mod test {
let json = json!({
"prop": 1
});
json_get(&json, "prop.a".to_string());
json_get(&json, "prop.a");
}

#[test]
fn replace_html_vars_simple() {
let res = replace_html_vars(r#"
let res = replace_html_vars(
r#"
<div>{{basics.name}} {{ basics.label }}</div>
<p>{{ basics.location.city}} {{basics.location.country }}</p>
"#, get_resume_json());
assert_eq!(res, r#"
"#,
get_resume_json(),
);
assert_eq!(
res,
r#"
<div>John Doe Programmer</div>
<p>San Francisco The Johnited States Of Doe</p>
"#);
"#
);
}

#[test]
fn replace_html_array_vars() {
let escape = |s: &str| {
s.replace(" ", "")
};
let html_1 = escape(r#"
let escape = |s: &str| s.replace(" ", "");
let html_1 = escape(
r#"
{!basics.profiles
<p>{network}</p>
<h1>{ username }</h1>
<font>{ url}</font>
!}
"#);
"#,
);
let res_1 = replace_html_vars(&html_1, get_resume_json());
assert_eq!(res_1, escape(r#"
assert_eq!(
res_1,
escape(
r#"
<p>a</p>
<h1>b</h1>
<font>c</font>
<p>a2</p>
<h1>b2</h1>
<font>c2</font>
"#));
"#
)
);

let html_2 = escape(r#"
let html_2 = escape(
r#"
{! languages
<p>{language }</p>
<h1>{ level}</h1>
<font id="{language}"></font>
!}
{! projects
{name} { description }!}
"#);
"#,
);
let res_2 = replace_html_vars(&html_2, get_resume_json());
assert_eq!(res_2, escape(r#"
assert_eq!(
res_2,
escape(
r#"
<p>English</p>
<h1>Native</h1>
<font id="English"></font>
Expand All @@ -219,6 +240,8 @@ mod test {
<font id="Spanish"></font>
a b
"#));
"#
)
);
}
}

0 comments on commit 48598d5

Please sign in to comment.