From 1b28a8e0891be4c1fc59f3f4ec79ed7fa4a521a6 Mon Sep 17 00:00:00 2001 From: uncenter <47499684+uncenter@users.noreply.github.com> Date: Tue, 10 Dec 2024 17:45:56 -0500 Subject: [PATCH 1/3] Support ~ and $HOME components in path option --- src/config.rs | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/src/config.rs b/src/config.rs index 3381f43..d92bfd4 100644 --- a/src/config.rs +++ b/src/config.rs @@ -2,7 +2,7 @@ use clap::ValueEnum; use serde::{Deserialize, Serialize}; -use std::path::PathBuf; +use std::path::{Path, PathBuf}; use std::{env, fs, io}; /// This struct is the actual config type consumed through the codebase. It is boostrapped via its @@ -64,7 +64,7 @@ impl Config { fn from_entry_config(entry_config: &EntryConfig) -> io::Result { Ok(Config { path: match &entry_config.path { - Some(path) => path.clone(), + Some(path) => normalize_path(path), None => env::current_dir()?.canonicalize()?, }, display_mode: match &entry_config.display_mode { @@ -79,6 +79,17 @@ impl Config { } } +fn normalize_path(path: &Path) -> PathBuf { + let path = path.canonicalize().unwrap_or_else(|_| path.to_path_buf()); + match path + .strip_prefix("~") + .or_else(|_| path.strip_prefix("$HOME")) + { + Ok(stripped) => user_dirs::home_dir().unwrap().join(stripped), + Err(_) => path, + } +} + /// This struct is a reflection of [`Config`] with its fields wrapped with [`Option`], which /// ensures that we can deserialize from partial config file contents and populate empty fields /// with defaults. Moreover, enum fields cannot set defaults values currently, so we need to From 2801b4a484539d9069b76ae183d7b0206b388862 Mon Sep 17 00:00:00 2001 From: uncenter <47499684+uncenter@users.noreply.github.com> Date: Tue, 10 Dec 2024 18:18:57 -0500 Subject: [PATCH 2/3] Clean up error handling, canonicalize after replacing components --- src/config.rs | 23 ++++++++++++----------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/src/config.rs b/src/config.rs index d92bfd4..ca87c16 100644 --- a/src/config.rs +++ b/src/config.rs @@ -1,9 +1,10 @@ //! This module contains the config specification and functionality for creating a config. +use anyhow::Result; use clap::ValueEnum; use serde::{Deserialize, Serialize}; use std::path::{Path, PathBuf}; -use std::{env, fs, io}; +use std::{env, fs}; /// This struct is the actual config type consumed through the codebase. It is boostrapped via its /// public methods and uses [`EntryConfig`], a private struct, under the hood in order to @@ -22,7 +23,7 @@ impl Config { /// This method tries to deserialize the config file (empty, non-existent, partial or complete) /// and uses [`EntryConfig`] as an intermediary struct. This is the primary method used when /// creating a config. - pub fn try_config() -> anyhow::Result { + pub fn try_config() -> Result { // Within this method, we check if the config file is empty before deserializing it. Users // should be able to proceed with empty config files. If empty or not found, then we fall // back to the "EntryConfig" default before conversion. @@ -51,20 +52,20 @@ impl Config { /// This method does not look for the config file and uses [`EntryConfig`]'s defaults instead. /// Use this method when the user wishes to skip config file lookup. - pub fn try_config_default() -> io::Result { + pub fn try_config_default() -> Result { Self::from_entry_config(&EntryConfig::default()) } /// This method prints the full config (merged with config file, as needed) as valid, pretty TOML. - pub fn print(self) -> std::result::Result<(), toml::ser::Error> { + pub fn print(self) -> Result<(), toml::ser::Error> { print!("{}", toml::to_string_pretty(&self)?); Ok(()) } - fn from_entry_config(entry_config: &EntryConfig) -> io::Result { + fn from_entry_config(entry_config: &EntryConfig) -> Result { Ok(Config { path: match &entry_config.path { - Some(path) => normalize_path(path), + Some(path) => normalize_path(path)?, None => env::current_dir()?.canonicalize()?, }, display_mode: match &entry_config.display_mode { @@ -79,15 +80,15 @@ impl Config { } } -fn normalize_path(path: &Path) -> PathBuf { - let path = path.canonicalize().unwrap_or_else(|_| path.to_path_buf()); - match path +fn normalize_path(path: &Path) -> Result { + Ok(match path .strip_prefix("~") .or_else(|_| path.strip_prefix("$HOME")) { - Ok(stripped) => user_dirs::home_dir().unwrap().join(stripped), - Err(_) => path, + Ok(stripped) => user_dirs::home_dir()?.join(stripped), + Err(_) => path.to_path_buf(), } + .canonicalize()?) } /// This struct is a reflection of [`Config`] with its fields wrapped with [`Option`], which From 967d013a963852a8c223dc6bba484ff008e92085 Mon Sep 17 00:00:00 2001 From: uncenter <47499684+uncenter@users.noreply.github.com> Date: Tue, 10 Dec 2024 18:24:14 -0500 Subject: [PATCH 3/3] Clean up error handling further --- src/config.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/config.rs b/src/config.rs index ca87c16..575f9f2 100644 --- a/src/config.rs +++ b/src/config.rs @@ -38,7 +38,7 @@ impl Config { let path = match paths.into_iter().find(|p| p.exists()) { Some(path) => path, - None => return Ok(Self::try_config_default()?), + None => return Self::try_config_default(), }; let contents = fs::read_to_string(path)?; @@ -47,7 +47,7 @@ impl Config { } else { toml::from_str(&contents)? }; - Ok(Self::from_entry_config(&entry_config)?) + Self::from_entry_config(&entry_config) } /// This method does not look for the config file and uses [`EntryConfig`]'s defaults instead.