use anyhow::Result;
use serde::{Deserialize, Serialize};
use std::fs;
use std::path::PathBuf;
use crate::integrations::IntegrationKind;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Config {
#[serde(default)]
pub integration: IntegrationKind,
#[serde(default = "default_open_command")]
pub open_command: String,
#[serde(default = "default_open_worklog_command")]
pub open_worklog_command: String,
#[serde(default = "default_colors")]
pub colors: [String; 6],
#[serde(default = "default_date_format")]
pub date_format: String,
#[serde(default = "default_legacy_time_format")]
pub legacy_time_format: bool,
#[serde(default)]
pub hide_eye_candy: bool,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub jira_url_setting: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub jira_email: Option<String>,
#[serde(default)]
pub triggers: TriggersConfig,
#[serde(skip_serializing_if = "Option::is_none")]
pub jira_url: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub browser_command: Option<String>,
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct TriggersConfig {
#[serde(default)]
pub day_start: TriggerConfig,
#[serde(default)]
pub ooo_start: TriggerConfig,
#[serde(default)]
pub ooo_end: TriggerConfig,
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct TriggerConfig {
#[serde(default)]
pub enabled: bool,
#[serde(default)]
pub url: String,
#[serde(default)]
pub body: String,
}
fn default_colors() -> [String; 6] {
[
"Blue".to_string(),
"Cyan".to_string(),
"Green".to_string(),
"Yellow".to_string(),
"Magenta".to_string(),
"Red".to_string(),
]
}
fn default_date_format() -> String {
"%d.%m.-%y".to_string()
}
fn default_legacy_time_format() -> bool {
false
}
fn default_open_command() -> String {
return "curl -X POST https://jira.example.com/rest/api/2/issue/[[issue_key]]/worklog -H \"Content-Type: application/json\" -d '{\"timeSpent\":\"[[task_duration]]\",\"started\":\"[[entry_started]]\",\"comment\":\"Work logged from TimeTrack\"}'".to_string();
}
fn default_open_worklog_command() -> String {
#[cfg(target_os = "linux")]
return "xdg-open https://jira.example.com/browse/[[issue_key]]".to_string();
#[cfg(target_os = "macos")]
return "open https://jira.example.com/browse/[[issue_key]]".to_string();
#[cfg(target_os = "windows")]
return "start https://jira.example.com/browse/[[issue_key]]".to_string();
#[cfg(not(any(target_os = "linux", target_os = "macos", target_os = "windows")))]
return "xdg-open https://jira.example.com/browse/[[issue_key]]".to_string();
}
impl Config {
pub fn color_names() -> [&'static str; 6] {
["Color 1", "Color 2", "Color 3", "Color 4", "Color 5", "Color 6"]
}
pub fn available_colors() -> [&'static str; 16] {
[
"Blue", "Cyan", "Green", "Yellow", "Magenta", "Red",
"LightBlue", "LightCyan", "LightGreen", "LightYellow",
"LightMagenta", "LightRed", "DarkGray", "Gray", "White", "Black"
]
}
pub fn available_variables() -> [&'static str; 5] {
["[[issue_key]]", "[[entry_started]]", "[[entry_ended]]", "[[task_duration]]", "[[description]]"]
}
pub fn available_trigger_variables() -> [&'static str; 5] {
["[[event]]", "[[date]]", "[[time]]", "[[datetime]]", "[[description]]"]
}
pub fn substitute_trigger_variables(
body: &str,
event: &str,
date: &str,
time: &str,
datetime: &str,
description: &str,
) -> String {
body.replace("[[event]]", event)
.replace("[[date]]", date)
.replace("[[time]]", time)
.replace("[[datetime]]", datetime)
.replace("[[description]]", description)
}
pub fn substitute_variables(
&self,
issue_key: &str,
entry_started: &str,
entry_ended: &str,
task_duration: &str,
description: &str,
) -> String {
self.open_command
.replace("[[issue_key]]", issue_key)
.replace("[[entry_started]]", entry_started)
.replace("[[entry_ended]]", entry_ended)
.replace("[[task_duration]]", task_duration)
.replace("[[description]]", description)
}
}
impl Default for Config {
fn default() -> Self {
Config {
integration: IntegrationKind::CustomCommands,
open_command: default_open_command(),
open_worklog_command: default_open_worklog_command(),
colors: [
"Blue".to_string(),
"Cyan".to_string(),
"Green".to_string(),
"Yellow".to_string(),
"Magenta".to_string(),
"Red".to_string(),
],
date_format: "%d.%m.-%y".to_string(),
legacy_time_format: false,
hide_eye_candy: false,
jira_url_setting: None,
jira_email: None,
triggers: TriggersConfig::default(),
jira_url: None,
browser_command: None,
}
}
}
impl Config {
pub fn load() -> Result<Self> {
let config_path = Self::config_path()?;
if config_path.exists() {
let contents = fs::read_to_string(&config_path)?;
let mut config: Config = toml::from_str(&contents)?;
if let (Some(jira_url), Some(browser_command)) = (&config.jira_url, &config.browser_command) {
config.open_command = format!("{} {}/[[issue_key]]", browser_command, jira_url);
config.jira_url = None;
config.browser_command = None;
config.save()?;
}
Ok(config)
} else {
let config = Config::default();
config.save()?;
Ok(config)
}
}
pub fn save(&self) -> Result<()> {
let config_path = Self::config_path()?;
if let Some(parent) = config_path.parent() {
fs::create_dir_all(parent)?;
}
let contents = toml::to_string_pretty(self)?;
fs::write(&config_path, contents)?;
Ok(())
}
fn config_path() -> Result<PathBuf> {
let config_dir = dirs::home_dir()
.ok_or_else(|| anyhow::anyhow!("Could not find home directory"))?;
Ok(config_dir.join(".timetrack.toml"))
}
}