From 562c2aa0a205ac7cdf1d80a83f567cd6c150a5a9 Mon Sep 17 00:00:00 2001 From: peshwar9 Date: Sun, 9 Aug 2020 00:19:25 +0530 Subject: [PATCH] Added chapter 5 --- .DS_Store | Bin 6148 -> 6148 bytes chapter4/.DS_Store | Bin 6148 -> 6148 bytes chapter5/.DS_Store | Bin 0 -> 6148 bytes chapter5/.gitignore | 1 + chapter5/Cargo.lock | 6 ++ chapter5/Cargo.toml | 9 +++ chapter5/src/lib.rs | 189 +++++++++++++++++++++++++++++++++++++++++++ chapter5/src/main.rs | 23 ++++++ 8 files changed, 228 insertions(+) create mode 100644 chapter5/.DS_Store create mode 100644 chapter5/.gitignore create mode 100644 chapter5/Cargo.lock create mode 100644 chapter5/Cargo.toml create mode 100644 chapter5/src/lib.rs create mode 100644 chapter5/src/main.rs diff --git a/.DS_Store b/.DS_Store index 02fed40cf72cc30e68a88445e05f528c335556c5..96d12d23ee7146296613680c7b9444e857139f73 100644 GIT binary patch delta 330 zcmZoMXfc=|#>B)qu~2NHo}wrR0|Nsi1A_nqLkUAFLlJ{XPP$?6=8cTY87Jzo@g_55 zFeEY*K;$MXFp8RUFrbJrnB?ZWxFqG|CjrgjkUg_SCobiXBZ!TvZgK&mWIdV~gGoV# zVQ_MOZUIm|44|tq1-XZi8e}(ol)RrZH6NQBv=~ft^HboC#jTLRbn_g>+bo;eIruq% bAq5PL@640=MJzdh0R==144VT)wlD(#f`~~h delta 228 zcmZoMXfc=|#>B`mu~2NHo}wrV0|Nsi1A_nqLkUAFLlJ{6gGo;M#KPtEAPG)}WQGie zM1}%nNyFge{M-VdEDT^#m7DM4l9ZF51XRKyduEGHT*@IwARDX76s#(RFsCJ*F;Q%yo+3XB0|Nsi1A_pAVQ_MOZo$MvT}GD8EsV`B`mF;Q%yo}wTJ0|Nsi1A_oVPP$=ma(-^XWJbo-^&n9e20ey!hD?SM zm<&)12sZTt3AkKtzKcszPJR+ljDw3W^H^-kAxBjC6uj~U84z26dKeg3!6Y+7@y1ED iY@69R_&I>?+|0=Moq009h#?2inG6j;yxBu!4>JJ&Ybt>N diff --git a/chapter5/.DS_Store b/chapter5/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..8b3d777a2fde9966273b1bde2d3995be3045e4ca GIT binary patch literal 6148 zcmeHKJ5Iwu5S<}W6p1D!1;xS%NP(!3OfWrC8cIllLJ{K#Oz)Bla3KzWM8!2Y0&jK( z;thrpA()YN-+I35CwbRHL_E5i4T%Os)W;BHS+t0#TTL70EJl_b!|}8%mMtlMuS)jb zp0;j8|MttuQj~cQO}(s5qxfs*3KFK}~PGui1&VV!E3^)V-lL6e>BK=K8pPd0`z!~^p zK+cDNA($D~it*^c6k7mb7wRZjOD`cY!7wwd6=8v}h5|K|t;Jvshd!8JW>_m~II%S! zY?a@e7mn4DKSX!pOwng&z!^vx=;-4>?*9{fGObO18sbmRfHUyN7~pO`&PRADyIViK zp4_zo;~Yao{AyW1uugsg@F3^NRy3+T$Qpi`VXY`xL?6R}{v!|x@yQwZ0S4XxszpU( literal 0 HcmV?d00001 diff --git a/chapter5/.gitignore b/chapter5/.gitignore new file mode 100644 index 0000000..ea8c4bf --- /dev/null +++ b/chapter5/.gitignore @@ -0,0 +1 @@ +/target diff --git a/chapter5/Cargo.lock b/chapter5/Cargo.lock new file mode 100644 index 0000000..5c99d01 --- /dev/null +++ b/chapter5/Cargo.lock @@ -0,0 +1,6 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +[[package]] +name = "template-engine" +version = "0.1.0" + diff --git a/chapter5/Cargo.toml b/chapter5/Cargo.toml new file mode 100644 index 0000000..5f31645 --- /dev/null +++ b/chapter5/Cargo.toml @@ -0,0 +1,9 @@ +[package] +name = "template-engine" +version = "0.1.0" +authors = ["peshwar9"] +edition = "2018" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] diff --git a/chapter5/src/lib.rs b/chapter5/src/lib.rs new file mode 100644 index 0000000..de5863a --- /dev/null +++ b/chapter5/src/lib.rs @@ -0,0 +1,189 @@ +// Standard library imports +use std::collections::HashMap; + +// Each line in template file can be of one of following types +#[derive(PartialEq, Debug)] +pub enum ContentType { + Literal(String), + TemplateVariable(ExpressionData), + Tag(TagType), + Unrecognized, +} + +//If ContentType is TemplateVariable, the contents of the line are parsed and stored in the ExpressionData struct +#[derive(PartialEq, Debug, Clone)] +pub struct ExpressionData { + pub expression: String, + pub var_map: Vec, + pub gen_html: String, +} +#[derive(PartialEq, Debug)] +pub enum TagType { + ForTag, + IfTag, +} + +//this checks if a symbol string is contained within another string +pub fn check_symbol_string(input: &str, symbol: &str) -> bool { + let symbols = input.chars(); + symbols.as_str().contains(symbol) +} + +// This checks for matching symbol strings in a given input +pub fn check_matching_pair(input: &str, symbol1: &str, symbol2: &str) -> bool { + let input_iter = input.chars(); + input_iter.as_str().contains(symbol1) && input_iter.as_str().contains(symbol2) +} + +// this returns index of given char symbol, if symbol is present. +pub fn get_index_for_symbol(input: &str, symbol: char) -> (bool, usize) { + let mut characters = input.char_indices(); + let mut does_exist = false; + let mut index = 0; + while let Some((c, d)) = characters.next() { + if d == symbol { + does_exist = true; + index = c; + break; + } + } + (does_exist, index) +} + +// Reads one line of template file and returns type of Content. The types of content are defined in ContentType enum +// If contentType is TemplateVariable, it parses the line further to separate out the head, variable and tail components. +// IF ContentType is Literal, it returns the read input without any modifications. + +pub fn get_content_type(input_line: &str) -> ContentType { + // Tag expressions are enclosed within {% and %} + let is_tag_expression = check_matching_pair(&input_line, "{%", "%}"); + + // ForTag expressions begin with keywords 'for' and 'in' enclosed within {% and %} + // ForTag expressions end with keyword 'endfor' enclosed within {% and %} + let is_for_tag = (check_symbol_string(&input_line, "for") + && check_symbol_string(&input_line, "in")) + || check_symbol_string(&input_line, "endfor"); + // IfTag expressions begin with keyword 'if' enclosed within {% and %} + // IfTag expressions end with keyword 'endif' enclosed within {% and %} + let is_if_tag = + check_symbol_string(&input_line, "if") || check_symbol_string(&input_line, "endif"); + // If template variable + let is_template_variable = check_matching_pair(&input_line, "{{", "}}"); + let return_val; + // case: For Tag + if is_tag_expression && is_for_tag { + return_val = ContentType::Tag(TagType::ForTag); + //case: If Tag + } else if is_tag_expression && is_if_tag { + return_val = ContentType::Tag(TagType::IfTag); + // case: Template variable + } else if is_template_variable { + let content = get_expression_data(&input_line); + return_val = ContentType::TemplateVariable(content); + // case: Literal + } else if !is_tag_expression && !is_template_variable { + return_val = ContentType::Literal(input_line.to_string()); + // Unknown type + } else { + return_val = ContentType::Unrecognized; + } + return_val +} + +// Function to generate HTML for line containing template variable +// Supports multiple template vars in one line and no limit on number of string literals +pub fn generate_html_template_var( + content: &mut ExpressionData, + context: HashMap, +) -> &mut ExpressionData { + content.gen_html = content.expression.clone(); + for var in &content.var_map { + let (_h, i) = get_index_for_symbol(&var, '{'); + let (_j, k) = get_index_for_symbol(&var, '}'); + let var_without_braces = &var[i + 2..k]; + let val = context.get(var_without_braces).unwrap(); + content.gen_html = content.gen_html.replace(var, val); + } + content +} + +// Helper function to parse template variable +pub fn get_expression_data(input_line: &str) -> ExpressionData { + let expression_iter = input_line.split_whitespace(); + let mut template_var_map: Vec = vec![]; + for word in expression_iter { + if check_symbol_string(word, "{{") && check_symbol_string(word, "}}") { + template_var_map.push(word.to_string()); + } + } + ExpressionData { + expression: input_line.into(), + var_map: template_var_map, + gen_html: "".into(), + } +} + +//Unit tests +#[cfg(test)] +mod tests { + use super::*; + #[test] + fn check_get_index_for_symbol_test() { + assert_eq!((true, 3), get_index_for_symbol("Hi {name} bye", '{')); + } + #[test] + fn check_template_var_test() { + let content = "Hi {{name}} bye".into(); + let expr_data = ExpressionData { + expression: content, + var_map: vec!["{{name}}".to_string()], + gen_html: "".into(), + }; + assert_eq!( + ContentType::TemplateVariable(expr_data), + get_content_type("Hi {{name}} bye") + ); + } + #[test] + fn check_for_tag_test() { + assert_eq!( + ContentType::Tag(TagType::ForTag), + get_content_type("{% for name in names %} bye") + ); + } + #[test] + fn check_if_tag_test() { + assert_eq!( + ContentType::Tag(TagType::IfTag), + get_content_type("{% if name == 'Bob' %}") + ); + } + #[test] + fn check_literal_test() { + let s = "

