mirror of
https://github.com/lise-henry/crowbook
synced 2024-12-06 09:52:38 +01:00
Update dependencies and formatting.
This commit is contained in:
parent
f84c98438e
commit
b8d35ddc32
2349
Cargo.lock
generated
2349
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
34
build.rs
34
build.rs
@ -1,34 +1,44 @@
|
||||
use crowbook_intl::{Extractor, Localizer};
|
||||
|
||||
use crowbook_intl::{Localizer, Extractor};
|
||||
|
||||
use std::path::Path;
|
||||
use std::env;
|
||||
|
||||
use std::path::Path;
|
||||
|
||||
fn main() {
|
||||
println!("cargo:rerun-if-changed=build.rs");
|
||||
println!("cargo:rerun-if-changed=lang/fr.po");
|
||||
// Extract and localize src/lib
|
||||
let mut extractor = Extractor::new();
|
||||
extractor.add_messages_from_dir(concat!(env!("CARGO_MANIFEST_DIR"), "/src/lib")).unwrap();
|
||||
extractor
|
||||
.add_messages_from_dir(concat!(env!("CARGO_MANIFEST_DIR"), "/src/lib"))
|
||||
.unwrap();
|
||||
// Uncomment to update crowbook.pot
|
||||
//extractor.write_pot_file(concat!(env!("CARGO_MANIFEST_DIR"), "/lang/lib/crowbook.pot")).unwrap();
|
||||
|
||||
let mut localizer = Localizer::new(&extractor);
|
||||
localizer.add_lang("fr", include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/lang/lib/fr.po"))).unwrap();
|
||||
let dest_path = Path::new(&env::var("OUT_DIR").unwrap())
|
||||
.join("localize_macros.rs");
|
||||
localizer
|
||||
.add_lang(
|
||||
"fr",
|
||||
include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/lang/lib/fr.po")),
|
||||
)
|
||||
.unwrap();
|
||||
let dest_path = Path::new(&env::var("OUT_DIR").unwrap()).join("localize_macros.rs");
|
||||
localizer.write_macro_file(dest_path).unwrap();
|
||||
|
||||
// Extract and localize src/bin
|
||||
let mut extractor = Extractor::new();
|
||||
extractor.add_messages_from_dir(concat!(env!("CARGO_MANIFEST_DIR"), "/src/bin")).unwrap();
|
||||
extractor
|
||||
.add_messages_from_dir(concat!(env!("CARGO_MANIFEST_DIR"), "/src/bin"))
|
||||
.unwrap();
|
||||
// Uncomment to update crowbook.pot
|
||||
//extractor.write_pot_file(concat!(env!("CARGO_MANIFEST_DIR"), "/lang/bin/crowbook.pot")).unwrap();
|
||||
|
||||
let mut localizer = Localizer::new(&extractor);
|
||||
localizer.add_lang("fr", include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/lang/bin/fr.po"))).unwrap();
|
||||
let dest_path = Path::new(&env::var("OUT_DIR").unwrap())
|
||||
.join("localize_macros_bin.rs");
|
||||
localizer
|
||||
.add_lang(
|
||||
"fr",
|
||||
include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/lang/bin/fr.po")),
|
||||
)
|
||||
.unwrap();
|
||||
let dest_path = Path::new(&env::var("OUT_DIR").unwrap()).join("localize_macros_bin.rs");
|
||||
localizer.write_macro_file(dest_path).unwrap();
|
||||
}
|
||||
|
@ -15,15 +15,14 @@
|
||||
// You should have received a copy of the GNU Lesser General Public License
|
||||
// along with Crowbook. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
use crowbook::Book;
|
||||
use clap::{App, Arg, ArgMatches, AppSettings};
|
||||
use clap::{App, AppSettings, Arg, ArgMatches};
|
||||
use console::style;
|
||||
use crowbook::Book;
|
||||
|
||||
use std::env;
|
||||
use std::fs;
|
||||
use std::io::{self, Write};
|
||||
use std::process::exit;
|
||||
use std::fs;
|
||||
use std::env;
|
||||
|
||||
|
||||
static BIRD: &str = "🐦 ";
|
||||
static ERROR: &str = "💣 ";
|
||||
@ -34,9 +33,7 @@ pub fn print_warning(msg: &str, emoji: bool) {
|
||||
if emoji {
|
||||
eprint!("{}", style(WARNING).yellow());
|
||||
}
|
||||
eprintln!("{} {}",
|
||||
style(lformat!("WARNING")).bold().yellow(),
|
||||
msg);
|
||||
eprintln!("{} {}", style(lformat!("WARNING")).bold().yellow(), msg);
|
||||
}
|
||||
|
||||
/// Prints an error
|
||||
@ -44,9 +41,7 @@ pub fn print_error(s: &str, emoji: bool) {
|
||||
if emoji {
|
||||
eprint!("{}", style(ERROR).red());
|
||||
}
|
||||
eprintln!("{} {}",
|
||||
style(lformat!("ERROR")).bold().red(),
|
||||
s);
|
||||
eprintln!("{} {}", style(lformat!("ERROR")).bold().red(), s);
|
||||
}
|
||||
|
||||
/// Prints an error on stderr and exit the program
|
||||
@ -55,7 +50,6 @@ pub fn print_error_and_exit(s: &str, emoji: bool) -> ! {
|
||||
exit(0);
|
||||
}
|
||||
|
||||
|
||||
/// Display version number
|
||||
pub fn display_header(emoji: bool) {
|
||||
if emoji {
|
||||
@ -81,15 +75,19 @@ pub fn get_lang() -> Option<String> {
|
||||
None
|
||||
}
|
||||
|
||||
|
||||
/// Gets the book options in a (key, value) list, or print an error
|
||||
pub fn get_book_options<'a>(matches: &'a ArgMatches) -> Vec<(&'a str, &'a str)> {
|
||||
let mut output = vec![];
|
||||
if let Some(iter) = matches.values_of("set") {
|
||||
let v: Vec<_> = iter.collect();
|
||||
if v.len() % 2 != 0 {
|
||||
print_error_and_exit(&lformat!("An odd number of arguments was passed to --set, but it takes \
|
||||
a list of key value pairs."), false);
|
||||
print_error_and_exit(
|
||||
&lformat!(
|
||||
"An odd number of arguments was passed to --set, but it takes \
|
||||
a list of key value pairs."
|
||||
),
|
||||
false,
|
||||
);
|
||||
}
|
||||
|
||||
for i in 0..v.len() / 2 {
|
||||
@ -104,7 +102,6 @@ pub fn get_book_options<'a>(matches: &'a ArgMatches) -> Vec<(&'a str, &'a str)>
|
||||
output
|
||||
}
|
||||
|
||||
|
||||
/// Sets the book options according to command line arguments
|
||||
/// Also print these options to a string, so it can be used at
|
||||
/// the creation of a book to check that parameters are OK and
|
||||
@ -128,7 +125,10 @@ pub fn set_book_options(book: &mut Book, matches: &ArgMatches) -> String {
|
||||
pub fn create_book(matches: &ArgMatches) -> ! {
|
||||
let mut f: Box<dyn Write> = if let Some(book) = matches.value_of("BOOK") {
|
||||
if fs::metadata(book).is_ok() {
|
||||
print_error_and_exit(&lformat!("Could not create file {}: it already exists!", book), false);
|
||||
print_error_and_exit(
|
||||
&lformat!("Could not create file {}: it already exists!", book),
|
||||
false,
|
||||
);
|
||||
}
|
||||
Box::new(fs::File::create(book).unwrap())
|
||||
} else {
|
||||
@ -141,7 +141,9 @@ pub fn create_book(matches: &ArgMatches) -> ! {
|
||||
let s = set_book_options(&mut book, matches);
|
||||
f.write_all(s.as_bytes()).unwrap();
|
||||
} else {
|
||||
f.write_all(lformat!("author: Your name
|
||||
f.write_all(
|
||||
lformat!(
|
||||
"author: Your name
|
||||
title: Your title
|
||||
lang: en
|
||||
|
||||
@ -156,16 +158,22 @@ lang: en
|
||||
# output: [pdf, epub, html]
|
||||
|
||||
# Uncomment and fill to set cover image (for EPUB)
|
||||
# cover: some_cover.png\n").as_bytes())
|
||||
.unwrap();
|
||||
# cover: some_cover.png\n"
|
||||
)
|
||||
.as_bytes(),
|
||||
)
|
||||
.unwrap();
|
||||
}
|
||||
f.write_all(lformat!("\n## List of chapters\n").as_bytes()).unwrap();
|
||||
f.write_all(lformat!("\n## List of chapters\n").as_bytes())
|
||||
.unwrap();
|
||||
for file in values {
|
||||
f.write_all(format!("+ {}\n", file).as_bytes()).unwrap();
|
||||
}
|
||||
if let Some(s) = matches.value_of("BOOK") {
|
||||
println!("{}",
|
||||
lformat!("Created {}, now you'll have to complete it!", s));
|
||||
println!(
|
||||
"{}",
|
||||
lformat!("Created {}, now you'll have to complete it!", s)
|
||||
);
|
||||
}
|
||||
exit(0);
|
||||
} else {
|
||||
@ -213,7 +221,6 @@ ARGS:
|
||||
");
|
||||
}
|
||||
|
||||
|
||||
let app = App::new("crowbook")
|
||||
.version(env!("CARGO_PKG_VERSION"))
|
||||
.author("Élisabeth Henry <liz.henry@ouvaton.org>")
|
||||
@ -225,42 +232,51 @@ ARGS:
|
||||
.arg(Arg::from_usage("-n, --no-fancy").help(NO_FANCY.as_str()))
|
||||
.arg(Arg::from_usage("-v, --verbose").help(VERBOSE.as_str()))
|
||||
.arg(Arg::from_usage("-a, --autograph").help(AUTOGRAPH.as_str()))
|
||||
.arg(Arg::from_usage("-q, --quiet")
|
||||
.help(QUIET.as_str())
|
||||
.conflicts_with("verbose"))
|
||||
.arg(
|
||||
Arg::from_usage("-q, --quiet")
|
||||
.help(QUIET.as_str())
|
||||
.conflicts_with("verbose"),
|
||||
)
|
||||
.arg(Arg::from_usage("-h, --help").help(HELP.as_str()))
|
||||
.arg(Arg::from_usage("-V, --version").help(VERSION.as_str()))
|
||||
.arg(Arg::from_usage("-p, --proofread").help(PROOFREAD.as_str()))
|
||||
.arg(Arg::from_usage("-c, --create [FILES]...").help(CREATE.as_str()))
|
||||
.arg(Arg::from_usage("-o, --output [FILE]")
|
||||
.help(OUTPUT.as_str())
|
||||
.requires("to"))
|
||||
.arg(Arg::from_usage("-t, --to [FORMAT]")
|
||||
.help(TO.as_str())
|
||||
.possible_values(&["epub",
|
||||
"pdf",
|
||||
"html",
|
||||
"tex",
|
||||
"odt",
|
||||
"html.dir",
|
||||
"proofread.html",
|
||||
"proofread.html.dir",
|
||||
"proofread.pdf",
|
||||
"proofread.tex"]))
|
||||
.arg(Arg::from_usage("--set [KEY_VALUES]")
|
||||
.help(SET.as_str())
|
||||
.min_values(2))
|
||||
.arg(
|
||||
Arg::from_usage("-o, --output [FILE]")
|
||||
.help(OUTPUT.as_str())
|
||||
.requires("to"),
|
||||
)
|
||||
.arg(
|
||||
Arg::from_usage("-t, --to [FORMAT]")
|
||||
.help(TO.as_str())
|
||||
.possible_values(&[
|
||||
"epub",
|
||||
"pdf",
|
||||
"html",
|
||||
"tex",
|
||||
"odt",
|
||||
"html.dir",
|
||||
"proofread.html",
|
||||
"proofread.html.dir",
|
||||
"proofread.pdf",
|
||||
"proofread.tex",
|
||||
]),
|
||||
)
|
||||
.arg(
|
||||
Arg::from_usage("--set [KEY_VALUES]")
|
||||
.help(SET.as_str())
|
||||
.min_values(2),
|
||||
)
|
||||
.arg(Arg::from_usage("-l --list-options").help(LIST_OPTIONS.as_str()))
|
||||
.arg(Arg::from_usage("--list-options-md")
|
||||
.help(LIST_OPTIONS_MD.as_str())
|
||||
.hidden(true))
|
||||
.arg(Arg::from_usage("-L --lang [LANG]")
|
||||
.help(LANG.as_str()))
|
||||
.arg(
|
||||
Arg::from_usage("--list-options-md")
|
||||
.help(LIST_OPTIONS_MD.as_str())
|
||||
.hidden(true),
|
||||
)
|
||||
.arg(Arg::from_usage("-L --lang [LANG]").help(LANG.as_str()))
|
||||
.arg(Arg::from_usage("--print-template [TEMPLATE]").help(PRINT_TEMPLATE.as_str()))
|
||||
.arg(Arg::from_usage("--stats -S").help(STATS.as_str()))
|
||||
.arg(Arg::with_name("BOOK")
|
||||
.index(1)
|
||||
.help(BOOK.as_str()))
|
||||
.arg(Arg::with_name("BOOK").index(1).help(BOOK.as_str()))
|
||||
.template(TEMPLATE.as_str());
|
||||
|
||||
// Write help and version now since it `app` is moved when `get_matches` is run
|
||||
@ -277,11 +293,15 @@ ARGS:
|
||||
(matches, help, version)
|
||||
}
|
||||
|
||||
|
||||
/// Pre-check the matches to see if there isn't illegal options not detected by clap
|
||||
fn pre_check(matches: &ArgMatches) {
|
||||
if matches.is_present("files") && !matches.is_present("create") {
|
||||
print_error_and_exit(&lformat!("A list of additional files is only valid with the --create \
|
||||
option."), false);
|
||||
print_error_and_exit(
|
||||
&lformat!(
|
||||
"A list of additional files is only valid with the --create \
|
||||
option."
|
||||
),
|
||||
false,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -2,25 +2,22 @@ extern crate crowbook;
|
||||
extern crate crowbook_intl_runtime;
|
||||
extern crate yaml_rust;
|
||||
|
||||
#[cfg(feature= "binary")]
|
||||
extern crate simplelog;
|
||||
#[cfg(feature = "binary")]
|
||||
extern crate clap;
|
||||
#[cfg(feature = "binary")]
|
||||
extern crate tempdir;
|
||||
#[cfg(feature= "binary")]
|
||||
extern crate console;
|
||||
|
||||
#[cfg(feature = "binary")]
|
||||
extern crate simplelog;
|
||||
#[cfg(feature = "binary")]
|
||||
extern crate tempdir;
|
||||
|
||||
#[macro_use]
|
||||
mod localize_macros;
|
||||
#[cfg(feature = "binary")]
|
||||
mod real_main;
|
||||
#[cfg(feature = "binary")]
|
||||
mod helpers;
|
||||
#[cfg(feature = "binary")]
|
||||
|
||||
|
||||
mod real_main;
|
||||
#[cfg(feature = "binary")]
|
||||
#[cfg(feature = "binary")]
|
||||
#[macro_use]
|
||||
extern crate lazy_static;
|
||||
|
@ -17,19 +17,19 @@
|
||||
|
||||
use crate::helpers::*;
|
||||
|
||||
use yaml_rust::Yaml;
|
||||
use console;
|
||||
use crowbook::{Result, Book, BookOptions};
|
||||
use crowbook_intl_runtime::set_lang;
|
||||
use crowbook::Stats;
|
||||
use tempdir::TempDir;
|
||||
use clap::ArgMatches;
|
||||
use std::process::exit;
|
||||
use std::io;
|
||||
use std::io::Read;
|
||||
use console;
|
||||
use crowbook::Stats;
|
||||
use crowbook::{Book, BookOptions, Result};
|
||||
use crowbook_intl_runtime::set_lang;
|
||||
use simplelog::{ConfigBuilder, LevelFilter, SimpleLogger, TermLogger, WriteLogger};
|
||||
use std::env;
|
||||
use std::fs::File;
|
||||
use simplelog::{ConfigBuilder, TermLogger, LevelFilter, SimpleLogger, WriteLogger};
|
||||
use std::io;
|
||||
use std::io::Read;
|
||||
use std::process::exit;
|
||||
use tempdir::TempDir;
|
||||
use yaml_rust::Yaml;
|
||||
|
||||
/// Render a book to specific format
|
||||
fn render_format(book: &mut Book, emoji: bool, matches: &ArgMatches, format: &str) {
|
||||
@ -49,13 +49,10 @@ fn render_format(book: &mut Book, emoji: bool, matches: &ArgMatches, format: &st
|
||||
|
||||
let res = book.options.get_path(&key);
|
||||
|
||||
let result = match(file, res, stdout) {
|
||||
(Some(file), _, _) |
|
||||
(None, Ok(file), false) => book.render_format_to_file(format, file),
|
||||
let result = match (file, res, stdout) {
|
||||
(Some(file), _, _) | (None, Ok(file), false) => book.render_format_to_file(format, file),
|
||||
|
||||
(None, Err(_), _) |
|
||||
(None, _, true)
|
||||
=> book.render_format_to(format, &mut io::stdout()),
|
||||
(None, Err(_), _) | (None, _, true) => book.render_format_to(format, &mut io::stdout()),
|
||||
};
|
||||
|
||||
match result {
|
||||
@ -65,15 +62,10 @@ fn render_format(book: &mut Book, emoji: bool, matches: &ArgMatches, format: &st
|
||||
}
|
||||
|
||||
pub fn try_main() -> Result<()> {
|
||||
let lang = get_lang()
|
||||
.or_else(|| {
|
||||
match env::var("LANG") {
|
||||
Ok(val) => {
|
||||
Some(val)
|
||||
},
|
||||
Err(_) => None,
|
||||
}
|
||||
});
|
||||
let lang = get_lang().or_else(|| match env::var("LANG") {
|
||||
Ok(val) => Some(val),
|
||||
Err(_) => None,
|
||||
});
|
||||
if let Some(val) = lang {
|
||||
if val.starts_with("fr") {
|
||||
set_lang("fr");
|
||||
@ -120,9 +112,10 @@ pub fn try_main() -> Result<()> {
|
||||
println!("{}", s);
|
||||
exit(0);
|
||||
}
|
||||
Err(_) => print_error_and_exit(&lformat!("{} is not a valid template name.",
|
||||
template),
|
||||
emoji),
|
||||
Err(_) => print_error_and_exit(
|
||||
&lformat!("{} is not a valid template name.", template),
|
||||
emoji,
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
@ -141,14 +134,16 @@ pub fn try_main() -> Result<()> {
|
||||
}
|
||||
|
||||
if !matches.is_present("BOOK") {
|
||||
print_error_and_exit(&lformat!("You must pass the file of a book configuration \
|
||||
print_error_and_exit(
|
||||
&lformat!(
|
||||
"You must pass the file of a book configuration \
|
||||
file.\n\n{}\n\nFor more information try --help.",
|
||||
matches.usage()),
|
||||
emoji);
|
||||
matches.usage()
|
||||
),
|
||||
emoji,
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
|
||||
// ok to unwrap since clap checks it's there
|
||||
let s = matches.value_of("BOOK").unwrap();
|
||||
|
||||
@ -172,14 +167,19 @@ pub fn try_main() -> Result<()> {
|
||||
};
|
||||
let log_config = builder.build();
|
||||
|
||||
|
||||
let error_dir = TempDir::new("crowbook").unwrap();
|
||||
let error_path = "error.log";
|
||||
if fancy_ui {
|
||||
let errors = File::create(error_dir.path().join(error_path)).unwrap();
|
||||
let _ = WriteLogger::init(verbosity, log_config, errors);
|
||||
} else {
|
||||
if TermLogger::init(verbosity, log_config.clone(), simplelog::TerminalMode::Stderr).is_err() {
|
||||
if TermLogger::init(
|
||||
verbosity,
|
||||
log_config.clone(),
|
||||
simplelog::TerminalMode::Stderr,
|
||||
)
|
||||
.is_err()
|
||||
{
|
||||
// If it failed, not much we can do, we just won't display log
|
||||
let _ = SimpleLogger::init(verbosity, log_config);
|
||||
}
|
||||
@ -192,8 +192,13 @@ pub fn try_main() -> Result<()> {
|
||||
let mut autograph = String::new();
|
||||
match io::stdin().read_to_string(&mut autograph) {
|
||||
Ok(_) => {
|
||||
book.options.set_yaml(Yaml::String("autograph".to_string()), Yaml::String(autograph)).unwrap();
|
||||
},
|
||||
book.options
|
||||
.set_yaml(
|
||||
Yaml::String("autograph".to_string()),
|
||||
Yaml::String(autograph),
|
||||
)
|
||||
.unwrap();
|
||||
}
|
||||
Err(_) => print_error(&lformat!("could not read autograph from stdin"), emoji),
|
||||
}
|
||||
}
|
||||
@ -214,10 +219,11 @@ pub fn try_main() -> Result<()> {
|
||||
book.load_file(s)
|
||||
} else {
|
||||
book.read_config(io::stdin())
|
||||
}.map(|_| ());
|
||||
}
|
||||
.map(|_| ());
|
||||
|
||||
match res {
|
||||
Ok(..) => {},
|
||||
Ok(..) => {}
|
||||
Err(err) => {
|
||||
book.set_error(&format!("{}", err));
|
||||
return Err(err);
|
||||
@ -234,7 +240,7 @@ pub fn try_main() -> Result<()> {
|
||||
}
|
||||
|
||||
if let Some(format) = matches.value_of("to") {
|
||||
render_format(&mut book, emoji,& matches, format);
|
||||
render_format(&mut book, emoji, &matches, format);
|
||||
} else {
|
||||
book.render_all();
|
||||
}
|
||||
@ -244,10 +250,12 @@ pub fn try_main() -> Result<()> {
|
||||
let mut file = File::open(error_dir.path().join(error_path)).unwrap();
|
||||
file.read_to_string(&mut errors).unwrap();
|
||||
if !errors.is_empty() {
|
||||
print_warning(&lformat!("Crowbook exited successfully, but the following errors occurred:"),
|
||||
emoji);
|
||||
print_warning(
|
||||
&lformat!("Crowbook exited successfully, but the following errors occurred:"),
|
||||
emoji,
|
||||
);
|
||||
// Non-efficient dedup algorithm but we need to keep the order
|
||||
let mut lines: Vec<String> = vec!();
|
||||
let mut lines: Vec<String> = vec![];
|
||||
for line in errors.lines().into_iter() {
|
||||
let mut contains = false;
|
||||
for l in &lines {
|
||||
@ -272,7 +280,6 @@ pub fn try_main() -> Result<()> {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
846
src/lib/book.rs
846
src/lib/book.rs
@ -15,34 +15,34 @@
|
||||
// You should have received a copy of the GNU Lesser General Public License
|
||||
// along with Crowbook. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
use crate::error::{Error, Result, Source};
|
||||
use crate::cleaner::{Cleaner, CleanerParams, French, Off, Default};
|
||||
use crate::bookoptions::BookOptions;
|
||||
use crate::parser::Parser;
|
||||
use crate::parser::Features;
|
||||
use crate::epub::{Epub};
|
||||
use crate::html_single::{HtmlSingle, ProofHtmlSingle};
|
||||
use crate::html_dir::{HtmlDir, ProofHtmlDir};
|
||||
use crate::html_if::{HtmlIf};
|
||||
use crate::latex::{Latex, ProofLatex, Pdf, ProofPdf};
|
||||
use crate::odt::{Odt};
|
||||
use crate::templates::{epub, html, epub3, latex, html_dir, highlight, html_single, html_if};
|
||||
use crate::number::Number;
|
||||
use crate::resource_handler::ResourceHandler;
|
||||
use crate::lang;
|
||||
use crate::misc;
|
||||
use crate::book_renderer::BookRenderer;
|
||||
use crate::chapter::Chapter;
|
||||
use crate::token::Token;
|
||||
use crate::text_view::view_as_text;
|
||||
use crate::book_bars::Bars;
|
||||
use crate::book_renderer::BookRenderer;
|
||||
use crate::bookoptions::BookOptions;
|
||||
use crate::chapter::Chapter;
|
||||
use crate::cleaner::{Cleaner, CleanerParams, Default, French, Off};
|
||||
use crate::epub::Epub;
|
||||
use crate::error::{Error, Result, Source};
|
||||
use crate::html_dir::{HtmlDir, ProofHtmlDir};
|
||||
use crate::html_if::HtmlIf;
|
||||
use crate::html_single::{HtmlSingle, ProofHtmlSingle};
|
||||
use crate::lang;
|
||||
use crate::latex::{Latex, Pdf, ProofLatex, ProofPdf};
|
||||
use crate::misc;
|
||||
use crate::number::Number;
|
||||
use crate::odt::Odt;
|
||||
use crate::parser::Features;
|
||||
use crate::parser::Parser;
|
||||
use crate::resource_handler::ResourceHandler;
|
||||
use crate::templates::{epub, epub3, highlight, html, html_dir, html_if, html_single, latex};
|
||||
use crate::text_view::view_as_text;
|
||||
use crate::token::Token;
|
||||
|
||||
#[cfg(feature = "proofread")]
|
||||
use crate::repetition_check::RepetitionDetector;
|
||||
use crate::grammalecte::GrammalecteChecker;
|
||||
#[cfg(feature = "proofread")]
|
||||
use crate::grammar_check::GrammarChecker;
|
||||
#[cfg(feature = "proofread")]
|
||||
use crate::grammalecte::GrammalecteChecker;
|
||||
use crate::repetition_check::RepetitionDetector;
|
||||
// Dummy grammarchecker thas does nothing to let the compiler compile
|
||||
#[cfg(not(feature = "proofread"))]
|
||||
struct GrammarChecker {}
|
||||
@ -71,21 +71,20 @@ impl RepetitionDetector {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
use std::fs::File;
|
||||
use std::io::{Write, Read};
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::borrow::Cow;
|
||||
use std::iter::IntoIterator;
|
||||
use std::collections::HashMap;
|
||||
use std::cmp::Ordering;
|
||||
use std::collections::HashMap;
|
||||
use std::fmt;
|
||||
use std::fs::File;
|
||||
use std::io::{Read, Write};
|
||||
use std::iter::IntoIterator;
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
use rayon::prelude::*;
|
||||
use mustache;
|
||||
use mustache::{MapBuilder, Template};
|
||||
use yaml_rust::{YamlLoader, Yaml};
|
||||
use numerals::roman::Roman;
|
||||
use rayon::prelude::*;
|
||||
use yaml_rust::{Yaml, YamlLoader};
|
||||
|
||||
/// Type of header (part or chapter)
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
@ -96,7 +95,6 @@ pub enum Header {
|
||||
Part,
|
||||
}
|
||||
|
||||
|
||||
/// Header data (for chapter or part)
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct HeaderData {
|
||||
@ -126,7 +124,6 @@ pub enum CrowbarState {
|
||||
Error,
|
||||
}
|
||||
|
||||
|
||||
impl fmt::Display for HeaderData {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
write!(f, "{}", self.text)
|
||||
@ -209,17 +206,45 @@ impl Book {
|
||||
features: Features::new(),
|
||||
bars: Bars::new(),
|
||||
};
|
||||
book.add_format("html", lformat!("HTML (standalone page)"), Box::new(HtmlSingle{}))
|
||||
.add_format("proofread.html", lformat!("HTML (standalone page/proofreading)"), Box::new(ProofHtmlSingle{}))
|
||||
.add_format("html.dir", lformat!("HTML (multiple pages)"), 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("proofread.tex", lformat!("LaTeX (proofreading)"), Box::new(ProofLatex{}))
|
||||
.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("odt", lformat!("ODT"), Box::new(Odt{}))
|
||||
.add_format("html.if", lformat!("HTML (interactive fiction)"), Box::new(HtmlIf{}));
|
||||
book.add_format(
|
||||
"html",
|
||||
lformat!("HTML (standalone page)"),
|
||||
Box::new(HtmlSingle {}),
|
||||
)
|
||||
.add_format(
|
||||
"proofread.html",
|
||||
lformat!("HTML (standalone page/proofreading)"),
|
||||
Box::new(ProofHtmlSingle {}),
|
||||
)
|
||||
.add_format(
|
||||
"html.dir",
|
||||
lformat!("HTML (multiple pages)"),
|
||||
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(
|
||||
"proofread.tex",
|
||||
lformat!("LaTeX (proofreading)"),
|
||||
Box::new(ProofLatex {}),
|
||||
)
|
||||
.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("odt", lformat!("ODT"), Box::new(Odt {}))
|
||||
.add_format(
|
||||
"html.if",
|
||||
lformat!("HTML (interactive fiction)"),
|
||||
Box::new(HtmlIf {}),
|
||||
);
|
||||
book
|
||||
}
|
||||
|
||||
@ -228,8 +253,6 @@ impl Book {
|
||||
self.bar_finish(Crowbar::Main, CrowbarState::Error, msg)
|
||||
}
|
||||
|
||||
|
||||
|
||||
/// Adds a progress bar where where info should be written.
|
||||
///
|
||||
/// See [indicatif doc](https://docs.rs/indicatif) for more information.
|
||||
@ -259,10 +282,12 @@ impl Book {
|
||||
/// "Some dummy implementation",
|
||||
/// Box::new(Dummy{}));
|
||||
/// ```
|
||||
pub fn add_format<S: Into<String>>(&mut self,
|
||||
format: &'static str,
|
||||
description: S,
|
||||
renderer: Box<dyn BookRenderer>) -> &mut Self {
|
||||
pub fn add_format<S: Into<String>>(
|
||||
&mut self,
|
||||
format: &'static str,
|
||||
description: S,
|
||||
renderer: Box<dyn BookRenderer>,
|
||||
) -> &mut Self {
|
||||
self.formats.insert(format, (description.into(), renderer));
|
||||
self
|
||||
}
|
||||
@ -282,15 +307,21 @@ impl Book {
|
||||
/// assert_eq!(book.options.get_str("title").unwrap(), "Bar");
|
||||
/// ```
|
||||
pub fn set_options<'a, I>(&mut self, options: I) -> &mut Book
|
||||
where I: IntoIterator<Item = &'a (&'a str, &'a str)>
|
||||
where
|
||||
I: IntoIterator<Item = &'a (&'a str, &'a str)>,
|
||||
{
|
||||
// set options
|
||||
for &(key, value) in options {
|
||||
if let Err(err) = self.options.set(key, value) {
|
||||
error!("{}", lformat!("Error initializing book: could not set {key} to {value}: {error}",
|
||||
key = key,
|
||||
value = value,
|
||||
error = err));
|
||||
error!(
|
||||
"{}",
|
||||
lformat!(
|
||||
"Error initializing book: could not set {key} to {value}: {error}",
|
||||
key = key,
|
||||
value = value,
|
||||
error = err
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
// set cleaner according to lang and autoclean settings
|
||||
@ -317,10 +348,9 @@ impl Book {
|
||||
self.source = Source::new(filename.as_str());
|
||||
self.options.source = Source::new(filename.as_str());
|
||||
|
||||
let f = File::open(path.as_ref())
|
||||
.map_err(|_| {
|
||||
Error::file_not_found(Source::empty(), lformat!("book"), filename.clone())
|
||||
})?;
|
||||
let f = File::open(path.as_ref()).map_err(|_| {
|
||||
Error::file_not_found(Source::empty(), lformat!("book"), filename.clone())
|
||||
})?;
|
||||
// Set book path to book's directory
|
||||
if let Some(parent) = path.as_ref().parent() {
|
||||
self.root = parent.to_owned();
|
||||
@ -332,11 +362,15 @@ impl Book {
|
||||
Ok(book) => Ok(book),
|
||||
Err(err) => {
|
||||
if err.is_config_parser() && path.as_ref().ends_with(".md") {
|
||||
let err = Error::default(Source::empty(),
|
||||
lformat!("could not parse {file} as a book \
|
||||
let err = Error::default(
|
||||
Source::empty(),
|
||||
lformat!(
|
||||
"could not parse {file} as a book \
|
||||
file.\nMaybe you meant to run crowbook \
|
||||
with the --single argument?",
|
||||
file = misc::normalize(path)));
|
||||
file = misc::normalize(path)
|
||||
),
|
||||
);
|
||||
Err(err)
|
||||
} else {
|
||||
Err(err)
|
||||
@ -360,7 +394,7 @@ impl Book {
|
||||
/// let mut book = Book::new();
|
||||
/// 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<&mut Self> {
|
||||
let filename = format!("{}", path.as_ref().display());
|
||||
self.source = Source::new(filename.as_str());
|
||||
|
||||
@ -374,12 +408,7 @@ impl Book {
|
||||
|
||||
// Add the file as chapter with hidden title
|
||||
// hideous line, but basically transforms foo/bar/baz.md to baz.md
|
||||
let relative_path = Path::new(path
|
||||
.as_ref()
|
||||
.components()
|
||||
.last()
|
||||
.unwrap()
|
||||
.as_os_str());
|
||||
let relative_path = Path::new(path.as_ref().components().last().unwrap().as_os_str());
|
||||
|
||||
// Update grammar checker according to options
|
||||
self.add_chapter(Number::Hidden, &relative_path.to_string_lossy(), false)?;
|
||||
@ -424,9 +453,10 @@ impl Book {
|
||||
self.options.source = self.source.clone();
|
||||
match YamlLoader::load_from_str(yaml) {
|
||||
Err(err) => {
|
||||
return Err(Error::config_parser(&self.source,
|
||||
lformat!("YAML block was not valid YAML: {error}",
|
||||
error = err)))
|
||||
return Err(Error::config_parser(
|
||||
&self.source,
|
||||
lformat!("YAML block was not valid YAML: {error}", error = err),
|
||||
))
|
||||
}
|
||||
Ok(mut docs) => {
|
||||
if docs.len() == 1 && docs[0].as_hash().is_some() {
|
||||
@ -440,9 +470,13 @@ impl Book {
|
||||
unreachable!();
|
||||
}
|
||||
} else {
|
||||
return Err(Error::config_parser(&self.source,
|
||||
lformat!("YAML part of the book is not a \
|
||||
valid hashmap")));
|
||||
return Err(Error::config_parser(
|
||||
&self.source,
|
||||
lformat!(
|
||||
"YAML part of the book is not a \
|
||||
valid hashmap"
|
||||
),
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -482,11 +516,18 @@ impl Book {
|
||||
fn get_filename<'a>(source: &Source, s: &'a str) -> Result<&'a str> {
|
||||
let words: Vec<&str> = (&s[1..]).split_whitespace().collect();
|
||||
if words.len() > 1 {
|
||||
return Err(Error::config_parser(source,
|
||||
lformat!("chapter filenames must not contain \
|
||||
whitespace")));
|
||||
return Err(Error::config_parser(
|
||||
source,
|
||||
lformat!(
|
||||
"chapter filenames must not contain \
|
||||
whitespace"
|
||||
),
|
||||
));
|
||||
} else if words.len() < 1 {
|
||||
return Err(Error::config_parser(source, lformat!("no chapter name specified")));
|
||||
return Err(Error::config_parser(
|
||||
source,
|
||||
lformat!("no chapter name specified"),
|
||||
));
|
||||
}
|
||||
Ok(words[0])
|
||||
}
|
||||
@ -494,10 +535,12 @@ impl Book {
|
||||
self.bar_set_message(Crowbar::Main, &lformat!("setting options"));
|
||||
|
||||
let mut s = String::new();
|
||||
source.read_to_string(&mut s)
|
||||
.map_err(|err| Error::config_parser(Source::empty(),
|
||||
lformat!("could not read source: {error}",
|
||||
error = err)))?;
|
||||
source.read_to_string(&mut s).map_err(|err| {
|
||||
Error::config_parser(
|
||||
Source::empty(),
|
||||
lformat!("could not read source: {error}", error = err),
|
||||
)
|
||||
})?;
|
||||
|
||||
// Parse the YAML block, that is, until first chapter
|
||||
let mut yaml = String::new();
|
||||
@ -510,7 +553,7 @@ impl Book {
|
||||
loop {
|
||||
if let Some(next_line) = lines.peek() {
|
||||
if next_line.starts_with(|c| match c {
|
||||
'-' | '+' | '!' | '@' => true,
|
||||
'-' | '+' | '!' | '@' => true,
|
||||
_ => c.is_digit(10),
|
||||
}) {
|
||||
break;
|
||||
@ -588,7 +631,8 @@ impl Book {
|
||||
let line = line.trim();
|
||||
if line.is_empty() || line.starts_with('#') {
|
||||
continue;
|
||||
} if line.starts_with("--") {
|
||||
}
|
||||
if line.starts_with("--") {
|
||||
// Subchapter
|
||||
let mut level = 0;
|
||||
for b in line.bytes() {
|
||||
@ -616,29 +660,35 @@ impl Book {
|
||||
self.add_chapter(Number::Hidden, file, false)?;
|
||||
} else if line.starts_with(|c: char| c.is_digit(10)) {
|
||||
// chapter with specific number
|
||||
let parts: Vec<_> = line.splitn(2, |c: char| c == '.' || c == ':' || c == '+')
|
||||
let parts: Vec<_> = line
|
||||
.splitn(2, |c: char| c == '.' || c == ':' || c == '+')
|
||||
.collect();
|
||||
if parts.len() != 2 {
|
||||
return Err(Error::config_parser(&self.source,
|
||||
lformat!("ill-formatted line specifying \
|
||||
chapter number")));
|
||||
return Err(Error::config_parser(
|
||||
&self.source,
|
||||
lformat!(
|
||||
"ill-formatted line specifying \
|
||||
chapter number"
|
||||
),
|
||||
));
|
||||
}
|
||||
let file = get_filename(&self.source, parts[1])?;
|
||||
let number = parts[0].parse::<i32>()
|
||||
.map_err(|err| {
|
||||
Error::config_parser(&self.source,
|
||||
lformat!("error parsing chapter number: {error}",
|
||||
error = err))})?;
|
||||
let number = parts[0].parse::<i32>().map_err(|err| {
|
||||
Error::config_parser(
|
||||
&self.source,
|
||||
lformat!("error parsing chapter number: {error}", error = err),
|
||||
)
|
||||
})?;
|
||||
self.add_chapter(Number::Specified(number), file, true)?;
|
||||
} else if line.starts_with('@') {
|
||||
/* Part */
|
||||
let subline = &line[1..];
|
||||
if subline.starts_with(|c: char| c.is_whitespace()) {
|
||||
let subline = subline.trim();
|
||||
let ast = Parser::from(&self)
|
||||
.parse_inline(subline)?;
|
||||
let ast = vec!(Token::Header(1, ast));
|
||||
self.chapters.push(Chapter::new(Number::DefaultPart, String::new(), ast));
|
||||
let ast = Parser::from(&self).parse_inline(subline)?;
|
||||
let ast = vec![Token::Header(1, ast)];
|
||||
self.chapters
|
||||
.push(Chapter::new(Number::DefaultPart, String::new(), ast));
|
||||
} else if subline.starts_with('-') {
|
||||
/* Unnumbered part */
|
||||
let file = get_filename(&self.source, subline)?;
|
||||
@ -649,28 +699,40 @@ impl Book {
|
||||
self.add_chapter(Number::DefaultPart, file, true)?;
|
||||
} else if subline.starts_with(|c: char| c.is_digit(10)) {
|
||||
/* Specified part*/
|
||||
let parts: Vec<_> = subline.splitn(2, |c: char| c == '.' || c == ':' || c == '+')
|
||||
let parts: Vec<_> = subline
|
||||
.splitn(2, |c: char| c == '.' || c == ':' || c == '+')
|
||||
.collect();
|
||||
if parts.len() != 2 {
|
||||
return Err(Error::config_parser(&self.source,
|
||||
lformat!("ill-formatted line specifying \
|
||||
part number")));
|
||||
return Err(Error::config_parser(
|
||||
&self.source,
|
||||
lformat!(
|
||||
"ill-formatted line specifying \
|
||||
part number"
|
||||
),
|
||||
));
|
||||
}
|
||||
let file = get_filename(&self.source, parts[1])?;
|
||||
let number = parts[0].parse::<i32>()
|
||||
.map_err(|err| {
|
||||
Error::config_parser(&self.source,
|
||||
lformat!("error parsing part number: {error}",
|
||||
error = err))})?;
|
||||
let number = parts[0].parse::<i32>().map_err(|err| {
|
||||
Error::config_parser(
|
||||
&self.source,
|
||||
lformat!("error parsing part number: {error}", error = err),
|
||||
)
|
||||
})?;
|
||||
self.add_chapter(Number::SpecifiedPart(number), file, true)?;
|
||||
} else {
|
||||
return Err(Error::config_parser(&self.source,
|
||||
lformat!("found invalid part definition in the chapter list")));
|
||||
return Err(Error::config_parser(
|
||||
&self.source,
|
||||
lformat!("found invalid part definition in the chapter list"),
|
||||
));
|
||||
}
|
||||
} else {
|
||||
return Err(Error::config_parser(&self.source,
|
||||
lformat!("found invalid chapter definition in \
|
||||
the chapter list")));
|
||||
return Err(Error::config_parser(
|
||||
&self.source,
|
||||
lformat!(
|
||||
"found invalid chapter definition in \
|
||||
the chapter list"
|
||||
),
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
@ -683,10 +745,10 @@ impl Book {
|
||||
|
||||
/// Determine whether proofreading is activated or not
|
||||
fn is_proofread(&self) -> bool {
|
||||
self.options.get_bool("proofread").unwrap() &&
|
||||
(self.options.get("output.proofread.html").is_ok() ||
|
||||
self.options.get("output.proofread.html.dir").is_ok() ||
|
||||
self.options.get("output.proofread.pdf").is_ok())
|
||||
self.options.get_bool("proofread").unwrap()
|
||||
&& (self.options.get("output.proofread.html").is_ok()
|
||||
|| self.options.get("output.proofread.html.dir").is_ok()
|
||||
|| self.options.get("output.proofread.pdf").is_ok())
|
||||
}
|
||||
|
||||
/// Initialize the grammar checker and repetetion detector if they needs to be
|
||||
@ -700,7 +762,10 @@ impl Book {
|
||||
match checker {
|
||||
Ok(checker) => self.checker = Some(checker),
|
||||
Err(e) => {
|
||||
error!("{}", lformat!("{error}. Proceeding without using languagetool.", error = e))
|
||||
error!(
|
||||
"{}",
|
||||
lformat!("{error}. Proceeding without using languagetool.", error = e)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -711,7 +776,10 @@ impl Book {
|
||||
match checker {
|
||||
Ok(checker) => self.grammalecte = Some(checker),
|
||||
Err(e) => {
|
||||
error!("{}", lformat!("{error}. Proceeding without using grammalecte.", error = e))
|
||||
error!(
|
||||
"{}",
|
||||
lformat!("{error}. Proceeding without using grammalecte.", error = e)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -762,7 +830,8 @@ impl Book {
|
||||
/// .render_all(); // renders foo.tex in /tmp
|
||||
/// ```
|
||||
pub fn render_all(&mut self) -> () {
|
||||
let mut keys: Vec<_> = self.formats
|
||||
let mut keys: Vec<_> = self
|
||||
.formats
|
||||
.keys()
|
||||
.filter(|fmt| {
|
||||
if !self.is_proofread() {
|
||||
@ -789,11 +858,9 @@ impl Book {
|
||||
self.add_spinner_to_multibar(key);
|
||||
}
|
||||
|
||||
keys.par_iter()
|
||||
.enumerate()
|
||||
.for_each(|(i, fmt)| {
|
||||
self.render_format_with_bar(fmt, i);
|
||||
});
|
||||
keys.par_iter().enumerate().for_each(|(i, fmt)| {
|
||||
self.render_format_with_bar(fmt, i);
|
||||
});
|
||||
|
||||
self.bar_finish(Crowbar::Main, CrowbarState::Success, &lformat!("Finished"));
|
||||
|
||||
@ -803,7 +870,7 @@ impl Book {
|
||||
// }
|
||||
}
|
||||
|
||||
/// Renders the book to the given format and reports to progress bar if set
|
||||
/// Renders the book to the given format and reports to progress bar if set
|
||||
pub fn render_format_with_bar(&self, format: &str, bar: usize) -> () {
|
||||
let mut key = String::from("output.");
|
||||
key.push_str(format);
|
||||
@ -811,64 +878,83 @@ impl Book {
|
||||
self.bar_set_message(Crowbar::Spinner(bar), &lformat!("rendering..."));
|
||||
let result = self.render_format_to_file_with_bar(format, path, bar);
|
||||
if let Err(err) = result {
|
||||
self.bar_finish(Crowbar::Spinner(bar),
|
||||
CrowbarState::Error,
|
||||
&format!("{}", err));
|
||||
error!("{}", lformat!("Error rendering {name}: {error}", name = format, error = err));
|
||||
self.bar_finish(
|
||||
Crowbar::Spinner(bar),
|
||||
CrowbarState::Error,
|
||||
&format!("{}", err),
|
||||
);
|
||||
error!(
|
||||
"{}",
|
||||
lformat!(
|
||||
"Error rendering {name}: {error}",
|
||||
name = format,
|
||||
error = err
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn render_format_to_file_with_bar<P:Into<PathBuf>>(&self,
|
||||
format: &str,
|
||||
path: P,
|
||||
bar: usize) -> Result<()> {
|
||||
debug!("{}", lformat!("Attempting to generate {format}...",
|
||||
format = format));
|
||||
pub fn render_format_to_file_with_bar<P: Into<PathBuf>>(
|
||||
&self,
|
||||
format: &str,
|
||||
path: P,
|
||||
bar: usize,
|
||||
) -> Result<()> {
|
||||
debug!(
|
||||
"{}",
|
||||
lformat!("Attempting to generate {format}...", format = format)
|
||||
);
|
||||
let path = path.into();
|
||||
match self.formats.get(format) {
|
||||
Some(&(ref description, ref renderer)) => {
|
||||
let path = if path.ends_with("auto") {
|
||||
let file = if let Some(s) = self.source
|
||||
let file = if let Some(s) = self
|
||||
.source
|
||||
.file
|
||||
.as_ref()
|
||||
.and_then(|f| Path::new(f).file_stem()) {
|
||||
.and_then(|f| Path::new(f).file_stem())
|
||||
{
|
||||
s.to_string_lossy().into_owned()
|
||||
} else {
|
||||
return Err(Error::default(&self.source, lformat!("output to {format} set to auto but can't find book file name to infer it",
|
||||
format = description)));
|
||||
};
|
||||
let file = renderer.auto_path(&file)
|
||||
.map_err(|_| Error::default(&self.source,
|
||||
lformat!("the {format} renderer does not support auto for output path",
|
||||
format = description)))?;
|
||||
};
|
||||
let file = renderer.auto_path(&file).map_err(|_| {
|
||||
Error::default(
|
||||
&self.source,
|
||||
lformat!(
|
||||
"the {format} renderer does not support auto for output path",
|
||||
format = description
|
||||
),
|
||||
)
|
||||
})?;
|
||||
path.with_file_name(file)
|
||||
} else {
|
||||
path
|
||||
};
|
||||
renderer.render_to_file(self, &path)?;
|
||||
let path = misc::normalize(path);
|
||||
let msg = lformat!("Succesfully generated {format}: {path}",
|
||||
format = description,
|
||||
path = &path);
|
||||
let msg = lformat!(
|
||||
"Succesfully generated {format}: {path}",
|
||||
format = description,
|
||||
path = &path
|
||||
);
|
||||
info!("{}", &msg);
|
||||
self.bar_finish(Crowbar::Spinner(bar),
|
||||
CrowbarState::Success,
|
||||
&lformat!("generated {path}",
|
||||
path = path));
|
||||
self.bar_finish(
|
||||
Crowbar::Spinner(bar),
|
||||
CrowbarState::Success,
|
||||
&lformat!("generated {path}", path = path),
|
||||
);
|
||||
Ok(())
|
||||
},
|
||||
None => {
|
||||
Err(Error::default(Source::empty(),
|
||||
lformat!("unknown format {format}",
|
||||
format = format)))
|
||||
}
|
||||
None => Err(Error::default(
|
||||
Source::empty(),
|
||||
lformat!("unknown format {format}", format = format),
|
||||
)),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
/// Render book to specified format according to book options, and write the results
|
||||
/// in the `Write` object.
|
||||
///
|
||||
@ -881,38 +967,46 @@ impl Book {
|
||||
/// * `render_format`, which won't do anything if `output.{format}` isn't specified
|
||||
/// in the book configuration file.
|
||||
pub fn render_format_to<T: Write>(&mut self, format: &str, f: &mut T) -> Result<()> {
|
||||
debug!("{}", lformat!("Attempting to generate {format}...",
|
||||
format = format));
|
||||
debug!(
|
||||
"{}",
|
||||
lformat!("Attempting to generate {format}...", format = format)
|
||||
);
|
||||
let bar = self.add_spinner_to_multibar(format);
|
||||
match self.formats.get(format) {
|
||||
Some(&(ref description, ref renderer)) => {
|
||||
match renderer.render(self, f) {
|
||||
Ok(_) => {
|
||||
self.bar_finish(Crowbar::Spinner(bar),
|
||||
CrowbarState::Success,
|
||||
&lformat!("generated {format}",
|
||||
format = format));
|
||||
self.bar_finish(Crowbar::Main, CrowbarState::Success, &lformat!("Finished"));
|
||||
info!("{}", lformat!("Succesfully generated {format}",
|
||||
format = description));
|
||||
Ok(())
|
||||
},
|
||||
Err(e) => {
|
||||
self.bar_finish(Crowbar::Spinner(bar),
|
||||
CrowbarState::Error,
|
||||
&lformat!("{error}", error = e));
|
||||
self.bar_finish(Crowbar::Main, CrowbarState::Error, &lformat!("ERROR"));
|
||||
Err(e)
|
||||
}
|
||||
Some(&(ref description, ref renderer)) => match renderer.render(self, f) {
|
||||
Ok(_) => {
|
||||
self.bar_finish(
|
||||
Crowbar::Spinner(bar),
|
||||
CrowbarState::Success,
|
||||
&lformat!("generated {format}", format = format),
|
||||
);
|
||||
self.bar_finish(Crowbar::Main, CrowbarState::Success, &lformat!("Finished"));
|
||||
info!(
|
||||
"{}",
|
||||
lformat!("Succesfully generated {format}", format = description)
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
Err(e) => {
|
||||
self.bar_finish(
|
||||
Crowbar::Spinner(bar),
|
||||
CrowbarState::Error,
|
||||
&lformat!("{error}", error = e),
|
||||
|