Use anyhow (#1816)

Also fixes #1783
This commit is contained in:
Vincent Prouillet 2022-04-01 21:37:38 +02:00 committed by GitHub
parent 62a0e7b1fd
commit c11ae6ef28
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
25 changed files with 147 additions and 281 deletions

2
Cargo.lock generated
View File

@ -648,6 +648,7 @@ dependencies = [
name = "errors" name = "errors"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"anyhow",
"libs", "libs",
] ]
@ -3926,7 +3927,6 @@ dependencies = [
"errors", "errors",
"front_matter", "front_matter",
"hyper", "hyper",
"lazy_static",
"libs", "libs",
"mime_guess", "mime_guess",
"notify", "notify",

View File

@ -24,7 +24,6 @@ name = "zola"
[dependencies] [dependencies]
atty = "0.2.11" atty = "0.2.11"
clap = { version = "3", features = ["derive"] } clap = { version = "3", features = ["derive"] }
lazy_static = "1.1"
termcolor = "1.0.4" termcolor = "1.0.4"
# Below is for the serve cmd # Below is for the serve cmd
hyper = { version = "0.14.1", default-features = false, features = ["runtime", "server", "http2", "http1"] } hyper = { version = "0.14.1", default-features = false, features = ["runtime", "server", "http2", "http1"] }

View File

@ -13,7 +13,7 @@ use libs::toml::Value as Toml;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use crate::theme::Theme; use crate::theme::Theme;
use errors::{bail, Error, Result}; use errors::{anyhow, bail, Result};
use utils::fs::read_file; use utils::fs::read_file;
// We want a default base url for tests // We want a default base url for tests
@ -155,13 +155,12 @@ impl Config {
/// Parses a config file from the given path /// Parses a config file from the given path
pub fn from_file<P: AsRef<Path>>(path: P) -> Result<Config> { pub fn from_file<P: AsRef<Path>>(path: P) -> Result<Config> {
let path = path.as_ref(); let path = path.as_ref();
let content = let content = read_file(path)?;
read_file(path).map_err(|e| errors::Error::chain("Failed to load config", e))?;
let mut config = Config::parse(&content)?; let mut config = Config::parse(&content)?;
let config_dir = path let config_dir = path
.parent() .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 // 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)?; config.markdown.init_extra_syntaxes_and_highlight_themes(config_dir)?;
@ -272,10 +271,7 @@ impl Config {
.translations .translations
.get(key) .get(key)
.ok_or_else(|| { .ok_or_else(|| {
Error::msg(format!( anyhow!("Translation key '{}' for language '{}' is missing", key, lang)
"Translation key '{}' for language '{}' is missing",
key, lang
))
}) })
.map(|term| term.to_string()) .map(|term| term.to_string())
} else { } else {
@ -325,7 +321,7 @@ pub fn merge(into: &mut Toml, from: &Toml) -> Result<()> {
} }
_ => { _ => {
// Trying to merge a table with something else // 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))
} }
} }
} }

View File

@ -4,7 +4,7 @@ use std::path::Path;
use libs::toml::Value as Toml; use libs::toml::Value as Toml;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use errors::{bail, Result}; use errors::{bail, Context, Result};
use utils::fs::read_file; use utils::fs::read_file;
/// Holds the data from a `theme.toml` file. /// Holds the data from a `theme.toml` file.
@ -40,8 +40,8 @@ impl Theme {
/// Parses a theme file from the given path /// Parses a theme file from the given path
pub fn from_file(path: &Path, theme_name: &str) -> Result<Theme> { pub fn from_file(path: &Path, theme_name: &str) -> Result<Theme> {
let content = read_file(path) let content =
.map_err(|e| errors::Error::chain(format!("Failed to load theme {}", theme_name), e))?; read_file(path).with_context(|| format!("Failed to load theme {}", theme_name))?;
Theme::parse(&content) Theme::parse(&content)
} }
} }

View File

@ -5,3 +5,4 @@ edition = "2021"
[dependencies] [dependencies]
libs = { path = "../libs" } libs = { path = "../libs" }
anyhow = "1.0.56"

View File

@ -1,121 +1 @@
use std::convert::Into; pub use anyhow::*;
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<Box<dyn StdError + Send + Sync>>,
}
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<Box<dyn StdError + Send + Sync>>) -> 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<String>)>) -> 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<String> for Error {
fn from(e: String) -> Self {
Self::msg(e)
}
}
impl From<toml::de::Error> for Error {
fn from(e: toml::de::Error) -> Self {
Self { kind: ErrorKind::Toml(e), source: None }
}
}
impl From<syntect::LoadingError> for Error {
fn from(e: syntect::LoadingError) -> Self {
Self { kind: ErrorKind::Syntect(e), source: None }
}
}
impl From<tera::Error> 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<image::ImageError> for Error {
fn from(e: image::ImageError) -> Self {
Self { kind: ErrorKind::Image(e), source: None }
}
}
/// Convenient wrapper around std::Result.
pub type Result<T> = ::std::result::Result<T, Error>;
// 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());
};
}

