use std::{
fs::read_to_string,
path::{Path, PathBuf},
};
use anyhow::{Context, Result};
use chrono::TimeDelta;
use colored::Colorize;
use directories::ProjectDirs;
use log::info;
use serde::{Deserialize, Serialize};
#[derive(Clone, Eq, PartialEq, Hash, Debug, Deserialize, Serialize)]
pub struct Config {
#[serde(default = "default_hooks_directory")]
pub hooks_directory: PathBuf,
#[serde(default = "default_state_path")]
pub state_file_path: PathBuf,
#[serde(default = "default_history_path")]
pub history_file_path: PathBuf,
#[serde(
default = "default_pomodoro_duration",
with = "crate::time::duration::seconds"
)]
pub pomodoro_duration: TimeDelta,
#[serde(
default = "default_short_break_duration",
with = "crate::time::duration::seconds"
)]
pub short_break_duration: TimeDelta,
#[serde(
default = "default_long_break_duration",
with = "crate::time::duration::seconds"
)]
pub long_break_duration: TimeDelta,
}
impl Config {
pub fn init(config_path: &Path) -> Result<Self> {
if let Some(conf) = Config::load(config_path)? {
Ok(conf)
} else {
let conf = Config::default();
info!(
"Creating config file at {}",
config_path.display().to_string().cyan()
);
conf.save(config_path)
.with_context(|| format!("Unable to save config to {}", config_path.display()))?;
Ok(conf)
}
}
pub fn init_default() -> Result<Self> {
let path = crate::default_config_path()?;
Self::init(&path)
}
pub fn load(path: &Path) -> Result<Option<Self>> {
if path.exists() {
let config_str = read_to_string(path)
.with_context(|| format!("Failed to read {}", path.display()))?;
toml::from_str(&config_str).with_context(|| "Failed to parse config from TOML")
} else {
Ok(None)
}
}
pub fn save(&self, path: &Path) -> Result<()> {
let toml = toml::to_string(&self).with_context(|| "Unable to format config as TOML")?;
std::fs::write(path, toml)
.with_context(|| format!("Unable to write config TOML to path {}", path.display()))
}
}
impl Default for Config {
fn default() -> Self {
Self {
hooks_directory: default_hooks_directory(),
state_file_path: default_state_path(),
history_file_path: default_history_path(),
pomodoro_duration: default_pomodoro_duration(),
short_break_duration: default_short_break_duration(),
long_break_duration: default_long_break_duration(),
}
}
}
pub fn default_config_path() -> Result<PathBuf> {
let conf_path = ProjectDirs::from("dev", "Cosmicrose", "Tomate")
.with_context(|| "Unable to determine XDG directories")?
.config_dir()
.join("config.toml");
Ok(conf_path)
}
fn default_hooks_directory() -> PathBuf {
let project_dirs = ProjectDirs::from("dev", "Cosmicrose", "Tomate")
.with_context(|| "Unable to determine XDG directories")
.unwrap();
project_dirs.config_dir().join("hooks")
}
fn default_state_path() -> PathBuf {
ProjectDirs::from("dev", "Cosmicrose", "Tomate")
.with_context(|| "Unable to determine XDG directories")
.unwrap()
.state_dir()
.with_context(|| "Getting state dir")
.unwrap()
.join("current.toml")
}
fn default_history_path() -> PathBuf {
ProjectDirs::from("dev", "Cosmicrose", "Tomate")
.with_context(|| "Unable to determine XDG directories")
.unwrap()
.data_dir()
.join("history.toml")
}
fn default_pomodoro_duration() -> TimeDelta {
TimeDelta::new(25 * 60, 0).unwrap()
}
fn default_short_break_duration() -> TimeDelta {
TimeDelta::new(5 * 60, 0).unwrap()
}
fn default_long_break_duration() -> TimeDelta {
TimeDelta::new(20 * 60, 0).unwrap()
}