1
0
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:
Florian Bottke 2022-01-15 19:21:03 +01:00
parent f84c98438e
commit b8d35ddc32
41 changed files with 4206 additions and 3268 deletions

2349
Cargo.lock generated

File diff suppressed because it is too large Load Diff

@ -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(())
}

@ -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),