Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support ~ and $HOME components in path option #278

Merged
merged 3 commits into from
Dec 10, 2024
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 21 additions & 9 deletions src/config.rs
Original file line number Diff line number Diff line change
@@ -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::PathBuf;
use std::{env, fs, io};
use std::path::{Path, PathBuf};
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
Expand All @@ -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<Self> {
pub fn try_config() -> Result<Self> {
// 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.
Expand All @@ -37,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)?;
Expand All @@ -46,25 +47,25 @@ 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.
/// Use this method when the user wishes to skip config file lookup.
pub fn try_config_default() -> io::Result<Self> {
pub fn try_config_default() -> Result<Self> {
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<Self> {
fn from_entry_config(entry_config: &EntryConfig) -> Result<Self> {
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 {
Expand All @@ -79,6 +80,17 @@ impl Config {
}
}

fn normalize_path(path: &Path) -> Result<PathBuf> {
Ok(match path
.strip_prefix("~")
.or_else(|_| path.strip_prefix("$HOME"))
{
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
/// 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
Expand Down
Loading