use crate::{Filters, Block, Blocks, Template, Templates};
use glob::glob;
use regex::Regex;
use std::collections::HashMap;
use std::env::current_dir;
use std::ffi::OsString;
use std::fs::{DirEntry, read_dir, read_to_string};
use std::io::{ErrorKind, Error as IoError, Result as IoResult};
use std::path::{PathBuf, Ancestors};
const ERROR_READ: &str = "ReadError: Failed to read file";
const ERROR_GLOB: &str = "GlobError: Failed to glob directory";
const ERROR_GLOB_UNWRAP: &str = "GlobError: failed to unwrap PathBuf";
const ERROR_REGEX_UNWRAP: &str = "RegexError: failed to unwrap Regex";
const ERROR_STR_UNWRAP: &str = "RegexError: failed to unwrap &str";
const RE_EXTENDS_STATEMENT: &str = r#"\{%[\s]{0,}extends[\s]{0,}"(.*?)"[\s]{0,}%\}"#;
const RE_PARENT_BLOCK: &str = r#"\{%[\s]{0,}block[\s]{1,}(.*?)[\s]{0,}%\}((.|\n)*?)\{%[\s]{0,}endblock[\s]{1,}(.*?)[\s]{0,}%\}"#;
const RE_MD_EXTENSION: &str = r"\.md$";
fn project_root() -> IoResult<PathBuf> {
let path: PathBuf = current_dir()?;
let mut path_ancestors: Ancestors = path.as_path().ancestors();
while let Some(p) = path_ancestors.next() {
if read_dir(p)?.into_iter().any(| p: Result<DirEntry, IoError> | {
p.unwrap().file_name() == OsString::from("Cargo.lock")
}) {
return Ok(PathBuf::from(p))
}
}
Err(IoError::new(ErrorKind::NotFound, "could not find Cargo.lock"))
}
pub (crate) fn parse_templates(glob_path: &str) -> Templates {
let path_root: PathBuf = match project_root() {
Ok(path) => path,
Err(error) => panic!("Error: failed to obtain project root: {:?}", error)
};
let path_dist: PathBuf = path_root.join("dist");
let path_dist_str: &str = path_dist.to_str().expect(ERROR_STR_UNWRAP);
let path_public: PathBuf = path_root.join("public");
let path_public_str: &str = path_public.to_str().expect(ERROR_STR_UNWRAP);
let re_extends_statement: Regex = Regex::new(RE_EXTENDS_STATEMENT).expect(ERROR_REGEX_UNWRAP);
let re_parent_block: Regex = Regex::new(RE_PARENT_BLOCK).expect(ERROR_REGEX_UNWRAP);
let re_md_extension: Regex = Regex::new(RE_MD_EXTENSION).expect(ERROR_REGEX_UNWRAP);
let mut templates: Templates = HashMap::new();
for globbed in glob(path_root.join(glob_path).to_str().expect(ERROR_STR_UNWRAP)).expect(ERROR_GLOB) {
let file_path: String = globbed.expect(ERROR_GLOB_UNWRAP).display().to_string();
let file_content: String = read_to_string(&file_path).expect(ERROR_READ);
let mut extends: String = String::new();
for (i, capture) in re_extends_statement.captures_iter(&file_content).enumerate() {
if i > 0 {
panic!("TemplateError: multiple extends statements in \"{}\"", file_path)
}
else if file_path.ends_with(".html") {
panic!("TemplateError: cannot use a \".html\" file as a child template \"{}\"", file_path);
}
extends = path_root.join("public").join(&capture[1].trim()).display().to_string();
}
let has_extends: bool = extends.len() > 0;
let output: String = re_md_extension.replace(&file_path.replace(path_public_str, path_dist_str), ".html").to_string();
let output_dir: String = PathBuf::from(&output).parent().unwrap().to_str().unwrap().to_string();
let mut blocks: Blocks = HashMap::new();
for capture in re_parent_block.captures_iter(&file_content) {
let (mut name, mut filters, end_name, content): (&str, Filters, &str, &str) = (&capture[1].trim(), vec![], &capture[4].trim(), &capture[2]);
for (i, mut item) in name.split("|").enumerate() {
item = item.trim();
if i == 0 {
name = item;
}
else {
filters.push(item.to_string());
}
}
if name != end_name {
panic!("TemplateError: mismatching block names (\"{}\" / \"{}\") in template \"{}\"", name, end_name, file_path);
}
blocks.insert(name.to_string(), Block {
filters,
content: content.to_string(),
content_outer: capture[0].to_string()
});
}
templates.insert(file_path, Template {
extends,
output: if has_extends { output } else { String::new() },
output_dir: if has_extends { output_dir } else { String::new() },
content: file_content,
blocks
});
}
templates
}