parent
62a0e7b1fd
commit
c11ae6ef28
2
Cargo.lock
generated
2
Cargo.lock
generated
@ -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",
|
||||
|
@ -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"] }
|
||||
|
@ -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<P: AsRef<Path>>(path: P) -> Result<Config> {
|
||||
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))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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<Theme> {
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
@ -5,3 +5,4 @@ edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
libs = { path = "../libs" }
|
||||
anyhow = "1.0.56"
|
||||
|
@ -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<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());
|
||||
};
|
||||
}
|
||||
pub use anyhow::*;
|
||||
|
@ -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))
|
||||
}
|
||||
|
@ -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<u8>,
|
||||
) -> Result<EnqueueResponse> {
|
||||
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::<Result<()>>()
|
||||
@ -571,29 +569,27 @@ pub fn read_image_metadata<P: AsRef<Path>>(path: P) -> Result<ImageMetaResponse>
|
||||
let path = path.as_ref();
|
||||
let ext = path.extension().and_then(OsStr::to_str).unwrap_or("").to_lowercase();
|
||||
|
||||
let error = |e: Box<dyn StdError + Send + Sync>| {
|
||||
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),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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.
|
||||
|
@ -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?
|
||||
|
@ -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))
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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> {
|
||||
|
@ -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))
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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())));
|
||||
}
|
||||
|
||||
|
@ -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)
|
||||
|
@ -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(())
|
||||
|
@ -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::<Vec<_>>()
|
||||
.join("\n");
|
||||
|
||||
Err(Error { kind: ErrorKind::Msg(msg), source: None })
|
||||
Err(anyhow!(msg))
|
||||
}
|
||||
|
@ -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();
|
||||
|
@ -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<Tera> = Lazy::new(|| {
|
||||
@ -48,7 +48,7 @@ pub fn render_redirect_template(url: &str, tera: &Tera) -> Result<String> {
|
||||
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<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
|
||||
// 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<Tera> {
|
||||
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)
|
||||
|
@ -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<bool> {
|
||||
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<String> {
|
||||
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
|
||||
)
|
||||
})?;
|
||||
}
|
||||
|
@ -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<S: BuildHasher>(
|
||||
// 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]),
|
||||
|
@ -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() {
|
||||
|
@ -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<ColorChoice> =
|
||||
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
|
||||
|
@ -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)
|
||||
}
|
||||
|
||||
|
@ -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<String> {
|
||||
@ -14,7 +14,7 @@ fn read_line() -> Result<String> {
|
||||
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
|
||||
|
Loading…
Reference in New Issue
Block a user