Skip to content

Commit

Permalink
Render filesystem templates.
Browse files Browse the repository at this point in the history
  • Loading branch information
Luis Moreno committed Aug 10, 2020
1 parent 998acd5 commit 196b85c
Show file tree
Hide file tree
Showing 11 changed files with 156 additions and 26 deletions.
81 changes: 81 additions & 0 deletions src/filesystem_handler/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
use std::collections::HashMap;
use std::fs::File;
use std::io::BufReader;
use std::io::Read;
use std::path::PathBuf;
use std::time::SystemTime;

pub trait FileSystemHandler {
fn open_stream<'a>(&'a self, name: &str) -> Option<Box<dyn Read + 'a>>;
fn get_last_modification(&self, name: &str) -> Option<SystemTime>;
}

#[derive(Clone, Debug, Default)]
pub struct MemoryFileSystem {
files_map: HashMap<String, String>,
}

impl MemoryFileSystem {
pub fn new() -> Self {
Self::default()
}
pub fn add_file(&mut self, filename: String, file_content: String) {
self.files_map.insert(filename, file_content);
}
}

impl FileSystemHandler for MemoryFileSystem {
fn open_stream<'a>(&'a self, name: &str) -> Option<Box<dyn Read + 'a>> {
if let Some(body) = self.files_map.get(name) {
Some(Box::new(BufReader::new(body.as_bytes())))
} else {
None
}
}
fn get_last_modification(&self, _name: &str) -> Option<SystemTime> {
Some(SystemTime::now())
}
}
#[derive(Clone, Debug)]
pub struct RealFileSystem {
root_folder: String,
}

impl RealFileSystem {
pub fn new(root_folder: String) -> Self {
Self { root_folder }
}
pub fn set_root_folder(&mut self, new_root: String) {
self.root_folder = new_root;
}
pub fn get_root_folder(&self) -> &str {
&self.root_folder
}
pub fn get_full_file_path(&self, name: &str) -> PathBuf {
let mut path = PathBuf::from(&self.root_folder);
path.push(name);
path
}
}
impl FileSystemHandler for RealFileSystem {
fn open_stream<'a>(&'a self, name: &str) -> Option<Box<dyn Read + 'a>> {
let path = self.get_full_file_path(name);

let file_exists = File::open(path);
if let Ok(file) = file_exists {
Some(Box::new(BufReader::new(file)))
} else {
None
}
}
fn get_last_modification(&self, name: &str) -> Option<SystemTime> {
let path = self.get_full_file_path(name);
let file_exists = File::open(path);
if let Ok(file) = file_exists {
let metadata = file.metadata().unwrap().modified().unwrap();
Some(metadata)
} else {
None
}
}
}
2 changes: 2 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ pub mod value;
mod context;
mod expression_evaluator;
mod expression_parser;
mod filesystem_handler;
mod filters;
mod keyword;
mod lexer;
Expand All @@ -14,6 +15,7 @@ mod template_env;
mod template_parser;

pub use context::Context;
pub use filesystem_handler::{FileSystemHandler, MemoryFileSystem, RealFileSystem};
pub use template::Template;
pub use template_env::TemplateEnv;

Expand Down
12 changes: 4 additions & 8 deletions src/template.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,16 +6,14 @@ use crate::template_parser::TemplateParser;
use std::borrow::Cow;
use std::io::Write;
use std::sync::Arc;

#[derive(Debug)]
pub struct Template<'a> {
body: Cow<'a, str>,
template_env: Arc<TemplateEnv>,
template_env: Arc<&'a TemplateEnv<'a>>,
renderer: Option<ComposedRenderer<'a>>,
}

