use std::{fs, path::{Path, PathBuf}, io, collections::{HashMap, HashSet}, sync::Arc, ffi::OsString};
use acetewm::selector;
use color_eyre::eyre::Result;
use lazy_static::lazy_static;
use log::{debug, warn, error};
use scraper::{Html, Node as HtmlNode, Selector, ElementRef, node::Text};
use ego_tree::{Tree, NodeId};
use crate::{consts::{ATTRIBUTE_ACE_NAME, VALID_CUSTOM_ELEMENT_NAME, INVALID_CUSTOM_ELEMENT_NAME, ATTRIBUTE_ID, ATTRIBUTE_INLINE, ATTRIBUTE_ACE_ATTRIBUTES, ATTRIBUTE_ACE_EXTENDS}, workarounds::{html_node_editable::EditableHtmlNode, ego_tree_addons::NodeMutAddons}};
use super::recursive_template_search;
#[derive(Debug)]
pub(crate) struct TemplateBundle {
frontend_templates: String,
backend_templates: HashMap<Arc<str>, Arc<str>>
}
impl TemplateBundle {
pub fn new() -> Result<Self> {
Ok(
TemplateBundle {
frontend_templates: String::new(),
backend_templates: HashMap::new()
}
)
}
pub fn add_to_bundle(&mut self, file_path: &Path) -> Result<()> {
debug!("add_to_bundle: process file: {}", file_path.to_string_lossy());
let template_markup = Html::parse_fragment(
&String::from_utf8_lossy(&fs::read(&file_path)?)
);
if template_markup.errors.len() != 0 {
warn!("Parse error(s) were detected while reading the following file: {}", file_path.to_string_lossy());
warn!("Parse error(s) are as follows:");
}
for error_msg in template_markup.errors.iter() {
warn!(" {}", error_msg);
}
let template_markup_root_elem = template_markup.root_element();
Ok(())
}
}
pub(crate) fn traverse_node_modules<P: AsRef<Path>>(path_dir: P) -> Result<()> {
let mut path_dir = fs::canonicalize(path_dir)?;
while path_dir.parent().is_some() {
path_dir.push("node_modules");
let modules_folder_meta = match fs::metadata(&path_dir) {
Ok(meta) => meta,
Err(e) if e.kind() == io::ErrorKind::NotFound => {
path_dir.pop();
path_dir.pop();
continue;
}
Err(e) => return Err(e.into()),
};
if modules_folder_meta.is_dir() {
}
path_dir.pop();
path_dir.pop();
}
Ok(())
}
pub(crate) struct IncludeElementChecker {
include: HashSet<Arc<str>>,
exclude: HashSet<Arc<str>>
}
impl IncludeElementChecker {
pub fn from_string_vecs(include: Vec<String>, exclude: Vec<String>) -> Self {
IncludeElementChecker {
include: include.into_iter().map(|s| {s.into()}).collect(),
exclude: exclude.into_iter().map(|s| {s.into()}).collect()
}
}
pub fn should_include(&self, elem_tag: &str) -> bool {
if self.exclude.contains(elem_tag) {
return false;
}
return self.include.len() == 0 || self.include.contains(elem_tag);
}
}
lazy_static! {
pub static ref NO_MAIN_TEMPLATE: HashSet<OsString> = {
let mut m = HashSet::new();
m.insert("main_template.html".into());
m
};
}
pub(crate) fn do_bundle_spa<P: AsRef<Path>>(
output_file: P,
include: IncludeElementChecker,
input_dir: P
) -> Result<()> {
let mut file_path = input_dir.as_ref().to_path_buf();
file_path.push("main_template.html");
debug!("do_bundle_spa: process file: {}", file_path.to_string_lossy());
let mut main_template_markup = Html::parse_document(
&String::from_utf8_lossy(&fs::read(&file_path)?)
);
file_path.pop();
recursive_template_search(file_path, &NO_MAIN_TEMPLATE, &mut |file_path, _| {
debug!("do_bundle_spa: process file: {}", file_path.to_string_lossy());
let mut template_markup = Html::parse_fragment(
&String::from_utf8_lossy(&fs::read(&file_path)?)
);
if template_markup.errors.len() != 0 {
warn!("Parse error(s) were detected while reading the following file: {}", file_path.to_string_lossy());
warn!("Parse error(s) are as follows:")
}
for error_msg in template_markup.errors.iter() {
warn!(" {}", error_msg);
}
let template_markup_root_elem = template_markup.root_element();
let template_tree_nodes: Vec<NodeId> = template_markup_root_elem
.children()
.filter_map(|node_ref| {
let HtmlNode::Element(elem) = node_ref.value() else {
return None;
};
if elem.name() != "template" {
return None;
}
let Some(template_elem_tag) = elem.attrs.get(&ATTRIBUTE_ACE_NAME) else {
warn!("Skipping template without \"ace-name\" attribute");
return None;
};
if
!VALID_CUSTOM_ELEMENT_NAME.is_match(&template_elem_tag) ||
INVALID_CUSTOM_ELEMENT_NAME.is_match(&template_elem_tag)
{
error!("\"{}\" is not a valid custom element name", template_elem_tag);
return None;
}
if !include.should_include(&template_elem_tag) {
return None;
}
Some(node_ref.id())
})
.collect();
for node_id in template_tree_nodes.iter() {
let Some(mut node_ref) = template_markup.tree.get_mut(*node_id) else {
continue;
};
let elem = node_ref.value().as_element_mut().unwrap();
let template_elem_tag = elem.attrs.get(&ATTRIBUTE_ACE_NAME).unwrap().clone();
let template_template_id = format!("ace-template-{}", template_elem_tag);
elem.attrs.remove(&ATTRIBUTE_ACE_NAME);
elem.attrs.remove(&ATTRIBUTE_ACE_ATTRIBUTES);
elem.attrs.remove(&ATTRIBUTE_ACE_EXTENDS);
elem.attrs.insert(ATTRIBUTE_ID.clone(), template_template_id.clone().into());
let is_inline = elem.attrs.contains_key(&ATTRIBUTE_INLINE);
let node_ref = template_markup.tree.get(*node_id).unwrap();
if is_inline {
let nodes_to_replace: Vec<NodeId> = main_template_markup
.select(selector!(&template_elem_tag))
.map(|elem_ref| {elem_ref.id()})
.collect();
for main_t_node_id in nodes_to_replace.iter() {
let mut main_t_node_ref = main_template_markup.tree.get_mut(*main_t_node_id).unwrap();
main_t_node_ref.insert_cloned_descendants_before(&node_ref);
main_t_node_ref.detach();
}
} else {
let mut main_template_body_node = main_template_markup.tree.get_mut(
main_template_markup.select(selector!("body"))
.next()
.expect("main_template.html should have a <body>")
.id()
).unwrap();
main_template_body_node.append_cloned_tree(&node_ref);
main_template_body_node.append(HtmlNode::Text(Text { text: "\n".into()}));
}
}
Ok(())
})?;
fs::write(output_file, main_template_markup.html())?;
Ok(())
}