1
0
Fork 0
mirror of https://github.com/lise-henry/crowbook synced 2024-05-23 21:36:15 +02:00

Replace mustache with upon (wip)

This compiles but probably wrecks everything since the template syntax
is a bit different
This commit is contained in:
Lise Henry 2023-08-09 13:30:07 +02:00
parent 86f7cd5ea9
commit fe8d98ccd7
14 changed files with 364 additions and 457 deletions

12
Cargo.lock generated
View File

@ -361,6 +361,7 @@ dependencies = [
"syntect", "syntect",
"tempfile", "tempfile",
"textwrap", "textwrap",
"upon",
"uuid", "uuid",
"walkdir 2.3.3", "walkdir 2.3.3",
"yaml-rust", "yaml-rust",
@ -1642,6 +1643,17 @@ version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "39ec24b3121d976906ece63c9daad25b85969647682eee313cb5779fdd69e14e" checksum = "39ec24b3121d976906ece63c9daad25b85969647682eee313cb5779fdd69e14e"
[[package]]
name = "upon"
version = "0.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "21a9260fe394dfd8ab204a8eab40f88eb9a331bb852147d24fc0aff6b30daa02"
dependencies = [
"serde",
"unicode-ident",
"unicode-width",
]
[[package]] [[package]]
name = "utf8-ranges" name = "utf8-ranges"
version = "1.0.5" version = "1.0.5"

View File

@ -51,6 +51,7 @@ html-escape = "0.2"
mime_guess = "2" mime_guess = "2"
comrak = "0.18" comrak = "0.18"
yaml-rust = "0.4" yaml-rust = "0.4"
upon = "0.7"
mustache = "0.9" mustache = "0.9"
uuid = { version = "1", features = ["v4"] } uuid = { version = "1", features = ["v4"] }
walkdir = "2" walkdir = "2"

View File

@ -22,11 +22,11 @@ use crate::chapter::Chapter;
use crate::cleaner::{Cleaner, CleanerParams, Default, French, Off}; use crate::cleaner::{Cleaner, CleanerParams, Default, French, Off};
use crate::epub::Epub; use crate::epub::Epub;
use crate::error::{Error, Result, Source}; use crate::error::{Error, Result, Source};
use crate::html_dir::{HtmlDir, ProofHtmlDir}; use crate::html_dir::HtmlDir;
use crate::html_if::HtmlIf; use crate::html_if::HtmlIf;
use crate::html_single::{HtmlSingle, ProofHtmlSingle}; use crate::html_single::HtmlSingle;
use crate::lang; use crate::lang;
use crate::latex::{Latex, Pdf, ProofLatex, ProofPdf}; use crate::latex::{Latex, Pdf};
use crate::misc; use crate::misc;
use crate::number::Number; use crate::number::Number;
use crate::parser::Features; use crate::parser::Features;
@ -38,14 +38,14 @@ use crate::token::Token;
use std::borrow::Cow; use std::borrow::Cow;
use std::cmp::Ordering; use std::cmp::Ordering;
use std::collections::HashMap; use std::collections::{HashMap, BTreeMap};
use std::fmt; use std::fmt;
use std::fs::File; use std::fs::File;
use std::io::{Read, Write}; use std::io::{Read, Write};
use std::iter::IntoIterator; use std::iter::IntoIterator;
use std::path::{Path, PathBuf}; use std::path::{Path, PathBuf};
use mustache::{MapBuilder, Template}; use mustache::{Template};
use numerals::roman::Roman; use numerals::roman::Roman;
use rayon::prelude::*; use rayon::prelude::*;
use yaml_rust::{Yaml, YamlLoader}; use yaml_rust::{Yaml, YamlLoader};
@ -120,7 +120,7 @@ impl fmt::Display for HeaderData {
/// // Render the book as html to stdout /// // Render the book as html to stdout
/// book.render_format_to("html", &mut std::io::stdout()).unwrap(); /// book.render_format_to("html", &mut std::io::stdout()).unwrap();
/// ``` /// ```
pub struct Book { pub struct Book<'a> {
/// Internal structure. You should not accesss this directly except if /// Internal structure. You should not accesss this directly except if
/// you are writing a new renderer. /// you are writing a new renderer.
pub chapters: Vec<Chapter>, pub chapters: Vec<Chapter>,
@ -147,11 +147,14 @@ pub struct Book {
#[doc(hidden)] #[doc(hidden)]
pub bars: Bars, pub bars: Bars,
/// Store the templates registry
pub registry: upon::Engine<'a>,
} }
impl Book { impl<'a> Book<'a> {
/// Creates a new, empty `Book` /// Creates a new, empty `Book`
pub fn new() -> Book { pub fn new() -> Book<'a> {
let mut book = Book { let mut book = Book {
source: Source::empty(), source: Source::empty(),
chapters: vec![], chapters: vec![],
@ -163,47 +166,26 @@ impl Book {
formats: HashMap::new(), formats: HashMap::new(),
features: Features::new(), features: Features::new(),
bars: Bars::new(), bars: Bars::new(),
registry: upon::Engine::new(),
}; };
book.add_format( book.add_format(
"html", "html",
lformat!("HTML (standalone page)"), lformat!("HTML (standalone page)"),
Box::new(HtmlSingle {}), Box::new(HtmlSingle {}),
) )
.add_format(
"proofread.html",
lformat!("HTML (standalone page/proofreading)"),
Box::new(ProofHtmlSingle {}),
)
.add_format( .add_format(
"html.dir", "html.dir",
lformat!("HTML (multiple pages)"), lformat!("HTML (multiple pages)"),
Box::new(HtmlDir {}), Box::new(HtmlDir {}),
) )
.add_format(
"proofread.html.dir",
lformat!("HTML (multiple pages/proofreading)"),
Box::new(ProofHtmlDir {}),
)
.add_format("tex", lformat!("LaTeX"), Box::new(Latex {})) .add_format("tex", lformat!("LaTeX"), Box::new(Latex {}))
.add_format(
"proofread.tex",
lformat!("LaTeX (proofreading)"),
Box::new(ProofLatex {}),
)
.add_format("pdf", lformat!("PDF"), Box::new(Pdf {})) .add_format("pdf", lformat!("PDF"), Box::new(Pdf {}))
.add_format(
"proofread.pdf",
lformat!("PDF (proofreading)"),
Box::new(ProofPdf {}),
)
.add_format("epub", lformat!("EPUB"), Box::new(Epub {})) .add_format("epub", lformat!("EPUB"), Box::new(Epub {}))
.add_format( .add_format(
"html.if", "html.if",
lformat!("HTML (interactive fiction)"), lformat!("HTML (interactive fiction)"),
Box::new(HtmlIf {}), Box::new(HtmlIf {}),
); );
#[cfg(feature = "odt")]
book.add_format("odt", lformat!("ODT"), Box::new(Odt {}));
book book
} }
@ -265,9 +247,9 @@ impl Book {
/// assert_eq!(book.options.get_str("author").unwrap(), "Foo"); /// assert_eq!(book.options.get_str("author").unwrap(), "Foo");
/// assert_eq!(book.options.get_str("title").unwrap(), "Bar"); /// assert_eq!(book.options.get_str("title").unwrap(), "Bar");
/// ``` /// ```
pub fn set_options<'a, I>(&mut self, options: I) -> &mut Book pub fn set_options<'b, I>(&mut self, options: I) -> &mut Self
where where
I: IntoIterator<Item = &'a (&'a str, &'a str)>, I: IntoIterator<Item = &'b (&'b str, &'b str)>,
{ {
// set options // set options
for (key, value) in options { for (key, value) in options {
@ -302,7 +284,7 @@ impl Book {
/// let mut book = Book::new(); /// let mut book = Book::new();
/// let result = book.load_file("some.book"); /// let result = book.load_file("some.book");
/// ``` /// ```
pub fn load_file<P: AsRef<Path>>(&mut self, path: P) -> Result<&mut Book> { pub fn load_file<P: AsRef<Path>>(&mut self, path: P) -> Result<()> {
let filename = format!("{}", path.as_ref().display()); let filename = format!("{}", path.as_ref().display());
self.source = Source::new(filename.as_str()); self.source = Source::new(filename.as_str());
self.options.source = Source::new(filename.as_str()); self.options.source = Source::new(filename.as_str());
@ -316,9 +298,8 @@ impl Book {
self.options.root = self.root.clone(); self.options.root = self.root.clone();
} }
let result = self.read_config(&f); match self.read_config(&f) {
match result { Ok(_) => Ok(()),
Ok(book) => Ok(book),
Err(err) => { Err(err) => {
if err.is_config_parser() && path.as_ref().ends_with(".md") { if err.is_config_parser() && path.as_ref().ends_with(".md") {
let err = Error::default( let err = Error::default(
@ -353,7 +334,7 @@ impl Book {
/// let mut book = Book::new(); /// let mut book = Book::new();
/// book.load_markdown_file("foo.md"); // not unwraping since foo.md doesn't exist /// book.load_markdown_file("foo.md"); // not unwraping since foo.md doesn't exist
/// ``` /// ```
pub fn load_markdown_file<P: AsRef<Path>>(&mut self, path: P) -> Result<&mut Self> { pub fn load_markdown_file<P: AsRef<Path>>(&mut self, path: P) -> Result<()> {
let filename = format!("{}", path.as_ref().display()); let filename = format!("{}", path.as_ref().display());
self.source = Source::new(filename.as_str()); self.source = Source::new(filename.as_str());
@ -372,7 +353,7 @@ impl Book {
// Update grammar checker according to options // Update grammar checker according to options
self.add_chapter(Number::Hidden, &relative_path.to_string_lossy(), false)?; self.add_chapter(Number::Hidden, &relative_path.to_string_lossy(), false)?;
Ok(self) Ok(())
} }
/// Reads a single markdown config from a `Read`able object. /// Reads a single markdown config from a `Read`able object.
@ -397,18 +378,18 @@ impl Book {
/// book.read_markdown_config(content.as_bytes()).unwrap(); /// book.read_markdown_config(content.as_bytes()).unwrap();
/// assert_eq!(book.options.get_str("title").unwrap(), "Bar"); /// assert_eq!(book.options.get_str("title").unwrap(), "Bar");
/// ``` /// ```
pub fn read_markdown_config<R: Read>(&mut self, source: R) -> Result<&mut Self> { pub fn read_markdown_config<R: Read>(&mut self, source: R) -> Result<()> {
self.options.set("tex.class", "article").unwrap(); self.options.set("tex.class", "article").unwrap();
self.options.set("input.yaml_blocks", "true").unwrap(); self.options.set("input.yaml_blocks", "true").unwrap();
// Update grammar checker according to options // Update grammar checker according to options
self.add_chapter_from_source(Number::Hidden, source, false)?; self.add_chapter_from_source(Number::Hidden, source, false)?;
Ok(self) Ok(())
} }
/// Sets options from a YAML block /// Sets options from a YAML block
fn set_options_from_yaml(&mut self, yaml: &str) -> Result<&mut Book> { fn set_options_from_yaml(&mut self, yaml: &str) -> Result<&mut Self> {
self.options.source = self.source.clone(); self.options.source = self.source.clone();
match YamlLoader::load_from_str(yaml) { match YamlLoader::load_from_str(yaml) {
Err(err) => { Err(err) => {
@ -471,8 +452,8 @@ impl Book {
/// let mut book = Book::new(); /// let mut book = Book::new();
/// book.read_config(content.as_bytes()); // no unwraping as `intro.md` and `chapter_01.md` don't exist /// book.read_config(content.as_bytes()); // no unwraping as `intro.md` and `chapter_01.md` don't exist
/// ``` /// ```
pub fn read_config<R: Read>(&mut self, mut source: R) -> Result<&mut Book> { pub fn read_config<R: Read>(&mut self, mut source: R) -> Result<()> {
fn get_filename<'a>(source: &Source, s: &'a str) -> Result<&'a str> { fn get_filename<'b>(source: &Source, s: &'b str) -> Result<&'b str> {
let words: Vec<&str> = (s[1..]).split_whitespace().collect(); let words: Vec<&str> = (s[1..]).split_whitespace().collect();
if words.len() > 1 { if words.len() > 1 {
return Err(Error::config_parser( return Err(Error::config_parser(
@ -691,7 +672,7 @@ impl Book {
self.source.unset_line(); self.source.unset_line();
self.set_chapter_template()?; self.set_chapter_template()?;
Ok(self) Ok(())
} }
/// Determine whether proofreading is activated or not /// Determine whether proofreading is activated or not
@ -1179,12 +1160,16 @@ impl Book {
/// Sets the chapter_template once and for all /// Sets the chapter_template once and for all
fn set_chapter_template(&mut self) -> Result<()> { fn set_chapter_template(&mut self) -> Result<()> {
let template = compile_str( self.registry.add_template("rendering.chapter.template",
self.options.get_str("rendering.chapter.template").unwrap(), self.options.get_str("rendering.chapter.template").unwrap().to_owned())
&self.source, .map_err(|e| Error::template(
"rendering.chapter.template", &self.source,
)?; lformat!(
self.chapter_template = Some(template); "could not compile '{template}': {error}",
template = "rendering.chapter.template",
error = e
))
)?;
Ok(()) Ok(())
} }
@ -1236,7 +1221,7 @@ impl Book {
}; };
let mut data = self.get_metadata(&mut f)?; let mut data = self.get_metadata(&mut f)?;
if !title.is_empty() { if !title.is_empty() {
data = data.insert_bool(format!("has_{header_type}_title"), true); data.insert(format!("has_{header_type}_title"), true.into());
} }
let number = self.get_header_number(header, n)?; let number = self.get_header_number(header, n)?;
let header_name = self let header_name = self
@ -1245,43 +1230,24 @@ impl Book {
.map(|s| s.to_owned()) .map(|s| s.to_owned())
.unwrap_or_else(|_| lang::get_str(self.options.get_str("lang").unwrap(), header_type)); .unwrap_or_else(|_| lang::get_str(self.options.get_str("lang").unwrap(), header_type));
data = data data.insert(format!("{header_type}_title"), title.clone().into());
.insert_str(format!("{header_type}_title"), title.clone()) data.insert(header_type.into(), header_name.clone().into());
.insert_str(header_type, header_name.clone()) data.insert("number".into(), number.clone().into());
.insert_str("number", number.clone());
let data = data.build();
let mut res: Vec<u8> = vec![];
let opt_template = match header { let opt_template = match header {
Header::Part => &self.part_template, Header::Part => &self.part_template,
Header::Chapter => &self.chapter_template, Header::Chapter => &self.chapter_template,
}; };
let res = self.registry.get_template(&format!("rendering.{header_type}.template"))
if let Some(ref template) = *opt_template { .unwrap()
template.render_data(&mut res, &data)?; .render(&data)
} else { .to_string()?;
let template = compile_str( Ok(HeaderData {
self.options text: res,
.get_str(&format!("rendering.{header_type}.template")) number,
.unwrap(), header: header_name,
&self.source, title,
&format!("rendering.{header_type}.template"), })
)?;
template.render_data(&mut res, &data)?;
}
match String::from_utf8(res) {
Err(_) => panic!(
"{}",
lformat!("header generated by mustache was not valid utf-8")
),
Ok(res) => Ok(HeaderData {
text: res,
number,
header: header_name,
title,
}),
}
} }
/// Returns the string corresponding to a number, title, and the numbering template for chapter /// Returns the string corresponding to a number, title, and the numbering template for chapter
@ -1302,24 +1268,22 @@ impl Book {
self.get_header(Header::Part, n, title, f) self.get_header(Header::Part, n, title, f)
} }
/// Returns a `MapBuilder` (used by `Mustache` for templating), to be used (and completed) /// Returns a `Map of Key/Value` (used by `Upon` for templating), to be used (and completed)
/// by renderers. It fills it with the metadata options. /// by renderers. It fills it with the metadata options.
/// ///
/// It also uses the lang/xx.yaml file corresponding to the language and fills /// It also uses the lang/xx.yaml file corresponding to the language and fills
/// `loc_xxx` fiels with it that corresponds to translated versions. /// `loc_xxx` fiels with it that corresponds to translated versions.
/// ///
/// This method treats the metadata as Markdown and thus calls `f` to render it. /// This method treats the metadata as Markdown and thus calls `f` to render it.
/// This is why we cant really cache this as it will depend on the renderer.
#[doc(hidden)] #[doc(hidden)]
pub fn get_metadata<F>(&self, mut f: F) -> Result<MapBuilder> pub fn get_metadata<F>(&self, mut f: F) -> Result<BTreeMap<String, upon::Value>>
where where
F: FnMut(&str) -> Result<String>, F: FnMut(&str) -> Result<String>,
{ {
let mut mapbuilder = MapBuilder::new(); let mut m: BTreeMap<String, upon::Value> = BTreeMap::new();
mapbuilder = mapbuilder.insert_str("crowbook_version", env!("CARGO_PKG_VERSION")); m.insert("crowbook_version".into(), env!("CARGO_PKG_VERSION").into());
mapbuilder = mapbuilder.insert_bool( m.insert(format!("lang_{}", self.options.get_str("lang").unwrap()), true.into());
format!("lang_{}", self.options.get_str("lang").unwrap()),
true,
);
// Add metadata to mapbuilder // Add metadata to mapbuilder
for key in self.options.get_metadata() { for key in self.options.get_metadata() {
@ -1335,12 +1299,12 @@ impl Book {
match content { match content {
Ok(content) => { Ok(content) => {
if !content.is_empty() { if !content.is_empty() {
mapbuilder = mapbuilder.insert_str(format!("{key}_raw"), raw); m.insert(format!("{key}_raw"), raw.into());
mapbuilder = mapbuilder.insert_str(key.clone(), content); m.insert(key.clone(), content.into());
mapbuilder = mapbuilder.insert_bool(format!("has_{key}"), true); m.insert(format!("has_{key}"), true.into());
} else { } else {
mapbuilder = mapbuilder.insert_bool(format!("has_{key}"), false); m.insert(format!("has_{key}"), false.into());
} }
} }
Err(err) => { Err(err) => {
@ -1356,7 +1320,7 @@ impl Book {
} }
} }
} else { } else {
mapbuilder = mapbuilder.insert_bool(format!("has_{key}"), false); m.insert(format!("has_{key}"), false.into());
} }
} }
@ -1365,11 +1329,32 @@ impl Book {
for (key, value) in hash { for (key, value) in hash {
let key = format!("loc_{}", key.as_str().unwrap()); let key = format!("loc_{}", key.as_str().unwrap());
let value = value.as_str().unwrap(); let value = value.as_str().unwrap();
mapbuilder = mapbuilder.insert_str(key, value); m.insert(key, value.into());
} }
Ok(mapbuilder) Ok(m)
} }
/// Calls upon::engine::compile, does NOT registre the complete
pub fn compile_str<'s, O>(&self, template: &'s str, source: O, template_name: &str) -> Result<upon::Template<'_, 's>>
where
O: Into<Source>,
{
let input: String = template.to_owned();
let result = self.registry.compile(template);
match result {
Ok(result) => Ok(result),
Err(err) => Err(Error::template(
source,
lformat!(
"could not compile '{template}': {error}",
template = template_name,
error = err
),
)),
}
}
/// Remove YAML blocks from a string and try to parse them to set options /// Remove YAML blocks from a string and try to parse them to set options
/// ///
/// YAML blocks start with /// YAML blocks start with
@ -1495,28 +1480,9 @@ impl Book {
} }
} }
impl std::default::Default for Book { impl std::default::Default for Book<'_> {
fn default() -> Self { fn default() -> Self {
Self::new() Self::new()
} }
} }
/// Calls mustache::compile_str but catches panics and returns a result
pub fn compile_str<O>(template: &str, source: O, template_name: &str) -> Result<mustache::Template>
where
O: Into<Source>,
{
let input: String = template.to_owned();
let result = mustache::compile_str(&input);
match result {
Ok(result) => Ok(result),
Err(err) => Err(Error::template(
source,
lformat!(
"could not compile '{template}': {error}",
template = template_name,
error = err
),
)),
}
}

View File

@ -1,4 +1,4 @@
// Copyright (C) 2017-2022 Élisabeth HENRY. // Copyright (C) 2017-2023 Élisabeth HENRY.
// //
// This file is part of Crowbook. // This file is part of Crowbook.
// //
@ -63,7 +63,7 @@ impl Default for Bars {
/// Return the style of a bar /// Return the style of a bar
impl Book { impl Book<'_> {
/// Adds a progress bar where where info should be written. /// Adds a progress bar where where info should be written.
/// ///
/// See [indicatif doc](https://docs.rs/indicatif) for more information. /// See [indicatif doc](https://docs.rs/indicatif) for more information.
@ -270,7 +270,7 @@ impl Book {
} }
} }
impl Drop for Book { impl Drop for Book<'_> {
fn drop(&mut self) { fn drop(&mut self) {
if let Some(ref bar) = self.bars.secondbar { if let Some(ref bar) = self.bars.secondbar {
bar.finish_and_clear(); bar.finish_and_clear();

View File

@ -1,4 +1,4 @@
// Copyright (C) 2017 Élisabeth HENRY. // Copyright (C) 2017-2023 Élisabeth HENRY.
// //
// This file is part of Crowbook. // This file is part of Crowbook.
// //
@ -28,7 +28,7 @@ impl Bars {
} }
} }
impl Book { impl Book<'_> {
pub fn private_add_progress_bar(&mut self, _: bool) {} pub fn private_add_progress_bar(&mut self, _: bool) {}
/// Sets a finished message to the progress bar, if it is set /// Sets a finished message to the progress bar, if it is set

View File

@ -559,8 +559,7 @@ impl BookOptions {
})?; })?;
let mut book = Book::new(); let mut book = Book::new();
book.load_file(file)?; book.load_file(file)?;
let options = mem::replace(&mut book.options, BookOptions::new()); self.merge(&book.options)?;
self.merge(options)?;
Ok(None) Ok(None)
} else { } else {
Ok(self.options.insert(key, BookOption::Path(value))) Ok(self.options.insert(key, BookOption::Path(value)))
@ -910,7 +909,7 @@ impl BookOptions {
/// If option is already set in self, don't add it, unless it was the default. /// If option is already set in self, don't add it, unless it was the default.
/// Option is not inserted either if new value is equal to default. /// Option is not inserted either if new value is equal to default.
#[doc(hidden)] #[doc(hidden)]
pub fn merge(&mut self, other: BookOptions) -> Result<()> { pub fn merge(&mut self, other: &BookOptions) -> Result<()> {
for (key, value) in &other.options { for (key, value) in &other.options {
// Check if option was already set, and if it was to default or to something else // Check if option was already set, and if it was to default or to something else
if self.defaults.contains_key(key) { if self.defaults.contains_key(key) {

View File

@ -16,7 +16,7 @@
// along with Crowbook. If not, see <http://www.gnu.org/licenses/>. // along with Crowbook. If not, see <http://www.gnu.org/licenses/>.
use crate::book::Header; use crate::book::Header;
use crate::book::{compile_str, Book}; use crate::book::Book;
use crate::book_renderer::BookRenderer; use crate::book_renderer::BookRenderer;
use crate::error::{Error, Result, Source}; use crate::error::{Error, Result, Source};
use crate::html::HtmlRenderer; use crate::html::HtmlRenderer;
@ -34,7 +34,7 @@ use epub_builder::{
EpubBuilder, EpubContent, EpubVersion, ReferenceType, ZipCommand, ZipCommandOrLibrary, EpubBuilder, EpubContent, EpubVersion, ReferenceType, ZipCommand, ZipCommandOrLibrary,
ZipLibrary, ZipLibrary,
}; };
use mustache::Template; use upon::Template;
use std::borrow::Cow; use std::borrow::Cow;
use std::convert::{AsMut, AsRef}; use std::convert::{AsMut, AsRef};
@ -194,8 +194,9 @@ impl<'a> EpubRenderer<'a> {
} }
// Write chapters // Write chapters
let template_chapter = compile_str( let template_chapter_src = self.html.book.get_template("epub.chapter.xhtml")?;
self.html.book.get_template("epub.chapter.xhtml")?.as_ref(), let template_chapter = self.html.book.compile_str(
template_chapter_src.as_ref(),
&self.html.book.source, &self.html.book.source,
"epub.chapter.xhtml", "epub.chapter.xhtml",
)?; )?;
@ -229,23 +230,21 @@ impl<'a> EpubRenderer<'a> {
self.html.source = Source::empty(); self.html.source = Source::empty();
// Render the CSS file and write it // Render the CSS file and write it
let template_css = compile_str( let template_css_src = self.html.book.get_template("epub.css").unwrap();
self.html.book.get_template("epub.css").unwrap().as_ref(), let template_css = self.html.book.compile_str(
template_css_src.as_ref(),
&self.html.book.source, &self.html.book.source,
"epub.css", "epub.css",
)?; )?;
let mut data = self let mut data = self
.html .html
.book .book
.get_metadata(|s| self.render_vec(&Parser::new().parse_inline(s)?))? .get_metadata(|s| self.render_vec(&Parser::new().parse_inline(s)?))?;
.insert_bool(self.html.book.options.get_str("lang").unwrap(), true); data.insert(self.html.book.options.get_str("lang").unwrap().into(), true.into());
if let Ok(epub_css_add) = self.html.book.options.get_str("epub.css.add") { if let Ok(epub_css_add) = self.html.book.options.get_str("epub.css.add") {
data = data.insert_str("additional_code", epub_css_add); data.insert("additional_code".into(), epub_css_add.into());
} }
let data = data.build(); let css = template_css.render(&data).to_string()?;
let mut res: Vec<u8> = vec![];
template_css.render_data(&mut res, &data)?;
let css = String::from_utf8_lossy(&res);
maker.stylesheet(css.as_bytes()) maker.stylesheet(css.as_bytes())
.map_err(|err| Error::render(Source::empty(), format!("{}", err)))?; .map_err(|err| Error::render(Source::empty(), format!("{}", err)))?;
@ -308,25 +307,17 @@ impl<'a> EpubRenderer<'a> {
/// Render the titlepgae /// Render the titlepgae
fn render_titlepage(&mut self) -> Result<String> { fn render_titlepage(&mut self) -> Result<String> {
let template = compile_str( let template_src = self.html.book.get_template("epub.titlepage.xhtml")?;
self.html let template = self.html.book.compile_str(
.book template_src.as_ref(),
.get_template("epub.titlepage.xhtml")?
.as_ref(),
&self.html.book.source, &self.html.book.source,
"epub.titlepage.xhtml", "epub.titlepage.xhtml",
)?; )?;
let data = self let data = self
.html .html
.book .book
.get_metadata(|s| self.render_vec(&Parser::new().parse_inline(s)?))? .get_metadata(|s| self.render_vec(&Parser::new().parse_inline(s)?))?;
.build(); Ok(template.render(&data).to_string()?)
let mut res: Vec<u8> = vec![];
template.render_data(&mut res, &data)?;
match String::from_utf8(res) {
Err(_) => panic!("generated HTML in titlepage was not utf-8 valid"),
Ok(res) => Ok(res),
}
} }
/// Render cover.xhtml /// Render cover.xhtml
@ -341,32 +332,22 @@ impl<'a> EpubRenderer<'a> {
)); ));
} }
let epub3 = self.html.book.options.get_i32("epub.version").unwrap() == 3; let epub3 = self.html.book.options.get_i32("epub.version").unwrap() == 3;
let template = compile_str( let template = self.html.book.compile_str(
if epub3 { epub3::COVER } else { COVER }, if epub3 { epub3::COVER } else { COVER },
&self.html.book.source, &self.html.book.source,
"cover.xhtml", "cover.xhtml",
)?; )?;
let data = self let mut data = self
.html .html
.book .book
.get_metadata(|s| self.render_vec(&Parser::new().parse_inline(s)?))? .get_metadata(|s| self.render_vec(&Parser::new().parse_inline(s)?))?;
.insert_str( data.insert(
"cover", "cover".into(),
self.html self.html
.handler .handler
.map_image(&self.html.source, Cow::Owned(cover))? .map_image(&self.html.source, Cow::Owned(cover))?
.into_owned(), .into());
) Ok(template.render(&data).to_string()?)
.build();
let mut res: Vec<u8> = vec![];
template.render_data(&mut res, &data)?;
match String::from_utf8(res) {
Err(_) => panic!(
"{}",
lformat!("generated HTML for cover.xhtml was not utf-8 valid")
),
Ok(res) => Ok(res),
}
} else { } else {
panic!( panic!(
"{}", "{}",
@ -421,21 +402,15 @@ impl<'a> EpubRenderer<'a> {
} }
self.toc.push(self.chapter_title.clone()); self.toc.push(self.chapter_title.clone());
let data = self let mut data = self
.html .html
.book .book
.get_metadata(|s| self.render_vec(&Parser::new().parse_inline(s)?))? .get_metadata(|s| self.render_vec(&Parser::new().parse_inline(s)?))?;
.insert_str("content", content) data.insert("content".into(), content.into());
.insert_str("chapter_title_raw", self.chapter_title_raw.clone()) data.insert("chapter_title_raw".into(), self.chapter_title_raw.clone(). into());
.insert_str("chapter_title", std::mem::take(&mut self.chapter_title)) data.insert("chapter_title".into(), std::mem::take(&mut self.chapter_title).into());
.build(); Ok((template.render(&data).to_string()?,
self.chapter_title = String::new(); std::mem::take(&mut self.chapter_title_raw)))
let mut res: Vec<u8> = vec![];
template.render_data(&mut res, &data)?;
match String::from_utf8(res) {
Err(_) => panic!("{}", lformat!("generated HTML was not utf-8 valid")),
Ok(res) => Ok((res, std::mem::take(&mut self.chapter_title_raw))),
}
} }
/// Renders the header section of the book, finding the title of the chapter /// Renders the header section of the book, finding the title of the chapter

View File

@ -361,8 +361,8 @@ impl fmt::Display for Error {
pub type Result<T> = result::Result<T, Error>; pub type Result<T> = result::Result<T, Error>;
/// Implement our Error from mustache::Error /// Implement our Error from mustache::Error
impl From<mustache::Error> for Error { impl From<upon::Error> for Error {
fn from(err: mustache::Error) -> Error { fn from(err: upon::Error) -> Error {
Error::template(Source::empty(), format!("{err}")) Error::template(Source::empty(), format!("{err}"))
} }
} }

View File

@ -17,7 +17,7 @@
use crate::book::Header; use crate::book::Header;
use crate::book::HeaderData; use crate::book::HeaderData;
use crate::book::{compile_str, Book}; use crate::book::Book;
use crate::error::{Error, Result, Source}; use crate::error::{Error, Result, Source};
use crate::lang; use crate::lang;
use crate::number::Number; use crate::number::Number;
@ -36,8 +36,6 @@ use std::fmt::Write;
use crowbook_text_processing::escape; use crowbook_text_processing::escape;
use epub_builder::Toc; use epub_builder::Toc;
use epub_builder::TocElement; use epub_builder::TocElement;
use mustache::MapBuilder;
use mustache::Template;
use numerals::roman::Roman; use numerals::roman::Roman;
#[derive(Debug, PartialEq, Copy, Clone)] #[derive(Debug, PartialEq, Copy, Clone)]
@ -63,7 +61,7 @@ pub struct HtmlRenderer<'a> {
filename: String, filename: String,
/// Book that must be rendered /// Book that must be rendered
pub book: &'a Book, pub book: &'a Book<'a>,
/// Proofread or not /// Proofread or not
pub proofread: bool, pub proofread: bool,
@ -109,8 +107,8 @@ pub struct HtmlRenderer<'a> {
syntax: Option<Syntax>, syntax: Option<Syntax>,
part_template_html: Template, part_template_html: upon::Template<'a, 'a>,
chapter_template_html: Template, chapter_template_html: upon::Template<'a, 'a>,
} }
impl<'a> HtmlRenderer<'a> { impl<'a> HtmlRenderer<'a> {
@ -161,12 +159,12 @@ impl<'a> HtmlRenderer<'a> {
proofread: false, proofread: false,
syntax, syntax,
highlight, highlight,
part_template_html: compile_str( part_template_html: book.compile_str(
book.options.get_str("html.part.template").unwrap(), book.options.get_str("html.part.template").unwrap(),
Source::empty(), Source::empty(),
"html.part.template", "html.part.template",
)?, )?,
chapter_template_html: compile_str( chapter_template_html: book.compile_str(
book.options.get_str("html.chapter.template").unwrap(), book.options.get_str("html.chapter.template").unwrap(),
Source::empty(), Source::empty(),
"html.chapter.template", "html.chapter.template",
@ -278,17 +276,15 @@ impl<'a> HtmlRenderer<'a> {
}; };
let has_number = !data.header.is_empty(); let has_number = !data.header.is_empty();
let has_title = !data.title.is_empty(); let has_title = !data.title.is_empty();
let data = MapBuilder::new() let data = upon::value!{
.insert_bool("has_number", has_number) has_number: has_number,
.insert_bool("has_title", has_title) has_title: has_title,
.insert_str("header", data.header) header: data.header,
.insert_str("number", data.number) number: data.number,
.insert_str("link", format!("{}", self.link_number)) title: data.title,
.insert_str("title", data.title) link: format!("{}", self.link_number)
.build(); };
let mut res = vec![]; Ok(template.render(&data).to_string()?)
template.render_data(&mut res, &data)?;
Ok(String::from_utf8(res)?)
} }
} else { } else {
Ok(format!( Ok(format!(
@ -667,12 +663,9 @@ impl<'a> HtmlRenderer<'a> {
/// Consider the html as a template /// Consider the html as a template
fn templatize(&mut self, s: &str) -> Result<String> { fn templatize(&mut self, s: &str) -> Result<String> {
let mapbuilder = self.book.get_metadata(|s| Ok(s.to_owned()))?; let data = self.book.get_metadata(|s| Ok(s.to_owned()))?;
let data = mapbuilder.build(); let template = self.book.compile_str(s, &self.book.source, "")?;
let template = compile_str(s, &self.book.source, "")?; Ok(template.render(&data).to_string()?)
let mut res = vec![];
template.render_data(&mut res, &data)?;
Ok(String::from_utf8_lossy(&res).into_owned())
} }
/// Renders the toc name /// Renders the toc name
@ -680,17 +673,14 @@ impl<'a> HtmlRenderer<'a> {
pub fn get_toc_name(&mut self) -> Result<String> { pub fn get_toc_name(&mut self) -> Result<String> {
let data = self let data = self
.book .book
.get_metadata(|s| self.render_vec(&Parser::new().parse_inline(s)?))? .get_metadata(|s| self.render_vec(&Parser::new().parse_inline(s)?))?;
.build();
let template = self let template = self
.book .book
.options .options
.get_str("rendering.inline_toc.name") .get_str("rendering.inline_toc.name")
.unwrap(); .unwrap();
let template = compile_str(template, &self.book.source, "rendering.inline_toc.name")?; let template = self.book.compile_str(template, &self.book.source, "rendering.inline_toc.name")?;
let mut res = vec![]; Ok(template.render(&data).to_string()?)
template.render_data(&mut res, &data)?;
Ok(String::from_utf8_lossy(&res).into_owned())
} }
/// Render a section containing schema.org JSON-LD code /// Render a section containing schema.org JSON-LD code

View File

@ -15,7 +15,7 @@
// You should have received ba copy of the GNU Lesser General Public License // You should have received ba copy of the GNU Lesser General Public License
// along with Crowbook. If not, see <http://www.gnu.org/licenses/>. // along with Crowbook. If not, see <http://www.gnu.org/licenses/>.
use crate::book::{compile_str, Book}; use crate::book::Book;
use crate::book_renderer::BookRenderer; use crate::book_renderer::BookRenderer;
use crate::error::{Error, Result, Source}; use crate::error::{Error, Result, Source};
use crate::html::Highlight; use crate::html::Highlight;
@ -273,8 +273,9 @@ impl<'a> HtmlDirRenderer<'a> {
let toc = self.html.toc.render(false, false); let toc = self.html.toc.render(false, false);
// render all chapters // render all chapters
let template = compile_str( let template_src = self.html.book.get_template("html.dir.template")?;
self.html.book.get_template("html.dir.template")?.as_ref(), let template = self.html.book.compile_str(
template_src.as_ref(),
&self.html.book.source, &self.html.book.source,
"html.dir.template", "html.dir.template",
)?; )?;
@ -308,39 +309,37 @@ impl<'a> HtmlDirRenderer<'a> {
}; };
// Render each HTML document // Render each HTML document
let mut mapbuilder = self let mut data = self
.html .html
.book .book
.get_metadata(|s| self.render_vec(&Parser::new().parse_inline(s)?))? .get_metadata(|s| self.render_vec(&Parser::new().parse_inline(s)?))?;
.insert_str("content", content?) data.insert("content".into(), content?.into());
.insert_str("chapter_title", titles[i].clone()) data.insert("chapter_title".into(), titles[i].clone().into());
.insert_str("json_data", self.html.get_json_ld()?) data.insert("json_data".into(), self.html.get_json_ld()?.into());
.insert_str("chapter_title_raw", titles_raw[i].clone()) data.insert("chapter_title_raw".into(), titles_raw[i].clone().into());
.insert_str("toc", toc.clone()) data.insert("toc".into(), toc.clone().into());
.insert_str("prev_chapter", prev_chapter) data.insert("prev_chapter".into(), prev_chapter.into());
.insert_str("next_chapter", next_chapter) data.insert("next_chapter".into(), next_chapter.into());
.insert_str("footer", HtmlRenderer::get_footer(self)?) data.insert("footer".into(), HtmlRenderer::get_footer(self)?.into());
.insert_str("header", HtmlRenderer::get_header(self)?) data.insert("header".into(), HtmlRenderer::get_header(self)?.into());
.insert_str("script", self.html.book.get_template("html.js").unwrap()) data.insert("script".into(), self.html.book.get_template("html.js").unwrap().into());
.insert_bool(self.html.book.options.get_str("lang").unwrap(), true); data.insert(self.html.book.options.get_str("lang").unwrap().into(), true.into());
if let Ok(favicon) = self.html.book.options.get_path("html.icon") { if let Ok(favicon) = self.html.book.options.get_path("html.icon") {
let favicon = self let favicon = self
.html .html
.handler .handler
.map_image(&self.html.book.source, favicon)?; .map_image(&self.html.book.source, favicon)?;
mapbuilder = mapbuilder.insert_str( data.insert(
"favicon", "favicon".into(),
format!("<link rel = \"icon\" href = \"{favicon}\">"), format!("<link rel = \"icon\" href = \"{favicon}\">").into(),
); );
} }
if self.html.highlight == Highlight::Js { if self.html.highlight == Highlight::Js {
mapbuilder = mapbuilder.insert_bool("highlight_code", true); data.insert("highlight_code".into(), true.into());
} }
let data = mapbuilder.build(); let res = template.render(&data).to_string()?;
let mut res = vec![]; self.write_file(&filenamer(i), res.as_bytes())?;
template.render_data(&mut res, &data)?;
self.write_file(&filenamer(i), &res)?;
} }
let mut content = if let Ok(cover) = self.html.book.options.get_path("cover") { let mut content = if let Ok(cover) = self.html.book.options.get_path("cover") {
@ -420,38 +419,37 @@ impl<'a> HtmlDirRenderer<'a> {
)?; )?;
} }
// Render index.html and write it too // Render index.html and write it too
let mut mapbuilder = self let mut data = self
.html .html
.book .book
.get_metadata(|s| self.render_vec(&Parser::new().parse_inline(s)?))? .get_metadata(|s| self.render_vec(&Parser::new().parse_inline(s)?))?;
.insert_str("content", content) data.insert("content".into(), content.into());
.insert_str("header", HtmlRenderer::get_header(self)?) data.insert("header".into(), HtmlRenderer::get_header(self)?.into());
.insert_str("footer", HtmlRenderer::get_footer(self)?) data.insert("footer".into(), HtmlRenderer::get_footer(self)?.into());
.insert_str("toc", toc.clone()) data.insert("toc".into(), toc.into());
.insert_str("script", self.html.book.get_template("html.js").unwrap()) data.insert("script".into(), self.html.book.get_template("html.js").unwrap().into());
.insert_bool(self.html.book.options.get_str("lang").unwrap(), true); data.insert(self.html.book.options.get_str("lang").unwrap().into(), true.into());
if let Ok(favicon) = self.html.book.options.get_path("html.icon") { if let Ok(favicon) = self.html.book.options.get_path("html.icon") {
let favicon = self let favicon = self
.html .html
.handler .handler
.map_image(&self.html.book.source, favicon)?; .map_image(&self.html.book.source, favicon)?;
mapbuilder = mapbuilder.insert_str( data.insert(
"favicon", "favicon".into(),
format!("<link rel = \"icon\" href = \"{favicon}\">"), format!("<link rel = \"icon\" href = \"{favicon}\">").into(),
); );
} }
if self.html.highlight == Highlight::Js { if self.html.highlight == Highlight::Js {
mapbuilder = mapbuilder.insert_bool("highlight_code", true); data.insert("highlight_code".into(), true.into());
} }
let data = mapbuilder.build(); let template_src = self.html.book.get_template("html.dir.template")?;
let template = compile_str( let template = self.html.book.compile_str(
self.html.book.get_template("html.dir.template")?.as_ref(), template_src.as_ref(),
&self.html.book.source, &self.html.book.source,
"html.dir.template", "html.dir.template",
)?; )?;
let mut res = vec![]; let res = template.render(&data).to_string()?;
template.render_data(&mut res, &data)?; self.write_file("index.html", res.as_bytes())?;
self.write_file("index.html", &res)?;
Ok(()) Ok(())
} }
@ -459,20 +457,19 @@ impl<'a> HtmlDirRenderer<'a> {
// Render the CSS file and write it // Render the CSS file and write it
fn write_css(&self) -> Result<()> { fn write_css(&self) -> Result<()> {
// Render the CSS // Render the CSS
let template_css = compile_str( let template_css_src = self.html.book.get_template("html.css")?;
self.html.book.get_template("html.css")?.as_ref(), let template_css = self.html.book.compile_str(
template_css_src.as_ref(),
&self.html.book.source, &self.html.book.source,
"html.css", "html.css",
)?; )?;
let mut data = self.html.book.get_metadata(|s| Ok(s.to_owned()))?; let mut data = self.html.book.get_metadata(|s| Ok(s.to_owned()))?;
data = data.insert_str("colors", self.html.book.get_template("html.css.colors")?); data.insert("colors".into(), self.html.book.get_template("html.css.colors")?.into());
if let Ok(html_css_add) = self.html.book.options.get_str("html.css.add") { if let Ok(html_css_add) = self.html.book.options.get_str("html.css.add") {
data = data.insert_str("additional_code", html_css_add); data.insert("additional_code".into(), html_css_add.into());
} }
let data = data.build();
let mut res: Vec<u8> = vec![]; let css = template_css.render(&data).to_string()?;
template_css.render_data(&mut res, &data)?;
let css = String::from_utf8_lossy(&res);
// Write it // Write it
self.write_file("stylesheet.css", css.as_bytes()) self.write_file("stylesheet.css", css.as_bytes())

View File

@ -15,7 +15,7 @@
// You should have received ba copy of the GNU Lesser General Public License // You should have received ba copy of the GNU Lesser General Public License
// along with Crowbook. If not, see <http://www.gnu.org/licenses/>. // along with Crowbook. If not, see <http://www.gnu.org/licenses/>.
use crate::book::{compile_str, Book}; use crate::book::Book;
use crate::book_renderer::BookRenderer; use crate::book_renderer::BookRenderer;
use crate::error::{Error, Result, Source}; use crate::error::{Error, Result, Source};
use crate::html::Highlight; use crate::html::Highlight;
@ -261,74 +261,71 @@ return crowbook_return_variable.replace(/<\\/ul><ul>/g, '');\n",
self.html.render_end_notes(&mut content, "section", ""); self.html.render_end_notes(&mut content, "section", "");
// Render the CSS // Render the CSS
let template_css = compile_str( let template_css_src = self.html.book.get_template("html.css")?;
self.html.book.get_template("html.css")?.as_ref(), let template_css = self.html.book.compile_str(
template_css_src.as_ref(),
&self.html.book.source, &self.html.book.source,
"html.css", "html.css",
)?; )?;
let mut data = self let mut data = self
.html .html
.book .book
.get_metadata(|s| self.render_vec(&Parser::new().parse_inline(s)?))? .get_metadata(|s| self.render_vec(&Parser::new().parse_inline(s)?))?;
.insert_str("colors", self.html.book.get_template("html.css.colors")?); data.insert("colors".into(), self.html.book.get_template("html.css.colors")?.into());
if let Ok(html_css_add) = self.html.book.options.get_str("html.css.add") { if let Ok(html_css_add) = self.html.book.options.get_str("html.css.add") {
data = data.insert_str("additional_code", html_css_add); data.insert("additional_code".into(), html_css_add.into());
} }
let data = data.build(); let css:String = template_css.render(&data).to_string()?;
let mut res: Vec<u8> = vec![];
template_css.render_data(&mut res, &data)?;
let css = String::from_utf8_lossy(&res);
// Render the JS // Render the JS
let template_js = compile_str( let template_js_src = self.html.book.get_template("html.if.js")?;
self.html.book.get_template("html.if.js")?.as_ref(), let template_js = self.html.book.compile_str(
template_js_src.as_ref(),
&self.html.book.source, &self.html.book.source,
"html.standalone.js", "html.standalone.js",
)?; )?;
let data = self let mut data = self
.html .html
.book .book
.get_metadata(|s| Ok(s.to_owned()))? .get_metadata(|s| Ok(s.to_owned()))?;
.insert_bool("one_chapter", true) data.insert("one_chapter".into(), true.into());
.insert_str("js_prelude", std::mem::take(&mut self.fn_defs)) data.insert("js_prelude".into(), self.fn_defs.clone().into());
.insert_str( data.insert(
"new_game", "new_game".into(),
self.html.book.get_template("html.if.new_game").unwrap(), self.html.book.get_template("html.if.new_game").unwrap().into(),
) );
.insert_str( data.insert(
"common_script", "common_script".into(),
self.html.book.get_template("html.js").unwrap().as_ref(), self.html.book.get_template("html.js").unwrap().into(),
) );
.build(); let js = template_js.render(&data).to_string()?;
let mut res: Vec<u8> = vec![];
template_js.render_data(&mut res, &data)?;
let js = String::from_utf8_lossy(&res);
// Render the HTML document // Render the HTML document
let mut mapbuilder = self let mut data = self
.html .html
.book .book
.get_metadata(|s| self.render_vec(&Parser::new().parse_inline(s)?))? .get_metadata(|s| self.render_vec(&Parser::new().parse_inline(s)?))?;
.insert_str("content", content) data.insert("content".into(), content.into());
.insert_str("script", js) data.insert("script".into(), js.into());
.insert_bool(self.html.book.options.get_str("lang").unwrap(), true) data.insert(self.html.book.options.get_str("lang").unwrap().into(), true.into());
.insert_bool("one_chapter", true) data.insert("one_chapter".into(), true.into());
.insert_str("style", css.as_ref()) data.insert("style".into(), css.into());
.insert_str( data.insert(
"print_style", "print_style".into(),
self.html.book.get_template("html.css.print").unwrap(), self.html.book.get_template("html.css.print").unwrap().into(),
) );
.insert_str("footer", HtmlRenderer::get_footer(self)?) data.insert("footer".into(), HtmlRenderer::get_footer(self)?.into());
.insert_str("header", HtmlRenderer::get_header(self)?) data.insert("header".into(), HtmlRenderer::get_header(self)?.into());
.insert_bool("has_toc", false); data.insert("has_toc".into(), false.into());
if let Ok(favicon) = self.html.book.options.get_path("html.icon") { if let Ok(favicon) = self.html.book.options.get_path("html.icon") {
let favicon = self let favicon = self
.html .html
.handler .handler
.map_image(&self.html.book.source, favicon)?; .map_image(&self.html.book.source, favicon)?;
mapbuilder = mapbuilder.insert_str( data.insert(
"favicon", "favicon".into(),
format!("<link rel = \"icon\" href = \"{favicon}\">"), format!("<link rel = \"icon\" href = \"{favicon}\">").into(),
); );
} }
if self.html.highlight == Highlight::Js { if self.html.highlight == Highlight::Js {
@ -338,26 +335,20 @@ return crowbook_return_variable.replace(/<\\/ul><ul>/g, '');\n",
.get_template("html.highlight.js")? .get_template("html.highlight.js")?
.as_bytes()); .as_bytes());
let highlight_js = format!("data:text/javascript;base64,{highlight_js}"); let highlight_js = format!("data:text/javascript;base64,{highlight_js}");
mapbuilder = mapbuilder data.insert("highlight_code".into(), true.into());
.insert_bool("highlight_code", true) data.insert(
.insert_str( "highlight_css".into(),
"highlight_css", self.html.book.get_template("html.highlight.css")?.into(),
self.html.book.get_template("html.highlight.css")?, );
) data.insert("highlight_js".into(), highlight_js.into());
.insert_str("highlight_js", highlight_js);
} }
let data = mapbuilder.build(); let template_src = self.html.book.get_template("html.standalone.template")?;
let template = compile_str( let template = self.html.book.compile_str(
self.html template_src.as_ref(),
.book
.get_template("html.standalone.template")?
.as_ref(),
&self.html.book.source, &self.html.book.source,
"html.standalone.template", "html.standalone.template",
)?; )?;
let mut res = vec![]; Ok(template.render(&data).to_string()?)
template.render_data(&mut res, &data)?;
Ok(String::from_utf8_lossy(&res).into_owned())
} }
} }

View File

@ -15,7 +15,7 @@
// You should have received ba copy of the GNU Lesser General Public License // You should have received ba copy of the GNU Lesser General Public License
// along with Crowbook. If not, see <http://www.gnu.org/licenses/>. // along with Crowbook. If not, see <http://www.gnu.org/licenses/>.
use crate::book::{compile_str, Book}; use crate::book::Book;
use crate::book_renderer::BookRenderer; use crate::book_renderer::BookRenderer;
use crate::error::{Error, Result, Source}; use crate::error::{Error, Result, Source};
use crate::html::Highlight; use crate::html::Highlight;
@ -215,93 +215,91 @@ impl<'a> HtmlSingleRenderer<'a> {
} }
// Render the CSS // Render the CSS
let template_css = compile_str( let template_css_src = self.html.book.get_template("html.css")?;
self.html.book.get_template("html.css")?.as_ref(), let template_css = self.html.book.compile_str(
template_css_src.as_ref(),
&self.html.book.source, &self.html.book.source,
"html.css", "html.css",
)?; )?;
let mut data = self let mut data = self
.html .html
.book .book
.get_metadata(|s| self.render_vec(&Parser::new().parse_inline(s)?))? .get_metadata(|s| self.render_vec(&Parser::new().parse_inline(s)?))?;
.insert_str("colors", self.html.book.get_template("html.css.colors")?); data.insert("colors".into(), self.html.book.get_template("html.css.colors")?.into());
if let Ok(html_css_add) = self.html.book.options.get_str("html.css.add") { if let Ok(html_css_add) = self.html.book.options.get_str("html.css.add") {
data = data.insert_str("additional_code", html_css_add); data.insert("additional_code".into(), html_css_add.into());
} }
let data = data.build(); let css = template_css.render(&data).to_string()?;
let mut res: Vec<u8> = vec![];
template_css.render_data(&mut res, &data)?;
let css = String::from_utf8_lossy(&res);
// Render the JS // Render the JS
let template_js = compile_str( let template_js_src = self.html.book.get_template("html.standalone.js")?;
self.html.book.get_template("html.standalone.js")?.as_ref(), let template_js = self.html.book.compile_str(
template_js_src.as_ref(),
&self.html.book.source, &self.html.book.source,
"html.standalone.js", "html.standalone.js",
)?; )?;
let data = self let mut data = self
.html .html
.book .book
.get_metadata(|s| Ok(s.to_owned()))? .get_metadata(|s| Ok(s.to_owned()))?;
.insert_str("book_svg", book_svg.clone()) data.insert("book_svg".into(), book_svg.clone().into());
.insert_str("pages_svg", pages_svg.clone()) data.insert("pages_svg".into(), pages_svg.clone().into());
.insert_bool( data.insert(
"one_chapter", "one_chapter".into(),
self.html self.html
.book .book
.options .options
.get_bool("html.standalone.one_chapter") .get_bool("html.standalone.one_chapter")
.unwrap(), .unwrap()
) .into(),
.insert_str( );
"common_script", data.insert(
self.html.book.get_template("html.js").unwrap().as_ref(), "common_script".into(),
) self.html.book.get_template("html.js").unwrap().into(),
.build(); );
let mut res: Vec<u8> = vec![]; let js = template_js.render(&data).to_string()?;
template_js.render_data(&mut res, &data)?;
let js = String::from_utf8_lossy(&res);
// Render the HTML document // Render the HTML document
let mut mapbuilder = self let mut data = self
.html .html
.book .book
.get_metadata(|s| self.render_vec(&Parser::new().parse_inline(s)?))? .get_metadata(|s| self.render_vec(&Parser::new().parse_inline(s)?))?;
.insert_str("content", content) data.insert("content".into(), content.into());
.insert_str("script", js) data.insert("script".into(), js.into());
.insert_bool(self.html.book.options.get_str("lang").unwrap(), true) data.insert(self.html.book.options.get_str("lang").unwrap().into(), true.into());
.insert_bool( data.insert(
"one_chapter", "one_chapter".into(),
self.html self.html
.book .book
.options .options
.get_bool("html.standalone.one_chapter") .get_bool("html.standalone.one_chapter")
.unwrap(), .unwrap()
) .into(),
.insert_str("style", css.as_ref()) );
.insert_str( data.insert("style".into(), css.into());
"print_style", data.insert(
self.html.book.get_template("html.css.print").unwrap(), "print_style".into(),
) self.html.book.get_template("html.css.print").unwrap().into(),
.insert_str("menu_svg", menu_svg) );
.insert_str("book_svg", book_svg) data.insert("menu_svg".into(), menu_svg.clone().into());
.insert_str("pages_svg", pages_svg) data.insert("book_svg".into(), book_svg.clone().into());
.insert_str("json_data", self.html.get_json_ld()?) data.insert("pages_svg".into(), pages_svg.clone().into());
.insert_str("footer", HtmlRenderer::get_footer(self)?) data.insert("json_data".into(), self.html.get_json_ld()?.into());
.insert_str("header", HtmlRenderer::get_header(self)?); data.insert("footer".into(), HtmlRenderer::get_footer(self)?.into());
data.insert("header".into(), HtmlRenderer::get_header(self)?.into());
if let Ok(favicon) = self.html.book.options.get_path("html.icon") { if let Ok(favicon) = self.html.book.options.get_path("html.icon") {
let favicon = self let favicon = self
.html .html
.handler .handler
.map_image(&self.html.book.source, favicon)?; .map_image(&self.html.book.source, favicon)?;
mapbuilder = mapbuilder.insert_str( data.insert(
"favicon", "favicon".into(),
format!("<link rel = \"icon\" href = \"{favicon}\">"), format!("<link rel = \"icon\" href = \"{favicon}\">").into(),
); );
} }
if !self.html.toc.is_empty() { if !self.html.toc.is_empty() {
mapbuilder = mapbuilder.insert_bool("has_toc", true); data.insert("has_toc".into(), true.into());
mapbuilder = mapbuilder.insert_str("toc", toc) data.insert("toc".into(), toc.into());
} }
if self.html.highlight == Highlight::Js { if self.html.highlight == Highlight::Js {
let highlight_js = misc::u8_to_base64(&self let highlight_js = misc::u8_to_base64(&self
@ -310,26 +308,20 @@ impl<'a> HtmlSingleRenderer<'a> {
.get_template("html.highlight.js")? .get_template("html.highlight.js")?
.as_bytes()); .as_bytes());
let highlight_js = format!("data:text/javascript;base64,{highlight_js}"); let highlight_js = format!("data:text/javascript;base64,{highlight_js}");
mapbuilder = mapbuilder data.insert("highlight_code".into(), true.into());
.insert_bool("highlight_code", true) data.insert(
.insert_str( "highlight_css".into(),
"highlight_css", self.html.book.get_template("html.highlight.css")?.into(),
self.html.book.get_template("html.highlight.css")?, );
) data.insert("highlight_js".into(), highlight_js.into());
.insert_str("highlight_js", highlight_js);
} }
let data = mapbuilder.build(); let template_src = self.html.book.get_template("html.standalone.template")?;
let template = compile_str( let template = self.html.book.compile_str(
self.html template_src.as_ref(),
.book
.get_template("html.standalone.template")?
.as_ref(),
&self.html.book.source, &self.html.book.source,
"html.standalone.template", "html.standalone.template",
)?; )?;
let mut res = vec![]; Ok(template.render(&data).to_string()?)
template.render_data(&mut res, &data)?;
Ok(String::from_utf8_lossy(&res).into_owned())
} }
} }

View File

@ -15,7 +15,7 @@
// You should have received ba copy of the GNU Lesser General Public License // You should have received ba copy of the GNU Lesser General Public License
// along with Crowbook. If not, see <http://www.gnu.org/licenses/>. // along with Crowbook. If not, see <http://www.gnu.org/licenses/>.
use crate::book::{compile_str, Book}; use crate::book::Book;
use crate::book_renderer::BookRenderer; use crate::book_renderer::BookRenderer;
use crate::error::{Error, Result, Source}; use crate::error::{Error, Result, Source};
use crate::number::Number; use crate::number::Number;
@ -39,7 +39,7 @@ use std::iter::Iterator;
/// LaTeX renderer /// LaTeX renderer
pub struct LatexRenderer<'a> { pub struct LatexRenderer<'a> {
book: &'a Book, book: &'a Book<'a>,
current_chapter: Number, current_chapter: Number,
handler: ResourceHandler, handler: ResourceHandler,
source: Source, source: Source,
@ -210,96 +210,80 @@ impl<'a> LatexRenderer<'a> {
} }
}); });
let template = compile_str( let template_src = self.book.get_template("tex.template")?;
self.book.get_template("tex.template")?.as_ref(), let template = self.book.compile_str(
template_src.as_ref(),
&self.book.source, &self.book.source,
"tex.template", "tex.template",
)?; )?;
let mut data = self let mut data = self
.book .book
.get_metadata(|s| self.render_vec(&Parser::new().parse_inline(s)?))? .get_metadata(|s| self.render_vec(&Parser::new().parse_inline(s)?))?;
.insert_str("content", content) data.insert("content".into(), content.into());
.insert_str("class", self.book.options.get_str("tex.class").unwrap()) data.insert("class".into(), self.book.options.get_str("tex.class").unwrap().into());
.insert_bool( data.insert("tex_title".into(), self.book.options.get_bool("tex.title").unwrap().into());
"tex_title", data.insert("papersize".into(), self.book.options.get_str("tex.paper.size").unwrap().into());
self.book.options.get_bool("tex.title").unwrap(), data.insert("stdpage".into(), self.book.options.get_bool("tex.stdpage").unwrap().into());
) data.insert("use_url".into(), self.book.features.url.into());
.insert_str( data.insert("use_taskitem".into(), self.book.features.taskitem.into());
"papersize", data.insert("use_tables".into(), self.book.features.table.into());
self.book.options.get_str("tex.paper.size").unwrap(), data.insert("use_codeblocks".into(), self.book.features.codeblock.into());
) data.insert("use_images".into(), self.book.features.image.into());
.insert_bool( data.insert("use_strikethrough".into(), self.book.features.strikethrough.into());
"stdpage", data.insert("tex_lang".into(), tex_lang.into());
self.book.options.get_bool("tex.stdpage").unwrap(),
)
.insert_bool("use_url", self.book.features.url)
.insert_bool("use_taskitem", self.book.features.taskitem)
.insert_bool("use_tables", self.book.features.table)
.insert_bool("use_codeblocks", self.book.features.codeblock)
.insert_bool("use_images", self.book.features.image)
.insert_bool("use_strikethrough", self.book.features.strikethrough)
.insert_str("tex_lang", tex_lang);
if let Ok(tex_tmpl_add) = self.book.options.get_str("tex.template.add") { if let Ok(tex_tmpl_add) = self.book.options.get_str("tex.template.add") {
data = data.insert_str("additional_code", tex_tmpl_add); data.insert("additional_code".into(), tex_tmpl_add.into());
} }
if let Ok(tex_font_size) = self.book.options.get_i32("tex.font.size") { if let Ok(tex_font_size) = self.book.options.get_i32("tex.font.size") {
data = data data.insert("has_tex_size".into(), true.into());
.insert_bool("has_tex_size", true) data.insert("tex_size".into(), format!("{tex_font_size}").into());
.insert_str("tex_size", format!("{tex_font_size}"));
} }
// If class isn't book, set open_any to true, so margins are symetric. // If class isn't book, set open_any to true, so margins are symetric.
let mut book = false; let mut book = false;
if self.book.options.get_str("tex.class").unwrap() == "book" { if self.book.options.get_str("tex.class").unwrap() == "book" {
data = data.insert_bool("book", true); data.insert("book".into(), true.into());
book = true; book = true;
} }
data = data data.insert(
.insert_str( "margin_left".into(),
"margin_left",
self.book self.book
.options .options
.get_str("tex.margin.left") .get_str("tex.margin.left")
.unwrap_or(if book { "2.5cm" } else { "2cm" }), .unwrap_or(if book { "2.5cm" } else { "2cm" }).into(),
) );
.insert_str( data.insert(
"margin_right", "margin_right".into(),
self.book self.book
.options .options
.get_str("tex.margin.right") .get_str("tex.margin.right")
.unwrap_or(if book { "1.5cm" } else { "2cm" }), .unwrap_or(if book { "1.5cm" } else { "2cm" }).into(),
) );
.insert_str( data.insert(
"margin_bottom", "margin_bottom".into(),
self.book.options.get_str("tex.margin.bottom").unwrap(), self.book.options.get_str("tex.margin.bottom").unwrap().into(),
) );
.insert_str( data.insert(
"margin_top", "margin_top".into(),
self.book.options.get_str("tex.margin.top").unwrap(), self.book.options.get_str("tex.margin.top").unwrap().into(),
); );
if let Ok(chapter_name) = self.book.options.get_str("rendering.chapter") { if let Ok(chapter_name) = self.book.options.get_str("rendering.chapter") {
data = data.insert_str("chapter_name", chapter_name); data.insert("chapter_name".into(), chapter_name.into());
} }
if let Ok(part_name) = self.book.options.get_str("rendering.part") { if let Ok(part_name) = self.book.options.get_str("rendering.part") {
data = data.insert_str("part_name", part_name); data.insert("part_name".into(), part_name.into());
} }
if self.book.options.get_bool("rendering.initials") == Ok(true) { if self.book.options.get_bool("rendering.initials") == Ok(true) {
data = data.insert_bool("initials", true); data.insert("initials".into(), true.into());
} }
// Insert xelatex if tex.command is set to xelatex or tectonic // Insert xelatex if tex.command is set to xelatex or tectonic
if (self.book.options.get_str("tex.command") == Ok("xelatex")) if (self.book.options.get_str("tex.command") == Ok("xelatex"))
| (self.book.options.get_str("tex.command") == Ok("tectonic")) | (self.book.options.get_str("tex.command") == Ok("tectonic"))
{ {
data = data.insert_bool("xelatex", true); data.insert("xelatex".into(), true.into());
}
let data = data.build();
let mut res: Vec<u8> = vec![];
template.render_data(&mut res, &data)?;
match String::from_utf8(res) {
Err(_) => panic!("{}", lformat!("generated LaTeX was not valid utf-8")),
Ok(res) => Ok(res),
} }
Ok(template.render(&data).to_string()?)
} }
} }

View File

@ -1,4 +1,4 @@
// Copyright (C) 2016, 2017 Élisabeth HENRY. // Copyright (C) 2016-2023 Élisabeth HENRY.
// //
// This file is part of Crowbook. // This file is part of Crowbook.
// //
@ -7,7 +7,7 @@
// by the Free Software Foundation, either version 2.1 of the License, or // by the Free Software Foundation, either version 2.1 of the License, or
// (at your option) any later version. // (at your option) any later version.
// //
// Caribon is distributed in the hope that it will be useful, // Crowbook is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of // but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Lesser General Public License for more details. // GNU Lesser General Public License for more details.