diff --git a/src/capability.rs b/src/capability.rs index a2f8b7c..a5d7006 100644 --- a/src/capability.rs +++ b/src/capability.rs @@ -17,8 +17,7 @@ use std::borrow::Cow; use std::io::Write; -use crate::error; -use crate::expand::{Context, Expand, Parameter}; +use crate::expand::{self, Context, Expand, Parameter}; /// A trait for any object that will represent a terminal capability. pub trait Capability<'a>: Sized { @@ -61,7 +60,7 @@ impl<'a, T: AsRef<[u8]>> Expansion<'a, T> { } /// Expand to the given output. - pub fn to(self, output: W) -> error::Result<()> { + pub fn to(self, output: W) -> Result<(), expand::Error> { self.string.as_ref().expand( output, &self.params, @@ -70,7 +69,7 @@ impl<'a, T: AsRef<[u8]>> Expansion<'a, T> { } /// Expand into a vector. - pub fn to_vec(self) -> error::Result> { + pub fn to_vec(self) -> Result, expand::Error> { let mut result = Vec::with_capacity(self.string.as_ref().len()); self.to(&mut result)?; Ok(result) diff --git a/src/database.rs b/src/database.rs index eda21f6..4d8ee06 100644 --- a/src/database.rs +++ b/src/database.rs @@ -11,17 +11,18 @@ // TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION // // 0. You just DO WHAT THE FUCK YOU WANT TO. +//! A capability database. use fnv::FnvHasher; use std::collections::HashMap; -use std::env; -use std::fs::{self, File}; +use std::error::Error; +use std::fmt::{self, Display, Formatter}; +use std::fs; use std::hash::BuildHasherDefault; -use std::io::Read; use std::path::{Path, PathBuf}; +use std::{env, io}; use crate::capability::{Capability, Value}; -use crate::error::{self, Error}; use crate::names; use crate::parser::compiled; @@ -139,18 +140,19 @@ impl Database { } /// Load a database from the current environment. - pub fn from_env() -> error::Result { + pub fn from_env() -> Result { if let Ok(name) = env::var("TERM") { - Self::from_name(name) + Self::from_name(name).map_err(FromEnvError::FromName) } else { - Err(Error::NotFound) + Err(FromEnvError::NoTerm(NoTerm)) } } /// Load a database for the given name. - pub fn from_name>(name: N) -> error::Result { + pub fn from_name>(name: N) -> Result { let name = name.as_ref(); - let first = name.chars().next().ok_or(Error::NotFound)?; + let not_found = || FromNameError::NotFound(NotFound { name: name.into() }); + let first = name.chars().next().ok_or_else(not_found)?; // See https://manpages.debian.org/buster/ncurses-bin/terminfo.5.en.html#Fetching_Compiled_Descriptions let mut search = Vec::::new(); @@ -193,7 +195,7 @@ impl Database { path.push(name); if fs::metadata(&path).is_ok() { - return Self::from_path(path); + return Self::from_path(path).map_err(FromNameError::Load); } } @@ -204,29 +206,30 @@ impl Database { path.push(name); if fs::metadata(&path).is_ok() { - return Self::from_path(path); + return Self::from_path(path).map_err(FromNameError::Load); } } } - Err(Error::NotFound) + Err(not_found()) } /// Load a database from the given path. - pub fn from_path>(path: P) -> error::Result { - let mut file = File::open(path)?; - let mut buffer = Vec::new(); - file.read_to_end(&mut buffer)?; - - Self::from_buffer(buffer) + pub fn from_path>(path: P) -> Result { + let path = path.as_ref(); + (|| { + let buffer = fs::read(path).map_err(LoadErrorKind::Read)?; + Self::from_buffer(buffer).map_err(LoadErrorKind::Parse) + })() + .map_err(|kind| LoadError { path: path.into(), kind }) } /// Load a database from a buffer. - pub fn from_buffer>(buffer: T) -> error::Result { + pub fn from_buffer>(buffer: T) -> Result { if let Ok((_, database)) = compiled::parse(buffer.as_ref()) { Ok(database.into()) } else { - Err(Error::Parse) + Err(ParseError) } } @@ -281,3 +284,138 @@ impl Database { self.inner.get(name) } } + +/// An error in [`Database::from_env`]. +#[derive(Debug)] +pub enum FromEnvError { + /// The `$TERM` environment variable was not set. + NoTerm(NoTerm), + /// The terminal name was read, but loading the database failed. + FromName(FromNameError), +} + +impl Display for FromEnvError { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + f.write_str("failed to load terminfo database") + } +} + +impl Error for FromEnvError { + fn source(&self) -> Option<&(dyn Error + 'static)> { + match self { + Self::NoTerm(e) => Some(e), + Self::FromName(e) => e.source(), + } + } +} + +/// The `$TERM` environment variable was not set. +/// +/// A root cause of [`FromEnvError`]. +#[derive(Debug)] +#[non_exhaustive] +pub struct NoTerm; + +impl Display for NoTerm { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + f.write_str("no `$TERM` environment variable") + } +} + +impl Error for NoTerm {} + +/// An error in [`Database::from_name`]. +#[derive(Debug)] +pub enum FromNameError { + /// The terminfo entry was not found. + NotFound(NotFound), + /// The terminfo file could not be loaded. + Load(LoadError), +} + +impl Display for FromNameError { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + f.write_str("failed to load terminfo database") + } +} + +impl Error for FromNameError { + fn source(&self) -> Option<&(dyn Error + 'static)> { + match self { + Self::NotFound(e) => Some(e), + Self::Load(e) => Some(e), + } + } +} + +/// No terminfo database was found. +/// +/// A root cause of [`FromNameError`]. +#[derive(Debug)] +#[non_exhaustive] +pub struct NotFound { + name: Box, +} + +impl NotFound { + /// Get the name of the terminfo database. + #[must_use] + pub fn name(&self) -> &str { + &self.name + } +} + +impl Display for NotFound { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + write!(f, "no terminfo database found for `{}`", self.name) + } +} + +impl Error for NotFound {} + +/// An error loading the database, returned by [`Database::from_path`]. +#[derive(Debug)] +#[non_exhaustive] +pub struct LoadError { + /// The path the database is located at. + pub path: Box, + /// The cause of the error. + pub kind: LoadErrorKind, +} + +impl Display for LoadError { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + write!(f, "failed to load terminfo database {}", self.path.display()) + } +} + +impl Error for LoadError { + fn source(&self) -> Option<&(dyn Error + 'static)> { + match &self.kind { + LoadErrorKind::Read(e) => Some(e), + LoadErrorKind::Parse(e) => Some(e), + } + } +} + +/// A cause of a [`LoadError`]. +#[derive(Debug)] +pub enum LoadErrorKind { + /// An error occurred reading the file. + Read(io::Error), + /// There was an error parsing the file. + Parse(ParseError), +} + +/// An error parsing the database, returned by [`Database::from_buffer`]. +#[derive(Debug)] +#[non_exhaustive] +pub struct ParseError; + +impl Display for ParseError { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + f.write_str("failed to parse terminfo database") + } +} + +impl Error for ParseError {} diff --git a/src/error.rs b/src/error.rs deleted file mode 100644 index c589fd2..0000000 --- a/src/error.rs +++ /dev/null @@ -1,80 +0,0 @@ -// DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE -// Version 2, December 2004 -// -// Copyleft (ↄ) meh. | http://meh.schizofreni.co -// -// Everyone is permitted to copy and distribute verbatim or modified -// copies of this license document, and changing it is allowed as long -// as the name is changed. -// -// DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE -// TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION -// -// 0. You just DO WHAT THE FUCK YOU WANT TO. - -use std::error; -use std::fmt; -use std::io; - -#[derive(Debug)] -pub enum Error { - /// IO error. - Io(io::Error), - - /// Database not found. - NotFound, - - /// Parsing error. - Parse, - - /// Expansion error. - Expand(Expand), -} - -#[derive(Eq, PartialEq, Copy, Clone, Debug)] -pub enum Expand { - /// The expansion string is invalid. - Invalid, - - /// There was a type mismatch while expanding. - TypeMismatch, - - /// The stack underflowed while expanding. - StackUnderflow, -} - -pub type Result = ::std::result::Result; - -impl From for Error { - fn from(value: io::Error) -> Self { - Error::Io(value) - } -} - -impl From for Error { - fn from(value: Expand) -> Self { - Error::Expand(value) - } -} - -impl fmt::Display for Error { - fn fmt(&self, f: &mut fmt::Formatter) -> ::std::result::Result<(), fmt::Error> { - match *self { - Error::Io(ref err) => err.fmt(f), - - Error::NotFound => f.write_str("Capability database not found."), - - Error::Parse => f.write_str("Failed to parse capability database."), - - Error::Expand(ref err) => match *err { - Expand::Invalid => f.write_str("The expansion string is invalid."), - - Expand::StackUnderflow => f.write_str("Not enough elements on the stack."), - - Expand::TypeMismatch => f.write_str("Type mismatch."), - }, - } - } -} - -impl error::Error for Error {} diff --git a/src/expand.rs b/src/expand.rs index 3c22c4d..8d13a8d 100644 --- a/src/expand.rs +++ b/src/expand.rs @@ -12,10 +12,10 @@ // // 0. You just DO WHAT THE FUCK YOU WANT TO. -use std::char; +use std::fmt::{self, Display, Formatter}; use std::io::{BufWriter, Write}; +use std::{char, io}; -use crate::error; use crate::parser::expansion::*; /// Trait for items that can be expanded. @@ -25,7 +25,7 @@ pub trait Expand { output: W, parameters: &[Parameter], context: &mut Context, - ) -> error::Result<()>; + ) -> Result<(), Error>; } /// An expansion parameter. @@ -173,7 +173,7 @@ impl Expand for [u8] { output: W, parameters: &[Parameter], context: &mut Context, - ) -> error::Result<()> { + ) -> Result<(), Error> { let mut output = BufWriter::new(output); let mut input = self; let mut params: [Parameter; 9] = Default::default(); @@ -193,7 +193,7 @@ impl Expand for [u8] { item } - Err(_) => return Err(error::Expand::Invalid.into()), + Err(_) => return Err(Error::Parse(ParseError)), } }; } @@ -229,12 +229,12 @@ impl Expand for [u8] { } } - return Err(error::Expand::Invalid.into()); + return Err(ParseError.into()); } Some(_) => (), - None => return Err(error::Expand::StackUnderflow.into()), + None => return Err(StackUnderflow.into()), }, Item::Conditional(Conditional::Else) if conditional => { @@ -252,12 +252,12 @@ impl Expand for [u8] { } } - return Err(error::Expand::Invalid.into()); + return Err(ParseError.into()); } - Item::Conditional(..) => return Err(error::Expand::Invalid.into()), + Item::Conditional(..) => return Err(ParseError.into()), - Item::String(value) => output.write_all(value)?, + Item::String(value) => output.write_all(value).map_err(Error::Write)?, Item::Constant(Constant::Character(ch)) => { stack.push(Parameter::Number(ch as i32)); @@ -273,11 +273,11 @@ impl Expand for [u8] { } Some(_) => { - return Err(error::Expand::TypeMismatch.into()); + return Err(TypeMismatch.into()); } None => { - return Err(error::Expand::StackUnderflow.into()); + return Err(StackUnderflow.into()); } }, @@ -293,7 +293,7 @@ impl Expand for [u8] { context.fixed[index as usize] = value.clone(); } } else { - return Err(error::Expand::StackUnderflow.into()); + return Err(StackUnderflow.into()); } } @@ -313,7 +313,7 @@ impl Expand for [u8] { params[0] = Parameter::Number(x + 1); params[1] = Parameter::Number(y + 1); } else { - return Err(error::Expand::TypeMismatch.into()); + return Err(TypeMismatch.into()); } } @@ -353,9 +353,9 @@ impl Expand for [u8] { })) } - (Some(_), Some(_)) => return Err(error::Expand::TypeMismatch.into()), + (Some(_), Some(_)) => return Err(TypeMismatch.into()), - _ => return Err(error::Expand::StackUnderflow.into()), + _ => return Err(StackUnderflow.into()), }, Item::Operation(Operation::Unary(operation)) => match stack.pop() { @@ -364,9 +364,9 @@ impl Expand for [u8] { Unary::NOT => !x, })), - Some(_) => return Err(error::Expand::TypeMismatch.into()), + Some(_) => return Err(TypeMismatch.into()), - _ => return Err(error::Expand::StackUnderflow.into()), + _ => return Err(StackUnderflow.into()), }, Item::Print(p) => { @@ -412,18 +412,19 @@ impl Expand for [u8] { macro_rules! w { ($value:expr) => ( - output.write_all($value)? + output.write_all($value).map_err(Error::Write)? ); ($($item:tt)*) => ( - write!(output, $($item)*)? + write!(output, $($item)*).map_err(Error::Write)? ); } macro_rules! f { (by $length:expr) => ( + let spacer = if p.flags.space { b" " } else { b"0" }; for _ in 0 .. p.flags.width - $length { - output.write_all(if p.flags.space { b" " } else { b"0" })?; + output.write_all(spacer).map_err(Error::Write)?; } ); @@ -465,10 +466,9 @@ impl Expand for [u8] { w!("{}", value as u8 as char) } - (Format::Uni, Some(Parameter::Number(value))) => w!( - "{}", - char::from_u32(value as u32).ok_or(error::Expand::TypeMismatch)? - ), + (Format::Uni, Some(Parameter::Number(value))) => { + w!("{}", char::from_u32(value as u32).ok_or(TypeMismatch)?) + } (Format::Dec, Some(Parameter::Number(value))) => { f!(before value); @@ -518,9 +518,9 @@ impl Expand for [u8] { f!(after value); } - (_, Some(_)) => return Err(error::Expand::TypeMismatch.into()), + (_, Some(_)) => return Err(TypeMismatch.into()), - (_, None) => return Err(error::Expand::StackUnderflow.into()), + (_, None) => return Err(StackUnderflow.into()), } } } @@ -530,6 +530,99 @@ impl Expand for [u8] { } } +/// An error in expanding. +#[derive(Debug)] +pub enum Error { + /// An error occurred writing out the expansion. + Write(io::Error), + /// An error occurred parsing expansion string. + Parse(ParseError), + /// There was a type mismatch while expanding. + TypeMismatch(TypeMismatch), + /// The stack underflowed while expanding. + StackUnderflow(StackUnderflow), +} + +impl From for Error { + fn from(e: ParseError) -> Self { + Self::Parse(e) + } +} + +impl From for Error { + fn from(e: TypeMismatch) -> Self { + Self::TypeMismatch(e) + } +} + +impl From for Error { + fn from(e: StackUnderflow) -> Self { + Self::StackUnderflow(e) + } +} + +impl Display for Error { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + f.write_str("failed to expand terminfo string") + } +} + +impl std::error::Error for Error { + fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { + match self { + Self::Write(e) => Some(e), + Self::Parse(e) => Some(e), + Self::TypeMismatch(e) => Some(e), + Self::StackUnderflow(e) => Some(e), + } + } +} + +/// An error occurred parsing the expansion. +/// +/// A cause of [`Error`]. +#[derive(Debug)] +#[non_exhaustive] +pub struct ParseError; + +impl Display for ParseError { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + f.write_str("expansion string is invalid") + } +} + +impl std::error::Error for ParseError {} + +/// There was a type mismatch while expanding. +/// +/// A root cause of [`Error`]. +#[derive(Debug)] +#[non_exhaustive] +pub struct TypeMismatch; + +impl Display for TypeMismatch { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + f.write_str("type mismatch") + } +} + +impl std::error::Error for TypeMismatch {} + +/// The stack underflowed while expanding. +/// +/// A root cause of [`Error`]. +#[derive(Debug)] +#[non_exhaustive] +pub struct StackUnderflow; + +impl Display for StackUnderflow { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + f.write_str("stack underflow") + } +} + +impl std::error::Error for StackUnderflow {} + #[cfg(test)] mod test { #[test] diff --git a/src/lib.rs b/src/lib.rs index 12ea509..790c36a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -16,9 +16,6 @@ #[doc = include_str!("../README.md")] extern "C" {} -mod error; -pub use crate::error::{Error, Result}; - /// Parsers for various formats. mod parser; @@ -31,7 +28,7 @@ pub use crate::expand::Expand; pub mod capability; pub use crate::capability::{Capability, Value}; -mod database; +pub mod database; pub use crate::database::Database; /// Constants to deal with name differences across terminfo and termcap.