View File

@ -3,7 +3,7 @@ use std::path::Path;
use libs::once_cell::sync::Lazy; use libs::once_cell::sync::Lazy;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use errors::{bail, Error, Result}; use errors::{bail, Context, Result};
use libs::regex::Regex; use libs::regex::Regex;
use libs::{serde_yaml, toml}; use libs::{serde_yaml, toml};
@ -39,7 +39,7 @@ impl RawFrontMatter<'_> {
RawFrontMatter::Toml(s) => toml::from_str(s)?, RawFrontMatter::Toml(s) => toml::from_str(s)?,
RawFrontMatter::Yaml(s) => match serde_yaml::from_str(s) { RawFrontMatter::Yaml(s) => match serde_yaml::from_str(s) {
Ok(d) => d, Ok(d) => d,
Err(e) => bail!(format!("YAML deserialize error: {:?}", e)), Err(e) => bail!("YAML deserialize error: {:?}", e),
}, },
}; };
Ok(f) Ok(f)
@ -105,12 +105,10 @@ pub fn split_section_content<'c>(
content: &'c str, content: &'c str,
) -> Result<(SectionFrontMatter, &'c str)> { ) -> Result<(SectionFrontMatter, &'c str)> {
let (front_matter, content) = split_content(file_path, content)?; let (front_matter, content) = split_content(file_path, content)?;
let meta = SectionFrontMatter::parse(&front_matter).map_err(|e| { let meta = SectionFrontMatter::parse(&front_matter).with_context(|| {
Error::chain( format!("Error when parsing front matter of section `{}`", file_path.to_string_lossy())
format!("Error when parsing front matter of section `{}`", file_path.to_string_lossy()),
e,
)
})?; })?;
Ok((meta, content)) Ok((meta, content))
} }
@ -121,11 +119,8 @@ pub fn split_page_content<'c>(
content: &'c str, content: &'c str,
) -> Result<(PageFrontMatter, &'c str)> { ) -> Result<(PageFrontMatter, &'c str)> {
let (front_matter, content) = split_content(file_path, content)?; let (front_matter, content) = split_content(file_path, content)?;
let meta = PageFrontMatter::parse(&front_matter).map_err(|e| { let meta = PageFrontMatter::parse(&front_matter).with_context(|| {
Error::chain( format!("Error when parsing front matter of section `{}`", file_path.to_string_lossy())
format!("Error when parsing front matter of page `{}`", file_path.to_string_lossy()),
e,
)
})?; })?;
Ok((meta, content)) Ok((meta, content))
} }

View File

