use crate::model::{DirNode, FileNode, Node, NodeName};
use crate::raw::{RawNode, RawNodeType, RawRoot};
use anyhow::{Result, anyhow};
use regex::Regex;
use std::cell::RefCell;
use std::collections::HashMap;
use std::rc::Rc;
pub fn load_root(raw: &RawRoot) -> Result<Node> {
let mut ctx = TemplateContext {
templates: &raw.template,
built_templates: HashMap::new(),
};
let global = &raw.global;
build_node(&raw.root, &mut ctx, global)
}
struct TemplateContext<'a> {
templates: &'a HashMap<String, RawNode>,
built_templates: HashMap<String, Rc<RefCell<DirNode>>>,
}
fn build_node(
raw: &RawNode,
ctx: &mut TemplateContext,
global: &Option<crate::raw::RawGlobal>,
) -> Result<Node> {
match &raw.r#type {
RawNodeType::Dir => build_dir_node(raw, ctx, global),
RawNodeType::File => build_file_node(raw, global),
RawNodeType::Ref => build_ref_node(raw, ctx, global),
}
}
fn build_dir_node(
raw: &RawNode,
ctx: &mut TemplateContext,
global: &Option<crate::raw::RawGlobal>,
) -> Result<Node> {
let name = match (&raw.name, &raw.pattern) {
(Some(n), None) => NodeName::Literal(n.clone()),
(None, Some(p)) => NodeName::Pattern(p.clone()),
_ => {
return Err(anyhow!(
"Node must have either 'name' or 'pattern', but not both"
));
}
};
let excluded = match &raw.excluded {
None => vec![],
Some(e) => {
let mut cloned_excluded = e.clone();
cloned_excluded.extend_from_slice(
global
.as_ref()
.and_then(|c| c.excluded.as_ref())
.and_then(|c| Some(c.as_ref()))
.unwrap_or(vec![].as_slice()),
);
let excluded_patterns: Vec<Regex> = cloned_excluded
.iter()
.map(|p| Regex::new(p))
.collect::<Result<Vec<_>, _>>()?;
excluded_patterns
}
};
let required = raw
.required
.or(global.as_ref().and_then(|c| c.required))
.unwrap_or(false);
let allow_defined_only = raw
.allow_defined_only
.or(global.as_ref().and_then(|c| c.allow_defined_only))
.unwrap_or(false);
let children = match &raw.children {
Some(raw_children) => {
let mut out = Vec::new();
for c in raw_children {
out.push(build_node(c, ctx, global)?);
}
out
}
None => vec![],
};
Ok(DirNode::new(
name,
children,
required,
allow_defined_only,
excluded,
))
}
fn build_file_node(raw: &RawNode, global: &Option<crate::raw::RawGlobal>) -> Result<Node> {
let name = match (&raw.name, &raw.pattern) {
(Some(n), None) => NodeName::Literal(n.clone()),
(None, Some(p)) => NodeName::Pattern(p.clone()),
_ => {
return Err(anyhow!(
"Node must have either 'name' or 'pattern', but not both"
));
}
};
let required = raw
.required
.or(global.as_ref().and_then(|c| c.required))
.unwrap_or(false);
Ok(FileNode::new(name, required))
}
fn build_ref_node(
raw: &RawNode,
ctx: &mut TemplateContext,
global: &Option<crate::raw::RawGlobal>,
) -> Result<Node> {
let ref_key = raw
.r#ref
.as_ref()
.ok_or_else(|| anyhow!("Ref node missing 'ref' field"))?;
if let Some(built_template) = ctx.built_templates.get(ref_key) {
return Ok(Node::Dir(built_template.clone()));
}
let excluded = match &raw.excluded {
None => vec![],
Some(e) => {
let mut cloned_excluded = e.clone();
cloned_excluded.extend_from_slice(
global
.as_ref()
.and_then(|c| c.excluded.as_ref())
.and_then(|c| Some(c.as_ref()))
.unwrap_or(vec![].as_slice()),
);
let excluded_patterns: Vec<Regex> = cloned_excluded
.iter()
.map(|p| Regex::new(p))
.collect::<Result<Vec<_>, _>>()?;
excluded_patterns
}
};
let template = ctx
.templates
.get(ref_key)
.ok_or_else(|| anyhow!("Unknown template ref: {}", ref_key))?;
let name = match (&template.name, &template.pattern) {
(Some(n), None) => NodeName::Literal(n.clone()),
(None, Some(p)) => NodeName::Pattern(p.clone()),
_ => {
return Err(anyhow!(
"Node must have either 'name' or 'pattern', but not both"
));
}
};
let required = template
.required
.or(global.as_ref().and_then(|c| c.required))
.unwrap_or(false);
let allow_defined_only = template
.allow_defined_only
.or(global.as_ref().and_then(|c| c.allow_defined_only))
.unwrap_or(false);
let node = Rc::new(RefCell::new(DirNode {
name,
children: Vec::new(),
required,
allow_defined_only,
excluded,
}));
ctx.built_templates.insert(ref_key.clone(), node.clone());
if let Some(children) = &template.children {
for c in children {
let child_node = build_node(c, ctx, global)?;
node.borrow_mut().children.push(child_node);
}
};
Ok(Node::Dir(node.clone()))
}