1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
//! Configuration file parser.

use crate::app::style::Style;
use crate::args::Args;
use crate::widget::style::Color;
use anyhow::Result;
use serde::{Deserialize, Serialize};
use std::fs;
use std::str::FromStr;

/// Application configuration.
#[derive(Debug, Default, Serialize, Deserialize)]
pub struct Config {
	/// General configuration.
	pub general: GeneralConfig,
	/// GnuPG configuration.
	pub gpg: GpgConfig,
}

/// General configuration.
#[derive(Debug, Default, Serialize, Deserialize)]
pub struct GeneralConfig {
	/// [`Args::splash`]
	pub splash: bool,
	/// [`Args::tick_rate`]
	pub tick_rate: u64,
	/// [`Args::color`]
	pub color: String,
	/// [`Args::style`]
	pub style: String,
	/// [`Args::file_explorer`]
	pub file_explorer: String,
}

/// GnuPG configuration.
#[derive(Debug, Default, Serialize, Deserialize)]
pub struct GpgConfig {
	/// [`Args::armor`]
	pub armor: bool,
	/// [`Args::homedir`]
	pub homedir: Option<String>,
	/// [`Args::outdir`]
	pub outdir: Option<String>,
	/// [`Args::default_key`]
	pub default_key: Option<String>,
}

impl Config {
	/// Checks the possible locations for the configuration file.
	///
	/// - `<config_dir>/gpg-tui.toml`
	/// - `<config_dir>/gpg-tui/gpg-tui.toml`
	/// - `<config_dir>/gpg-tui/config`
	///
	/// Returns the path if the configuration file is found.
	pub fn get_default_location() -> Option<String> {
		if let Some(config_dir) = dirs_next::config_dir() {
			let file_name = format!("{}.toml", env!("CARGO_PKG_NAME"));
			for config_file in vec![
				config_dir.join(&file_name),
				config_dir.join(env!("CARGO_PKG_NAME")).join(&file_name),
				config_dir.join(env!("CARGO_PKG_NAME")).join("config"),
			] {
				if config_file.exists() {
					return config_file.to_str().map(String::from);
				}
			}
		}
		None
	}

	/// Parses the configuration file.
	pub fn parse_config(file: &str) -> Result<Config> {
		let contents = fs::read_to_string(file)?;
		let config: Config = toml::from_str(&contents)?;
		Ok(config)
	}

	/// Update the command-line arguments based on configuration.
	pub fn update_args(self, mut args: Args) -> Args {
		args.armor = self.gpg.armor;
		args.splash = self.general.splash;
		args.homedir = self.gpg.homedir;
		args.outdir = self.gpg.outdir;
		args.default_key = self.gpg.default_key;
		args.tick_rate = self.general.tick_rate;
		args.color = Color::from(self.general.color.as_ref());
		args.style = Style::from_str(&self.general.style).unwrap_or_default();
		args.file_explorer = self.general.file_explorer;
		args
	}
}

#[cfg(test)]
mod tests {
	use super::*;
	use pretty_assertions::assert_eq;
	use std::path::PathBuf;
	#[test]
	fn test_parse_config() -> Result<()> {
		let mut path = PathBuf::from(env!("CARGO_MANIFEST_DIR"))
			.join("config")
			.join(format!("{}.toml", env!("CARGO_PKG_NAME")))
			.to_string_lossy()
			.into_owned();
		if let Some(global_path) = Config::get_default_location() {
			path = global_path;
		}
		let mut config = Config::parse_config(&path)?;
		config.gpg.default_key = Some(String::from("test_key"));
		let args = config.update_args(Args::default());
		assert_eq!(Some(String::from("test_key")), args.default_key);
		Ok(())
	}
}