Skip to content

Commit

Permalink
Support ~ and $HOME components in path option (#278)
Browse files Browse the repository at this point in the history
* Support ~ and $HOME components in path option

* Clean up error handling, canonicalize after replacing components

* Clean up error handling further
  • Loading branch information
uncenter authored Dec 10, 2024
1 parent c1ad4d6 commit bb4aa6a
Showing 1 changed file with 21 additions and 9 deletions.
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

0 comments on commit bb4aa6a

Please sign in to comment.