use crate::error::MpsError;
use crate::meta::MetaConfig;
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::path::{Path, PathBuf};
pub use crate::meta::NotifyConfig;
fn default_serve_port() -> u16 {
3000
}
fn default_serve_host() -> String {
"127.0.0.1".into()
}
fn default_git_remote() -> String {
"origin".into()
}
fn default_git_branch() -> String {
"master".into()
}
fn default_command() -> String {
"open".into()
}
fn default_type_aliases() -> HashMap<String, String> {
HashMap::new()
}
fn default_command_aliases() -> HashMap<String, String> {
HashMap::new()
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ServeConfig {
#[serde(default = "default_serve_port")]
pub port: u16,
#[serde(default = "default_serve_host")]
pub host: String,
#[serde(default)]
pub token: String,
}
impl Default for ServeConfig {
fn default() -> Self {
Self {
port: default_serve_port(),
host: default_serve_host(),
token: String::new(),
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Config {
pub mps_dir: PathBuf,
pub storage_dir: PathBuf,
pub log_file: PathBuf,
#[serde(default = "default_git_remote")]
pub git_remote: String,
#[serde(default = "default_git_branch")]
pub git_branch: String,
#[serde(default = "default_command")]
pub default_command: String,
#[serde(default = "default_type_aliases", alias = "aliases")]
pub type_aliases: HashMap<String, String>,
#[serde(default = "default_command_aliases")]
pub command_aliases: HashMap<String, String>,
#[serde(default)]
pub custom_tags: Vec<String>,
#[serde(default)]
pub notify: NotifyConfig,
#[serde(default)]
pub serve: ServeConfig,
}
impl Config {
pub fn default_config() -> Result<Self, MpsError> {
let home = dirs::home_dir()
.ok_or_else(|| MpsError::ConfigInvalid("cannot determine home directory".into()))?;
let mps_dir = home.join(".mps");
Ok(Config {
storage_dir: mps_dir.join("mps"),
log_file: mps_dir.join("mps.log"),
mps_dir,
git_remote: "origin".into(),
git_branch: "master".into(),
default_command: "open".into(),
type_aliases: HashMap::new(),
command_aliases: HashMap::new(),
custom_tags: Vec::new(),
notify: NotifyConfig::default(),
serve: ServeConfig::default(),
})
}
pub fn merge_meta(&mut self, meta: &MetaConfig) {
for (k, v) in &meta.type_aliases {
self.type_aliases
.entry(k.clone())
.or_insert_with(|| v.clone());
}
for (k, v) in &meta.command_aliases {
self.command_aliases
.entry(k.clone())
.or_insert_with(|| v.clone());
}
if let Some(ref dc) = meta.default_command {
self.default_command = dc.clone();
}
for t in &meta.custom_tags {
if !self.custom_tags.contains(t) {
self.custom_tags.push(t.clone());
}
}
let def = NotifyConfig::default();
let n = &meta.notify;
if !n.enabled {
self.notify.enabled = false;
}
if !n.notify_open_tasks {
self.notify.notify_open_tasks = false;
}
if n.task_notify_at.is_some() {
self.notify.task_notify_at = n.task_notify_at.clone();
}
if !n.open_task_tags.is_empty() {
self.notify.open_task_tags = n.open_task_tags.clone();
}
if n.window_minutes != def.window_minutes {
self.notify.window_minutes = n.window_minutes;
}
if n.task_cooldown_minutes != def.task_cooldown_minutes {
self.notify.task_cooldown_minutes = n.task_cooldown_minutes;
}
if n.overdue_days != def.overdue_days {
self.notify.overdue_days = n.overdue_days;
}
}
pub fn load(path: &Path) -> Result<Self, MpsError> {
if !path.exists() {
return Err(MpsError::ConfigNotFound(path.to_path_buf()));
}
let content = std::fs::read_to_string(path)?;
let normalised = content
.lines()
.map(|line| {
if let Some(rest) = line.strip_prefix(':') {
rest.to_string()
} else {
line.to_string()
}
})
.collect::<Vec<_>>()
.join("\n");
let cfg: Config = serde_yaml::from_str(&normalised)
.map_err(|e| MpsError::ConfigInvalid(e.to_string()))?;
Ok(cfg)
}
pub fn init(path: &Path) -> Result<(), MpsError> {
if path.exists() {
return Ok(());
}
let cfg = Self::default_config()?;
let yaml = serde_yaml::to_string(&cfg)?;
std::fs::write(path, yaml)?;
Ok(())
}
pub fn save(&self, path: &Path) -> Result<(), MpsError> {
let yaml = serde_yaml::to_string(self)?;
let tmp = path.with_extension(format!("yaml.tmp.{}", std::process::id()));
std::fs::write(&tmp, &yaml)?;
std::fs::rename(&tmp, path)?;
Ok(())
}
pub fn ensure_dirs(&self) -> Result<(), MpsError> {
std::fs::create_dir_all(&self.mps_dir)?;
std::fs::create_dir_all(&self.storage_dir)?;
if !self.log_file.exists() {
std::fs::write(&self.log_file, "")?;
}
Ok(())
}
}
pub fn default_config_path() -> PathBuf {
std::env::var("MPS_CONFIG")
.map(PathBuf::from)
.unwrap_or_else(|_| {
dirs::home_dir()
.unwrap_or_else(|| PathBuf::from("."))
.join(".mps_config.yaml")
})
}