@ -1,6 +1,5 @@
use std::collections::hash_map::Entry as HEntry; use std::collections::hash_map::Entry as HEntry;
use std::collections::HashMap; use std::collections::HashMap;
use std::error::Error as StdError;
use std::ffi::OsStr; use std::ffi::OsStr;
use std::fs::{self, File}; use std::fs::{self, File};
use std::hash::{Hash, Hasher}; use std::hash::{Hash, Hasher};
@ -19,7 +18,7 @@ use serde::{Deserialize, Serialize};
use svg_metadata::Metadata as SvgMetadata; use svg_metadata::Metadata as SvgMetadata;
use config::Config; use config::Config;
use errors::{Error, Result}; use errors::{anyhow, Context, Error, Result};
use utils::fs as ufs; use utils::fs as ufs;
static RESIZED_SUBDIR: &str = "processed_images"; static RESIZED_SUBDIR: &str = "processed_images";
@ -83,22 +82,20 @@ impl ResizeArgs {
match op { match op {
"fit_width" => { "fit_width" => {
if width.is_none() { 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" => { "fit_height" => {
if height.is_none() { 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" => { "scale" | "fit" | "fill" => {
if width.is_none() || height.is_none() { if width.is_none() || height.is_none() {
return Err( return Err(anyhow!("op={} requires a `width` and `height` argument", op));
format!("op={} requires a `width` and `height` argument", op).into()
);
} }
} }
_ => return Err(format!("Invalid image resize operation: {}", op).into()), _ => return Err(anyhow!("Invalid image resize operation: {}", op)),
}; };
Ok(match op { Ok(match op {
@ -224,7 +221,7 @@ impl Format {
"jpeg" | "jpg" => Ok(Jpeg(jpg_quality)), "jpeg" | "jpg" => Ok(Jpeg(jpg_quality)),
"png" => Ok(Png), "png" => Ok(Png),
"webp" => Ok(WebP(quality)), "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))?; img.write_to(&mut f, ImageOutputFormat::Jpeg(q))?;
} }
Format::WebP(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 { let memory = match q {
Some(q) => encoder.encode(q as f32), Some(q) => encoder.encode(q as f32),
None => encoder.encode_lossless(), None => encoder.encode_lossless(),
@ -415,9 +413,8 @@ impl Processor {
format: &str, format: &str,
quality: Option<u8>, quality: Option<u8>,
) -> Result<EnqueueResponse> { ) -> Result<EnqueueResponse> {
let meta = ImageMeta::read(&input_path).map_err(|e| { let meta = ImageMeta::read(&input_path)
Error::chain(format!("Failed to read image: {}", input_path.display()), e) .with_context(|| format!("Failed to read image: {}", input_path.display()))?;
})?;
let args = ResizeArgs::from_args(op, width, height)?; let args = ResizeArgs::from_args(op, width, height)?;
let op = ResizeOp::new(args, meta.size); let op = ResizeOp::new(args, meta.size);
@ -529,8 +526,9 @@ impl Processor {
.map(|(hash, op)| { .map(|(hash, op)| {
let target = let target =
self.output_dir.join(Self::op_filename(*hash, op.collision_id, op.format)); 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::<Result<()>>() .collect::<Result<()>>()
@ -571,29 +569,27 @@ pub fn read_image_metadata<P: AsRef<Path>>(path: P) -> Result<ImageMetaResponse>
let path = path.as_ref(); let path = path.as_ref();
let ext = path.extension().and_then(OsStr::to_str).unwrap_or("").to_lowercase(); let ext = path.extension().and_then(OsStr::to_str).unwrap_or("").to_lowercase();
let error = |e: Box<dyn StdError + Send + Sync>| { let err_context = || format!("Failed to read image: {}", path.display());
Error::chain(format!("Failed to read image: {}", path.display()), e)
};
match ext.as_str() { match ext.as_str() {
"svg" => { "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()) { match (img.height(), img.width(), img.view_box()) {
(Some(h), Some(w), _) => Ok((h, w)), (Some(h), Some(w), _) => Ok((h, w)),
(_, _, Some(view_box)) => Ok((view_box.height, view_box.width)), (_, _, 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)) .map(|(h, w)| ImageMetaResponse::new_svg(h as u32, w as u32))
} }
"webp" => { "webp" => {
// Unfortunatelly we have to load the entire image here, unlike with the others :| // 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[..]); let decoder = webp::Decoder::new(&data[..]);
decoder.decode().map(ImageMetaResponse::from).ok_or_else(|| { decoder.decode().map(ImageMetaResponse::from).ok_or_else(|| {
Error::msg(format!("Failed to decode WebP image: {}", path.display())) 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),
} }
} }

View File

@ -9,7 +9,7 @@ use libs::tera::{Context as TeraContext, Tera};
use crate::library::Library; use crate::library::Library;
use config::Config; use config::Config;
use errors::{Error, Result}; use errors::{Context, Result};
use front_matter::{split_page_content, InsertAnchor, PageFrontMatter}; use front_matter::{split_page_content, InsertAnchor, PageFrontMatter};
use rendering::{render_content, Heading, RenderContext}; use rendering::{render_content, Heading, RenderContext};
use utils::site::get_reading_analytics; use utils::site::get_reading_analytics;
@ -240,9 +240,8 @@ impl Page {
context.set_current_page_path(&self.file.relative); context.set_current_page_path(&self.file.relative);
context.tera_context.insert("page", &SerializingPage::from_page_basic(self, None)); context.tera_context.insert("page", &SerializingPage::from_page_basic(self, None));
let res = render_content(&self.raw_content, &context).map_err(|e| { let res = render_content(&self.raw_content, &context)
Error::chain(format!("Failed to render content of {}", self.file.path.display()), e) .with_context(|| format!("Failed to render content of {}", self.file.path.display()))?;
})?;
self.summary = res self.summary = res
.summary_len .summary_len
@ -270,9 +269,8 @@ impl Page {
context.insert("page", &self.to_serialized(library)); context.insert("page", &self.to_serialized(library));
context.insert("lang", &self.lang); context.insert("lang", &self.lang);
render_template(tpl_name, tera, context, &config.theme).map_err(|e| { render_template(tpl_name, tera, context, &config.theme)
Error::chain(format!("Failed to render page '{}'", self.file.path.display()), e) .with_context(|| format!("Failed to render page '{}'", self.file.path.display()))
})
} }
/// Creates a vectors of asset URLs. /// Creates a vectors of asset URLs.

View File

@ -5,7 +5,7 @@ use libs::slotmap::DefaultKey;
use libs::tera::{Context as TeraContext, Tera}; use libs::tera::{Context as TeraContext, Tera};
use config::Config; use config::Config;
use errors::{Error, Result}; use errors::{Context, Result};
use front_matter::{split_section_content, SectionFrontMatter}; use front_matter::{split_section_content, SectionFrontMatter};
use rendering::{render_content, Heading, RenderContext}; use rendering::{render_content, Heading, RenderContext};
use utils::fs::read_file; use utils::fs::read_file;
@ -161,9 +161,8 @@ impl Section {
context.set_current_page_path(&self.file.relative); context.set_current_page_path(&self.file.relative);
context.tera_context.insert("section", &SerializingSection::from_section_basic(self, None)); context.tera_context.insert("section", &SerializingSection::from_section_basic(self, None));
let res = render_content(&self.raw_content, &context).map_err(|e| { let res = render_content(&self.raw_content, &context)
Error::chain(format!("Failed to render content of {}", self.file.path.display()), e) .with_context(|| format!("Failed to render content of {}", self.file.path.display()))?;
})?;
self.content = res.body; self.content = res.body;
self.toc = res.toc; self.toc = res.toc;
self.external_links = res.external_links; self.external_links = res.external_links;
@ -183,9 +182,8 @@ impl Section {
context.insert("section", &self.to_serialized(library)); context.insert("section", &self.to_serialized(library));
context.insert("lang", &self.lang); context.insert("lang", &self.lang);
render_template(tpl_name, tera, context, &config.theme).map_err(|e| { render_template(tpl_name, tera, context, &config.theme)
Error::chain(format!("Failed to render section '{}'", self.file.path.display()), e) .with_context(|| format!("Failed to render section '{}'", self.file.path.display()))
})
} }
/// Is this the index section? /// Is this the index section?

View File

@ -6,7 +6,7 @@ use libs::tera::{to_value, Context, Tera, Value};
use serde::Serialize; use serde::Serialize;
use config::Config; use config::Config;
use errors::{Error, Result}; use errors::{Context as ErrorContext, Result};
use utils::templates::{check_template_fallbacks, render_template}; use utils::templates::{check_template_fallbacks, render_template};
use crate::content::{Section, SerializingPage, SerializingSection}; use crate::content::{Section, SerializingPage, SerializingSection};
@ -247,7 +247,7 @@ impl<'a> Paginator<'a> {
context.insert("paginator", &self.build_paginator_context(pager)); context.insert("paginator", &self.build_paginator_context(pager));
render_template(&self.template, tera, context, &config.theme) 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))
} }
} }

View File

@ -6,7 +6,7 @@ use libs::tera::{Context, Tera};
use serde::Serialize; use serde::Serialize;
use config::{Config, Taxonomy as TaxonomyConfig}; 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 utils::templates::{check_template_fallbacks, render_template};
use crate::content::SerializingPage; use crate::content::SerializingPage;
@ -207,9 +207,8 @@ impl Taxonomy {
let template = check_template_fallbacks(&specific_template, tera, &config.theme) let template = check_template_fallbacks(&specific_template, tera, &config.theme)
.unwrap_or("taxonomy_single.html"); .unwrap_or("taxonomy_single.html");
render_template(template, tera, context, &config.theme).map_err(|e| { render_template(template, tera, context, &config.theme)
Error::chain(format!("Failed to render single term {} page.", self.kind.name), e) .with_context(|| format!("Failed to render single term {} page.", self.kind.name))
})
} }
pub fn render_all_terms( pub fn render_all_terms(
@ -233,9 +232,8 @@ impl Taxonomy {
let template = check_template_fallbacks(&specific_template, tera, &config.theme) let template = check_template_fallbacks(&specific_template, tera, &config.theme)
.unwrap_or("taxonomy_list.html"); .unwrap_or("taxonomy_list.html");
render_template(template, tera, context, &config.theme).map_err(|e| { render_template(template, tera, context, &config.theme)
Error::chain(format!("Failed to render a list of {} page.", self.kind.name), e) .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> { pub fn to_serialized<'a>(&'a self, library: &'a Library) -> SerializedTaxonomy<'a> {

View File

@ -7,6 +7,7 @@ use libs::reqwest::header::{HeaderMap, ACCEPT};
use libs::reqwest::{blocking::Client, StatusCode}; use libs::reqwest::{blocking::Client, StatusCode};
use config::LinkChecker; use config::LinkChecker;
use errors::anyhow;
use utils::links::has_anchor_id; 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) { if has_anchor_id(&body, anchor) {
Ok(()) Ok(())
} else { } else {
Err(errors::Error::from(format!("Anchor `#{}` not found on page", anchor))) Err(anyhow!("Anchor `#{}` not found on page", anchor))
} }
} }

View File

@ -1,12 +1,13 @@
use std::fmt::Write;
use libs::gh_emoji::Replacer as EmojiReplacer; use libs::gh_emoji::Replacer as EmojiReplacer;
use libs::once_cell::sync::Lazy; use libs::once_cell::sync::Lazy;
use libs::pulldown_cmark as cmark; use libs::pulldown_cmark as cmark;
use libs::tera; use libs::tera;
use std::fmt::Write;
use crate::context::RenderContext; use crate::context::RenderContext;
use crate::table_of_contents::{make_table_of_contents, Heading}; 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 front_matter::InsertAnchor;
use libs::pulldown_cmark::escape::escape_html; use libs::pulldown_cmark::escape::escape_html;
use utils::site::resolve_internal_link; use utils::site::resolve_internal_link;
@ -119,7 +120,7 @@ fn fix_link(
resolved.permalink resolved.permalink
} }
Err(_) => { Err(_) => {
return Err(format!("Relative link {} not found.", link).into()); return Err(anyhow!("Relative link {} not found.", link));
} }
} }
} else if is_external_link(link) { } else if is_external_link(link) {
@ -472,7 +473,7 @@ pub fn markdown_to_html(
c, c,
&None, &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()))); anchors_to_insert.push((anchor_idx, Event::Html(anchor_link.into())));
} }

View File

@ -1,6 +1,6 @@
use std::ops::Range; 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 libs::tera::{to_value, Context, Map, Tera, Value};
use pest::iterators::Pair; use pest::iterators::Pair;
use pest::Parser; use pest::Parser;
@ -43,7 +43,7 @@ impl Shortcode {
new_context.extend(context.clone()); new_context.extend(context.clone());
let res = utils::templates::render_template(&tpl_name, tera, new_context, &None) 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"); .replace("\r\n", "\n");
Ok(res) Ok(res)

View File

@ -16,7 +16,7 @@ use libs::tera::{Context, Tera};
use libs::walkdir::{DirEntry, WalkDir}; use libs::walkdir::{DirEntry, WalkDir};
use config::{get_config, Config}; use config::{get_config, Config};
use errors::{bail, Error, Result}; use errors::{anyhow, bail, Context as ErrorContext, Result};
use front_matter::InsertAnchor; use front_matter::InsertAnchor;
use library::{find_taxonomies, Library, Page, Paginator, Section, Taxonomy}; use library::{find_taxonomies, Library, Page, Paginator, Section, Taxonomy};
use libs::relative_path::RelativePathBuf; use libs::relative_path::RelativePathBuf;
@ -274,7 +274,12 @@ impl Site {
let library = self.library.read().unwrap(); let library = self.library.read().unwrap();
let collisions = library.check_for_path_collisions(); let collisions = library.check_for_path_collisions();
if !collisions.is_empty() { 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<()> { pub fn clean(&self) -> Result<()> {
if self.output_path.exists() { if self.output_path.exists() {
// Delete current `public` directory so we can start fresh // Delete current `public` directory so we can start fresh
remove_dir_all(&self.output_path) remove_dir_all(&self.output_path).context("Couldn't delete output directory")?;
.map_err(|e| Error::chain("Couldn't delete output directory", e))?;
} }
Ok(()) Ok(())

View File

@ -1,12 +1,12 @@
use core::time;
use std::{collections::HashMap, path::PathBuf, thread};
use libs::rayon::prelude::*; use libs::rayon::prelude::*;
use crate::Site; use crate::{anyhow, Site};
use core::time;
use errors::{bail, Result}; use errors::{bail, Result};
use errors::{Error, ErrorKind};
use libs::rayon; use libs::rayon;
use libs::url::Url; use libs::url::Url;
use std::{collections::HashMap, path::PathBuf, thread};
/// Check whether all internal links pointing to explicit anchor fragments are valid. /// 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.", "> Checked {} internal link(s) with anchors: {} target(s) missing.",
anchors_total, errors_total, 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 // (almost) all pages simultaneously, limiting all links for a single
// domain to one thread to avoid rate-limiting // domain to one thread to avoid rate-limiting
let threads = std::cmp::min(links_by_domain.len(), 8); let threads = std::cmp::min(links_by_domain.len(), 8);
let pool = rayon::ThreadPoolBuilder::new() let pool = rayon::ThreadPoolBuilder::new().num_threads(threads).build()?;
.num_threads(threads)
.build()
.map_err(|e| Error { kind: ErrorKind::Msg(e.to_string()), source: None })?;
let errors = pool.install(|| { let errors = pool.install(|| {
links_by_domain links_by_domain
@ -209,5 +206,5 @@ pub fn check_external_links(site: &Site) -> Result<()> {
.collect::<Vec<_>>() .collect::<Vec<_>>()
.join("\n"); .join("\n");
Err(Error { kind: ErrorKind::Msg(msg), source: None }) Err(anyhow!(msg))
} }

View File

@ -4,6 +4,7 @@ use std::path::{Path, PathBuf};
use libs::glob::glob; use libs::glob::glob;
use libs::sass_rs::{compile_file, Options, OutputStyle}; use libs::sass_rs::{compile_file, Options, OutputStyle};
use crate::anyhow;
use errors::{bail, Result}; use errors::{bail, Result};
use utils::fs::{create_file, ensure_directory_exists}; use utils::fs::{create_file, ensure_directory_exists};
@ -47,7 +48,7 @@ fn compile_sass_glob(
let mut compiled_paths = Vec::new(); let mut compiled_paths = Vec::new();
for file in files { 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 path_inside_sass = file.strip_prefix(&sass_path).unwrap();
let parent_inside_sass = path_inside_sass.parent(); let parent_inside_sass = path_inside_sass.parent();

View File

@ -7,7 +7,7 @@ use config::Config;
use libs::once_cell::sync::Lazy; use libs::once_cell::sync::Lazy;
use libs::tera::{Context, Tera}; use libs::tera::{Context, Tera};
use errors::{bail, Error, Result}; use errors::{bail, Context as ErrorContext, Result};
use utils::templates::rewrite_theme_paths; use utils::templates::rewrite_theme_paths;
pub static ZOLA_TERA: Lazy<Tera> = Lazy::new(|| { pub static ZOLA_TERA: Lazy<Tera> = Lazy::new(|| {
@ -48,7 +48,7 @@ pub fn render_redirect_template(url: &str, tera: &Tera) -> Result<String> {
context.insert("url", &url); context.insert("url", &url);
tera.render("internal/alias.html", &context) 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<Tera> { pub fn load_tera(path: &Path, config: &Config) -> Result<Tera> {
@ -58,7 +58,7 @@ pub fn load_tera(path: &Path, config: &Config) -> Result<Tera> {
// Only parsing as we might be extending templates from themes and that would error // Only parsing as we might be extending templates from themes and that would error
// as we haven't loaded them yet // as we haven't loaded them yet
let mut tera = 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 { if let Some(ref theme) = config.theme {
// Test that the templates folder exist for that theme // Test that the templates folder exist for that theme
@ -72,8 +72,8 @@ pub fn load_tera(path: &Path, config: &Config) -> Result<Tera> {
path.to_string_lossy().replace('\\', "/"), path.to_string_lossy().replace('\\', "/"),
theme theme
); );
let mut tera_theme = Tera::parse(&theme_tpl_glob) let mut tera_theme =
.map_err(|e| Error::chain("Error parsing templates from themes", e))?; Tera::parse(&theme_tpl_glob).context("Error parsing templates from themes")?;
rewrite_theme_paths(&mut tera_theme, theme); rewrite_theme_paths(&mut tera_theme, theme);
// TODO: add tests for theme-provided robots.txt (https://github.com/getzola/zola/pull/1722) // TODO: add tests for theme-provided robots.txt (https://github.com/getzola/zola/pull/1722)

View File

@ -5,23 +5,23 @@ use std::io::prelude::*;
use std::path::Path; use std::path::Path;
use std::time::SystemTime; use std::time::SystemTime;
use errors::{Error, Result}; use errors::{Context, Result};
pub fn is_path_in_directory(parent: &Path, path: &Path) -> Result<bool> { pub fn is_path_in_directory(parent: &Path, path: &Path) -> Result<bool> {
let canonical_path = path let canonical_path = path
.canonicalize() .canonicalize()
.map_err(|e| format!("Failed to canonicalize {}: {}", path.display(), e))?; .with_context(|| format!("Failed to canonicalize {}", path.display()))?;
let canonical_parent = parent let canonical_parent = parent
.canonicalize() .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)) Ok(canonical_path.starts_with(canonical_parent))
} }
/// Create a file with the content given /// Create a file with the content given
pub fn create_file(path: &Path, content: &str) -> Result<()> { pub fn create_file(path: &Path, content: &str) -> Result<()> {
let mut file = File::create(&path) let mut file =
.map_err(|e| Error::chain(format!("Failed to create file {}", path.display()), e))?; File::create(&path).with_context(|| format!("Failed to create file {}", path.display()))?;
file.write_all(content.as_bytes())?; file.write_all(content.as_bytes())?;
Ok(()) Ok(())
} }
@ -38,9 +38,8 @@ pub fn ensure_directory_exists(path: &Path) -> Result<()> {
/// exists before creating it /// exists before creating it
pub fn create_directory(path: &Path) -> Result<()> { pub fn create_directory(path: &Path) -> Result<()> {
if !path.exists() { if !path.exists() {
create_dir_all(path).map_err(|e| { create_dir_all(path)
Error::chain(format!("Was not able to create folder {}", path.display()), e) .with_context(|| format!("Failed to create folder {}", path.display()))?;
})?;
} }
Ok(()) Ok(())
} }
@ -49,7 +48,7 @@ pub fn create_directory(path: &Path) -> Result<()> {
pub fn read_file(path: &Path) -> Result<String> { pub fn read_file(path: &Path) -> Result<String> {
let mut content = String::new(); let mut content = String::new();
File::open(path) 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)?; .read_to_string(&mut content)?;
// Remove utf-8 BOM if any. // 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); let target_path = dest.join(relative_path);
if let Some(parent_directory) = target_path.parent() { if let Some(parent_directory) = target_path.parent() {
create_dir_all(parent_directory).map_err(|e| { create_dir_all(parent_directory).with_context(|| {
Error::chain(format!("Was not able to create folder {}", parent_directory.display()), e) 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. /// 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<()> { pub fn copy_file_if_needed(src: &Path, dest: &Path, hard_link: bool) -> Result<()> {
if let Some(parent_directory) = dest.parent() { if let Some(parent_directory) = dest.parent() {
create_dir_all(parent_directory).map_err(|e| { create_dir_all(parent_directory).with_context(|| {
Error::chain(format!("Was not able to create folder {}", parent_directory.display()), e) 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_metadata = metadata(&dest)?;
let target_mtime = FileTime::from_last_modification_time(&target_metadata); let target_mtime = FileTime::from_last_modification_time(&target_metadata);
if !(src_mtime == target_mtime && src_metadata.len() == target_metadata.len()) { if !(src_mtime == target_mtime && src_metadata.len() == target_metadata.len()) {
copy(src, &dest).map_err(|e| { copy(src, &dest).with_context(|| {
Error::chain( format!("Was not able to copy file {} to {}", src.display(), dest.display())
format!(
"Was not able to copy file {} to {}",
src.display(),
dest.display()
),
e,
)
})?; })?;
set_file_mtime(&dest, src_mtime)?; set_file_mtime(&dest, src_mtime)?;
} }
} else { } else {
copy(src, &dest).map_err(|e| { copy(src, &dest).with_context(|| {
Error::chain( format!("Was not able to copy directory {} to {}", src.display(), dest.display())
format!("Was not able to copy file {} to {}", src.display(), dest.display()),
e,
)
})?; })?;
set_file_mtime(&dest, src_mtime)?; 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)?; create_directory(&target_path)?;
} }
} else { } else {
copy_file(entry.path(), dest, src, hard_link).map_err(|e| { copy_file(entry.path(), dest, src, hard_link).with_context(|| {
Error::chain( format!(
format!( "Was not able to copy {} to {} (hard_link={})",
"Was not able to copy file {} to {}", entry.path().display(),
entry.path().display(), dest.display(),
dest.display() hard_link
),
e,
) )
})?; })?;
} }

View File

@ -3,7 +3,7 @@ use libs::unicode_segmentation::UnicodeSegmentation;
use std::collections::HashMap; use std::collections::HashMap;
use std::hash::BuildHasher; use std::hash::BuildHasher;
use errors::Result; use errors::{anyhow, Result};
/// Get word count and estimated reading time /// Get word count and estimated reading time
pub fn get_reading_analytics(content: &str) -> (usize, usize) { pub fn get_reading_analytics(content: &str) -> (usize, usize) {
@ -41,7 +41,7 @@ pub fn resolve_internal_link<S: BuildHasher>(
// to decode them first // to decode them first
let decoded = percent_decode(parts[0].as_bytes()).decode_utf8_lossy().to_string(); let decoded = percent_decode(parts[0].as_bytes()).decode_utf8_lossy().to_string();
let target = 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 { if parts.len() > 1 {
Ok(ResolvedInternalLink { Ok(ResolvedInternalLink {
permalink: format!("{}#{}", target, parts[1]), permalink: format!("{}#{}", target, parts[1]),

View File

@ -32,16 +32,16 @@ use hyper::header;
use hyper::server::Server; use hyper::server::Server;
use hyper::service::{make_service_fn, service_fn}; use hyper::service::{make_service_fn, service_fn};
use hyper::{Body, Method, Request, Response, StatusCode}; use hyper::{Body, Method, Request, Response, StatusCode};
use mime_guess::from_path as mimetype_from_path;
use time::macros::format_description; use time::macros::format_description;
use time::{OffsetDateTime, UtcOffset}; use time::{OffsetDateTime, UtcOffset};
use mime_guess::from_path as mimetype_from_path;
use libs::percent_encoding; use libs::percent_encoding;
use libs::serde_json; use libs::serde_json;
use notify::{watcher, RecursiveMode, Watcher}; use notify::{watcher, RecursiveMode, Watcher};
use ws::{Message, Sender, WebSocket}; use ws::{Message, Sender, WebSocket};
use errors::{Error as ZolaError, Result}; use errors::{anyhow, Context, Result};
use libs::globset::GlobSet; use libs::globset::GlobSet;
use libs::relative_path::{RelativePath, RelativePathBuf}; use libs::relative_path::{RelativePath, RelativePathBuf};
use pathdiff::diff_paths; use pathdiff::diff_paths;
@ -287,7 +287,7 @@ pub fn serve(
open: bool, open: bool,
include_drafts: bool, include_drafts: bool,
fast_rebuild: bool, fast_rebuild: bool,
utc_offset: UtcOffset utc_offset: UtcOffset,
) -> Result<()> { ) -> Result<()> {
let start = Instant::now(); let start = Instant::now();
let (mut site, address) = create_new_site( 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 // Stop right there if we can't bind to the address
let bind_address: SocketAddrV4 = match address.parse() { let bind_address: SocketAddrV4 = match address.parse() {
Ok(a) => a, 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() { 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); let config_path = PathBuf::from(config_file);
@ -347,7 +347,7 @@ pub fn serve(
if should_watch { if should_watch {
watcher watcher
.watch(root_dir.join(entry), RecursiveMode::Recursive) .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()); watchers.push(entry.to_string());
} }
} }
@ -415,7 +415,7 @@ pub fn serve(
let ws_server = ws_server let ws_server = ws_server
.bind(&*ws_address) .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 || { thread::spawn(move || {
ws_server.run().unwrap(); ws_server.run().unwrap();
@ -532,8 +532,10 @@ pub fn serve(
continue; continue;
} }
let format = format_description!("[year]-[month]-[day] [hour]:[minute]:[second]"); let format =
let current_time = OffsetDateTime::now_utc().to_offset(utc_offset).format(&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 { if let Ok(time_str) = current_time {
println!("Change detected @ {}", time_str); println!("Change detected @ {}", time_str);
} else { } else {
@ -559,7 +561,7 @@ pub fn serve(
} else { } else {
// an asset changed? a folder renamed? // an asset changed? a folder renamed?
// should we make it smarter so it doesn't reload the whole site? // should we make it smarter so it doesn't reload the whole site?
Err("dummy".into()) Err(anyhow!("dummy"))
}; };
if res.is_err() { if res.is_err() {

View File

@ -1,4 +1,3 @@
use std::error::Error as StdError;
use std::io::Write; use std::io::Write;
use std::time::Instant; use std::time::Instant;
use std::{convert::TryInto, env}; use std::{convert::TryInto, env};
@ -17,28 +16,39 @@ static COLOR_CHOICE: Lazy<ColorChoice> =
Lazy::new(|| if has_color() { ColorChoice::Always } else { ColorChoice::Never }); Lazy::new(|| if has_color() { ColorChoice::Always } else { ColorChoice::Never });
pub fn info(message: &str) { 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) { 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) { 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) { 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 /// Print a colorized message to stdout
fn colorize(message: &str, color: &ColorSpec) { fn colorize(message: &str, color: &ColorSpec, mut stream: StandardStream) {
let mut stdout = StandardStream::stdout(*COLOR_CHOICE); stream.set_color(color).unwrap();
stdout.set_color(color).unwrap(); write!(stream, "{}", message).unwrap();
write!(stdout, "{}", message).unwrap(); stream.set_color(&ColorSpec::new()).unwrap();
stdout.set_color(&ColorSpec::new()).unwrap(); writeln!(stream).unwrap();
writeln!(stdout).unwrap();
} }
/// Display in the console the number of pages/sections in the site /// Display in the console the number of pages/sections in the site

View File

@ -12,7 +12,6 @@ mod cmd;
mod console; mod console;
mod prompt; mod prompt;
fn get_config_file_path(dir: &PathBuf, config_path: &Path) -> (PathBuf, PathBuf) { fn get_config_file_path(dir: &PathBuf, config_path: &Path) -> (PathBuf, PathBuf) {
let root_dir = dir let root_dir = dir
.ancestors() .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")); .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 // 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) (root_dir.to_path_buf(), config_file)
} }

View File

@ -4,7 +4,7 @@ use std::time::Duration;
use libs::url::Url; use libs::url::Url;
use crate::console; use crate::console;
use errors::Result; use errors::{anyhow, Result};
/// Wait for user input and return what they typed /// Wait for user input and return what they typed
fn read_line() -> Result<String> { fn read_line() -> Result<String> {
@ -14,7 +14,7 @@ fn read_line() -> Result<String> {
lines lines
.next() .next()
.and_then(|l| l.ok()) .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 /// Ask a yes/no question to the user