diff --git a/src/cache.rs b/src/cache.rs index 2eef24a..457d68e 100644 --- a/src/cache.rs +++ b/src/cache.rs @@ -4,19 +4,17 @@ use std::{ fs::{self, File}, io::{BufReader, Cursor, Read, Seek}, path::{Path, PathBuf}, + time::{Duration, SystemTime}, }; +use anyhow::{ensure, Context, Result}; use app_dirs::{get_app_root, AppDataType}; use log::debug; use reqwest::{blocking::Client, Proxy}; -use std::time::{Duration, SystemTime}; use walkdir::{DirEntry, WalkDir}; use zip::ZipArchive; -use crate::{ - error::TealdeerError::{self, CacheError, UpdateError}, - types::{PathSource, PlatformType}, -}; +use crate::types::{PathSource, PlatformType}; static CACHE_DIR_ENV_VAR: &str = "TEALDEER_CACHE_DIR"; @@ -53,16 +51,16 @@ impl PageLookupResult { /// /// This will return an error if either the page file or the patch file /// cannot be opened. - pub fn reader(&self) -> Result>, String> { + pub fn reader(&self) -> Result>> { // Open page file let page_file = File::open(&self.page_path) - .map_err(|msg| format!("Could not open page file at {:?}: {}", self.page_path, msg))?; + .with_context(|| format!("Could not open page file at {:?}", self.page_path))?; // Open patch file let patch_file_opt = match &self.patch_path { Some(path) => Some( File::open(path) - .map_err(|msg| format!("Could not open patch file at {:?}: {}", path, msg))?, + .with_context(|| format!("Could not open patch file at {:?}", path))?, ), None => None, }; @@ -102,26 +100,25 @@ impl Cache { } /// Return the path to the cache directory. - pub fn get_cache_dir() -> Result<(PathBuf, PathSource), TealdeerError> { + pub fn get_cache_dir() -> Result<(PathBuf, PathSource)> { // Allow overriding the cache directory by setting the env variable. if let Ok(value) = env::var(CACHE_DIR_ENV_VAR) { let path = PathBuf::from(value); let (path_exists, path_is_dir) = path .metadata() .map_or((false, false), |md| (true, md.is_dir())); - if path_exists && !path_is_dir { - return Err(CacheError(format!( - "Path specified by ${} is not a directory.", - CACHE_DIR_ENV_VAR - ))); - } + ensure!( + !path_exists || path_is_dir, + "Path specified by ${} is not a directory", + CACHE_DIR_ENV_VAR + ); if !path_exists { // Try to create the complete directory path. - fs::create_dir_all(&path).map_err(|_| { - CacheError(format!( - "Directory path specified by ${} cannot be created.", + fs::create_dir_all(&path).with_context(|| { + format!( + "Directory path specified by ${} cannot be created", CACHE_DIR_ENV_VAR - )) + ) })?; eprintln!( "Successfully created cache directory path `{}`.", @@ -132,16 +129,13 @@ impl Cache { }; // Otherwise, fall back to user cache directory. - match get_app_root(AppDataType::UserCache, &crate::APP_INFO) { - Ok(dirs) => Ok((dirs, PathSource::OsConvention)), - Err(_) => Err(CacheError( - "Could not determine user cache directory.".into(), - )), - } + let dirs = get_app_root(AppDataType::UserCache, &crate::APP_INFO) + .context("Could not determine user cache directory")?; + Ok((dirs, PathSource::OsConvention)) } /// Download the archive - fn download(&self) -> Result, TealdeerError> { + fn download(&self) -> Result> { let mut builder = Client::builder(); if let Ok(ref host) = env::var("HTTP_PROXY") { if let Ok(proxy) = Proxy::http(host) { @@ -153,9 +147,9 @@ impl Cache { builder = builder.proxy(proxy); } } - let client = builder.build().map_err(|e| { - TealdeerError::UpdateError(format!("Could not instantiate HTTP client: {}", e)) - })?; + let client = builder + .build() + .context("Could not instantiate HTTP client")?; let mut resp = client.get(&self.url).send()?; let mut buf: Vec = vec![]; let bytes_downloaded = resp.copy_to(&mut buf)?; @@ -169,7 +163,7 @@ impl Cache { } /// Update the pages cache. - pub fn update(&self) -> Result<(), TealdeerError> { + pub fn update(&self) -> Result<()> { // First, download the compressed data let bytes: Vec = self.download()?; @@ -182,8 +176,7 @@ impl Cache { // Make sure that cache directory exists debug!("Ensure cache directory {:?} exists", &cache_dir); - fs::create_dir_all(&cache_dir) - .map_err(|e| UpdateError(format!("Could not create cache directory: {}", e)))?; + fs::create_dir_all(&cache_dir).context("Could not create cache directory")?; // Clear cache directory // Note: This is not the best solution. Ideally we would download the @@ -191,12 +184,12 @@ impl Cache { // But renaming a directory doesn't work across filesystems and Rust // does not yet offer a recursive directory copying function. So for // now, we'll use this approach. - Self::clear()?; + Self::clear().context("Could not clear the cache directory")?; // Extract archive archive .extract(&pages_dir) - .map_err(|e| UpdateError(format!("Could not unpack compressed data: {}", e)))?; + .context("Could not unpack compressed data")?; Ok(()) } @@ -308,7 +301,7 @@ impl Cache { } /// Return the available pages. - pub fn list_pages(&self) -> Result, TealdeerError> { + pub fn list_pages(&self) -> Result> { // Determine platforms directory and platform let (cache_dir, _) = Self::get_cache_dir()?; let platforms_dir = cache_dir.join(TLDR_PAGES_DIR).join("pages"); @@ -353,35 +346,36 @@ impl Cache { } /// Delete the cache directory. - pub fn clear() -> Result<(), TealdeerError> { + pub fn clear() -> Result<()> { let (path, _) = Self::get_cache_dir()?; - if path.exists() && path.is_dir() { - // Delete old tldr-pages cache location as well if present - // TODO: To be removed in the future - for pages_dir_name in [TLDR_PAGES_DIR, TLDR_OLD_PAGES_DIR] { - let pages_dir = path.join(pages_dir_name); - if pages_dir.exists() { - fs::remove_dir_all(&pages_dir).map_err(|e| { - CacheError(format!( - "Could not remove cache directory ({}): {}", - pages_dir.display(), - e - )) - })?; - } + // Check preconditions + ensure!( + path.exists(), + "Cache path ({}) does not exist.", + path.display(), + ); + ensure!( + path.is_dir(), + "Cache path ({}) is not a directory.", + path.display() + ); + + // Delete old tldr-pages cache location as well if present + // TODO: To be removed in the future + for pages_dir_name in [TLDR_PAGES_DIR, TLDR_OLD_PAGES_DIR] { + let pages_dir = path.join(pages_dir_name); + + if pages_dir.exists() { + fs::remove_dir_all(&pages_dir).with_context(|| { + format!( + "Could not remove the cache directory at {}", + pages_dir.display() + ) + })?; } - } else if path.exists() { - return Err(CacheError(format!( - "Cache path ({}) is not a directory.", - path.display() - ))); - } else { - return Err(CacheError(format!( - "Cache path ({}) does not exist.", - path.display() - ))); - }; + } + Ok(()) } } diff --git a/src/error.rs b/src/error.rs index 77169e6..4a1b0b3 100644 --- a/src/error.rs +++ b/src/error.rs @@ -2,23 +2,10 @@ use std::fmt; use reqwest::Error as ReqwestError; - #[derive(Debug)] #[allow(clippy::enum_variant_names)] pub enum TealdeerError { - CacheError(String), UpdateError(String), - WriteError(String), -} - -impl TealdeerError { - pub fn message(&self) -> &str { - match self { - Self::CacheError(msg) - | Self::UpdateError(msg) - | Self::WriteError(msg) => msg, - } - } } impl From for TealdeerError { @@ -30,9 +17,7 @@ impl From for TealdeerError { impl fmt::Display for TealdeerError { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match self { - Self::CacheError(e) => write!(f, "CacheError: {}", e), Self::UpdateError(e) => write!(f, "UpdateError: {}", e), - Self::WriteError(e) => write!(f, "WriteError: {}", e), } } } diff --git a/src/main.rs b/src/main.rs index 3644361..25f31e4 100644 --- a/src/main.rs +++ b/src/main.rs @@ -40,7 +40,7 @@ use crate::{ extensions::Dedup, output::print_page, types::{ColorOptions, PlatformType}, - utils::{print_anyhow_error, print_error, print_warning}, + utils::{print_error, print_warning}, }; const NAME: &str = "tealdeer"; @@ -210,10 +210,7 @@ fn check_cache(args: &Args, enable_styles: bool) -> CheckCacheResult { /// Clear the cache fn clear_cache(quietly: bool, enable_styles: bool) { Cache::clear().unwrap_or_else(|e| { - print_error( - enable_styles, - &format!("Could not delete cache: {}", e.message()), - ); + print_error(enable_styles, &e.context("Could not clear cache")); process::exit(1); }); if !quietly { @@ -224,10 +221,7 @@ fn clear_cache(quietly: bool, enable_styles: bool) { /// Update the cache fn update_cache(cache: &Cache, quietly: bool, enable_styles: bool) { cache.update().unwrap_or_else(|e| { - print_error( - enable_styles, - &format!("Could not update cache: {}", e.message()), - ); + print_error(enable_styles, &e.context("Could not update cache")); process::exit(1); }); if !quietly { @@ -242,7 +236,7 @@ fn show_config_path(enable_styles: bool) { println!("Config path is: {}", config_file_path.to_str().unwrap()); } Err(e) => { - print_anyhow_error(enable_styles, &e.context("Could not look up config path")); + print_error(enable_styles, &e.context("Could not look up config path")); process::exit(1); } } @@ -309,7 +303,7 @@ fn create_config_and_exit(enable_styles: bool) { process::exit(0); } Err(e) => { - print_anyhow_error(enable_styles, &e); + print_error(enable_styles, &e); process::exit(1); } } @@ -414,7 +408,7 @@ fn main() { let config = match Config::load(enable_styles) { Ok(config) => config, Err(e) => { - print_anyhow_error(enable_styles, &e.context("Could not load config")); + print_error(enable_styles, &e.context("Could not load config")); process::exit(1); } }; @@ -440,8 +434,8 @@ fn main() { // If a local file was passed in, render it and exit if let Some(file) = args.render { let path = PageLookupResult::with_page(file); - if let Err(msg) = print_page(&path, args.raw, &config) { - print_error(enable_styles, &msg); + if let Err(ref e) = print_page(&path, args.raw, &config) { + print_error(enable_styles, e); process::exit(1); } else { process::exit(0); @@ -476,10 +470,7 @@ fn main() { if args.list { // Get list of pages let pages = cache.list_pages().unwrap_or_else(|e| { - print_error( - enable_styles, - &format!("Could not get list of pages: {}", e.message()), - ); + print_error(enable_styles, &e.context("Could not get list of pages")); process::exit(1); }); @@ -506,8 +497,8 @@ fn main() { &languages, config.directories.custom_pages_dir.as_deref(), ) { - if let Err(msg) = print_page(&lookup_result, args.raw, &config) { - print_error(enable_styles, &msg); + if let Err(ref e) = print_page(&lookup_result, args.raw, &config) { + print_error(enable_styles, e); process::exit(1); } process::exit(0); diff --git a/src/output.rs b/src/output.rs index cad0690..44464dd 100644 --- a/src/output.rs +++ b/src/output.rs @@ -2,10 +2,11 @@ use std::io::{self, BufRead, Write}; +use anyhow::{Context, Result}; + use crate::{ cache::PageLookupResult, config::{Config, StyleConfig}, - error::TealdeerError::WriteError, formatter::{highlight_lines, PageSnippet}, line_iterator::LineIterator, }; @@ -15,7 +16,7 @@ pub fn print_page( lookup_result: &PageLookupResult, enable_markdown: bool, config: &Config, -) -> Result<(), String> { +) -> Result<()> { // Create reader from file(s) let reader = lookup_result.reader()?; @@ -26,8 +27,8 @@ pub fn print_page( if enable_markdown { // Print the raw markdown of the file. for line in reader.lines() { - writeln!(handle, "{}", line.unwrap()) - .map_err(|_| "Could not write to stdout".to_string())?; + let line = line.context("Error while reading from a page")?; + writeln!(handle, "{}", line).context("Could not write to stdout")?; } } else { // Closure that processes a page snippet and writes it to stdout @@ -35,8 +36,7 @@ pub fn print_page( if snip.is_empty() { Ok(()) } else { - print_snippet(&mut handle, snip, &config.style) - .map_err(|e| WriteError(e.to_string())) + print_snippet(&mut handle, snip, &config.style).context("Failed to print snippet") } }; @@ -46,13 +46,11 @@ pub fn print_page( &mut process_snippet, !config.display.compact, ) - .map_err(|e| format!("Could not write to stdout: {}", e.message()))?; + .context("Could not write to stdout")?; }; // We're done outputting data, flush stdout now! - handle - .flush() - .map_err(|_| "Could not flush stdout".to_string())?; + handle.flush().context("Could not flush stdout")?; Ok(()) } @@ -61,7 +59,7 @@ fn print_snippet( writer: &mut impl Write, snip: PageSnippet<'_>, style: &StyleConfig, -) -> Result<(), io::Error> { +) -> io::Result<()> { use PageSnippet::*; match snip { diff --git a/src/utils.rs b/src/utils.rs index 912e147..b47d9f6 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -6,15 +6,9 @@ pub fn print_warning(enable_styles: bool, message: &str) { print_msg(enable_styles, message, "Warning: ", Color::Yellow); } -/// Print an error to stderr. If `enable_styles` is true, then a red message -/// will be printed. -pub fn print_error(enable_styles: bool, message: &str) { - print_msg(enable_styles, message, "Error: ", Color::Red); -} - /// Print an anyhow error to stderr. If `enable_styles` is true, then a red /// message will be printed. -pub fn print_anyhow_error(enable_styles: bool, error: &anyhow::Error) { +pub fn print_error(enable_styles: bool, error: &anyhow::Error) { print_msg( enable_styles, &format!("{:?}", error), diff --git a/tests/lib.rs b/tests/lib.rs index dd03135..0f3128e 100644 --- a/tests/lib.rs +++ b/tests/lib.rs @@ -263,7 +263,7 @@ fn test_cache_location_not_a_directory() { .assert() .failure() .stderr(contains(format!( - "Path specified by ${} is not a directory.", + "Path specified by ${} is not a directory", CACHE_DIR_ENV_VAR ))); }