use log::info;
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use std::env::home_dir;
use std::error::Error as StdError;
use std::fs::{create_dir_all, read_to_string, write};
use std::path::PathBuf;
#[derive(Debug, Serialize, Deserialize, JsonSchema)]
pub struct Config {
pub authors: Vec<String>,
#[serde(default = "default_theme_color")]
pub theme_color: String,
#[serde(default = "default_quote_style")]
pub quote_style: String,
#[serde(default = "default_author_style")]
pub author_style: String,
#[serde(default = "default_nested_quote_style")]
pub nested_quote_style: String,
#[serde(default = "default_max_tries")]
pub max_tries: usize,
#[serde(default = "default_log_file")]
pub log_file: String,
#[serde(default = "default_rainbow_mode")]
pub rainbow_mode: bool,
#[serde(default = "default_layout")]
pub layout: Layout,
#[serde(default = "default_box_corners")]
pub box_corners: BoxCorners,
#[serde(default = "default_prefer_cache")]
pub prefer_cache: bool,
#[serde(default = "default_api_calls_per_minute")]
pub api_calls_per_minute: usize,
}
#[derive(Debug, Serialize, Deserialize, JsonSchema, Clone, Copy, PartialEq, Eq)]
#[serde(rename_all = "lowercase")]
pub enum Layout {
Default,
Box,
}
#[derive(Debug, Serialize, Deserialize, JsonSchema, Clone, Copy, PartialEq, Eq)]
#[serde(rename_all = "lowercase")]
pub enum BoxCorners {
Pointy,
Rounded,
}
pub fn default_theme_color() -> String {
"#B7FFFA".to_string()
}
pub fn default_quote_style() -> String {
"bold".to_string()
}
pub fn default_author_style() -> String {
"green".to_string()
}
pub fn default_nested_quote_style() -> String {
String::new()
}
pub fn default_max_tries() -> usize {
30
}
pub fn default_log_file() -> String {
String::from("getquotes.log")
}
pub fn default_rainbow_mode() -> bool {
false
}
pub fn default_layout() -> Layout {
Layout::Default
}
pub fn default_box_corners() -> BoxCorners {
BoxCorners::Pointy
}
pub fn default_prefer_cache() -> bool {
true
}
pub fn default_api_calls_per_minute() -> usize {
10
}
pub fn default_authors() -> Vec<String> {
vec![
"Mahatma Gandhi".into(),
"Albert Einstein".into(),
"Martin Luther King, Jr.".into(),
"Leonardo da Vinci".into(),
"Walt Disney".into(),
"Edgar Allan Poe".into(),
"Sigmund Freud".into(),
"Thomas A. Edison".into(),
"Robin Williams".into(),
"Steve Jobs".into(),
]
}
pub fn load_or_create_config() -> Result<Config, Box<dyn StdError + Send + Sync>> {
let config_path = get_config_path()?;
if !config_path.exists() {
if let Some(parent_dir) = config_path.parent() {
create_dir_all(parent_dir)?;
}
let default_config = Config {
authors: default_authors(),
theme_color: default_theme_color(),
quote_style: default_quote_style(),
author_style: default_author_style(),
nested_quote_style: default_nested_quote_style(),
max_tries: default_max_tries(),
log_file: default_log_file(),
rainbow_mode: default_rainbow_mode(),
layout: default_layout(),
box_corners: default_box_corners(),
prefer_cache: default_prefer_cache(),
api_calls_per_minute: default_api_calls_per_minute(),
};
let toml_string = toml::to_string_pretty(&default_config)?;
write(&config_path, toml_string)?;
info!("Config file created at: {config_path:?}");
return Ok(default_config);
}
let toml_content = read_to_string(&config_path)?;
let config: Config = toml::from_str(&toml_content)?;
info!("Config file loaded from: {config_path:?}");
Ok(config)
}
pub fn get_config_path() -> Result<PathBuf, Box<dyn StdError + Send + Sync>> {
let home = home_dir()
.ok_or_else(|| Box::<dyn StdError + Send + Sync>::from("Unable to find home directory."))?;
let config_dir = home.join(".config/getquotes");
create_dir_all(&config_dir)?;
let config_path = config_dir.join("config.toml");
Ok(config_path)
}
pub fn load_or_create_config_from_path(
path: &str,
) -> Result<Config, Box<dyn StdError + Send + Sync>> {
let config_path = PathBuf::from(path);
if !config_path.exists() {
if let Some(parent_dir) = config_path.parent() {
create_dir_all(parent_dir)?;
}
let default_config = Config {
authors: default_authors(),
theme_color: default_theme_color(),
quote_style: default_quote_style(),
author_style: default_author_style(),
nested_quote_style: default_nested_quote_style(),
max_tries: default_max_tries(),
log_file: default_log_file(),
rainbow_mode: default_rainbow_mode(),
layout: default_layout(),
box_corners: default_box_corners(),
prefer_cache: default_prefer_cache(),
api_calls_per_minute: default_api_calls_per_minute(),
};
let toml_string = toml::to_string_pretty(&default_config)?;
write(&config_path, toml_string)?;
info!("Config file created at: {config_path:?}");
return Ok(default_config);
}
let toml_content = read_to_string(&config_path)?;
let config: Config = toml::from_str(&toml_content)?;
info!("Config file loaded from: {config_path:?}");
Ok(config)
}
pub fn parse_hex_color(hex_str: &str) -> Option<(u8, u8, u8)> {
let clean_hex = hex_str.strip_prefix('#').unwrap_or(hex_str);
if clean_hex.len() != 6 {
return None;
}
let r = u8::from_str_radix(&clean_hex[0..2], 16).ok()?;
let g = u8::from_str_radix(&clean_hex[2..4], 16).ok()?;
let b = u8::from_str_radix(&clean_hex[4..6], 16).ok()?;
Some((r, g, b))
}