use std::collections::HashMap;
use std::path::PathBuf;
use std::str::from_utf8;
use id_tree::InsertBehavior::*;
use id_tree::Node;
use id_tree::NodeId;
use id_tree::Tree;
use serde_json::Value;
use tera::Context;
use tera::Tera;
use crate::log;
use crate::ShFile;
use crate::{Plugin, RefIR};
pub fn plugin() -> Plugin {
Box::new(|mut ir: RefIR| {
let mut tree: Tree<PathBuf> = Tree::new();
let mut children: Vec<NodeId> = Vec::new();
let root_id = tree.insert(Node::new(PathBuf::default()), AsRoot).unwrap();
let ancestor_layouts: HashMap<&PathBuf, NodeId> = ir
.files
.iter()
.filter(|(_, v)| {
if let Value::Object(obj) = v.frontmatter.clone() {
obj.contains_key("is_layout") && !obj.contains_key("layout")
} else {
false
}
})
.map(|(path, _)| {
let id = tree
.insert(Node::new(path.clone()), UnderNode(&root_id))
.unwrap();
(path, id)
})
.collect();
ancestor_layouts.iter().for_each(|(_, node_id)| {
treeify_for(&mut tree, node_id, &ir, &mut children);
});
log::debug!("Layout tree:\n{}", print_tree(&tree));
children.iter().for_each(|id| {
let path = tree.get(id).unwrap().data();
log::debug!("Rendering {:?}", path);
let ancestors: Vec<&Node<PathBuf>> = tree.ancestors(&id).unwrap().collect();
let mut reviter = ancestors.iter().rev().peekable();
let mut acc: Option<String> = None;
reviter.next();
if reviter.len() > 1 {
while let Some(&node) = reviter.next() {
let mut context = Context::new();
if let Some(next_node) = reviter.peek() {
let val = ir.files.get(next_node.data()).unwrap();
context.insert("content", from_utf8(&val.content).unwrap());
match acc {
None => {
let layout = ir.files.get(node.data()).unwrap();
acc = Some(
Tera::one_off(
from_utf8(&layout.content).unwrap(),
&context,
false,
)
.unwrap(),
)
}
Some(layout) => {
acc = Some(Tera::one_off(layout.as_ref(), &context, false).unwrap())
}
}
}
}
} else {
let node = reviter.next().unwrap();
acc = Some(
from_utf8(&ir.files.get(node.data()).unwrap().content)
.unwrap()
.to_string(),
);
}
let mut context = Context::new();
let node_contents = ir.files.get(path).unwrap();
context.insert("content", from_utf8(&node_contents.content).unwrap());
let rendered = Tera::one_off(acc.unwrap().as_ref(), &context, false).unwrap();
let mut file = node_contents.clone();
ir.files.remove(path);
file.content = rendered.into();
ir.files.insert(path.to_path_buf(), file);
});
tree.traverse_level_order(&root_id)
.unwrap()
.for_each(|node| {
let file = ir.files.get(node.data());
if let Some(file) = file {
if let Value::Object(obj) = file.frontmatter.clone() {
if obj.contains_key("is_layout") {
ir.files.remove(node.data());
}
}
}
});
})
}
fn treeify_for(
tree: &mut Tree<PathBuf>,
node: &NodeId,
ir: &RefIR,
final_children: &mut Vec<NodeId>,
) {
let layout_name = tree.get(node).unwrap().data().file_stem().unwrap();
let children: HashMap<&PathBuf, &ShFile> = ir
.files
.iter()
.filter(|(_, file)| {
if let Value::Object(obj) = file.frontmatter.clone() {
obj.contains_key("layout")
&& obj.get("layout").unwrap().as_str() == layout_name.to_str()
} else {
false
}
})
.collect();
if !children.is_empty() {
children.iter().for_each(|(path, file)| {
let node_id = tree
.insert(Node::new(path.clone().to_path_buf()), UnderNode(node))
.unwrap();
let fm = file.frontmatter.clone();
let val = fm.as_object().unwrap();
if !val.contains_key("is_layout") {
final_children.push(node_id.clone());
} else {
treeify_for(tree, &node_id, ir, final_children);
}
})
}
}
fn print_tree(tree: &Tree<PathBuf>) -> String {
let mut s: String = String::new();
tree.write_formatted(&mut s).unwrap();
s
}
#[test]
fn it_works() {
use crate::Shtola;
use std::str::from_utf8;
let mut s = Shtola::new();
s.source("../fixtures/tera_layouts");
s.destination("../fixtures/tera_layouts/dest");
s.clean(true);
s.register(plugin());
let r = s.build().unwrap();
let file1 = r.files.get(&PathBuf::from("page.html")).unwrap();
let file2 = r.files.get(&PathBuf::from("3rdpage.html")).unwrap();
assert_eq!(
from_utf8(&file1.content).unwrap(),
"<h1>top layout</h1>\n\nhello"
);
assert_eq!(
from_utf8(&file2.content).unwrap(),
"<h1>top layout</h1>\n\n<p>hiii</p>"
);
}