diff options
author | Thomas Letan <contact@thomasletan.fr> | 2020-02-02 18:05:56 +0100 |
---|---|---|
committer | Thomas Letan <contact@thomasletan.fr> | 2020-02-02 18:05:56 +0100 |
commit | e2b6a8a20ee90eaed575495cfc0d0665227a3ed7 (patch) | |
tree | e61aa0f977cfd2ef2f3f3e96268ef0ffb5aa2a45 | |
parent | feature: List dependencies of a celtchar document (diff) |
refactor: Rework libceltchar organization
In this new iteration of libceltchar, there is now a very generic
`BookWriter` that the `EpubWriter` extends. This paves the way towards
a static website generator. In addition, we start using `rustfmt`,
which means the diff is actually a bit larger that what it shall be.
-rw-r--r-- | app/src/filesystem.rs | 74 | ||||
-rw-r--r-- | app/src/main.rs | 31 | ||||
-rw-r--r-- | lib/Cargo.toml | 2 | ||||
-rw-r--r-- | lib/src/assets.rs | 22 | ||||
-rw-r--r-- | lib/src/epub.rs | 153 | ||||
-rw-r--r-- | lib/src/error.rs | 6 | ||||
-rw-r--r-- | lib/src/lib.rs | 14 | ||||
-rw-r--r-- | lib/src/project.rs | 64 | ||||
-rw-r--r-- | lib/src/render.rs | 54 | ||||
-rw-r--r-- | lib/src/writer.rs | 24 | ||||
-rw-r--r-- | rustfmt.toml | 1 | ||||
-rw-r--r-- | templates/epub/chapter.xhtml (renamed from templates/chapter.xhtml) | 0 | ||||
-rw-r--r-- | templates/epub/container.xml (renamed from templates/container.xml) | 0 | ||||
-rw-r--r-- | templates/epub/content.opf (renamed from templates/content.opf) | 0 | ||||
-rw-r--r-- | templates/epub/main.css (renamed from templates/main.css) | 0 | ||||
-rw-r--r-- | templates/epub/toc.ncx (renamed from templates/toc.ncx) | 0 |
16 files changed, 228 insertions, 217 deletions
diff --git a/app/src/filesystem.rs b/app/src/filesystem.rs index 066e2d2..1ce55fd 100644 --- a/app/src/filesystem.rs +++ b/app/src/filesystem.rs @@ -1,15 +1,15 @@ -use std::path::{PathBuf}; -use std::fs; -use std::fs::{canonicalize}; use std::env::{current_dir, set_current_dir}; +use std::fs; +use std::fs::canonicalize; +use std::path::PathBuf; -use libceltchar::{Loader, Raise, Error, Project, Chapter, Cover}; +use libceltchar::{Chapter, Cover, Error, Loader, Project, Raise}; const PROJECT_FILE : &str = "Book.toml"; pub struct Fs; pub fn find_root() -> Result<PathBuf, Error> { - let mut cwd: PathBuf = current_dir().or_raise("cannot get current directory")?; + let mut cwd : PathBuf = current_dir().or_raise("cannot get current directory")?; loop { cwd.push(PROJECT_FILE); // (*) @@ -27,38 +27,42 @@ pub fn find_root() -> Result<PathBuf, Error> { // `pop` returns false, we are at the root of the current FS, and // there is no project file to find. if !cwd.pop() { - return Err(Error::new("could not find Book.toml")) + return Err(Error::new("could not find Book.toml")); } } } } -fn canonicalize_chapter( - chapter : &Chapter<Vec<PathBuf>> -) -> Result<Chapter<Vec<PathBuf>>, Error> { +fn canonicalize_chapter(chapter : &Chapter<Vec<PathBuf>>) -> Result<Chapter<Vec<PathBuf>>, Error> { let title = chapter.title.clone(); Ok(Chapter { - title: title, - content: chapter.content + title : title, + content : chapter + .content .iter() .map(|x| canonicalize(x).or_raise(&format!("Could not canonicalize {:?}", x))) - .collect::<Result<_, Error>>()? + .collect::<Result<_, Error>>()?, }) } fn canonicalize_project( - project : Project<PathBuf, Vec<PathBuf>> + project : Project<PathBuf, Vec<PathBuf>>, ) -> Result<Project<PathBuf, Vec<PathBuf>>, Error> { Ok(Project { - author: project.author, - title: project.title, - cover: project.cover.map(canonicalize) + author : project.author, + title : project.title, + cover : project + .cover + .map(canonicalize) .map_or(Ok(None), |r| r.map(Some)) .or_raise("…")?, - numbering: project.numbering, - chapters: project.chapters.iter().map(canonicalize_chapter) + numbering : project.numbering, + chapters : project + .chapters + .iter() + .map(canonicalize_chapter) .collect::<Result<_, Error>>()?, - language: project.language, + language : project.language, }) } @@ -67,49 +71,39 @@ impl Loader for Fs { type CovId = PathBuf; type DocId = PathBuf; - fn load_project( - &self, - id : &PathBuf - ) -> Result<Project<PathBuf, Vec<PathBuf>>, Error> { + fn load_project(&self, id : &PathBuf) -> Result<Project<PathBuf, Vec<PathBuf>>, Error> { let cwd = current_dir().or_raise("could not get current dir")?; - let input = fs::read_to_string(PROJECT_FILE) - .or_raise("found Book.toml, but cannot read it")?; + let input = + fs::read_to_string(PROJECT_FILE).or_raise("found Book.toml, but cannot read it")?; // We have to modify set the current directory to the PROJECT_FILE directory, // otherwise `canonicalize` will not work. set_current_dir(id).or_raise("could not change the current directory")?; let res = canonicalize_project( - toml::from_str(input.as_str()) - .or_raise(&format!("could not parse Book.toml"))? + toml::from_str(input.as_str()).or_raise(&format!("could not parse Book.toml"))?, )?; set_current_dir(cwd).or_raise("could not change the current directory")?; Ok(res) } - fn load_cover( - &self, - id : &PathBuf - ) -> Result<Cover, Error> { - let extension = id.extension() + fn load_cover(&self, id : &PathBuf) -> Result<Cover, Error> { + let extension = id + .extension() .or_raise("cover lacks an extension")? .to_str() .or_raise("cover extension is not valid utf-8")?; - let content = fs::read(id) - .or_raise(&format!("could not read cover from {:?}", id))?; + let content = fs::read(id).or_raise(&format!("could not read cover from {:?}", id))?; Ok(Cover { - extension: String::from(extension), - content: content, + extension : String::from(extension), + content : content, }) } - fn load_document( - &self, - id : &PathBuf - ) -> Result<String, Error> { + fn load_document(&self, id : &PathBuf) -> Result<String, Error> { fs::read_to_string(id).or_raise(&format!("Could not read {:?}", id)) } } diff --git a/app/src/main.rs b/app/src/main.rs index ea8fcbe..8b2f48e 100644 --- a/app/src/main.rs +++ b/app/src/main.rs @@ -1,20 +1,20 @@ extern crate clap; -extern crate serde_json; -extern crate toml; extern crate libceltchar; extern crate ogmarkup; +extern crate serde_json; extern crate tera; +extern crate toml; use std::path::PathBuf; use clap::{App, SubCommand}; -use libceltchar::{Loader, Error, Zip, Project, EpubWriter}; +use libceltchar::{EpubWriter, Error, Loader, Project, Zip}; #[cfg(debug_assertions)] -use std::env::current_dir; -#[cfg(debug_assertions)] use libceltchar::Raise; +#[cfg(debug_assertions)] +use std::env::current_dir; mod filesystem; use crate::filesystem::{find_root, Fs}; @@ -37,14 +37,14 @@ fn deps() -> Result<(), Error> { Ok(()) } -fn build(assets : &PathBuf) -> Result<(), Error> { +fn build_epub(assets : &PathBuf) -> Result<(), Error> { let root = find_root()?; let loader = Fs; let project = Project::load_and_render(&root, &loader)?; let mut zip_writer = Zip::init()?; - zip_writer.generate(&project, assets)?; + zip_writer.generate_epub(&project, assets)?; Ok(()) } @@ -64,22 +64,19 @@ fn main() -> Result<(), Error> { .version("0.1") .author("Thomas Letan") .about("A tool to generate novels") - .subcommand(SubCommand::with_name("new") - .about("Create a new celtchar document")) - .subcommand(SubCommand::with_name("build") - .about("Build a celtchar document")) - .subcommand(SubCommand::with_name("deps") - .about("List dependencies of a celtchar document")) + .subcommand(SubCommand::with_name("new").about("Create a new celtchar document")) + .subcommand(SubCommand::with_name("build").about("Build a celtchar document")) + .subcommand(SubCommand::with_name("deps").about("List dependencies of a celtchar document")) .get_matches(); let (subcommand, _args) = matches.subcommand(); - let assets: PathBuf = get_assets()?; + let assets : PathBuf = get_assets()?; match subcommand { - "build" => build(&assets)?, - "deps" => deps()?, - _ => unimplemented!(), + "build" => build_epub(&assets)?, + "deps" => deps()?, + _ => unimplemented!(), } Ok(()) diff --git a/lib/Cargo.toml b/lib/Cargo.toml index 3786d25..3f385ee 100644 --- a/lib/Cargo.toml +++ b/lib/Cargo.toml @@ -7,6 +7,6 @@ authors = ["Thomas Letan <contact@thomasletan.fr>"] serde = "1.0" serde_derive = "1.0" serde_json = "1.0" -tera = "0.11" +tera = "1.0" zip = "0.5" ogmarkup = { version = "0.1", git = "https://git.sr.ht/~lthms/ogmarkup" }
\ No newline at end of file diff --git a/lib/src/assets.rs b/lib/src/assets.rs new file mode 100644 index 0000000..af21d05 --- /dev/null +++ b/lib/src/assets.rs @@ -0,0 +1,22 @@ +use crate::error::{Error, Raise}; +use std::path::PathBuf; + +pub fn template_dir(assets : &PathBuf) -> Result<String, Error> { + let mut res = assets.clone(); + + res.push("templates"); + res.push("**"); + res.push("*"); + + res.to_str() + .map(String::from) + .or_raise("Compute template dir") +} + +pub fn fonts_dir(assets : &PathBuf) -> Result<PathBuf, Error> { + let mut res = assets.clone(); + + res.push("fonts"); + + Ok(res) +} diff --git a/lib/src/epub.rs b/lib/src/epub.rs index 518bdbf..8ef5363 100644 --- a/lib/src/epub.rs +++ b/lib/src/epub.rs @@ -3,44 +3,22 @@ use std::path::PathBuf; use serde_json::json; -use tera::{Tera, Context}; +use tera::{Context, Tera}; -use crate::error::{Raise, Error}; -use crate::project::{Project, Chapter, Cover, Language}; +use crate::error::{Error, Raise}; +use crate::project::{Chapter, Cover, Language, Project}; +use std::collections::HashSet; +use std::fs::File; use zip::write::FileOptions; use zip::ZipWriter; -use std::fs::File; -use std::collections::HashSet; -const EPUB_MIMETYPE: &'static str = "application/epub+zip"; +use crate::assets::{fonts_dir, template_dir}; +use crate::writer::BookWriter; -pub trait EpubWriter { - fn write_template( - &mut self, - dst : &PathBuf, - tera : & Tera, - template : &str, - ctx : &Context, - ) -> Result<(), Error> { - let content = tera.render(template, ctx) - .or_raise(&format!("cannot render {}", template))?; - - self.write_bytes(dst, content.as_bytes()) - } - - fn write_file( - &mut self, - dst : &PathBuf, - src : &PathBuf, - ) -> Result<(), Error>; - - fn write_bytes( - &mut self, - dst : &PathBuf, - input : &[u8] - ) -> Result<(), Error> ; +const EPUB_MIMETYPE : &'static str = "application/epub+zip"; +pub trait EpubWriter: BookWriter { fn create_mimetype(&mut self) -> Result<(), Error> { self.write_bytes(&PathBuf::from("mimetype"), EPUB_MIMETYPE.as_bytes()) } @@ -49,7 +27,7 @@ pub trait EpubWriter { self.write_template( &PathBuf::from("META-INF/container.xml"), tera, - "container.xml", + "epub/container.xml", &Context::default(), ) } @@ -61,7 +39,9 @@ pub trait EpubWriter { numbering : bool, lang : &Language, ) -> Result<(), Error> { - chapters.iter().enumerate() + chapters + .iter() + .enumerate() .map(|(idx, c)| { let mut ctx = Context::new(); ctx.insert("number", &(idx + 1)); @@ -74,7 +54,7 @@ pub trait EpubWriter { self.write_template( &PathBuf::from(format!("OEBPS/Text/{}", path)), tera, - "chapter.xhtml", + "epub/chapter.xhtml", &ctx, )?; @@ -101,19 +81,28 @@ pub trait EpubWriter { self.write_bytes(&dst, cover.content.as_slice()) } - fn generate(&mut self, project : &Project<Cover, String>, assets : &PathBuf) -> Result<(), Error> { - - let tera = compile_templates!(template_dir(assets)?.as_str()); + fn generate_epub( + &mut self, + project : &Project<Cover, String>, + assets : &PathBuf, + ) -> Result<(), Error> { + let tera = + Tera::new(template_dir(assets)?.as_str()).or_raise("Could not build templates")?; self.create_mimetype()?; self.create_container(&tera)?; - self.create_chapters(&tera, &project.chapters, project.numbering.unwrap_or(false), &project.language)?; + self.create_chapters( + &tera, + &project.chapters, + project.numbering.unwrap_or(false), + &project.language, + )?; self.write_template( &PathBuf::from("OEBPS/Style/main.css"), &tera, - "main.css", + "epub/main.css", &Context::new(), )?; @@ -129,14 +118,20 @@ pub trait EpubWriter { self.install_fonts(assets, &fonts)?; - let files = project.chapters.iter().enumerate() + 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", &project.cover.as_ref().map(|x| x.extension.clone())); + ctx.insert( + "cover_extension", + &project.cover.as_ref().map(|x| x.extension.clone()), + ); ctx.insert("files", &files); ctx.insert("fonts", &fonts); ctx.insert("language", &project.language); @@ -144,47 +139,31 @@ pub trait EpubWriter { self.write_template( &PathBuf::from("OEBPS/content.opf"), &tera, - "content.opf", + "epub/content.opf", &ctx, )?; - let chaps: Vec<_> = project.chapters.iter().enumerate() - .map(|(idx, chapter)| json!({ - "index": idx, - "title": chapter.title, - })) + 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, - )?; + self.write_template(&PathBuf::from("OEBPS/toc.ncx"), &tera, "epub/toc.ncx", &ctx)?; Ok(()) } } -fn template_dir(assets : &PathBuf) -> Result<String, Error> { - let mut res = assets.clone(); - - res.push("templates"); - res.push("**"); - res.push("*"); - - res.to_str().map(String::from).ok_or(Error(format!("Compute template dir"))) -} - -fn fonts_dir(assets : &PathBuf) -> Result<PathBuf, Error> { - let mut res = assets.clone(); - - res.push("fonts"); - - Ok(res) -} +impl<W> EpubWriter for W where W : BookWriter {} pub struct Zip { output : ZipWriter<File>, @@ -203,8 +182,9 @@ impl Zip { fn create_parent(&mut self, dst : &PathBuf) -> Result<(), Error> { if let Some(dir) = dst.parent() { - if self.dirs.contains(dir) { - self.output.add_directory_from_path(dir, FileOptions::default()) + if self.dirs.contains(dir) { + self.output + .add_directory_from_path(dir, FileOptions::default()) .or_raise(&format!("Could not create directory {:?}", dir))?; self.dirs.insert(dir.to_path_buf()); } @@ -214,38 +194,35 @@ impl Zip { } } -impl EpubWriter for Zip { - fn write_bytes( - &mut self, - dst : &PathBuf, - input : &[u8] - ) -> Result<(), Error> { +impl BookWriter for Zip { + fn write_bytes(&mut self, dst : &PathBuf, input : &[u8]) -> Result<(), Error> { self.create_parent(dst)?; - self.output.start_file_from_path(dst, FileOptions::default()) + self.output + .start_file_from_path(dst, FileOptions::default()) .or_raise(&format!("Could not add file {:?} to archive", dst))?; - self.output.write_all(input) + self.output + .write_all(input) .or_raise(&format!("Could not write {:?} content", dst))?; Ok(()) } - fn write_file( - &mut self, - dst : &PathBuf, - src : &PathBuf, - ) -> Result<(), Error> { + fn write_file(&mut self, dst : &PathBuf, src : &PathBuf) -> Result<(), Error> { let mut buffer = Vec::new(); let mut f = File::open(src).or_raise(&format!("Could not open {:?}", src))?; - f.read_to_end(&mut buffer).or_raise(&format!("Could not read {:?} content", src))?; + f.read_to_end(&mut buffer) + .or_raise(&format!("Could not read {:?} content", src))?; self.create_parent(dst)?; - self.output.start_file_from_path(dst, FileOptions::default()) + self.output + .start_file_from_path(dst, FileOptions::default()) .or_raise(&format!("Could not add file {:?} to archive", dst))?; - self.output.write_all(buffer.as_ref()) + self.output + .write_all(buffer.as_ref()) .or_raise(&format!("Could not write {:?} content", dst))?; Ok(()) diff --git a/lib/src/error.rs b/lib/src/error.rs index d92077e..b8fbf11 100644 --- a/lib/src/error.rs +++ b/lib/src/error.rs @@ -10,13 +10,13 @@ impl Error { pub trait Raise { type Out; - fn or_raise(self, msg: &str) -> Self::Out; + fn or_raise(self, msg : &str) -> Self::Out; } impl<T> Raise for Option<T> { type Out = Result<T, Error>; - fn or_raise(self, msg: &str) -> Result<T, Error> { + fn or_raise(self, msg : &str) -> Result<T, Error> { self.ok_or(Error(String::from(msg))) } } @@ -24,7 +24,7 @@ impl<T> Raise for Option<T> { impl<T, E> Raise for Result<T, E> { type Out = Result<T, Error>; - fn or_raise(self, msg: &str) -> Result<T, Error> { + fn or_raise(self, msg : &str) -> Result<T, Error> { self.map_err(|_| Error(String::from(msg))) } } diff --git a/lib/src/lib.rs b/lib/src/lib.rs index e0a9644..9859f0d 100644 --- a/lib/src/lib.rs +++ b/lib/src/lib.rs @@ -1,14 +1,16 @@ extern crate ogmarkup; -#[macro_use] extern crate tera; -extern crate zip; extern crate serde_derive; extern crate serde_json; +extern crate tera; +extern crate zip; -mod render; +mod assets; +mod epub; mod error; mod project; -mod epub; +mod render; +mod writer; -pub use error::{Error, Raise}; -pub use project::{Project, Chapter, Cover, Loader}; pub use epub::{EpubWriter, Zip}; +pub use error::{Error, Raise}; +pub use project::{Chapter, Cover, Loader, Project}; diff --git a/lib/src/project.rs b/lib/src/project.rs index c3af473..0a00ab2 100644 --- a/lib/src/project.rs +++ b/lib/src/project.rs @@ -1,8 +1,8 @@ -use ogmarkup::typography::{Typography, FRENCH, ENGLISH}; +use ogmarkup::typography::{Typography, ENGLISH, FRENCH}; use serde_derive::{Deserialize, Serialize}; +use crate::error::{Error, Raise}; use crate::render::Html; -use crate::error::{Raise, Error}; #[derive(Debug, Serialize, Deserialize)] pub enum Language { @@ -30,19 +30,13 @@ pub trait Loader { type DocId; type ProjId; - fn load_cover( - &self, - id : &Self::CovId - ) -> Result<Cover, Error>; + fn load_cover(&self, id : &Self::CovId) -> Result<Cover, Error>; - fn load_document( - &self, - id : &Self::DocId - ) -> Result<String, Error>; + fn load_document(&self, id : &Self::DocId) -> Result<String, Error>; fn load_project( &self, - id : &Self::ProjId + id : &Self::ProjId, ) -> Result<Project<Self::CovId, Vec<Self::DocId>>, Error>; } @@ -53,19 +47,16 @@ pub struct Chapter<I> { } impl<I> Chapter<Vec<I>> { - fn load_and_render<T, L>( - &self, - loader : &L, - typo : &T, - ) -> Result<Chapter<String>, Error> + fn load_and_render<T, L>(&self, loader : &L, typo : &T) -> Result<Chapter<String>, Error> where T : Typography + ?Sized, - L : Loader<DocId = I> + L : Loader<DocId = I>, { let title = &self.title; let content = &self.content; - let doc = content.iter() + let doc = content + .iter() .map(|ref x| { let input = loader.load_document(x)?; ogmarkup::compile(&input, typo) @@ -76,24 +67,24 @@ impl<I> Chapter<Vec<I>> { .join(""); Ok(Chapter { - title: title.clone(), - content: doc, + title : title.clone(), + content : doc, }) } } #[derive(Debug, Serialize, Deserialize)] pub struct Project<C, I> { - pub author: String, - pub title: String, - pub chapters: Vec<Chapter<I>>, - pub cover: Option<C>, - pub numbering: Option<bool>, - pub language: Language, + pub author : String, + pub title : String, + pub chapters : Vec<Chapter<I>>, + pub cover : Option<C>, + pub numbering : Option<bool>, + pub language : Language, } impl Project<Cover, String> { - pub fn load_and_render<'input, L> ( + pub fn load_and_render<'input, L>( id : &L::ProjId, loader : &L, ) -> Result<Project<Cover, String>, Error> @@ -107,20 +98,23 @@ impl Project<Cover, String> { let numbering = project.numbering; let author = project.author; let title = project.title; - let cover = project.cover + let cover = project + .cover .map(|x| loader.load_cover(&x).or_raise("cannot load the cover")) .map_or(Ok(None), |r| r.map(Some))?; - project.chapters.into_iter() + project + .chapters + .into_iter() .map(|chapter| chapter.load_and_render(loader, typo)) .collect::<Result<Vec<Chapter<String>>, Error>>() .map(|x| Project { - author: author, - title: title, - chapters: x, - cover: cover, - numbering: numbering, - language: lang, + author : author, + title : title, + chapters : x, + cover : cover, + numbering : numbering, + language : lang, }) } } diff --git a/lib/src/render.rs b/lib/src/render.rs index 3e737b0..5548d2d 100644 --- a/lib/src/render.rs +++ b/lib/src/render.rs @@ -1,10 +1,10 @@ -use ogmarkup::typography::Space; use ogmarkup::generator::Output; +use ogmarkup::typography::Space; pub struct Html(String); impl Html { - fn push_str(&mut self, s: &str) -> () { + fn push_str(&mut self, s : &str) -> () { self.0.push_str(s); } @@ -14,11 +14,11 @@ impl Html { } impl Output for Html { - fn empty(input_size: usize) -> Html { + fn empty(input_size : usize) -> Html { Html(String::with_capacity((15 * input_size) / 10)) } - fn render_space(&mut self, space: Space) -> () { + fn render_space(&mut self, space : Space) -> () { self.push_str(match space { Space::Normal => " ", Space::Nbsp => " ", @@ -26,48 +26,48 @@ impl Output for Html { }) } - fn render_word(&mut self, word: & str) -> () { + fn render_word(&mut self, word : &str) -> () { self.push_str(word) } - fn render_mark(&mut self, mark: &str) -> () { + fn render_mark(&mut self, mark : &str) -> () { self.push_str(mark) } - fn render_illformed(&mut self, err: &str) -> () { + fn render_illformed(&mut self, err : &str) -> () { self.push_str(err) } - fn emph_template<F>(&mut self, format: F) -> () + fn emph_template<F>(&mut self, format : F) -> () where - F : FnOnce(&mut Html) -> () + F : FnOnce(&mut Html) -> (), { self.push_str("<em>"); format(self); self.push_str("</em>"); } - fn strong_emph_template<F>(&mut self, format: F) -> () + fn strong_emph_template<F>(&mut self, format : F) -> () where - F : FnOnce(&mut Html) -> () + F : FnOnce(&mut Html) -> (), { self.push_str("<strong>"); format(self); self.push_str("</strong>"); } - fn reply_template<F>(&mut self, reply: F, _author: &Option<&str>) -> () + fn reply_template<F>(&mut self, reply : F, _author : &Option<&str>) -> () where - F : FnOnce(&mut Html) -> () + F : FnOnce(&mut Html) -> (), { self.push_str("<span class=\"reply\">"); reply(self); self.push_str("</span>"); } - fn thought_template<F>(&mut self, reply: F, author: &Option<&str>) -> () + fn thought_template<F>(&mut self, reply : F, author : &Option<&str>) -> () where - F : FnOnce(&mut Html) -> () + F : FnOnce(&mut Html) -> (), { self.push_str("<span class=\"thought"); author.map(|a| { @@ -79,9 +79,9 @@ impl Output for Html { self.push_str("</span>"); } - fn dialogue_template<F>(&mut self, reply: F, author: &Option<&str>) -> () + fn dialogue_template<F>(&mut self, reply : F, author : &Option<&str>) -> () where - F : FnOnce(&mut Html) -> () + F : FnOnce(&mut Html) -> (), { self.push_str("<span class=\"dialogue"); author.map(|a| { @@ -97,45 +97,45 @@ impl Output for Html { self.push_str("</p><p>"); } - fn illformed_inline_template<F>(&mut self, err: F) -> () + fn illformed_inline_template<F>(&mut self, err : F) -> () where - F: FnOnce(&mut Html) -> () + F : FnOnce(&mut Html) -> (), { self.push_str("<span class=\"illformed_inline\">"); err(self); self.push_str("</span>"); } - fn paragraph_template<F>(&mut self, para: F) -> () + fn paragraph_template<F>(&mut self, para : F) -> () where - F : FnOnce(&mut Html) -> () + F : FnOnce(&mut Html) -> (), { self.push_str("<p>"); para(self); self.push_str("</p>"); } - fn illformed_block_template<F>(&mut self, err: F) -> () + fn illformed_block_template<F>(&mut self, err : F) -> () where - F : FnOnce(&mut Html) -> () + F : FnOnce(&mut Html) -> (), { self.push_str("<div class=\"illformed_block\">"); err(self); self.push_str("</div>"); } - fn story_template<F>(&mut self, story: F) -> () + fn story_template<F>(&mut self, story : F) -> () where - F : FnOnce(&mut Html) -> () + F : FnOnce(&mut Html) -> (), { self.push_str("<div class=\"story\">"); story(self); self.push_str("</div>"); } - fn aside_template<F>(&mut self, cls: &Option<&str>, aside: F) -> () + fn aside_template<F>(&mut self, cls : &Option<&str>, aside : F) -> () where - F : FnOnce(&mut Html) -> () + F : FnOnce(&mut Html) -> (), { self.push_str("<div class=\"aside"); cls.map(|c| { diff --git a/lib/src/writer.rs b/lib/src/writer.rs new file mode 100644 index 0000000..60d6cdc --- /dev/null +++ b/lib/src/writer.rs @@ -0,0 +1,24 @@ +use std::path::PathBuf; +use tera::{Context, Tera}; + +use crate::error::{Error, Raise}; + +pub trait BookWriter { + fn write_file(&mut self, dst : &PathBuf, src : &PathBuf) -> Result<(), Error>; + + fn write_bytes(&mut self, dst : &PathBuf, input : &[u8]) -> Result<(), Error>; + + fn write_template( + &mut self, + dst : &PathBuf, + tera : &Tera, + template : &str, + ctx : &Context, + ) -> Result<(), Error> { + let content = tera + .render(template, ctx) + .or_raise(&format!("cannot render {}", template))?; + + self.write_bytes(dst, content.as_bytes()) + } +} diff --git a/rustfmt.toml b/rustfmt.toml new file mode 100644 index 0000000..98ad867 --- /dev/null +++ b/rustfmt.toml @@ -0,0 +1 @@ +space_before_colon = true
\ No newline at end of file diff --git a/templates/chapter.xhtml b/templates/epub/chapter.xhtml index cbbc9da..cbbc9da 100644 --- a/templates/chapter.xhtml +++ b/templates/epub/chapter.xhtml diff --git a/templates/container.xml b/templates/epub/container.xml index bad9982..bad9982 100644 --- a/templates/container.xml +++ b/templates/epub/container.xml diff --git a/templates/content.opf b/templates/epub/content.opf index 5160fae..5160fae 100644 --- a/templates/content.opf +++ b/templates/epub/content.opf diff --git a/templates/main.css b/templates/epub/main.css index 22903aa..22903aa 100644 --- a/templates/main.css +++ b/templates/epub/main.css diff --git a/templates/toc.ncx b/templates/epub/toc.ncx index 1f86972..1f86972 100644 --- a/templates/toc.ncx +++ b/templates/epub/toc.ncx |