1
0
Fork 0
mirror of https://github.com/lise-henry/crowbook synced 2024-05-20 18:36:13 +02:00
crowbook/src/lib/error.rs
2023-08-19 04:10:53 +02:00

414 lines
12 KiB
Rust

// Copyright (C) 2016-2023 Élisabeth HENRY.
//
// This file is part of Crowbook.
//
// Crowbook is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published
// by the Free Software Foundation, either version 2.1 of the License, or
// (at your option) any later version.
//
// Caribon is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Lesser General Public License for more details.
//
// You should have received ba copy of the GNU Lesser General Public License
// along with Crowbook. If not, see <http://www.gnu.org/licenses/>.
use std::borrow::Cow;
use std::error;
use std::fmt;
use std::result;
use std::string::FromUtf8Error;
use rust_i18n::t;
#[derive(Debug, PartialEq, Clone)]
/// Source of an error.
///
/// Contains (if it's possible) the file and ideally the line that the user should
/// look at to correct their error.
pub struct Source {
/// File name of the source
#[doc(hidden)]
pub file: Option<String>,
/// Line number of the source
#[doc(hidden)]
pub line: Option<u32>,
}
impl Source {
/// Create an empty source, with both fields set to None
pub fn empty() -> Source {
Source {
file: None,
line: None,
}
}
/// Create a new source pointing to file
pub fn new<S: Into<String>>(s: S) -> Source {
Source {
file: Some(s.into()),
line: None,
}
}
/// Sets line number of a source.
pub fn set_line(&mut self, line: u32) -> &mut Self {
self.line = Some(line);
self
}
/// Unsets a line number of a source
#[doc(hidden)]
pub fn unset_line(&mut self) -> &mut Self {
self.line = None;
self
}
}
impl fmt::Display for Source {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
if let Some(ref file) = self.file {
write!(f, "{file}")?;
if let Some(line) = self.line {
write!(f, ":{line}")?;
}
} else {
write!(f, "<UNKNOWN FILE>")?;
}
Ok(())
}
}
impl<'a> From<&'a Source> for Source {
fn from(s: &'a Source) -> Source {
s.clone()
}
}
#[derive(Debug, PartialEq)]
/// Crowbook Error type.
///
/// This type tries (when it can) to track where the error came from, to
/// pinpoint the file (at least) and, if possible, the line the user needs
/// to look at.
pub struct Error {
/// Origin (file, line) of the error, if there is one
source: Source,
inner: Inner,
}
impl Error {
/// Creates a new default error.
pub fn default<S: Into<Cow<'static, str>>, O: Into<Source>>(source: O, msg: S) -> Error {
Error {
source: source.into(),
inner: Inner::Default(msg.into()),
}
}
/// Creates a new parser error.
///
/// Error when parsing markdown file.
pub fn parser<S: Into<Cow<'static, str>>, O: Into<Source>>(source: O, msg: S) -> Error {
Error {
source: source.into(),
inner: Inner::Parser(msg.into()),
}
}
/// Creates a new syntect error.
///
/// Error when parsing (and higlighting) syntax code
pub fn syntect<S: Into<Cow<'static, str>>, O: Into<Source>>(source: O, msg: S) -> Error {
Error {
source: source.into(),
inner: Inner::Syntect(msg.into()),
}
}
/// Creates a new config parser error.
///
/// Error when parsing the book configuration file.
pub fn config_parser<S: Into<Cow<'static, str>>, O: Into<Source>>(source: O, msg: S) -> Error {
Error {
source: source.into(),
inner: Inner::ConfigParser(msg.into()),
}
}
/// Creates a new "file not found" error
///
/// # Arguments
/// * source: the source of the error.
/// * msg: description of why the file was needed.
/// * file: file name that wasn't found.
pub fn file_not_found<
S1: Into<Cow<'static, str>>,
S2: Into<Cow<'static, str>>,
O: Into<Source>,
>(
source: O,
msg: S1,
file: S2,
) -> Error {
Error {
source: source.into(),
inner: Inner::FileNotFound(msg.into(), file.into()),
}
}
/// Creates a new render error.
///
/// Error when rendering the book to a given format.
pub fn render<S: Into<Cow<'static, str>>, O: Into<Source>>(source: O, msg: S) -> Error {
Error {
source: source.into(),
inner: Inner::Render(msg.into()),
}
}
/// Creates a new template error.
///
/// Error when compiling a template.
pub fn template<S: Into<Cow<'static, str>>, O: Into<Source>>(source: O, msg: S) -> Error {
Error {
source: source.into(),
inner: Inner::Template(msg.into()),
}
}
/// Creates a new invalid option error.
///
/// Error when trying to set an option.
pub fn invalid_option<S: Into<Cow<'static, str>>, O: Into<Source>>(source: O, msg: S) -> Error {
Error {
source: source.into(),
inner: Inner::InvalidOption(msg.into()),
}
}
/// Creates a new zipper error.
///
/// Error when moving/copying files to temporary dir, e.g. using `zip` commmand.
pub fn zipper<S: Into<Cow<'static, str>>>(msg: S) -> Error {
Error {
source: Source::empty(),
inner: Inner::Zipper(msg.into()),
}
}
/// Creates a new book option error
///
/// Used when converting an error to invalid type.
pub fn book_option<S: Into<Cow<'static, str>>, O: Into<Source>>(source: O, msg: S) -> Error {
Error {
source: source.into(),
inner: Inner::BookOption(msg.into()),
}
}
/// Change the source of an error.
pub fn with_source<O: Into<Source>>(mut self, source: O) -> Error {
self.source = source.into();
self
}
/// Returns true if self is a default option error, false else.
pub fn is_default(&self) -> bool {
matches!(self.inner, Inner::Default(..))
}
/// Returns true if self is a parser error, false else.
pub fn is_parser(&self) -> bool {
matches!(self.inner, Inner::Parser(..))
}
/// Returns true if self is a config parser error, false else.
pub fn is_config_parser(&self) -> bool {
matches!(self.inner, Inner::ConfigParser(..))
}
/// Returns true if self is a file not found error, false else.
pub fn is_file_not_found(&self) -> bool {
matches!(self.inner, Inner::FileNotFound(..))
}
/// Returns true if self is a render error, false else.
pub fn is_render(&self) -> bool {
matches!(self.inner, Inner::Render(..))
}
/// Returns true if self is a zipper error, false else.
pub fn is_zipper(&self) -> bool {
matches!(self.inner, Inner::Zipper(..))
}
/// Returns true if self is a book option error, false else.
pub fn is_book_option(&self) -> bool {
matches!(self.inner, Inner::BookOption(..))
}
/// Returns true if self is an invalid option error, false else.
pub fn is_invalid_option(&self) -> bool {
matches!(self.inner, Inner::InvalidOption(..))
}
}
impl error::Error for Error {
fn description(&self) -> &str {
match self.inner {
Inner::Default(ref s)
| Inner::Parser(ref s)
| Inner::Zipper(ref s)
| Inner::BookOption(ref s)
| Inner::ConfigParser(ref s)
| Inner::InvalidOption(ref s)
| Inner::Render(ref s)
| Inner::Template(ref s)
| Inner::Syntect(ref s) => s.as_ref(),
Inner::FileNotFound(..) => "File not found",
}
}
}
impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let source = &self.source;
if let Some(ref file) = source.file {
write!(f, "{file}")?;
if let Some(line) = source.line {
write!(f, ":{line}")?;
}
write!(f, ": ")?;
}
match self.inner {
Inner::Default(ref s) => write!(f, "{s}"),
Inner::Parser(ref s) => {
write!(
f,
"{}",
t!("error.markdown", error = s)
)
}
Inner::ConfigParser(ref s) => {
f.write_str(&t!("error.config"))?;
f.write_str(s)
}
Inner::FileNotFound(ref description, ref file) => {
write!(
f,
"{}",
t!(
"error.file_not_found",
file = file,
description = description
)
)
}
Inner::Template(ref s) => {
write!(
f,
"{}",
t!("error.template", template = s)
)
}
Inner::Render(ref s) => {
f.write_str(&t!("error.render_error"))?;
f.write_str(s)
}
Inner::Zipper(ref s) => {
f.write_str(&t!("error.zipper"))?;
f.write_str(s)
}
Inner::BookOption(ref s) => {
f.write_str(&t!("error.bookoption"))?;
f.write_str(s)
}
Inner::InvalidOption(ref s) => {
f.write_str(&t!("error.invalid_option"))?;
f.write_str(s)
}
Inner::Syntect(ref s) => {
f.write_str(&t!("error.syntect"))?;
f.write_str(s)
}
}?;
Ok(())
}
}
/// Crowbook's Result type, used by many methods that can fail
pub type Result<T> = result::Result<T, Error>;
/// Implement our Error from upon::error
impl From<upon::Error> for Error {
fn from(err: upon::Error) -> Error {
Error::template(Source::empty(), format!("{:#}", err))
}
}
impl From<FromUtf8Error> for Error {
fn from(err: FromUtf8Error) -> Error {
Error::render(
Source::empty(),
t!("error.utf8_error", error = err),
)
}
}
impl From<std::str::Utf8Error> for Error {
fn from(err: std::str::Utf8Error) -> Error {
Error::render(
Source::empty(),
t!("error.utf8_error", error = err),
)
}
}
impl From<fmt::Error> for Error {
fn from(err: fmt::Error) -> Error {
Error::default(
Source::empty(),
lformat!("format error: {error}", error = err),
)
}
}
impl From<syntect::Error> for Error {
fn from(err: syntect::Error) -> Error {
Error::syntect(
Source::empty(),
lformat!("syntect error: {error}", error = err),
)
}
}
#[derive(Debug, PartialEq)]
enum Inner {
/// Default variant
Default(Cow<'static, str>),
/// An error in Parsing markdown file
Parser(Cow<'static, str>),
/// An error in parsing a book configuration file
ConfigParser(Cow<'static, str>),
/// An error when a file is not found
FileNotFound(Cow<'static, str>, Cow<'static, str>), // description, file
/// An error in a renderer
Render(Cow<'static, str>),
/// An error during "zipping" processus
Zipper(Cow<'static, str>),
/// An error relative to BookOption convertion (usually a type error)
BookOption(Cow<'static, str>),
/// An invalid option
InvalidOption(Cow<'static, str>),
/// Error when compiling template
Template(Cow<'static, str>),
/// Error when parsing code syntax
Syntect(Cow<'static, str>),
}