use crate::{
component_file::ComponentFile, component_not_found, transformers::Transformers, Context, Result,
};
use std::{
collections::HashMap,
path::{Path, PathBuf},
};
#[derive(Debug, Clone)]
pub struct StaticMapProvider {
templates: HashMap<String, (PathBuf, String)>,
}
impl StaticMapProvider {
pub fn new() -> Self {
Self {
templates: HashMap::new(),
}
}
#[must_use]
pub fn with_template<A: Into<String>, B: Into<String>>(
mut self,
name: A,
source: B,
path: Option<&Path>,
) -> Self {
let name = name.into();
self.templates.insert(
name,
(
path.unwrap_or_else(|| Path::new("inline")).to_owned(),
source.into(),
),
);
self
}
pub fn extend(&mut self, other: StaticMapProvider) {
self.templates.extend(other.templates);
}
pub fn insert<A: Into<String>, B: Into<String>>(
&mut self,
name: A,
source: B,
path: Option<&Path>,
) {
let name = name.into();
self.templates.insert(
name,
(
path.unwrap_or_else(|| Path::new("inline")).to_owned(),
source.into(),
),
);
}
pub fn get(&self, name: &str) -> Option<ComponentFile<'_, '_>> {
self.templates
.get(name)
.map(|t| ComponentFile::new(&t.0, &t.1))
}
pub fn remove(&mut self, name: &str) -> Option<ComponentFile<'_, '_>> {
self.templates
.remove(name)
.map(|t| ComponentFile::new(t.0, t.1))
}
pub fn template_names(&self) -> impl Iterator<Item = &String> {
self.templates.keys()
}
pub fn to_resolver_fn<'a>(&'a self) -> impl FnMut(&str) -> Result<ComponentFile<'a, 'a>> {
move |name| {
if let Some((file_path, data)) = self.templates.get(name) {
Ok(ComponentFile::new(file_path, data))
} else {
Err(component_not_found(name))
}
}
}
pub fn compile<'a: 'b, 'b>(
&self,
default_component_name: &str,
default_context: &mut Context,
) -> Result<String> {
crate::compile(
default_component_name,
default_context,
self.to_resolver_fn(),
)
}
pub fn transform_and_compile<'a: 'b, 'b>(
&self,
default_component_name: &str,
default_context: &mut Context,
transformers: &mut Transformers,
) -> Result<String> {
crate::transform_and_compile(
default_component_name,
default_context,
self.to_resolver_fn(),
transformers,
)
}
}
impl Default for StaticMapProvider {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use crate::{error::SpannedWithComponent, Error};
use super::*;
#[allow(dead_code)]
trait AssertSendSync: Send + Sync {}
impl AssertSendSync for StaticMapProvider {}
#[test]
fn compile_basic() {
let mut provider = StaticMapProvider::new().with_template(
"index",
"<h1>A title</h1><about-page />",
Some(Path::new("src/index.html")),
);
provider.insert(
"about-page",
"<h1>About page</h1>",
Some(Path::new("src/about.html")),
);
assert_eq!(
provider.compile("index", &mut Context::new()),
Ok("<h1>A title</h1><h1>About page</h1>".to_string())
);
}
#[test]
fn compile_low_level() {
let mut provider = StaticMapProvider::new().with_template(
"index",
"<h1>A title</h1><about-page />",
Some(Path::new("src/index.html")),
);
provider.insert(
"about-page",
"<h1>About page</h1>",
Some(Path::new("src/about.html")),
);
assert_eq!(
crate::compile("index", &mut Context::new(), provider.to_resolver_fn()),
Ok("<h1>A title</h1><h1>About page</h1>".to_string())
);
}
#[test]
fn errors() {
let provider = StaticMapProvider::new().with_template(
"index",
"<h1>A title</",
Some(Path::new("src/index.html")),
);
assert_eq!(
**provider.compile("index", &mut Context::new()).unwrap_err(),
Error::ParserError(crate::parser::Error::ExpectedTagName)
);
let provider =
StaticMapProvider::new().with_template("index", "<h1>A title</h1><about-page />", None);
assert_eq!(
crate::compile("index", &mut Context::new(), provider.to_resolver_fn()),
Err(SpannedWithComponent::new(Error::ComponentNotFound {
name: "about-page".to_string()
})
.with_file_path("inline")
.with_span(16..30)
.with_component_name("index")),
);
}
}