diff --git a/Cargo.lock b/Cargo.lock index 2988f287..5a863840 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -648,6 +648,7 @@ dependencies = [ name = "errors" version = "0.1.0" dependencies = [ + "anyhow", "libs", ] @@ -3926,7 +3927,6 @@ dependencies = [ "errors", "front_matter", "hyper", - "lazy_static", "libs", "mime_guess", "notify", diff --git a/Cargo.toml b/Cargo.toml index 0449bdda..c05001ff 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -24,7 +24,6 @@ name = "zola" [dependencies] atty = "0.2.11" clap = { version = "3", features = ["derive"] } -lazy_static = "1.1" termcolor = "1.0.4" # Below is for the serve cmd hyper = { version = "0.14.1", default-features = false, features = ["runtime", "server", "http2", "http1"] } diff --git a/components/config/src/config/mod.rs b/components/config/src/config/mod.rs index 4538eeed..f001503f 100644 --- a/components/config/src/config/mod.rs +++ b/components/config/src/config/mod.rs @@ -13,7 +13,7 @@ use libs::toml::Value as Toml; use serde::{Deserialize, Serialize}; use crate::theme::Theme; -use errors::{bail, Error, Result}; +use errors::{anyhow, bail, Result}; use utils::fs::read_file; // We want a default base url for tests @@ -155,13 +155,12 @@ impl Config { /// Parses a config file from the given path pub fn from_file>(path: P) -> Result { let path = path.as_ref(); - let content = - read_file(path).map_err(|e| errors::Error::chain("Failed to load config", e))?; + let content = read_file(path)?; let mut config = Config::parse(&content)?; let config_dir = path .parent() - .ok_or_else(|| Error::msg("Failed to find directory containing the config file."))?; + .ok_or_else(|| anyhow!("Failed to find directory containing the config file."))?; // this is the step at which missing extra syntax and highlighting themes are raised as errors config.markdown.init_extra_syntaxes_and_highlight_themes(config_dir)?; @@ -272,10 +271,7 @@ impl Config { .translations .get(key) .ok_or_else(|| { - Error::msg(format!( - "Translation key '{}' for language '{}' is missing", - key, lang - )) + anyhow!("Translation key '{}' for language '{}' is missing", key, lang) }) .map(|term| term.to_string()) } else { @@ -325,7 +321,7 @@ pub fn merge(into: &mut Toml, from: &Toml) -> Result<()> { } _ => { // Trying to merge a table with something else - Err(Error::msg(&format!("Cannot merge config.toml with theme.toml because the following values have incompatibles types:\n- {}\n - {}", into, from))) + Err(anyhow!("Cannot merge config.toml with theme.toml because the following values have incompatibles types:\n- {}\n - {}", into, from)) } } } diff --git a/components/config/src/theme.rs b/components/config/src/theme.rs index ed239af6..b2a6ac51 100644 --- a/components/config/src/theme.rs +++ b/components/config/src/theme.rs @@ -4,7 +4,7 @@ use std::path::Path; use libs::toml::Value as Toml; use serde::{Deserialize, Serialize}; -use errors::{bail, Result}; +use errors::{bail, Context, Result}; use utils::fs::read_file; /// Holds the data from a `theme.toml` file. @@ -40,8 +40,8 @@ impl Theme { /// Parses a theme file from the given path pub fn from_file(path: &Path, theme_name: &str) -> Result { - let content = read_file(path) - .map_err(|e| errors::Error::chain(format!("Failed to load theme {}", theme_name), e))?; + let content = + read_file(path).with_context(|| format!("Failed to load theme {}", theme_name))?; Theme::parse(&content) } } diff --git a/components/errors/Cargo.toml b/components/errors/Cargo.toml index da39c131..e629556d 100644 --- a/components/errors/Cargo.toml +++ b/components/errors/Cargo.toml @@ -5,3 +5,4 @@ edition = "2021" [dependencies] libs = { path = "../libs" } +anyhow = "1.0.56" diff --git a/components/errors/src/lib.rs b/components/errors/src/lib.rs index f2a6c9e3..d597aff9 100644 --- a/components/errors/src/lib.rs +++ b/components/errors/src/lib.rs @@ -1,121 +1 @@ -use std::convert::Into; -use std::error::Error as StdError; -use std::fmt; - -use libs::{image, syntect, tera, toml}; - -#[derive(Debug)] -pub enum ErrorKind { - Msg(String), - Tera(tera::Error), - Io(::std::io::Error), - Toml(toml::de::Error), - Image(image::ImageError), - Syntect(syntect::LoadingError), -} - -/// The Error type -#[derive(Debug)] -pub struct Error { - /// Kind of error - pub kind: ErrorKind, - pub source: Option>, -} - -impl StdError for Error { - fn source(&self) -> Option<&(dyn StdError + 'static)> { - match self.source { - Some(ref err) => Some(&**err), - None => match self.kind { - ErrorKind::Tera(ref err) => err.source(), - _ => None, - }, - } - } -} - -impl fmt::Display for Error { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - match self.kind { - ErrorKind::Msg(ref message) => write!(f, "{}", message), - ErrorKind::Tera(ref e) => write!(f, "{}", e), - ErrorKind::Io(ref e) => write!(f, "{}", e), - ErrorKind::Toml(ref e) => write!(f, "{}", e), - ErrorKind::Image(ref e) => write!(f, "{}", e), - ErrorKind::Syntect(ref e) => write!(f, "{}", e), - } - } -} - -impl Error { - /// Creates generic error - pub fn msg(value: impl ToString) -> Self { - Self { kind: ErrorKind::Msg(value.to_string()), source: None } - } - - /// Creates generic error with a cause - pub fn chain(value: impl ToString, source: impl Into>) -> Self { - Self { kind: ErrorKind::Msg(value.to_string()), source: Some(source.into()) } - } - - /// Create an error from a list of path collisions, formatting the output - pub fn from_collisions(collisions: Vec<(String, Vec)>) -> Self { - let mut msg = String::from("Found path collisions:\n"); - - for (path, filepaths) in collisions { - let row = format!("- `{}` from files {:?}\n", path, filepaths); - msg.push_str(&row); - } - - Self { kind: ErrorKind::Msg(msg), source: None } - } -} - -impl From<&str> for Error { - fn from(e: &str) -> Self { - Self::msg(e) - } -} -impl From for Error { - fn from(e: String) -> Self { - Self::msg(e) - } -} -impl From for Error { - fn from(e: toml::de::Error) -> Self { - Self { kind: ErrorKind::Toml(e), source: None } - } -} -impl From for Error { - fn from(e: syntect::LoadingError) -> Self { - Self { kind: ErrorKind::Syntect(e), source: None } - } -} -impl From for Error { - fn from(e: tera::Error) -> Self { - Self { kind: ErrorKind::Tera(e), source: None } - } -} -impl From<::std::io::Error> for Error { - fn from(e: ::std::io::Error) -> Self { - Self { kind: ErrorKind::Io(e), source: None } - } -} -impl From for Error { - fn from(e: image::ImageError) -> Self { - Self { kind: ErrorKind::Image(e), source: None } - } -} -/// Convenient wrapper around std::Result. -pub type Result = ::std::result::Result; - -// So we can use bail! in all other crates -#[macro_export] -macro_rules! bail { - ($e:expr) => { - return Err($e.into()); - }; - ($fmt:expr, $($arg:tt)+) => { - return Err(format!($fmt, $($arg)+).into()); - }; -} +pub use anyhow::*; diff --git a/components/front_matter/src/lib.rs b/components/front_matter/src/lib.rs index 121f70ff..35b3ab89 100644 --- a/components/front_matter/src/lib.rs +++ b/components/front_matter/src/lib.rs @@ -3,7 +3,7 @@ use std::path::Path; use libs::once_cell::sync::Lazy; use serde::{Deserialize, Serialize}; -use errors::{bail, Error, Result}; +use errors::{bail, Context, Result}; use libs::regex::Regex; use libs::{serde_yaml, toml}; @@ -39,7 +39,7 @@ impl RawFrontMatter<'_> { RawFrontMatter::Toml(s) => toml::from_str(s)?, RawFrontMatter::Yaml(s) => match serde_yaml::from_str(s) { Ok(d) => d, - Err(e) => bail!(format!("YAML deserialize error: {:?}", e)), + Err(e) => bail!("YAML deserialize error: {:?}", e), }, }; Ok(f) @@ -105,12 +105,10 @@ pub fn split_section_content<'c>( content: &'c str, ) -> Result<(SectionFrontMatter, &'c str)> { let (front_matter, content) = split_content(file_path, content)?; - let meta = SectionFrontMatter::parse(&front_matter).map_err(|e| { - Error::chain( - format!("Error when parsing front matter of section `{}`", file_path.to_string_lossy()), - e, - ) + let meta = SectionFrontMatter::parse(&front_matter).with_context(|| { + format!("Error when parsing front matter of section `{}`", file_path.to_string_lossy()) })?; + Ok((meta, content)) } @@ -121,11 +119,8 @@ pub fn split_page_content<'c>( content: &'c str, ) -> Result<(PageFrontMatter, &'c str)> { let (front_matter, content) = split_content(file_path, content)?; - let meta = PageFrontMatter::parse(&front_matter).map_err(|e| { - Error::chain( - format!("Error when parsing front matter of page `{}`", file_path.to_string_lossy()), - e, - ) + let meta = PageFrontMatter::parse(&front_matter).with_context(|| { + format!("Error when parsing front matter of section `{}`", file_path.to_string_lossy()) })?; Ok((meta, content)) } diff --git a/components/imageproc/src/lib.rs b/components/imageproc/src/lib.rs index de46c46c..03dc651a 100644 --- a/components/imageproc/src/lib.rs +++ b/components/imageproc/src/lib.rs @@ -1,6 +1,5 @@ use std::collections::hash_map::Entry as HEntry; use std::collections::HashMap; -use std::error::Error as StdError; use std::ffi::OsStr; use std::fs::{self, File}; use std::hash::{Hash, Hasher}; @@ -19,7 +18,7 @@ use serde::{Deserialize, Serialize}; use svg_metadata::Metadata as SvgMetadata; use config::Config; -use errors::{Error, Result}; +use errors::{anyhow, Context, Error, Result}; use utils::fs as ufs; static RESIZED_SUBDIR: &str = "processed_images"; @@ -83,22 +82,20 @@ impl ResizeArgs { match op { "fit_width" => { if width.is_none() { - return Err("op=\"fit_width\" requires a `width` argument".into()); + return Err(anyhow!("op=\"fit_width\" requires a `width` argument")); } } "fit_height" => { if height.is_none() { - return Err("op=\"fit_height\" requires a `height` argument".into()); + return Err(anyhow!("op=\"fit_height\" requires a `height` argument")); } } "scale" | "fit" | "fill" => { if width.is_none() || height.is_none() { - return Err( - format!("op={} requires a `width` and `height` argument", op).into() - ); + return Err(anyhow!("op={} requires a `width` and `height` argument", op)); } } - _ => return Err(format!("Invalid image resize operation: {}", op).into()), + _ => return Err(anyhow!("Invalid image resize operation: {}", op)), }; Ok(match op { @@ -224,7 +221,7 @@ impl Format { "jpeg" | "jpg" => Ok(Jpeg(jpg_quality)), "png" => Ok(Png), "webp" => Ok(WebP(quality)), - _ => Err(format!("Invalid image format: {}", format).into()), + _ => Err(anyhow!("Invalid image format: {}", format)), } } @@ -332,7 +329,8 @@ impl ImageOp { img.write_to(&mut f, ImageOutputFormat::Jpeg(q))?; } Format::WebP(q) => { - let encoder = webp::Encoder::from_image(&img)?; + let encoder = webp::Encoder::from_image(&img) + .map_err(|_| anyhow!("Unable to load this kind of image with webp"))?; let memory = match q { Some(q) => encoder.encode(q as f32), None => encoder.encode_lossless(), @@ -415,9 +413,8 @@ impl Processor { format: &str, quality: Option, ) -> Result { - let meta = ImageMeta::read(&input_path).map_err(|e| { - Error::chain(format!("Failed to read image: {}", input_path.display()), e) - })?; + let meta = ImageMeta::read(&input_path) + .with_context(|| format!("Failed to read image: {}", input_path.display()))?; let args = ResizeArgs::from_args(op, width, height)?; let op = ResizeOp::new(args, meta.size); @@ -529,8 +526,9 @@ impl Processor { .map(|(hash, op)| { let target = self.output_dir.join(Self::op_filename(*hash, op.collision_id, op.format)); - op.perform(&target).map_err(|e| { - Error::chain(format!("Failed to process image: {}", op.input_path.display()), e) + + op.perform(&target).with_context(|| { + format!("Failed to process image: {}", op.input_path.display()) }) }) .collect::>() @@ -571,29 +569,27 @@ pub fn read_image_metadata>(path: P) -> Result let path = path.as_ref(); let ext = path.extension().and_then(OsStr::to_str).unwrap_or("").to_lowercase(); - let error = |e: Box| { - Error::chain(format!("Failed to read image: {}", path.display()), e) - }; + let err_context = || format!("Failed to read image: {}", path.display()); match ext.as_str() { "svg" => { - let img = SvgMetadata::parse_file(&path).map_err(|e| error(e.into()))?; + let img = SvgMetadata::parse_file(&path).with_context(err_context)?; match (img.height(), img.width(), img.view_box()) { (Some(h), Some(w), _) => Ok((h, w)), (_, _, Some(view_box)) => Ok((view_box.height, view_box.width)), - _ => Err("Invalid dimensions: SVG width/height and viewbox not set.".into()), + _ => Err(anyhow!("Invalid dimensions: SVG width/height and viewbox not set.")), } .map(|(h, w)| ImageMetaResponse::new_svg(h as u32, w as u32)) } "webp" => { // Unfortunatelly we have to load the entire image here, unlike with the others :| - let data = fs::read(path).map_err(|e| error(e.into()))?; + let data = fs::read(path).with_context(err_context)?; let decoder = webp::Decoder::new(&data[..]); decoder.decode().map(ImageMetaResponse::from).ok_or_else(|| { Error::msg(format!("Failed to decode WebP image: {}", path.display())) }) } - _ => ImageMeta::read(path).map(ImageMetaResponse::from).map_err(|e| error(e.into())), + _ => ImageMeta::read(path).map(ImageMetaResponse::from).with_context(err_context), } } diff --git a/components/library/src/content/page.rs b/components/library/src/content/page.rs index cda0851d..364903da 100644 --- a/components/library/src/content/page.rs +++ b/components/library/src/content/page.rs @@ -9,7 +9,7 @@ use libs::tera::{Context as TeraContext, Tera}; use crate::library::Library; use config::Config; -use errors::{Error, Result}; +use errors::{Context, Result}; use front_matter::{split_page_content, InsertAnchor, PageFrontMatter}; use rendering::{render_content, Heading, RenderContext}; use utils::site::get_reading_analytics; @@ -240,9 +240,8 @@ impl Page { context.set_current_page_path(&self.file.relative); context.tera_context.insert("page", &SerializingPage::from_page_basic(self, None)); - let res = render_content(&self.raw_content, &context).map_err(|e| { - Error::chain(format!("Failed to render content of {}", self.file.path.display()), e) - })?; + let res = render_content(&self.raw_content, &context) + .with_context(|| format!("Failed to render content of {}", self.file.path.display()))?; self.summary = res .summary_len @@ -270,9 +269,8 @@ impl Page { context.insert("page", &self.to_serialized(library)); context.insert("lang", &self.lang); - render_template(tpl_name, tera, context, &config.theme).map_err(|e| { - Error::chain(format!("Failed to render page '{}'", self.file.path.display()), e) - }) + render_template(tpl_name, tera, context, &config.theme) + .with_context(|| format!("Failed to render page '{}'", self.file.path.display())) } /// Creates a vectors of asset URLs. diff --git a/components/library/src/content/section.rs b/components/library/src/content/section.rs index 188bb3db..bfe7baa0 100644 --- a/components/library/src/content/section.rs +++ b/components/library/src/content/section.rs @@ -5,7 +5,7 @@ use libs::slotmap::DefaultKey; use libs::tera::{Context as TeraContext, Tera}; use config::Config; -use errors::{Error, Result}; +use errors::{Context, Result}; use front_matter::{split_section_content, SectionFrontMatter}; use rendering::{render_content, Heading, RenderContext}; use utils::fs::read_file; @@ -161,9 +161,8 @@ impl Section { context.set_current_page_path(&self.file.relative); context.tera_context.insert("section", &SerializingSection::from_section_basic(self, None)); - let res = render_content(&self.raw_content, &context).map_err(|e| { - Error::chain(format!("Failed to render content of {}", self.file.path.display()), e) - })?; + let res = render_content(&self.raw_content, &context) + .with_context(|| format!("Failed to render content of {}", self.file.path.display()))?; self.content = res.body; self.toc = res.toc; self.external_links = res.external_links; @@ -183,9 +182,8 @@ impl Section { context.insert("section", &self.to_serialized(library)); context.insert("lang", &self.lang); - render_template(tpl_name, tera, context, &config.theme).map_err(|e| { - Error::chain(format!("Failed to render section '{}'", self.file.path.display()), e) - }) + render_template(tpl_name, tera, context, &config.theme) + .with_context(|| format!("Failed to render section '{}'", self.file.path.display())) } /// Is this the index section? diff --git a/components/library/src/pagination/mod.rs b/components/library/src/pagination/mod.rs index fab63c3b..02fd8da7 100644 --- a/components/library/src/pagination/mod.rs +++ b/components/library/src/pagination/mod.rs @@ -6,7 +6,7 @@ use libs::tera::{to_value, Context, Tera, Value}; use serde::Serialize; use config::Config; -use errors::{Error, Result}; +use errors::{Context as ErrorContext, Result}; use utils::templates::{check_template_fallbacks, render_template}; use crate::content::{Section, SerializingPage, SerializingSection}; @@ -247,7 +247,7 @@ impl<'a> Paginator<'a> { context.insert("paginator", &self.build_paginator_context(pager)); render_template(&self.template, tera, context, &config.theme) - .map_err(|e| Error::chain(format!("Failed to render pager {}", pager.index), e)) + .with_context(|| format!("Failed to render pager {}", pager.index)) } } diff --git a/components/library/src/taxonomies/mod.rs b/components/library/src/taxonomies/mod.rs index d134ca5d..bdd9a295 100644 --- a/components/library/src/taxonomies/mod.rs +++ b/components/library/src/taxonomies/mod.rs @@ -6,7 +6,7 @@ use libs::tera::{Context, Tera}; use serde::Serialize; use config::{Config, Taxonomy as TaxonomyConfig}; -use errors::{bail, Error, Result}; +use errors::{bail, Context as ErrorContext, Result}; use utils::templates::{check_template_fallbacks, render_template}; use crate::content::SerializingPage; @@ -207,9 +207,8 @@ impl Taxonomy { let template = check_template_fallbacks(&specific_template, tera, &config.theme) .unwrap_or("taxonomy_single.html"); - render_template(template, tera, context, &config.theme).map_err(|e| { - Error::chain(format!("Failed to render single term {} page.", self.kind.name), e) - }) + render_template(template, tera, context, &config.theme) + .with_context(|| format!("Failed to render single term {} page.", self.kind.name)) } pub fn render_all_terms( @@ -233,9 +232,8 @@ impl Taxonomy { let template = check_template_fallbacks(&specific_template, tera, &config.theme) .unwrap_or("taxonomy_list.html"); - render_template(template, tera, context, &config.theme).map_err(|e| { - Error::chain(format!("Failed to render a list of {} page.", self.kind.name), e) - }) + render_template(template, tera, context, &config.theme) + .with_context(|| format!("Failed to render a list of {} page.", self.kind.name)) } pub fn to_serialized<'a>(&'a self, library: &'a Library) -> SerializedTaxonomy<'a> { diff --git a/components/link_checker/src/lib.rs b/components/link_checker/src/lib.rs index 12af01dc..c1d7d399 100644 --- a/components/link_checker/src/lib.rs +++ b/components/link_checker/src/lib.rs @@ -7,6 +7,7 @@ use libs::reqwest::header::{HeaderMap, ACCEPT}; use libs::reqwest::{blocking::Client, StatusCode}; use config::LinkChecker; +use errors::anyhow; use utils::links::has_anchor_id; @@ -109,7 +110,7 @@ fn check_page_for_anchor(url: &str, body: String) -> errors::Result<()> { if has_anchor_id(&body, anchor) { Ok(()) } else { - Err(errors::Error::from(format!("Anchor `#{}` not found on page", anchor))) + Err(anyhow!("Anchor `#{}` not found on page", anchor)) } } diff --git a/components/rendering/src/markdown.rs b/components/rendering/src/markdown.rs index 68d0b237..68ce8aa3 100644 --- a/components/rendering/src/markdown.rs +++ b/components/rendering/src/markdown.rs @@ -1,12 +1,13 @@ +use std::fmt::Write; + use libs::gh_emoji::Replacer as EmojiReplacer; use libs::once_cell::sync::Lazy; use libs::pulldown_cmark as cmark; use libs::tera; -use std::fmt::Write; use crate::context::RenderContext; use crate::table_of_contents::{make_table_of_contents, Heading}; -use errors::{Error, Result}; +use errors::{anyhow, Context, Error, Result}; use front_matter::InsertAnchor; use libs::pulldown_cmark::escape::escape_html; use utils::site::resolve_internal_link; @@ -119,7 +120,7 @@ fn fix_link( resolved.permalink } Err(_) => { - return Err(format!("Relative link {} not found.", link).into()); + return Err(anyhow!("Relative link {} not found.", link)); } } } else if is_external_link(link) { @@ -472,7 +473,7 @@ pub fn markdown_to_html( c, &None, ) - .map_err(|e| Error::chain("Failed to render anchor link template", e))?; + .context("Failed to render anchor link template")?; anchors_to_insert.push((anchor_idx, Event::Html(anchor_link.into()))); } diff --git a/components/rendering/src/shortcode/parser.rs b/components/rendering/src/shortcode/parser.rs index 9ccc8ad2..230c36cf 100644 --- a/components/rendering/src/shortcode/parser.rs +++ b/components/rendering/src/shortcode/parser.rs @@ -1,6 +1,6 @@ use std::ops::Range; -use errors::{bail, Result}; +use errors::{bail, Context as ErrorContext, Result}; use libs::tera::{to_value, Context, Map, Tera, Value}; use pest::iterators::Pair; use pest::Parser; @@ -43,7 +43,7 @@ impl Shortcode { new_context.extend(context.clone()); let res = utils::templates::render_template(&tpl_name, tera, new_context, &None) - .map_err(|e| errors::Error::chain(format!("Failed to render {} shortcode", name), e))? + .with_context(|| format!("Failed to render {} shortcode", name))? .replace("\r\n", "\n"); Ok(res) diff --git a/components/site/src/lib.rs b/components/site/src/lib.rs index 8f99b505..60100ec1 100644 --- a/components/site/src/lib.rs +++ b/components/site/src/lib.rs @@ -16,7 +16,7 @@ use libs::tera::{Context, Tera}; use libs::walkdir::{DirEntry, WalkDir}; use config::{get_config, Config}; -use errors::{bail, Error, Result}; +use errors::{anyhow, bail, Context as ErrorContext, Result}; use front_matter::InsertAnchor; use library::{find_taxonomies, Library, Page, Paginator, Section, Taxonomy}; use libs::relative_path::RelativePathBuf; @@ -274,7 +274,12 @@ impl Site { let library = self.library.read().unwrap(); let collisions = library.check_for_path_collisions(); if !collisions.is_empty() { - return Err(Error::from_collisions(collisions)); + let mut msg = String::from("Found path collisions:\n"); + for (path, filepaths) in collisions { + let row = format!("- `{}` from files {:?}\n", path, filepaths); + msg.push_str(&row); + } + return Err(anyhow!(msg)); } } @@ -537,8 +542,7 @@ impl Site { pub fn clean(&self) -> Result<()> { if self.output_path.exists() { // Delete current `public` directory so we can start fresh - remove_dir_all(&self.output_path) - .map_err(|e| Error::chain("Couldn't delete output directory", e))?; + remove_dir_all(&self.output_path).context("Couldn't delete output directory")?; } Ok(()) diff --git a/components/site/src/link_checking.rs b/components/site/src/link_checking.rs index b6b14a06..57d72111 100644 --- a/components/site/src/link_checking.rs +++ b/components/site/src/link_checking.rs @@ -1,12 +1,12 @@ +use core::time; +use std::{collections::HashMap, path::PathBuf, thread}; + use libs::rayon::prelude::*; -use crate::Site; -use core::time; +use crate::{anyhow, Site}; use errors::{bail, Result}; -use errors::{Error, ErrorKind}; use libs::rayon; use libs::url::Url; -use std::{collections::HashMap, path::PathBuf, thread}; /// Check whether all internal links pointing to explicit anchor fragments are valid. /// @@ -91,7 +91,7 @@ pub fn check_internal_links_with_anchors(site: &Site) -> Result<()> { "> Checked {} internal link(s) with anchors: {} target(s) missing.", anchors_total, errors_total, ); - Err(Error { kind: ErrorKind::Msg(errors.join("\n")), source: None }) + Err(anyhow!(errors.join("\n"))) } } } @@ -146,10 +146,7 @@ pub fn check_external_links(site: &Site) -> Result<()> { // (almost) all pages simultaneously, limiting all links for a single // domain to one thread to avoid rate-limiting let threads = std::cmp::min(links_by_domain.len(), 8); - let pool = rayon::ThreadPoolBuilder::new() - .num_threads(threads) - .build() - .map_err(|e| Error { kind: ErrorKind::Msg(e.to_string()), source: None })?; + let pool = rayon::ThreadPoolBuilder::new().num_threads(threads).build()?; let errors = pool.install(|| { links_by_domain @@ -209,5 +206,5 @@ pub fn check_external_links(site: &Site) -> Result<()> { .collect::>() .join("\n"); - Err(Error { kind: ErrorKind::Msg(msg), source: None }) + Err(anyhow!(msg)) } diff --git a/components/site/src/sass.rs b/components/site/src/sass.rs index 42c2440a..ab6bb556 100644 --- a/components/site/src/sass.rs +++ b/components/site/src/sass.rs @@ -4,6 +4,7 @@ use std::path::{Path, PathBuf}; use libs::glob::glob; use libs::sass_rs::{compile_file, Options, OutputStyle}; +use crate::anyhow; use errors::{bail, Result}; use utils::fs::{create_file, ensure_directory_exists}; @@ -47,7 +48,7 @@ fn compile_sass_glob( let mut compiled_paths = Vec::new(); for file in files { - let css = compile_file(&file, options.clone())?; + let css = compile_file(&file, options.clone()).map_err(|e| anyhow!(e))?; let path_inside_sass = file.strip_prefix(&sass_path).unwrap(); let parent_inside_sass = path_inside_sass.parent(); diff --git a/components/templates/src/lib.rs b/components/templates/src/lib.rs index 750c4313..120b8fc8 100644 --- a/components/templates/src/lib.rs +++ b/components/templates/src/lib.rs @@ -7,7 +7,7 @@ use config::Config; use libs::once_cell::sync::Lazy; use libs::tera::{Context, Tera}; -use errors::{bail, Error, Result}; +use errors::{bail, Context as ErrorContext, Result}; use utils::templates::rewrite_theme_paths; pub static ZOLA_TERA: Lazy = Lazy::new(|| { @@ -48,7 +48,7 @@ pub fn render_redirect_template(url: &str, tera: &Tera) -> Result { context.insert("url", &url); tera.render("internal/alias.html", &context) - .map_err(|e| Error::chain(format!("Failed to render alias for '{}'", url), e)) + .with_context(|| format!("Failed to render alias for '{}'", url)) } pub fn load_tera(path: &Path, config: &Config) -> Result { @@ -58,7 +58,7 @@ pub fn load_tera(path: &Path, config: &Config) -> Result { // Only parsing as we might be extending templates from themes and that would error // as we haven't loaded them yet let mut tera = - Tera::parse(&tpl_glob).map_err(|e| Error::chain("Error parsing templates", e))?; + Tera::parse(&tpl_glob).context("Error parsing templates from the /templates directory")?; if let Some(ref theme) = config.theme { // Test that the templates folder exist for that theme @@ -72,8 +72,8 @@ pub fn load_tera(path: &Path, config: &Config) -> Result { path.to_string_lossy().replace('\\', "/"), theme ); - let mut tera_theme = Tera::parse(&theme_tpl_glob) - .map_err(|e| Error::chain("Error parsing templates from themes", e))?; + let mut tera_theme = + Tera::parse(&theme_tpl_glob).context("Error parsing templates from themes")?; rewrite_theme_paths(&mut tera_theme, theme); // TODO: add tests for theme-provided robots.txt (https://github.com/getzola/zola/pull/1722) diff --git a/components/utils/src/fs.rs b/components/utils/src/fs.rs index 24301521..6cb031e7 100644 --- a/components/utils/src/fs.rs +++ b/components/utils/src/fs.rs @@ -5,23 +5,23 @@ use std::io::prelude::*; use std::path::Path; use std::time::SystemTime; -use errors::{Error, Result}; +use errors::{Context, Result}; pub fn is_path_in_directory(parent: &Path, path: &Path) -> Result { let canonical_path = path .canonicalize() - .map_err(|e| format!("Failed to canonicalize {}: {}", path.display(), e))?; + .with_context(|| format!("Failed to canonicalize {}", path.display()))?; let canonical_parent = parent .canonicalize() - .map_err(|e| format!("Failed to canonicalize {}: {}", parent.display(), e))?; + .with_context(|| format!("Failed to canonicalize {}", parent.display()))?; Ok(canonical_path.starts_with(canonical_parent)) } /// Create a file with the content given pub fn create_file(path: &Path, content: &str) -> Result<()> { - let mut file = File::create(&path) - .map_err(|e| Error::chain(format!("Failed to create file {}", path.display()), e))?; + let mut file = + File::create(&path).with_context(|| format!("Failed to create file {}", path.display()))?; file.write_all(content.as_bytes())?; Ok(()) } @@ -38,9 +38,8 @@ pub fn ensure_directory_exists(path: &Path) -> Result<()> { /// exists before creating it pub fn create_directory(path: &Path) -> Result<()> { if !path.exists() { - create_dir_all(path).map_err(|e| { - Error::chain(format!("Was not able to create folder {}", path.display()), e) - })?; + create_dir_all(path) + .with_context(|| format!("Failed to create folder {}", path.display()))?; } Ok(()) } @@ -49,7 +48,7 @@ pub fn create_directory(path: &Path) -> Result<()> { pub fn read_file(path: &Path) -> Result { let mut content = String::new(); File::open(path) - .map_err(|e| Error::chain(format!("Failed to open '{}'", path.display()), e))? + .with_context(|| format!("Failed to open file {}", path.display()))? .read_to_string(&mut content)?; // Remove utf-8 BOM if any. @@ -67,8 +66,8 @@ pub fn copy_file(src: &Path, dest: &Path, base_path: &Path, hard_link: bool) -> let target_path = dest.join(relative_path); if let Some(parent_directory) = target_path.parent() { - create_dir_all(parent_directory).map_err(|e| { - Error::chain(format!("Was not able to create folder {}", parent_directory.display()), e) + create_dir_all(parent_directory).with_context(|| { + format!("Failed to create directory {}", parent_directory.display()) })?; } @@ -81,8 +80,8 @@ pub fn copy_file(src: &Path, dest: &Path, base_path: &Path, hard_link: bool) -> /// 3. Its filesize is identical to that of the src file. pub fn copy_file_if_needed(src: &Path, dest: &Path, hard_link: bool) -> Result<()> { if let Some(parent_directory) = dest.parent() { - create_dir_all(parent_directory).map_err(|e| { - Error::chain(format!("Was not able to create folder {}", parent_directory.display()), e) + create_dir_all(parent_directory).with_context(|| { + format!("Failed to create directory {}", parent_directory.display()) })?; } @@ -95,24 +94,14 @@ pub fn copy_file_if_needed(src: &Path, dest: &Path, hard_link: bool) -> Result<( let target_metadata = metadata(&dest)?; let target_mtime = FileTime::from_last_modification_time(&target_metadata); if !(src_mtime == target_mtime && src_metadata.len() == target_metadata.len()) { - copy(src, &dest).map_err(|e| { - Error::chain( - format!( - "Was not able to copy file {} to {}", - src.display(), - dest.display() - ), - e, - ) + copy(src, &dest).with_context(|| { + format!("Was not able to copy file {} to {}", src.display(), dest.display()) })?; set_file_mtime(&dest, src_mtime)?; } } else { - copy(src, &dest).map_err(|e| { - Error::chain( - format!("Was not able to copy file {} to {}", src.display(), dest.display()), - e, - ) + copy(src, &dest).with_context(|| { + format!("Was not able to copy directory {} to {}", src.display(), dest.display()) })?; set_file_mtime(&dest, src_mtime)?; } @@ -130,14 +119,12 @@ pub fn copy_directory(src: &Path, dest: &Path, hard_link: bool) -> Result<()> { create_directory(&target_path)?; } } else { - copy_file(entry.path(), dest, src, hard_link).map_err(|e| { - Error::chain( - format!( - "Was not able to copy file {} to {}", - entry.path().display(), - dest.display() - ), - e, + copy_file(entry.path(), dest, src, hard_link).with_context(|| { + format!( + "Was not able to copy {} to {} (hard_link={})", + entry.path().display(), + dest.display(), + hard_link ) })?; } diff --git a/components/utils/src/site.rs b/components/utils/src/site.rs index ef577555..0d57b858 100644 --- a/components/utils/src/site.rs +++ b/components/utils/src/site.rs @@ -3,7 +3,7 @@ use libs::unicode_segmentation::UnicodeSegmentation; use std::collections::HashMap; use std::hash::BuildHasher; -use errors::Result; +use errors::{anyhow, Result}; /// Get word count and estimated reading time pub fn get_reading_analytics(content: &str) -> (usize, usize) { @@ -41,7 +41,7 @@ pub fn resolve_internal_link( // to decode them first let decoded = percent_decode(parts[0].as_bytes()).decode_utf8_lossy().to_string(); let target = - permalinks.get(&decoded).ok_or_else(|| format!("Relative link {} not found.", link))?; + permalinks.get(&decoded).ok_or_else(|| anyhow!("Relative link {} not found.", link))?; if parts.len() > 1 { Ok(ResolvedInternalLink { permalink: format!("{}#{}", target, parts[1]), diff --git a/src/cmd/serve.rs b/src/cmd/serve.rs index 1ca01113..d8d00273 100644 --- a/src/cmd/serve.rs +++ b/src/cmd/serve.rs @@ -32,16 +32,16 @@ use hyper::header; use hyper::server::Server; use hyper::service::{make_service_fn, service_fn}; use hyper::{Body, Method, Request, Response, StatusCode}; +use mime_guess::from_path as mimetype_from_path; use time::macros::format_description; use time::{OffsetDateTime, UtcOffset}; -use mime_guess::from_path as mimetype_from_path; use libs::percent_encoding; use libs::serde_json; use notify::{watcher, RecursiveMode, Watcher}; use ws::{Message, Sender, WebSocket}; -use errors::{Error as ZolaError, Result}; +use errors::{anyhow, Context, Result}; use libs::globset::GlobSet; use libs::relative_path::{RelativePath, RelativePathBuf}; use pathdiff::diff_paths; @@ -287,7 +287,7 @@ pub fn serve( open: bool, include_drafts: bool, fast_rebuild: bool, - utc_offset: UtcOffset + utc_offset: UtcOffset, ) -> Result<()> { let start = Instant::now(); let (mut site, address) = create_new_site( @@ -305,10 +305,10 @@ pub fn serve( // Stop right there if we can't bind to the address let bind_address: SocketAddrV4 = match address.parse() { Ok(a) => a, - Err(_) => return Err(format!("Invalid address: {}.", address).into()), + Err(_) => return Err(anyhow!("Invalid address: {}.", address)), }; if (TcpListener::bind(&bind_address)).is_err() { - return Err(format!("Cannot start server on address {}.", address).into()); + return Err(anyhow!("Cannot start server on address {}.", address)); } let config_path = PathBuf::from(config_file); @@ -347,7 +347,7 @@ pub fn serve( if should_watch { watcher .watch(root_dir.join(entry), RecursiveMode::Recursive) - .map_err(|e| ZolaError::chain(format!("Can't watch `{}` for changes in folder `{}`. Does it exist, and do you have correct permissions?", entry, root_dir.display()), e))?; + .with_context(|| format!("Can't watch `{}` for changes in folder `{}`. Does it exist, and do you have correct permissions?", entry, root_dir.display()))?; watchers.push(entry.to_string()); } } @@ -415,7 +415,7 @@ pub fn serve( let ws_server = ws_server .bind(&*ws_address) - .map_err(|_| format!("Cannot bind to address {} for the websocket server. Maybe the port is already in use?", &ws_address))?; + .map_err(|_| anyhow!("Cannot bind to address {} for the websocket server. Maybe the port is already in use?", &ws_address))?; thread::spawn(move || { ws_server.run().unwrap(); @@ -532,8 +532,10 @@ pub fn serve( continue; } - let format = format_description!("[year]-[month]-[day] [hour]:[minute]:[second]"); - let current_time = OffsetDateTime::now_utc().to_offset(utc_offset).format(&format); + let format = + format_description!("[year]-[month]-[day] [hour]:[minute]:[second]"); + let current_time = + OffsetDateTime::now_utc().to_offset(utc_offset).format(&format); if let Ok(time_str) = current_time { println!("Change detected @ {}", time_str); } else { @@ -559,7 +561,7 @@ pub fn serve( } else { // an asset changed? a folder renamed? // should we make it smarter so it doesn't reload the whole site? - Err("dummy".into()) + Err(anyhow!("dummy")) }; if res.is_err() { diff --git a/src/console.rs b/src/console.rs index 1698c10b..ba389644 100644 --- a/src/console.rs +++ b/src/console.rs @@ -1,4 +1,3 @@ -use std::error::Error as StdError; use std::io::Write; use std::time::Instant; use std::{convert::TryInto, env}; @@ -17,28 +16,39 @@ static COLOR_CHOICE: Lazy = Lazy::new(|| if has_color() { ColorChoice::Always } else { ColorChoice::Never }); pub fn info(message: &str) { - colorize(message, ColorSpec::new().set_bold(true)); + colorize(message, ColorSpec::new().set_bold(true), StandardStream::stdout(*COLOR_CHOICE)); } pub fn warn(message: &str) { - colorize(message, ColorSpec::new().set_bold(true).set_fg(Some(Color::Yellow))); + colorize( + message, + ColorSpec::new().set_bold(true).set_fg(Some(Color::Yellow)), + StandardStream::stdout(*COLOR_CHOICE), + ); } pub fn success(message: &str) { - colorize(message, ColorSpec::new().set_bold(true).set_fg(Some(Color::Green))); + colorize( + message, + ColorSpec::new().set_bold(true).set_fg(Some(Color::Green)), + StandardStream::stdout(*COLOR_CHOICE), + ); } pub fn error(message: &str) { - colorize(message, ColorSpec::new().set_bold(true).set_fg(Some(Color::Red))); + colorize( + message, + ColorSpec::new().set_bold(true).set_fg(Some(Color::Red)), + StandardStream::stderr(*COLOR_CHOICE), + ); } /// Print a colorized message to stdout -fn colorize(message: &str, color: &ColorSpec) { - let mut stdout = StandardStream::stdout(*COLOR_CHOICE); - stdout.set_color(color).unwrap(); - write!(stdout, "{}", message).unwrap(); - stdout.set_color(&ColorSpec::new()).unwrap(); - writeln!(stdout).unwrap(); +fn colorize(message: &str, color: &ColorSpec, mut stream: StandardStream) { + stream.set_color(color).unwrap(); + write!(stream, "{}", message).unwrap(); + stream.set_color(&ColorSpec::new()).unwrap(); + writeln!(stream).unwrap(); } /// Display in the console the number of pages/sections in the site diff --git a/src/main.rs b/src/main.rs index 0519f49f..6cc26956 100644 --- a/src/main.rs +++ b/src/main.rs @@ -12,7 +12,6 @@ mod cmd; mod console; mod prompt; - fn get_config_file_path(dir: &PathBuf, config_path: &Path) -> (PathBuf, PathBuf) { let root_dir = dir .ancestors() @@ -20,7 +19,10 @@ fn get_config_file_path(dir: &PathBuf, config_path: &Path) -> (PathBuf, PathBuf) .unwrap_or_else(|| panic!("could not find directory containing config file")); // if we got here we found root_dir so config file should exist so we can unwrap safely - let config_file = root_dir.join(&config_path).canonicalize().unwrap_or_else(|_| panic!("could not find directory containing config file")); + let config_file = root_dir + .join(&config_path) + .canonicalize() + .unwrap_or_else(|_| panic!("could not find directory containing config file")); (root_dir.to_path_buf(), config_file) } diff --git a/src/prompt.rs b/src/prompt.rs index 343664b7..b4a68e4c 100644 --- a/src/prompt.rs +++ b/src/prompt.rs @@ -4,7 +4,7 @@ use std::time::Duration; use libs::url::Url; use crate::console; -use errors::Result; +use errors::{anyhow, Result}; /// Wait for user input and return what they typed fn read_line() -> Result { @@ -14,7 +14,7 @@ fn read_line() -> Result { lines .next() .and_then(|l| l.ok()) - .ok_or_else(|| "unable to read from stdin for confirmation".into()) + .ok_or_else(|| anyhow!("unable to read from stdin for confirmation")) } /// Ask a yes/no question to the user