mirror of
https://github.com/tealdeer-rs/tealdeer.git
synced 2026-08-09 09:49:10 +02:00
Port cache and output modules to anyhow results
This commit is contained in:
parent
405a2c9c8f
commit
fd307757d7
6 changed files with 78 additions and 116 deletions
116
src/cache.rs
116
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<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(())
|
||||
}
|
||||
}
|
||||
|
|
|
|||
15
src/error.rs
15
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<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),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
31
src/main.rs
31
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);
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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),
|
||||
|
|
|
|||
|
|
@ -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
|
||||
)));
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue