From ec644c7bf938300fc35cc293f7b068fd74cac3a3 Mon Sep 17 00:00:00 2001 From: James Hodgkinson Date: Mon, 30 Oct 2023 05:12:27 +1000 Subject: [PATCH] refactor: print errors instead of panicking (#40) * minor tweaks and error handling * adding quotes around messages * restoring test * Adding a 'sad path' test * Reverting a change and updating a message. --- src/lib.rs | 36 +++++++++++++++++------- src/links.rs | 77 ++++++++++++++++++++++++++++++++-------------------- 2 files changed, 74 insertions(+), 39 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 725b560..ab98653 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -13,6 +13,7 @@ pub mod utils; const MAX_LINK_NESTED_DEPTH: usize = 10; +#[derive(Default)] pub struct Template; impl Template { @@ -38,13 +39,8 @@ impl Preprocessor for Template { .map(|dir| src_dir.join(dir)) .expect("All book items have a parent"); - let content = replace_template( - &chapter.content, - &SystemFileReader::default(), - base, - source, - 0, - ); + let content = + replace_template(&chapter.content, &SystemFileReader, base, source, 0); chapter.content = content; } } @@ -79,7 +75,7 @@ where for link in links::extract_template_links(chapter_content) { replaced.push_str(&chapter_content[previous_end_index..link.start_index]); - match link.replace_args(&path, file_reader) { + match link.replace_args(path, file_reader) { Ok(new_content) => { if depth < MAX_LINK_NESTED_DEPTH { if let Some(rel_path) = link.link_type.relative_path(path) { @@ -202,8 +198,8 @@ mod lib_tests { let start_chapter_content = r" {{#template header.md title=Example Title}} Some content... - {{#template - footer.md + {{#template + footer.md authors=Goudham & Hazel}}"; let end_chapter_content = r" # Example Title @@ -296,4 +292,24 @@ mod lib_tests { assert_eq!(actual_chapter_content, start_chapter_content); } + + #[test] + fn test_sad_path_bad_template() { + let start_chapter_content = [ + "This is {{#template template.md", + "text=valid text", + "this has no key for the value and is going to break things}}", + ] + .join("\n"); + let end_chapter_content = "This is valid text"; + let file_name: PathBuf = PathBuf::from("template.md"); + let template_file_contents = "[[#text]]".to_string(); + let map = HashMap::from([(file_name, template_file_contents)]); + let file_reader = &TestFileReader::from(map); + + let actual_chapter_content = + replace_template(&start_chapter_content, file_reader, "", "", 0); + + assert_eq!(actual_chapter_content, end_chapter_content); + } } diff --git a/src/links.rs b/src/links.rs index b5d42cf..7916fae 100644 --- a/src/links.rs +++ b/src/links.rs @@ -17,26 +17,26 @@ lazy_static! { // r"(?x)\\\{\{\#.*\}\}|\{\{\s*\#(template)\s+([\S]+)\s*\}\}|\{\{\s*\#(template)\s+([\S]+)\s+([^}]+)\}\}" static ref TEMPLATE: Regex = Regex::new( r"(?x) # enable insignificant whitespace mode - + \\\{\{ # escaped link opening parens \#.* # match any character \}\} # escaped link closing parens - + | # or - + \{\{\s* # link opening parens and whitespace(s) \#(template) # link type - template \s+ # separating whitespace ([\S]+) # relative path to template file \s* # optional separating whitespaces(s) - \}\} # link closing parens - + \}\} # link closing parens + | # or - + \{\{\s* # link opening parens and whitespace(s) \#(template) # link type - template \s+ # separating whitespace - ([\S]+) # relative path to template file + ([\S]+) # relative path to template file \s+ # separating whitespace(s) ([^}]+) # get all template arguments \}\} # link closing parens" @@ -46,20 +46,20 @@ lazy_static! { // r"(?x)\\\[\[.*\]\]|\[\[\s*\#([\S]+)\s*\]\]|\[\[\s*\#([\S]+)\s+([^]]+)\]\]" static ref ARGS: Regex = Regex::new( r"(?x) # enable insignificant whitespace mode - + \\\[\[ # escaped link opening square brackets \#.* # match any character \]\] # escaped link closing parens - + | # or - + \[\[\s* # link opening parens and whitespace(s) \#([\S]+) # arg name \s* # optional separating whitespace(s) \]\] # link closing parens - + | # or - + \[\[\s* # link opening parens and whitespace(s) \#([\S]+) # arg name \s+ # optional separating whitespace(s) @@ -113,23 +113,39 @@ impl<'a> Link<'a> { .split(LINE_BREAKS) .map(|str| str.trim()) .filter(|trimmed| !trimmed.is_empty()) - .map(|mat| { + .filter_map(|mat| { let mut split_n = mat.splitn(2, '='); - let key = split_n.next().unwrap().trim(); - let value = split_n.next().unwrap(); - (key, value) + if let Some(key) = split_n.next() { + let key = key.trim(); + if let Some(value) = split_n.next() { + return Some((key, value)); + } + } + eprintln!( + "Couldn't find a key/value pair while parsing the argument '{}'", + mat + ); + None }) .collect::>(), // This looks like {{#template }} false => TEMPLATE_ARGS .captures_iter(args.as_str()) - .into_iter() - .map(|mat| { - let mut split_n = mat.unwrap().get(0).unwrap().as_str().splitn(2, '='); - let key = split_n.next().unwrap().trim(); - let value = split_n.next().unwrap(); - (key, value) + .filter_map(|mat| { + let captures = mat.ok()?; + let mut split_n = captures.get(0)?.as_str().splitn(2, '='); + if let Some(key) = split_n.next() { + let key = key.trim(); + if let Some(value) = split_n.next() { + return Some((key.trim(), value)); + } + } + eprintln!( + "Couldn't parse key or value while parsing '{:?}'", + &args.as_str() + ); + None }) .collect::>(), }; @@ -157,7 +173,7 @@ impl<'a> Link<'a> { FR: FileReader, { match self.link_type { - LinkType::Escaped => Ok((&self.link_text[1..]).to_owned()), + LinkType::Escaped => Ok((self.link_text[1..]).to_owned()), LinkType::Template(ref pat) => { let target = base.as_ref().join(pat); let contents = file_reader.read_to_string(&target, self.link_text)?; @@ -195,7 +211,7 @@ impl<'a> Iterator for LinkIter<'a> { fn next(&mut self) -> Option { for cap in &mut self.0 { - if let Some(inc) = Link::from_capture(cap.unwrap()) { + if let Some(inc) = Link::from_capture(cap.ok()?) { return Some(inc); } } @@ -499,11 +515,14 @@ year=2022 #[test] fn test_extract_template_links_with_newlines_malformed() { - let s = "{{#template test.rs - lang=rust - year=2022}}"; - - let res = extract_template_links(s).collect::>(); + let s = [ + "{{#template test.rs \n", + " lang=rust\n", + " year=2022}}", + ] + .concat(); + + let res = extract_template_links(&s).collect::>(); assert_eq!( res,