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

Configuration wizard #432

Merged
merged 19 commits into from
Jan 27, 2023
Merged
Show file tree
Hide file tree
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
56 changes: 50 additions & 6 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 5 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -27,14 +27,18 @@ tempfile = "3.3"
[dependencies]
anyhow = "1.0"
atty = "0.2"
chrono = "0.4"
clap = "4.0"
clap_complete = "4.0"
clap_mangen = "0.2"
console = "0.15.2"
dirs = "4.0.0"
dialoguer = "0.10.2"
email_address = "0.2.4"
env_logger = "0.8"
erased-serde = "0.3"
himalaya-lib = { git = "https://git.sr.ht/~soywod/himalaya-lib", branch = "develop" }
log = "0.4"
once_cell = "1.16.0"
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
shellexpand = "2.1"
Expand Down
86 changes: 35 additions & 51 deletions src/config/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,20 @@
//! user configuration file.

use anyhow::{anyhow, Context, Result};
use dirs::{config_dir, home_dir};
use himalaya_lib::{AccountConfig, BackendConfig, EmailHooks, EmailTextPlainFormat};
use log::{debug, trace};
use serde::Deserialize;
use std::{collections::HashMap, env, fs, path::PathBuf};
use serde::{Deserialize, Serialize};
use std::{collections::HashMap, fs, path::PathBuf};
use toml;

use crate::{account::DeserializedAccountConfig, config::prelude::*};
use crate::{
account::DeserializedAccountConfig,
config::{prelude::*, wizard::wizard},
};

/// Represents the user config file.
#[derive(Debug, Default, Clone, Eq, PartialEq, Deserialize)]
#[derive(Debug, Default, Clone, Eq, PartialEq, Deserialize, Serialize)]
#[serde(rename_all = "kebab-case")]
pub struct DeserializedConfig {
#[serde(alias = "name")]
Expand All @@ -27,14 +31,14 @@ pub struct DeserializedConfig {

pub email_listing_page_size: Option<usize>,
pub email_reading_headers: Option<Vec<String>>,
#[serde(default, with = "email_text_plain_format")]
#[serde(default, with = "EmailTextPlainFormatOptionDef", skip_serializing_if = "Option::is_none")]
pub email_reading_format: Option<EmailTextPlainFormat>,
pub email_reading_verify_cmd: Option<String>,
pub email_reading_decrypt_cmd: Option<String>,
pub email_writing_headers: Option<Vec<String>>,
pub email_writing_sign_cmd: Option<String>,
pub email_writing_encrypt_cmd: Option<String>,
#[serde(default, with = "email_hooks")]
#[serde(default, with = "EmailHooksOptionDef", skip_serializing_if = "Option::is_none")]
pub email_hooks: Option<EmailHooks>,

#[serde(flatten)]
Expand All @@ -46,9 +50,13 @@ impl DeserializedConfig {
pub fn from_opt_path(path: Option<&str>) -> Result<Self> {
debug!("path: {:?}", path);

let path = path.map(|s| s.into()).unwrap_or(Self::path()?);
let content = fs::read_to_string(path).context("cannot read config file")?;
let config: Self = toml::from_str(&content).context("cannot parse config file")?;
let config: Self = match path.map(|s| s.into()).or_else(Self::path) {
Some(path) => {
let content = fs::read_to_string(path).context("cannot read config file")?;
toml::from_str(&content).context("cannot parse config file")?
}
None => wizard()?,
};

if config.accounts.is_empty() {
return Err(anyhow!("config file must contain at least one account"));
Expand All @@ -58,48 +66,24 @@ impl DeserializedConfig {
Ok(config)
}

/// Tries to get the XDG config file path from XDG_CONFIG_HOME
/// environment variable.
fn path_from_xdg() -> Result<PathBuf> {
let path = env::var("XDG_CONFIG_HOME").context("cannot read env var XDG_CONFIG_HOME")?;
let path = PathBuf::from(path).join("himalaya").join("config.toml");
Ok(path)
}

/// Tries to get the XDG config file path from HOME environment
/// variable.
fn path_from_xdg_alt() -> Result<PathBuf> {
let home_var = if cfg!(target_family = "windows") {
"USERPROFILE"
} else {
"HOME"
};
let path = env::var(home_var).context(format!("cannot read env var {}", &home_var))?;
let path = PathBuf::from(path)
.join(".config")
.join("himalaya")
.join("config.toml");
Ok(path)
}

/// Tries to get the .himalayarc config file path from HOME
/// environment variable.
fn path_from_home() -> Result<PathBuf> {
let home_var = if cfg!(target_family = "windows") {
"USERPROFILE"
} else {
"HOME"
};
let path = env::var(home_var).context(format!("cannot read env var {}", &home_var))?;
let path = PathBuf::from(path).join(".himalayarc");
Ok(path)
}

/// Tries to get the config file path.
pub fn path() -> Result<PathBuf> {
Self::path_from_xdg()
.or_else(|_| Self::path_from_xdg_alt())
.or_else(|_| Self::path_from_home())
/// Tries to return a config path from a few default settings.
///
/// Tries paths in this order:
///
/// - `"$XDG_CONFIG_DIR/himalaya/config.toml"` (or equivalent to `$XDG_CONFIG_DIR` in other
/// OSes.)
/// - `"$HOME/.config/himalaya/config.toml"`
/// - `"$HOME/.himalayarc"`
///
/// Returns `Some(path)` if the path exists, otherwise `None`.
pub fn path() -> Option<PathBuf> {
config_dir()
.map(|p| p.join("himalaya").join("config.toml"))
.filter(|p| p.exists())
.or_else(|| home_dir().map(|p| p.join(".config").join("himalaya").join("config.toml")))
.filter(|p| p.exists())
.or_else(|| home_dir().map(|p| p.join(".himalayarc")))
.filter(|p| p.exists())
}

pub fn to_configs(&self, account_name: Option<&str>) -> Result<(AccountConfig, BackendConfig)> {
Expand Down
1 change: 1 addition & 0 deletions src/config/mod.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
pub mod args;
pub mod config;
pub mod prelude;
mod wizard;

pub use config::*;
Loading