Port cache and output modules to anyhow results

This commit is contained in:
Danilo Bargen 2022-01-02 20:17:22 +01:00
commit fd307757d7
6 changed files with 78 additions and 116 deletions

View file

@ -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<BufReader<Box<dyn Read>>, String> {
pub fn reader(&self) -> Result<BufReader<Box<dyn Read>>> {
// 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<Vec<u8>, TealdeerError> {
fn download(&self) -> Result<Vec<u8>> {
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<u8> = 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<u8> = 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<Vec<String>, TealdeerError> {
pub fn list_pages(&self) -> Result<Vec<String>> {
// 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(())
}
}

View file

@ -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<ReqwestError> for TealdeerError {
@ -30,9 +17,7 @@ impl From<ReqwestError> 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),
}
}
}

View file

@ -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);

View file

@ -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 {

View file

@ -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),

View file

@ -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
)));
}