Hello world

"; + assert_eq!(ContentType::Literal(s.to_string()), get_content_type(s)); + } + + #[test] + fn check_get_expression_data_test() { + let map_var = vec!["{{name}}".to_string(), "{{city}}".to_string()]; + let expression_data = ExpressionData { + expression: "Hi {{name}} , welcome to {{city}}".into(), + var_map: map_var, + gen_html: "".into(), + }; + + assert_eq!( + expression_data, + get_expression_data("Hi {{name}} , welcome to {{city}}") + ); + } + #[test] + fn check_symbol_string_test() { + assert_eq!(true, check_symbol_string("{{Hello}}", "{{")); + } + #[test] + fn check_symbol_pair_test() { + assert_eq!(true, check_matching_pair("{{Hello}}", "{{", "}}")); + } +} diff --git a/chapter5/src/main.rs b/chapter5/src/main.rs new file mode 100644 index 0000000..1432f40 --- /dev/null +++ b/chapter5/src/main.rs @@ -0,0 +1,23 @@ +use std::collections::HashMap; +use std::io; +use std::io::BufRead; +use template_engine::*; + +fn main() { + let mut context: HashMap = HashMap::new(); + context.insert("name".to_string(), "Bob".to_string()); + context.insert("city".to_string(), "Boston".to_string()); + + for line in io::stdin().lock().lines() { + match get_content_type(&line.unwrap().clone()) { + ContentType::TemplateVariable(mut content) => { + let html = generate_html_template_var(&mut content, context.clone()); + println!("{}", html.gen_html); + } + ContentType::Literal(text) => println!("{}", text), + ContentType::Tag(TagType::ForTag) => println!("For Tag not implemented"), + ContentType::Tag(TagType::IfTag) => println!("If Tag not implemented"), + ContentType::Unrecognized => println!("Unrecognized input"), + } + } +}