summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorPhilipp Korber <philippkorber@gmail.com>2018-05-30 18:28:03 +0200
committerPhilipp Korber <philippkorber@gmail.com>2018-05-30 18:54:01 +0200
commitf294689ea38d69ecb8c5a122682e3e65689d2eb6 (patch)
treec83915ff8dcffc7197205df538421d41e21d148c
parent40a6c11c4a4860af62f584e4b314f389b7894fb3 (diff)
chore(api): moved render_template_engine and tera into different crate
-rw-r--r--Cargo.toml8
-rw-r--r--notes/notes.md53
-rw-r--r--src/lib.rs17
-rw-r--r--src/render_template_engine/error.rs219
-rw-r--r--src/render_template_engine/from_dir.rs141
-rw-r--r--src/render_template_engine/mod.rs389
-rw-r--r--src/render_template_engine/settings.rs296
-rw-r--r--src/render_template_engine/utils.rs291
-rw-r--r--src/resource/impl_inspect.rs79
-rw-r--r--src/resource/mod.rs52
-rw-r--r--src/template_engine.rs2
-rw-r--r--src/tera/error.rs44
-rw-r--r--src/tera/mod.rs101
-rw-r--r--tests/rte/main.rs51
-rw-r--r--tests/tera/main.rs114
15 files changed, 161 insertions, 1696 deletions
diff --git a/Cargo.toml b/Cargo.toml
index 92d7ef9..3f46951 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -25,10 +25,7 @@ chrono = "0.4.0"
vec1 = { git="https://github.com/1aim/vec1" }
soft-ascii-string = "0.2.0"
-futures-cpupool = { version = "0.1.5", optional = true }
-tera = { version = "0.11.1", optional = true }
-conduit-mime-types = { version = "0.7.3", optional = true}
-lazy_static = { version = "1.0.0", optional = true }
+
[dependencies.mime]
git="https://github.com/1aim/mime"
@@ -41,7 +38,4 @@ serde_json = "1.0.2"
regex = "0.2.6"
[features]
-debug_trace_tokens = []
-render-template-engine = ["conduit-mime-types", "lazy_static"]
-tera-bindings = ["tera", "render-template-engine"]
default = [] \ No newline at end of file
diff --git a/notes/notes.md b/notes/notes.md
index 7f0ebac..a6d68ea 100644
--- a/notes/notes.md
+++ b/notes/notes.md
@@ -12,7 +12,7 @@ where recipients_data is a iterable mapping from address to recipient specific d
e.g. `Vec<(Address, Data)>`
and mail_gen is something like `trait MailGen { fn gen_mail( from, to, data, bits8support ) -> MailBody; }`
-
+
`MailBody` is not `tokio_smtp::MailBody` but has to implement nessesray contraints,
(e.g. implemnting `toki_smtp::IntoMailBody` not that for the beginning this will be
hard encoded but later one a generic variation allowing `smtp` to be switched out
@@ -22,7 +22,7 @@ MailGen implementations are not done by hand but implemented ontop of something
like a template spec e.g. `struct TemplateSpec { id_template: TemplateId, additional_appendixes: Vec<Appendix> }`
Where `TemplateId` can is e.g. `reset_link` leading to the creation of a `html` with alternate `plain`
-mail iff there is a `reset_link.html` and a `reset_link.plain` template. A `reset_link.html.data`
+mail iff there is a `reset_link.html` and a `reset_link.plain` template. A `reset_link.html.data`
folder could be used to define inline (mime related) appendixes like embedded images,
but we might want to have a way to define such embeddigns through the data (
E.g. by mapping `Data => TemplateEnginData` and replacing `EmbeddedFile` variations
@@ -57,11 +57,11 @@ MUST NOT occur in body (except for newline)
- there is a `domain-literal` version which does use somthing like `[some_thing]`,
we can use puny code for converting domains into ascii but probably can't use
this with `domain-literal`'s
-
+
- `local-part` is `dot-atom` which has leading and trailing `[CFWS]` so comments are alowed
- MessageId uses a email address like syntax but without supporting spaces/comments
-
+
# MIME
@@ -112,17 +112,17 @@ Contend types:
- `form-data`
- `x-mixed-replace` (for server push, don't use by now there are better ways)
- `byteranges`
-
+
Example mail structure:
```
-(multipart/mixed
+(multipart/mixed
(multipart/alternative
- (text/plain ... )
- (multipart/related
- (text/hmtl ... '<img src="cid:contentid@1aim.com"></img>' ... )
- (image/png (Content-ID <contentid@1aim.com>) ... )
+ (text/plain ... )
+ (multipart/related
+ (text/hmtl ... '<img src="cid:ContentId@1aim.com"></img>' ... )
+ (image/png (Content-ID <ContentId@1aim.com>) ... )
... ))
(image/png (Content-Disposition attachment) ...)
(image/png (Content-Disposition attachment) ...))
@@ -131,15 +131,15 @@ Example mail structure:
Possible alternate structure:
```
-(multipart/mixed
+(multipart/mixed
(multipart/related
-
+
(multipart/alternative
- (text/plain ... '[cid:contentid@1aim.com]' ... )
- (text/html ... '<img src="cid:contentid@1aim.com"></img>' ... ) )
-
- (image/png (Content-ID <contentid@1aim.com>) ... ) )
-
+ (text/plain ... '[cid:ContentId@1aim.com]' ... )
+ (text/html ... '<img src="cid:ContentId@1aim.com"></img>' ... ) )
+
+ (image/png (Content-ID <ContentId@1aim.com>) ... ) )
+
(image/png (Content-Disposition attachment) ...)
(image/png (Content-Disposition attachment) ...))
```
@@ -156,7 +156,7 @@ proposed filenames for attachments can be given through parameters of the dispos
it does not allow non ascii character there!
see rfc2231 for more information, it extends some part wrt.:
-
+
- splitting long parameters (e.g. long file names)
- specifying language and character set
- specifying language for encoded words
@@ -173,7 +173,7 @@ a "big" text chunk can be split in multiple encoded words seperated by b'\r\n '
non encoded words and encoded words can apear in the same header field, but
must be seperate by "linear-white-space" (space) which is NOT removed when
-decoding encoded words
+decoding encoded words
encoded words can appear in:
@@ -232,7 +232,7 @@ smtp still considers _the bytes_ corresponding to CR LF and DOT special.
- there is a line length limit, lines terminate with b'CRLF'
- b'.CRLF' does sill end the body (if preceeded by CRLF, or body starts with it)
- so dot-staching is still done on protocol level
-
+
## Hot to handle `obs-` parsings
@@ -290,7 +290,7 @@ therefore:
warn when encoding a Disposition of kind Attachment which's
-file_meta has no name set
+file_meta has no name set
// From RFC 2183:
@@ -301,7 +301,7 @@ file_meta has no name set
// be represented as `quoted-string'. Parameter values longer than 78
// characters, or which contain non-ASCII characters, MUST be encoded as
// specified in [RFC 2184].
-provide a gnneral way for encoding header parameter which follow the scheme:
+provide a gnneral way for encoding header parameter which follow the scheme:
`<mainvalue> *(";" <key>"="<value> )` this are ContentType and ContentDisposition
for now
@@ -313,20 +313,19 @@ possible checking for "more" validity then noew
email::quote => do not escape WSP, and use FWS when encoding
also make quote, generally available for library useers a
create_quoted_string( .. )
-
+
# Dependencies
quoted_printable and base64 have some problems:
1. it's speaking of a 76 character limit where it is 78
it seems they treated the RFC as 78 character including
CRLF where the RFC speaks of 78 characters EXCLUDING
- CRLF
+ CRLF
2. it's only suited for content transfer encoding the body
- as there is a limit of the length of encoded words (75)
+ as there is a limit of the length of encoded words (75)
which can't be handled by both
-
+
also quoted_printable has another problem:
3. in headers the number of character which can be displayed without
encoding is more limited (e.g. no ' ' ) quoted_printable does not
respect this? (TODO CHECK THIS)
- \ No newline at end of file
diff --git a/src/lib.rs b/src/lib.rs
index bec198a..d3b2d81 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -15,13 +15,8 @@ extern crate soft_ascii_string;
extern crate chrono;
#[macro_use]
extern crate vec1;
-#[cfg(feature="render-template-engine")]
-extern crate conduit_mime_types;
-#[cfg(feature="render-template-engine")]
-#[macro_use]
-extern crate lazy_static;
-#[cfg(feature="tera-bindings")]
-extern crate tera as tera_crate;
+
+
#[macro_use]
#[allow(unused_imports)]
extern crate mail_derive;
@@ -38,14 +33,6 @@ mod template_engine;
mod builder_extension;
mod compositor;
-#[cfg(feature="render-template-engine")]
-pub mod render_template_engine;
-#[cfg(feature="tera-bindings")]
-pub mod tera;
-
-//TODO consider using glob reexports and pub(crate) for
-// non public parts used by other modules
-
// reexports
pub use self::builder_extension::BuilderExt;
pub use self::compositor::*;
diff --git a/src/render_template_engine/error.rs b/src/render_template_engine/error.rs
deleted file mode 100644
index 6a0edbb..0000000
--- a/src/render_template_engine/error.rs
+++ /dev/null
@@ -1,219 +0,0 @@
-use std::fmt::{self, Display};
-use std::path::{Path, PathBuf};
-use std::ops::Deref;
-use std::cmp::PartialEq;
-use std::io;
-use std::ffi::{OsStr, OsString};
-
-use failure::{Fail, Backtrace, Context};
-
-#[derive(Debug, Fail)]
-pub enum Error<RE: Fail> {
- #[fail(display = "unknown template id: {:?}", template_id)]
- UnknownTemplateId {
- template_id: String,
- backtrace: Backtrace
- },
-
- #[fail(display = "{}", _0)]
- RenderError(RE),
-}
-
-impl<RE: Fail> Error<RE> {
-
- pub fn from_unknown_template_id<I>(tid: I) -> Self
- where I: Into<String>
- {
- Error::UnknownTemplateId {
- template_id: tid.into(),
- backtrace: Backtrace::new()
- }
- }
-}
-
-#[derive(Debug)]
-pub struct LoadingSpecError {
- inner: Context<LoadingSpecErrorVariant>
-}
-
-impl LoadingSpecError {
-
- pub fn variant(&self) -> &LoadingSpecErrorVariant {
- self.inner.get_context()
- }
-}
-impl Fail for LoadingSpecError {
- fn backtrace(&self) -> Option<&Backtrace> {
- self.inner.backtrace()
- }
-
- fn cause(&self) -> Option<&Fail> {
- self.inner.cause()
- }
-}
-
-impl Display for LoadingSpecError {
- fn fmt(&self, fter: &mut fmt::Formatter) -> fmt::Result {
- Display::fmt(&self.inner, fter)
- }
-}
-
-impl From<LoadingSpecErrorVariant>for LoadingSpecError {
- fn from(variant: LoadingSpecErrorVariant) -> Self {
- LoadingSpecError::from(Context::new(variant))
- }
-}
-
-impl From<Context<LoadingSpecErrorVariant>> for LoadingSpecError {
- fn from(inner: Context<LoadingSpecErrorVariant>) -> Self {
- LoadingSpecError { inner }
- }
-}
-
-impl From<io::Error> for LoadingSpecError {
- fn from(io_err: io::Error) -> Self {
- io_err.context(LoadingSpecErrorVariant::IoError).into()
- }
-}
-
-
-
-#[derive(Debug, Fail)]
-pub enum LoadingSpecErrorVariant {
-
- #[fail(display = "path must also be valid string, got: {}", _0)]
- NonStringPath(DisplayPath),
-
- #[fail(display = "no type info in settings for: {:?}", type_name)]
- MissingTypeInfo { type_name: String },
-
- #[fail(display = "media type creation for body failed")]
- BodyMediaTypeCreationFailure,
-
- #[fail(display = "media type creation for Embedding/Attachment failed")]
- ResourceMediaTypeCreationFailure,
-
- #[fail(display = "multiple embeddings with the in-template name {:?} where found", name)]
- DuplicateEmbeddingName { name: String },
-
- #[fail(display = "template dir has to contain at last one sub-template. dir: {}", dir)]
- NoSubTemplatesFound { dir: DisplayPath },
-
- #[fail(display = "sub-template folder does not contain a template file: {}", dir)]
- TemplateFileMissing { dir: DisplayPath },
-
- #[fail(display = "I/O Error occurred when loading the spec")]
- IoError,
-
- #[fail(display = "the template/embedding/attachment <{}> is not a file", _0)]
- NotAFile(DisplayPath),
-
- #[fail(display = "given file does not have a valid (i.e. us-ascii/utf8) file stem: {}", _0)]
- NoValidFileStem { file: DisplayPath },
-
- #[fail(display = "no media type is registered for the file stem {:?}", stem)]
- NoMediaTypeFor { stem: String },
-
- #[fail(display = "stem and content type differ: {:?} != {:?} wrt. {}",
- by_extension, by_content, path)]
- FileStemAndContentDifferInMediaType {
- path: DisplayPath,
- by_extension: String,
- by_content: String
- },
-
- #[fail(display = "media type is not valid utf-8")]
- NonUtf8MediaType,
-
- #[fail(display = "the media type generated by a media type sniffer is invalid")]
- NotAMediaType,
-
- #[fail(display = "the spec with the id {:?} was overriden through a load_from_* function", id)]
- AccidentalSpecOverride { id: String },
-
- #[fail(display = "constructing a IRI with the scheme {} and the path {} failed", scheme, tail)]
- IRIConstructionFailed {
- scheme: &'static str,
- tail: DisplayPath
- }
-}
-
-
-#[derive(Clone, Debug, PartialEq, Eq, Hash)]
-pub struct DisplayPath(pub PathBuf);
-
-impl DisplayPath {
- pub fn as_path(&self) -> &Path {
- self.0.as_path()
- }
-}
-
-impl Display for DisplayPath {
- fn fmt(&self, fter: &mut fmt::Formatter) -> fmt::Result {
- Display::fmt(&self.0.display(), fter)
- }
-}
-
-impl<'a> From<&'a OsStr> for DisplayPath {
- fn from(path: &'a OsStr) -> Self {
- DisplayPath(Path::new(path).to_owned())
- }
-}
-
-impl<'a> From<&'a Path> for DisplayPath {
- fn from(path: &'a Path) -> Self {
- DisplayPath(PathBuf::from(path))
- }
-}
-
-impl From<PathBuf> for DisplayPath {
- fn from(path: PathBuf) -> Self {
- DisplayPath(path)
- }
-}
-
-impl From<OsString> for DisplayPath {
- fn from(path: OsString) -> Self {
- DisplayPath(path.into())
- }
-}
-
-impl From<DisplayPath> for PathBuf {
- fn from(dp: DisplayPath) -> Self {
- dp.0
- }
-}
-
-impl AsRef<Path> for DisplayPath {
- fn as_ref(&self) -> &Path {
- self
- }
-}
-
-impl Deref for DisplayPath {
- type Target = Path;
-
- fn deref(&self) -> &Self::Target {
- self.as_path()
- }
-}
-
-impl PartialEq<Path> for DisplayPath {
-
- fn eq(&self, other: &Path) -> bool {
- self.as_path() == other
- }
-}
-
-impl<'a> PartialEq<&'a Path> for DisplayPath {
- fn eq(&self, other: &&'a Path) -> bool {
- self == *other
- }
-}
-
-impl PartialEq<PathBuf> for DisplayPath {
-
- fn eq(&self, other: &PathBuf) -> bool {
- self.as_path() == other
- }
-} \ No newline at end of file
diff --git a/src/render_template_engine/from_dir.rs b/src/render_template_engine/from_dir.rs
deleted file mode 100644
index 89906d8..0000000
--- a/src/render_template_engine/from_dir.rs
+++ /dev/null
@@ -1,141 +0,0 @@
-use std::path::{Path, PathBuf};
-use std::collections::HashMap;
-
-use vec1::Vec1;
-
-use mail::context::Source;
-use mail::{Resource, IRI};
-
-use ::render_template_engine::error::{LoadingSpecError, LoadingSpecErrorVariant};
-use ::render_template_engine::utils::{new_string_path, new_str_path};
-use ::render_template_engine::{TemplateSpec, SubTemplateSpec};
-use ::render_template_engine::settings::{LoadSpecSettings, Type};
-
-//TODO missing global template level embeddings
-//TODO missing caching (of Resources)
-
-
-pub(crate) fn from_dir(base_path: &Path, settings: &LoadSpecSettings) -> Result<TemplateSpec, LoadingSpecError> {
- let mut glob_embeddings = HashMap::new();
- let mut sub_template_dirs = Vec::new();
- for folder in base_path.read_dir()? {
- let entry = folder?;
- if entry.file_type()?.is_dir() {
- let type_name = entry.file_name()
- .into_string().map_err(|_| LoadingSpecErrorVariant::NonStringPath(entry.path().into()))?;
- let (prio, type_) = settings.get_type_with_priority(&*type_name)
- .ok_or_else(|| LoadingSpecErrorVariant::MissingTypeInfo { type_name: type_name.clone() })?;
- sub_template_dirs.push((prio, entry.path(), type_));
- } else {
- let (name, resource_spec) = embedding_from_path(entry.path(), settings)?;
- glob_embeddings.insert(name, resource_spec);
- }
- }
-
- sub_template_dirs.sort_by_key(|data| data.0);
-
- let mut sub_specs = Vec::with_capacity(sub_template_dirs.len());
- for (_, dir_path, type_) in sub_template_dirs {
- sub_specs.push(sub_template_from_dir(&*dir_path, type_, settings)?);
- }
-
- let sub_specs = Vec1::from_vec(sub_specs)
- .map_err(|_| LoadingSpecErrorVariant::NoSubTemplatesFound { dir: base_path.into() })?;
- TemplateSpec::new_with_embeddings_and_base_path(
- sub_specs, glob_embeddings, base_path.to_owned())
-}
-
-
-//NOTE: if this is provided as a pub utility provide a wrapper function instead which
-// only accepts dir_path + settings and gets the rest from it
-fn sub_template_from_dir(dir: &Path, type_: &Type, settings: &LoadSpecSettings)
- -> Result<SubTemplateSpec, LoadingSpecError>
-{
- let template_file = find_template_file(dir, type_)?;
- let media_type = type_.to_media_type_for(&*template_file)?;
- let embeddings = find_embeddings(dir, &*template_file, settings)?;
-
- SubTemplateSpec::new(template_file, media_type, embeddings, Vec::new())
-}
-
-fn find_template_file(dir: &Path, type_: &Type) -> Result<PathBuf, LoadingSpecError> {
- let base_name = type_.template_base_name();
- let file = type_.suffixes()
- .iter()
- .map(|suffix| dir.join(base_name.to_owned() + suffix))
- .find(|path| path.exists())
- .ok_or_else(|| LoadingSpecErrorVariant::TemplateFileMissing { dir: dir.into() })?;
-
- Ok(file)
-}
-
-
-fn find_embeddings(target_path: &Path, template_file: &Path, settings: &LoadSpecSettings)
- -> Result<HashMap<String, Resource>, LoadingSpecError>
-{
- use std::collections::hash_map::Entry::*;
-
- let mut embeddings = HashMap::new();
- for entry in target_path.read_dir()? {
- let entry = entry?;
- let path = entry.path();
- if path != template_file {
- let (key, value) = embedding_from_path(path, settings)?;
- match embeddings.entry(key) {
- Occupied(oe) => {
- return Err(LoadingSpecErrorVariant::DuplicateEmbeddingName { name: oe.key().clone() }.into());
- },
- Vacant(ve) => {ve.insert(value);}
- }
- }
- }
- Ok(embeddings)
-}
-
-fn embedding_from_path(path: PathBuf, settings: &LoadSpecSettings)
- -> Result<(String, Resource), LoadingSpecError>
-{
- if !path.is_file() {
- return Err(LoadingSpecErrorVariant::NotAFile(path.into()).into());
- }
-
- let file_name = new_string_path(
- path.file_name()
- // UNWRAP_SAFE: file_name returns the file (,dir,symlink) name which
- // has to exist for a dir_entry
- .unwrap())?;
-
- let name = file_name.split(".")
- .next()
- //UNWRAP_SAFE: Split iterator has always at last one element
- .unwrap()
- .to_owned();
-
- //TODO we can remove the media type sniffing from here
- let media_type = settings.determine_media_type(&path)?;
-
- let source = Source {
- iri: iri_from_path(path)?,
- use_name: None,
- use_media_type: Some(media_type)
- };
-
- let resource = Resource::new(source);
-
- Ok((name, resource))
-}
-
-fn iri_from_path<IP: AsRef<Path> + Into<PathBuf>>(path: IP) -> Result<IRI, LoadingSpecError> {
- {
- let path_ref = path.as_ref();
- if let Ok(strfy) = new_str_path(&path_ref) {
- if let Ok(iri) = IRI::from_parts("path", strfy) {
- return Ok(iri)
- }
- }
- }
- Err(LoadingSpecErrorVariant::IRIConstructionFailed {
- scheme: "path",
- tail: path.into().into()
- }.into())
-} \ No newline at end of file
diff --git a/src/render_template_engine/mod.rs b/src/render_template_engine/mod.rs
deleted file mode 100644
index 6b16035..0000000
--- a/src/render_template_engine/mod.rs
+++ /dev/null
@@ -1,389 +0,0 @@
-use std::path::{Path, PathBuf};
-use std::collections::HashMap;
-use std::mem::replace;
-
-use failure::Fail;
-use serde::{Serialize, Serializer};
-use vec1::Vec1;
-
-use mail::{Resource, Context};
-use mail::file_buffer::FileBuffer;
-use headers::components::MediaType;
-
-use ::TemplateEngine;
-use ::template::{BodyPart, MailParts};
-use ::resource::{Attachment, EmbeddingWithCId};
-
-
-use self::error::{Error, LoadingSpecError, LoadingSpecErrorVariant};
-use self::utils::{new_string_path, string_path_set, check_string_path, fix_newlines};
-
-pub mod error;
-mod utils;
-mod settings;
-pub use self::settings::*;
-mod from_dir;
-
-pub trait RenderEngine {
- const PRODUCES_VALID_NEWLINES: bool;
- type Error: Fail;
- //any caching is done inside transparently
- fn render<D: Serialize>(&self, id: &str, data: &D) -> Result<String, Self::Error>;
-
-}
-
-#[derive(Debug)]
-pub struct RenderTemplateEngine<R: RenderEngine> {
- fix_newlines: bool,
- render_engine: R,
- id2spec: HashMap<String, TemplateSpec>,
-}
-
-impl<R> RenderTemplateEngine<R>
- where R: RenderEngine
-{
- pub fn new(render_engine: R) -> Self {
- RenderTemplateEngine {
- render_engine,
- id2spec: Default::default(),
- fix_newlines: !R::PRODUCES_VALID_NEWLINES,
- }
- }
-
- pub fn set_fix_newlines(&mut self, should_fix_newlines: bool) {
- self.fix_newlines = should_fix_newlines
- }
-
- pub fn does_fix_newlines(&self) -> bool {
- self.fix_newlines
- }
-
- pub fn add_spec(&mut self, id: String, spec: TemplateSpec) -> Option<TemplateSpec> {
- self.id2spec.insert(id, spec)
- }
-
- pub fn remove_spec(&mut self, id: &str) -> Option<TemplateSpec> {
- self.id2spec.remove(id)
- }
-
- pub fn specs(&self) -> &HashMap<String, TemplateSpec> {
- &self.id2spec
- }
-
-// pub fn specs_mut(&mut self) -> &mut HashMap<String, TemplateSpec> {
-// &mut self.specs()
-// }
-
- pub fn lookup_spec(&self, template_id: &str) -> Result<&TemplateSpec, Error<R::Error>> {
- self.id2spec
- .get(template_id)
- .ok_or_else(|| Error::from_unknown_template_id(template_id))
- }
-
- pub fn load_specs_from_dir<P>(
- &mut self,
- dir_path: P,
- settings: &LoadSpecSettings
- ) -> Result<(), LoadingSpecError>
- where P: AsRef<Path>
- {
- self._load_specs_from_dir(dir_path.as_ref(), settings, false)
- }
-
- pub fn load_specs_from_dir_allow_override<P>(
- &mut self,
- dir_path: P,
- settings: &LoadSpecSettings
- ) -> Result<(), LoadingSpecError>
- where P: AsRef<Path>
- {
- self._load_specs_from_dir(dir_path.as_ref(), settings, true)
- }
-
- fn _load_specs_from_dir(
- &mut self,
- dir_path: &Path,
- settings: &LoadSpecSettings,
- allow_override: bool
- ) -> Result<(), LoadingSpecError>
- {
- for entry in dir_path.read_dir()? {
- let entry = entry?;
- if entry.metadata()?.is_dir() {
- let id = entry.file_name()
- .into_string()
- .map_err(|file_name| LoadingSpecErrorVariant::NonStringPath(file_name.into()))?;
- let spec = TemplateSpec::from_dir(entry.path(), settings)?;
- let old = self.add_spec(id, spec);
- if old.is_some() && !allow_override {
- // we already know that the file name can be converted into a string
- let file_name = entry.file_name().into_string().unwrap();
- return Err(LoadingSpecErrorVariant::AccidentalSpecOverride { id: file_name }.into());
- }
- }
- }
- Ok(())
- }
-
-}
-
-impl<R, C> TemplateEngine<C> for RenderTemplateEngine<R>
- where R: RenderEngine, C: Context
-{
- type TemplateId = str;
- type Error = Error<R::Error>;
-
- fn use_templates<D: Serialize>(
- &self,
- ctx: &C,
- template_id: &str,
- data: &D
- ) -> Result<MailParts, Self::Error >
- {
- let spec = self.lookup_spec(template_id)?;
-
- //OPTIMIZE there should be a more efficient way
- // maybe use Rc<str> as keys? and Rc<Resource> for embeddings?
- let shared_embeddings = spec.embeddings().iter()
- .map(|(key, resource_spec)|
- create_embedding(key.to_owned(),resource_spec.clone(), ctx))
- .collect::<HashMap<_,_>>();
-
- let mut attachments = Vec::new();
- let bodies = spec.sub_specs().try_mapped_ref(|template| {
-
- let embeddings = template.embeddings.iter()
- .map(|(key, resource_spec)|
- create_embedding(key.to_owned(),resource_spec.clone(), ctx))
- .collect::<HashMap<_,_>>();
-
- let rendered = {
- // make CIds available to render engine
- let data = DataWrapper { data, cids: (&embeddings, &shared_embeddings) };
- let path = template.str_path();
- self.render_engine.render(&*path, &data)
- .map_err(|re| Error::RenderError(re))?
- };
-
- let rendered =
- if self.fix_newlines {
- fix_newlines(rendered)
- } else {
- rendered
- };
-
- let buffer = FileBuffer::new(template.media_type().clone(), rendered.into());
- let resource = Resource::sourceless_from_buffer(buffer);
-
- attachments.extend(template.attachments().iter()
- .map(|resource| Attachment::new(resource.clone())));
-
- Ok(BodyPart {
- body_resource: resource,
- embeddings: embeddings.into_iter().map(|(_,v)| v).collect()
- })
- })?;
-
- Ok(MailParts {
- alternative_bodies: bodies,
- shared_embeddings: shared_embeddings.into_iter().map(|(_, v)| v).collect(),
- attachments,
- })
- }
-}
-
-fn create_embedding<C>(key: String, resource: Resource, ctx: &C) -> (String, EmbeddingWithCId)
- where C: Context
-{
- let cid = ctx.generate_content_id();
- (key, EmbeddingWithCId::new(resource, cid))
-}
-
-
-#[derive(Debug)]
-pub struct TemplateSpec {
- /// the `base_path` which was used to construct the template from,
- /// e.g. with `TemplateSpec::from_dir` and which is used for reloading
- base_path: Option<PathBuf>,
- templates: Vec1<SubTemplateSpec>,
- /// template level embeddings, i.e. embeddings shared between alternative bodies
- embeddings: HashMap<String, Resource>
-}
-
-impl TemplateSpec {
-
- ///
- /// ```no_rust
- /// templates/
- /// templateA/
- /// html/
- /// mail.html
- /// emb_logo.png
- /// text/
- /// mail.text
- /// ```
- ///
- /// Note: the file name "this.is.a" is interprete as name "this" with suffix/type ".is.a"
- /// so it's cid gan be accessed with "cids.this"
- #[inline]
- pub fn from_dir<P>(base_path: P, settings: &LoadSpecSettings) -> Result<TemplateSpec, LoadingSpecError>
- where P: AsRef<Path>
- {
- from_dir::from_dir(base_path.as_ref(), settings)
- }
-
- pub fn new(templates: Vec1<SubTemplateSpec>) -> Self {
- Self::new_with_embeddings(templates, Default::default())
- }
-
- pub fn new_with_embeddings(
- templates: Vec1<SubTemplateSpec>,
- embeddings: HashMap<String, Resource>
- ) -> Self {
- TemplateSpec { base_path: None, templates, embeddings }
- }
-
- pub fn new_with_base_path<P>(templates: Vec1<SubTemplateSpec>, base_path: P)
- -> Result<Self, LoadingSpecError>
- where P: AsRef<Path>
- {
- Self::new_with_embeddings_and_base_path(
- templates, Default::default(), base_path.as_ref()
- )
- }
-
- pub fn new_with_embeddings_and_base_path<P>(
- templates: Vec1<SubTemplateSpec>,
- embeddings: HashMap<String, Resource>,
- base_path: P
- ) -> Result<Self, LoadingSpecError>
- where P: AsRef<Path>
- {
- let path = base_path.as_ref().to_owned();
- check_string_path(&*path)?;
- Ok(TemplateSpec { base_path: Some(path), templates, embeddings })
- }
-
- pub fn sub_specs(&self) -> &Vec1<SubTemplateSpec> {
- &self.templates
- }
-
- pub fn sub_specs_mut(&mut self) -> &mut Vec1<SubTemplateSpec> {
- &mut self.templates
- }
-
- pub fn embeddings(&self) -> &HashMap<String, Resource> {
- &self.embeddings
- }
-
- pub fn embeddings_mut(&mut self) -> &mut HashMap<String, Resource> {
- &mut self.embeddings
- }
-
-
- pub fn base_path(&self) -> Option<&Path> {
- self.base_path.as_ref().map(|r| &**r)
- }
-
- pub fn set_base_path<P>(&mut self, new_path: P) -> Result<Option<PathBuf>, LoadingSpecError>
- where P: AsRef<Path>
- {
- let path = new_path.as_ref();
- check_string_path(path)?;
- Ok(replace(&mut self.base_path, Some(path.to_owned())))
- }
-
-}
-
-#[derive(Debug)]
-pub struct SubTemplateSpec {
- media_type: MediaType,
- /// The path to the template file if it is a relative path it is
- /// used relative to the working directory
- path: String,
- // (Name, Resource) | name is used by the template engine e.g. log, and differs to
- // resource spec use_name which would
- // e.g. be logo.png but referring to the file long_logo_name.png
- embeddings: HashMap<String, Resource>,//todo use ordered map
- attachments: Vec<Resource>
-}
-
-impl SubTemplateSpec {
-
- //FIXME to many arguments alternatives: builder,
- // default values (embedding, attachment)+then setter,
- // default values + then with_... methods
- pub fn new<P>(path: P,
- media_type: MediaType,
- embeddings: HashMap<String, Resource>,
- attachments: Vec<Resource>
- ) -> Result<Self, LoadingSpecError>
- where P: AsRef<Path>
- {
- let path = new_string_path(path.as_ref())?;
- Ok(SubTemplateSpec { path, media_type, embeddings, attachments })
- }
-
- pub fn path(&self) -> &Path {
- Path::new(&self.path)
- }
-
- pub fn str_path(&self) -> &str {
- &self.path
- }
-
- pub fn set_path<P>(&mut self, new_path: P) -> Result<PathBuf, LoadingSpecError>
- where P: AsRef<Path>
- {
- string_path_set(&mut self.path, new_path.as_ref())
- }
-
- pub fn media_type(&self) -> &MediaType {
- &self.media_type
- }
-
- pub fn set_media_type(&mut self, media_type: MediaType) -> MediaType {
- //we might wan't to add restrictions at some point,e.g. no multipart