use std::{
borrow::BorrowMut,
fs::File,
io::{BufReader, Read},
path::{Path, PathBuf},
};
use crate::{file_path::FilePath, iter_ext::Head};
use roxy_core::result::Result;
use tera::{to_value, Map, Value};
use toml::Table;
fn merge(a: &mut Value, b: Value) {
match (a, b) {
(&mut Value::Object(ref mut a), Value::Object(b)) => {
b.into_iter().for_each(|(k, v)| {
merge(a.entry(k).or_insert(Value::Null), v);
});
}
(a, b) => {
*a = b;
}
}
}
#[derive(Clone, Debug)]
pub(crate) struct Context {
inner: tera::Value,
}
impl Context {
pub fn new() -> Self {
Self {
inner: tera::Value::Null,
}
}
pub fn merge(&mut self, other: tera::Context) {
merge(self.inner.borrow_mut(), other.into_json())
}
fn as_formatted_path<P: AsRef<Path>>(path: &P) -> Option<PathBuf> {
let path = path.as_ref();
if path.with_extension("").file_name()? == "index" {
Some(path.with_file_name(""))
} else {
Some(path.with_extension(""))
}
}
pub fn from_files<'a, T: AsRef<Path> + std::fmt::Debug>(
files: Vec<&PathBuf>,
file_path: &'a FilePath<T>,
) -> Result<Context> {
let mut context = Context::new();
for path in files {
let mut buf = Vec::new();
let mut file = File::open(path).map(BufReader::new)?;
file.read_to_end(&mut buf)?;
let mut str = String::from_utf8(buf).map_err(anyhow::Error::from)?;
let toml: Table = toml::from_str(&mut str).map_err(anyhow::Error::from)?;
let path = file_path.strip_root(path).map_err(anyhow::Error::from)?;
let value = tera::to_value(&toml).map_err(anyhow::Error::from)?;
context.insert(&path, &value);
}
Ok(context)
}
pub fn build_paths<'a, T: AsRef<Path>>(
&mut self,
files: &Vec<&PathBuf>,
file_path: &'a FilePath<T>,
) -> Result<()> {
for path in files {
let path = file_path.strip_root(path).map_err(anyhow::Error::from)?;
if let Some(path) = Self::as_formatted_path(&path) {
if let Some(slug) = file_path.as_slug(&path) {
let slug = PathBuf::from("/").join(slug);
if let Ok(slug) = to_value(slug) {
self.insert(&path.join("path"), &slug);
}
}
}
}
Ok(())
}
fn path_to_string<P: AsRef<Path>>(path: &P) -> String {
path.as_ref().to_string_lossy().to_string()
}
fn create_path<P: AsRef<Path>>(path: &P, value: &Value) -> Option<Value> {
let path = path.as_ref();
let (head, tail) = path.components().head()?;
let mut map = Map::new();
if tail.clone().count() > 0 {
let child = Self::create_path(&tail, value)?;
map.insert(Self::path_to_string(&head), child);
} else {
let key = Self::path_to_string(&path.with_extension("").file_name()?);
map.insert(key, value.to_owned());
}
Some(map.into())
}
fn as_key<P: AsRef<Path>>(path: &P) -> Option<&Path> {
let path = path.as_ref();
if path
.with_extension("")
.file_name()
.map_or(false, |f| f.to_os_string() == "index")
{
path.parent()
} else {
Some(path)
}
}
pub fn remove<P: AsRef<Path>>(&mut self, path: &P) {
if let Some(value) = self.get_mut(path) {
*value = tera::Value::Null;
}
}
pub fn set<P: AsRef<Path>>(&mut self, path: &P, value: &Value) {
self.remove(path);
self.insert(path, value);
}
pub fn insert<P: AsRef<Path>>(&mut self, path: &P, value: &Value) {
let path = Self::as_key(path).map(|p| p.with_extension(""));
let path = Self::create_path(&path.unwrap(), value).or(Some(value.clone()));
if let Some(v) = path {
let value = tera::Context::from_value(v);
if let Ok(ctx) = value {
self.merge(ctx);
}
}
}
pub fn get<P: AsRef<Path>>(&self, path: &P) -> Option<&Value> {
Self::as_key(path)?
.with_extension("")
.components()
.filter_map(|c| c.as_os_str().to_str())
.try_fold(&self.inner, |acc, i| acc.get(i))
}
pub fn get_mut<P: AsRef<Path>>(&mut self, path: &P) -> Option<&mut Value> {
Self::as_key(path)?
.with_extension("")
.components()
.filter_map(|c| c.as_os_str().to_str())
.try_fold(&mut self.inner, |acc, i| acc.get_mut(i))
}
}
impl Into<tera::Value> for Context {
fn into(self) -> tera::Value {
self.inner
}
}
impl TryInto<tera::Context> for Context {
type Error = tera::Error;
fn try_into(self) -> std::result::Result<tera::Context, Self::Error> {
tera::Context::from_value(self.inner)
}
}