impl<'a> Template<'a> {
pub fn new(template_env: Arc<TemplateEnv>) -> Result<Self> {
pub fn new(template_env: Arc<&'a TemplateEnv>) -> Result<Self> {
Ok(Self {
template_env,
renderer: None,
Expand All @@ -31,11 +29,9 @@ impl<'a> Template<'a> {
Cow::Owned(_template_body_owned) => {
// This allows the parser to have references to the template body.
// This is safe as long as `body` field is never mutated or dropped.
let unsafe_source: &'a str = unsafe{
&*(&*self.body as *const str)
};
let unsafe_source: &'a str = unsafe { &*(&*self.body as *const str) };
TemplateParser::new(unsafe_source, self.template_env.clone())?
},
}
};
parser.parse()
}
Expand Down
36 changes: 31 additions & 5 deletions src/template_env.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
use crate::error::Result;
use crate::value::{Value, ValuesMap};
use crate::FileSystemHandler;
use crate::Template;
use std::sync::{Arc, RwLock};

#[derive(Clone, Debug, PartialEq)]
enum Jinja2CompatMode {
None,
Expand Down Expand Up @@ -57,13 +61,13 @@ impl Default for Extensions {
}
}

#[derive(Clone, Debug)]
pub struct TemplateEnv {
pub struct TemplateEnv<'a> {
settings: Settings,
global_values: Arc<RwLock<ValuesMap>>,
filesystem_handlers: Vec<Box<dyn FileSystemHandler + 'a>>,
}

impl TemplateEnv {
impl<'a> TemplateEnv<'a> {
pub fn add_global(&mut self, name: String, val: Value) {
self.global_values.write().unwrap().insert(name, val);
}
Expand All @@ -86,13 +90,35 @@ impl TemplateEnv {
pub fn settings_mut(&mut self) -> &mut Settings {
&mut self.settings
}
pub fn add_filesystem_handler(
&mut self,
handler: Box<dyn FileSystemHandler + 'a>,
) -> Result<()> {
self.filesystem_handlers.push(handler);
Ok(())
}
pub fn load_template(&mut self, filename: &str) -> Result<Template> {
let mut template = Template::new(Arc::new(self))?;
for handler in &self.filesystem_handlers {
let stream = handler.open_stream(filename);
let mut content = String::default();

if let Some(mut reader) = stream {
reader.read_to_string(&mut content)?;
template.load(content)?;
break;
}
}
Ok(template)
}
}

impl Default for TemplateEnv {
fn default() -> TemplateEnv {
impl<'a> Default for TemplateEnv<'a> {
fn default() -> TemplateEnv<'a> {
TemplateEnv {
settings: Settings::default(),
global_values: Arc::new(RwLock::new(ValuesMap::default())),
filesystem_handlers: vec![],
}
}
}
5 changes: 2 additions & 3 deletions src/template_parser.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,9 @@ use crate::template_env::TemplateEnv;
use regex::Regex;
use std::sync::{Arc, RwLock};

#[derive(Debug)]
pub struct TemplateParser<'a> {
template_body: &'a str,
env: RwLock<Arc<TemplateEnv>>,
env: RwLock<Arc<&'a TemplateEnv<'a>>>,
rough_tokenizer: Regex,
text_blocks: RwLock<Vec<TextBlockInfo>>,
current_block_info: RwLock<TextBlockInfo>,
Expand All @@ -21,7 +20,7 @@ pub struct TemplateParser<'a> {
}

impl<'a> TemplateParser<'a> {
pub fn new(body: &'a str, env: Arc<TemplateEnv>) -> Result<Self> {
pub fn new(body: &'a str, env: Arc<&'a TemplateEnv>) -> Result<Self> {
let rough_tokenizer = Regex::new(&ROUGH_TOKENIZER[..ROUGH_TOKENIZER.len() - 1]).unwrap();

Ok(Self {
Expand Down
12 changes: 6 additions & 6 deletions tests/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ use temple::{Context, Template, TemplateEnv};
#[test]
fn expected_endraw() -> Result<()> {
let temp_env = TemplateEnv::default();
let template_env = Arc::new(temp_env);
let template_env = Arc::new(&temp_env);
let mut template = Template::new(template_env)?;
let result = template.load("{% raw %} there is not endraw");
assert_matches!(
Expand All @@ -22,7 +22,7 @@ fn expected_endraw() -> Result<()> {
#[test]
fn unexpected_endraw() -> Result<()> {
let temp_env = TemplateEnv::default();
let template_env = Arc::new(temp_env);
let template_env = Arc::new(&temp_env);
let mut template = Template::new(template_env)?;
let result = template.load("{% raw %} {% endraw %} {% endraw %}");
assert_matches!(
Expand All @@ -40,7 +40,7 @@ fn unexpected_endraw() -> Result<()> {
#[test]
fn unexpected_endcomment() -> Result<()> {
let temp_env = TemplateEnv::default();
let template_env = Arc::new(temp_env);
let template_env = Arc::new(&temp_env);
let mut template = Template::new(template_env)?;
let result = template.load("end of comment #}");
assert_matches!(
Expand All @@ -57,7 +57,7 @@ fn unexpected_endcomment() -> Result<()> {
#[test]
fn expected_expression() -> Result<()> {
let temp_env = TemplateEnv::default();
let template_env = Arc::new(temp_env);
let template_env = Arc::new(&temp_env);
let mut template = Template::new(template_env)?;
let result = template.load("{{ }}");
assert_matches!(
Expand All @@ -84,7 +84,7 @@ fn expected_expression() -> Result<()> {
#[test]
fn expected_right_bracket() -> Result<()> {
let temp_env = TemplateEnv::default();
let template_env = Arc::new(temp_env);
let template_env = Arc::new(&temp_env);
let mut template = Template::new(template_env)?;
let result = template.load("{{ \"text\"[2 }}");
assert_matches!(
Expand Down Expand Up @@ -112,7 +112,7 @@ fn expected_right_bracket() -> Result<()> {
#[test]
fn undefined_value() -> Result<()> {
let temp_env = TemplateEnv::default();
let template_env = Arc::new(temp_env);
let template_env = Arc::new(&temp_env);
let mut template = Template::new(template_env)?;
template.load("{{ undefinedValue }}")?;
let context = Context::default();
Expand Down
24 changes: 24 additions & 0 deletions tests/filesystem_templates.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
use temple::error::Result;
use temple::value::{Value, ValuesMap};
use temple::Context;
use temple::RealFileSystem;
use temple::TemplateEnv;

#[test]
pub fn filesystem_basic_template() -> Result<()> {
let mut temp_env = TemplateEnv::default();
temp_env.add_global("key".to_string(), Value::String("Global value".to_string()));
let handler = RealFileSystem::new("tests/tests_data".to_string());
temp_env.add_filesystem_handler(Box::new(handler))?;
let template = temp_env.load_template("simple.j2")?;
let mut context = ValuesMap::default();
context.insert(
"key".to_string(),
Value::String("overrided value".to_string()),
);

let context = Context::new(context);
let result = template.render_as_string(context)?;
assert_eq!(result, "Hello World!\n".to_string());
Ok(())
}
1 change: 1 addition & 0 deletions tests/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ extern crate assert_matches;
mod basic;
mod error;
mod expressions;
mod filesystem_templates;
mod filters;
mod scoped_context;
mod statement_for;
Expand Down
6 changes: 3 additions & 3 deletions tests/scoped_context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ fn test_global_variable() -> Result<()> {
"GLOBAL_VAR".to_string(),
Value::String("Global".to_string()),
);
let template_env = Arc::new(temp_env);
let template_env = Arc::new(&temp_env);
let mut template = Template::new(template_env)?;
template.load("{{ GLOBAL_VAR }}")?;
let context = Context::default();
Expand All @@ -26,7 +26,7 @@ fn test_both_global_and_external_variables() -> Result<()> {
"GLOBAL_VAR".to_string(),
Value::String("Global".to_string()),
);
let template_env = Arc::new(temp_env);
let template_env = Arc::new(&temp_env);
let mut template = Template::new(template_env)?;
template.load(
"global: {{ GLOBAL_VAR }}
Expand Down Expand Up @@ -54,7 +54,7 @@ fn test_override_value() -> Result<()> {
let mut temp_env = TemplateEnv::default();

temp_env.add_global("key".to_string(), Value::String("Global value".to_string()));
let template_env = Arc::new(temp_env);
let template_env = Arc::new(&temp_env);
let mut template = Template::new(template_env)?;
template.load("{{ key }}")?;
let mut context = ValuesMap::default();
Expand Down
1 change: 1 addition & 0 deletions tests/tests_data/simple.j2
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Hello World!
2 changes: 1 addition & 1 deletion tests/utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ pub fn assert_render_template_eq(
params: Option<Context>,
) -> Result<()> {
let temp_env = TemplateEnv::default();
let template_env = Arc::new(temp_env);
let template_env = Arc::new(&temp_env);
let mut template = Template::new(template_env)?;
template.load(input)?;
let default_context = Context::default();
Expand Down

0 comments on commit 196b85c

Please sign in to comment.