diff options
author | Thomas Letan <contact@thomasletan.fr> | 2019-08-14 14:38:56 +0200 |
---|---|---|
committer | Thomas Letan <contact@thomasletan.fr> | 2019-08-14 14:38:56 +0200 |
commit | a33af1b18f27782273b078843c8f6d7d1250e194 (patch) | |
tree | 193b399b074d88cf837f5f6b0abe1a6b04937153 | |
parent | feature: In release mode, look for assets in `/usr/local' (diff) |
refactor: Introduce the [EpubWriter] trait
The purpose of this trait is to abstract the medium onto which the
epub is generated. Previously, the use of the filesystem was
hard-coded. With this patch, we add an abstraction level which will
eventually allows us from using the same code to generate a zip
archive or an in-file-system directory.
-rw-r--r-- | src/epub.rs | 306 | ||||
-rw-r--r-- | src/main.rs | 27 |
2 files changed, 197 insertions, 136 deletions
diff --git a/src/epub.rs b/src/epub.rs index db910e0..15167c1 100644 --- a/src/epub.rs +++ b/src/epub.rs @@ -1,6 +1,7 @@ use std::fs; -use std::fs::{create_dir_all}; +use std::fs::{create_dir, remove_dir_all, create_dir_all}; use std::path::{Path, PathBuf}; +use std::env::set_current_dir; use tera::{Tera, Context}; use serde_json::json; @@ -10,62 +11,153 @@ use crate::project::{Project, Chapter}; const EPUB_MIMETYPE: &'static str = "application/epub+zip"; -fn write_template_to(tera : &Tera, template : &str, ctx : &Context, path : &PathBuf) -> Result<(), Error> { - let directory : &Path = path.parent().ok_or(Error(String::from("is not a file")))?; +pub trait EpubWriter { + fn write_template( + &mut self, + dst : &PathBuf, + tera : & Tera, + template : &str, + ctx : &Context, + ) -> Result<(), Error> ; + + fn write_file( + &mut self, + dst : &PathBuf, + src : &PathBuf, + ) -> Result<(), Error> ; + + fn write_str( + &mut self, + dst : &PathBuf, + input : &str + ) -> Result<(), Error> ; +} - if !directory.exists() { - create_dir_all(directory) - .or_raise(&format!("cannot create directory {:?}", directory))?; +impl EpubWriter { + fn create_mimetype(&mut self) -> Result<(), Error> { + self.write_str(&PathBuf::from("mimetype"), EPUB_MIMETYPE) } - let content = tera.render(template, ctx) - .or_raise(&format!("cannot render {}", template))?; + fn create_container(&mut self, tera : &Tera) -> Result<(), Error> { + self.write_template( + &PathBuf::from("META-INF/container.xml"), + tera, + "container.xml", + &Context::default(), + ) + } - fs::write(path, content).or_raise(&format!("cannot create {:?}", path))?; + fn create_chapters(&mut self, tera : &Tera, chapters : &Vec<Chapter<String>>) -> Result<(), Error> { + chapters.iter().enumerate() + .map(|(idx, c)| { + let mut ctx = Context::new(); + ctx.insert("number", &(idx + 1)); + ctx.insert("chapter", &c); - Ok(()) -} + let path : String = format!("{}.xhtml", idx); -fn create_mimetype() -> Result<(), Error> { - fs::write("mimetype", EPUB_MIMETYPE).or_raise("cannot create mimetype")?; + self.write_template( + &PathBuf::from(format!("OEBPS/Text/{}", path)), + tera, + "chapter.xhtml", + &ctx, + )?; - Ok(()) -} + Ok(path) + }) + .collect::<Result<Vec<String>, Error>>()?; -fn create_container(tera : &Tera) -> Result<(), Error> { - write_template_to( - tera, - "container.xml", - &Context::default(), - &PathBuf::from("META-INF/container.xml") - )?; + Ok(()) + } + fn install_fonts(&mut self, assets : &PathBuf, fonts : &Vec<&str>) -> Result<(), Error> { + for f in fonts { + let src = fonts_dir(assets)?.join(f); + let dst = PathBuf::from("OEBPS/Fonts").join(f); - Ok(()) -} + self.write_file(&dst, &src)?; + } -fn create_chapters(tera : &Tera, chapters : &Vec<Chapter<String>>) -> Result<(), Error> { + Ok(()) + } - chapters.iter().enumerate() - .map(|(idx, c)| { - let mut ctx = Context::new(); - ctx.insert("number", &(idx + 1)); - ctx.insert("chapter", &c); + fn install_cover(&mut self, cover : &PathBuf) -> Result<String, Error> { + let extension = cover.extension() + .or_raise("cover lacks an extension")? + .to_str() + .or_raise("cover extension is not valid utf-8")?; - let path : String = format!("{}.xhtml", idx); + let dst = PathBuf::from("OEBPS").join(format!("cover.{}", extension)); - write_template_to( - tera, - "chapter.xhtml", - &ctx, - &PathBuf::from(format!("OEBPS/Text/{}", path)) - )?; + self.write_file(&dst, cover)?; - Ok(path) - }) - .collect::<Result<Vec<String>, Error>>()?; + Ok(extension.into()) + } - Ok(()) + pub fn generate(&mut self, project : &Project<String>, assets : &PathBuf) -> Result<(), Error> { + + let tera = compile_templates!(template_dir(assets)?.as_str()); + + self.create_mimetype()?; + self.create_container(&tera)?; + + self.create_chapters(&tera, &project.chapters)?; + + self.write_template( + &PathBuf::from("OEBPS/Style/main.css"), + &tera, + "main.css", + &Context::new(), + )?; + + let cover_extension = project.cover.clone().map(|cov| self.install_cover(&cov)) + // from Option<Result<_, E>> to Result<Option<_>, E> + .map_or(Ok(None), |r| r.map(Some))?; + + let fonts = vec![ + "et-book-roman-line-figures.ttf", + "et-book-bold-line-figures.ttf", + "et-book-display-italic-old-style-figures.ttf", + ]; + + self.install_fonts(assets, &fonts)?; + + let files = project.chapters.iter().enumerate() + .map(|(idx, _)| idx) + .collect::<Vec<usize>>(); + + let mut ctx = Context::new(); + ctx.insert("title", &project.title); + ctx.insert("author", &project.author); + ctx.insert("cover_extension", &cover_extension); + ctx.insert("files", &files); + ctx.insert("fonts", &fonts); + + self.write_template( + &PathBuf::from("OEBPS/content.opf"), + &tera, + "content.opf", + &ctx, + )?; + + let chaps: Vec<_> = project.chapters.iter().enumerate() + .map(|(idx, chapter)| json!({ + "index": idx, + "title": chapter.title, + })) + .collect(); + + let mut ctx = Context::new(); + ctx.insert("chapters", &chaps); + self.write_template( + &PathBuf::from("OEBPS/toc.ncx"), + &tera, + "toc.ncx", + &ctx, + )?; + + Ok(()) + } } fn template_dir(assets : &PathBuf) -> Result<String, Error> { @@ -86,92 +178,68 @@ fn fonts_dir(assets : &PathBuf) -> Result<PathBuf, Error> { Ok(res) } -fn install_fonts(assets : &PathBuf, fonts : &Vec<&str>) -> Result<(), Error> { - create_dir_all("OEBPS/Fonts/").or_raise("cannot create directory OEBPS/Fonts/")?; +pub struct Fs; - for f in fonts { - let src = fonts_dir(assets)?.join(f); - let dst = PathBuf::from("OEBPS/Fonts").join(f); +const BUILD_DIR : &'static str = "_build"; - fs::copy(src, dst).or_raise(&format!("cannot copy {}", f))?; +impl Fs { + pub fn init() -> Result<Fs, Error> { + remove_dir_all(BUILD_DIR).or_raise("cannot clean up _build/")?; + create_dir(BUILD_DIR).or_raise("cannot create _build/")?; + set_current_dir(BUILD_DIR).or_raise("cannot set current directory to _build/")?; + + Ok(Fs) } - Ok(()) + fn create_parent(&mut self, dst : &PathBuf) -> Result<(), Error> { + let directory : &Path = dst.parent().ok_or(Error(String::from("is not a file")))?; + + if !directory.exists() { + create_dir_all(directory) + .or_raise(&format!("cannot create directory {:?}", directory))?; + } + + Ok(()) + } } -fn install_cover(cover : &PathBuf) -> Result<String, Error> { - let extension = cover.extension() - .or_raise("cover lacks an extension")? - .to_str() - .or_raise("cover extension is not valid utf-8")?; +impl EpubWriter for Fs { + fn write_template( + &mut self, + dst : &PathBuf, + tera : & Tera, + template : &str, + ctx : &Context, + ) -> Result<(), Error> { + self.create_parent(dst)?; - let dst = PathBuf::from("OEBPS").join(format!("cover.{}", extension)); + let content = tera.render(template, ctx) + .or_raise(&format!("cannot render {}", template))?; - fs::copy(cover, dst).or_raise(&format!("cannot copy {:?}", cover))?; + fs::write(dst, content).or_raise(&format!("cannot create {:?}", dst))?; - Ok(extension.into()) -} + Ok(()) + } + + fn write_str( + &mut self, + dst : &PathBuf, + input : &str + ) -> Result<(), Error> { + self.create_parent(dst)?; -pub fn generate(project : &Project<String>, assets : &PathBuf) -> Result<(), Error> { - - let tera = compile_templates!(template_dir(assets)?.as_str()); - - create_mimetype()?; - create_container(&tera)?; - - create_chapters(&tera, &project.chapters)?; - - write_template_to( - &tera, - "main.css", - &Context::new(), - &PathBuf::from("OEBPS/Style/main.css") - )?; - - let cover_extension = project.cover.clone().map(|cov| install_cover(&cov)) - // from Option<Result<_, E>> to Result<Option<_>, E> - .map_or(Ok(None), |r| r.map(Some))?; - - let fonts = vec![ - "et-book-roman-line-figures.ttf", - "et-book-bold-line-figures.ttf", - "et-book-display-italic-old-style-figures.ttf", - ]; - - install_fonts(assets, &fonts)?; - - let files = project.chapters.iter().enumerate() - .map(|(idx, _)| idx) - .collect::<Vec<usize>>(); - - let mut ctx = Context::new(); - ctx.insert("title", &project.title); - ctx.insert("author", &project.author); - ctx.insert("cover_extension", &cover_extension); - ctx.insert("files", &files); - ctx.insert("fonts", &fonts); - write_template_to( - &tera, - "content.opf", - &ctx, - &PathBuf::from("OEBPS/content.opf") - )?; - - let chaps: Vec<_> = project.chapters.iter().enumerate() - .map(|(idx, chapter)| json!({ - "index": idx, - "title": chapter.title, - })) - .collect(); - - let mut ctx = Context::new(); - ctx.insert("chapters", &chaps); - write_template_to( - &tera, - "toc.ncx", - &ctx, - &PathBuf::from("OEBPS/toc.ncx") - )?; - - Ok(()) + fs::write(dst, input).or_raise(&format!("cannot create {:?}", dst)) + } + + fn write_file( + &mut self, + dst : &PathBuf, + src : &PathBuf, + ) -> Result<(), Error> { + self.create_parent(dst)?; + + fs::copy(src, dst).or_raise(&format!("cannot copy {:?} to {:?}", src, dst))?; + + Ok(()) + } } diff --git a/src/main.rs b/src/main.rs index 0e7ebca..63f80c3 100644 --- a/src/main.rs +++ b/src/main.rs @@ -13,26 +13,20 @@ pub mod render; pub mod project; pub mod epub; -use std::env::{current_dir, set_current_dir}; -use std::fs::{create_dir, remove_dir_all}; use std::path::PathBuf; +#[cfg(debug_assertions)] +use std::env::current_dir; +#[cfg(debug_assertions)] +use error::Raise; + use ogmarkup::typography::FRENCH; -use error::{Raise, Error}; +use error::Error; use project::Project; +use epub::Fs; -const BUILD_DIR : &'static str = "_build"; - -fn cd_clean_build_dir() -> Result<(), Error> { - remove_dir_all(BUILD_DIR).or_raise("cannot clean up _build/")?; - - create_dir(BUILD_DIR).or_raise("cannot create _build/")?; - - set_current_dir(BUILD_DIR).or_raise("cannot set current directory to _build/")?; - - Ok(()) -} +use epub::EpubWriter; pub fn build(assets : &PathBuf) -> Result<(), Error> { Project::cd_root()?; @@ -40,9 +34,8 @@ pub fn build(assets : &PathBuf) -> Result<(), Error> { let project = Project::find_project()? .load_and_render(&FRENCH)?; - cd_clean_build_dir()?; - - epub::generate(&project, assets)?; + let mut fs_writer = Fs::init()?; + EpubWriter::generate(&mut fs_writer, &project, assets)?; Ok(()) } |