aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorThomas Letan <contact@thomasletan.fr>2019-08-14 14:38:56 +0200
committerThomas Letan <contact@thomasletan.fr>2019-08-14 14:38:56 +0200
commita33af1b18f27782273b078843c8f6d7d1250e194 (patch)
tree193b399b074d88cf837f5f6b0abe1a6b04937153
parentfeature: 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.rs306
-rw-r--r--src/main.rs27
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(())
}