use serde::{Deserialize, Serialize};
use std::path::PathBuf;
fn default_patterns() -> Vec<String> {
vec![
r"#(\d+)".to_string(),
r"(?i)(?:refs|fixes|closes|resolves)\s+#(\d+)".to_string(),
r"/issues/(\d+)".to_string(),
]
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct RedmineConfig {
pub redmine_url: String,
#[serde(default = "default_patterns")]
pub ticket_patterns: Vec<String>,
}
impl Default for RedmineConfig {
fn default() -> Self {
Self {
redmine_url: String::new(),
ticket_patterns: default_patterns(),
}
}
}
pub fn get_config_path() -> Option<PathBuf> {
let dirs = directories::ProjectDirs::from("com", "gitlab-tracker", "gitlab-tracker")?;
Some(dirs.config_dir().join("redmine.yaml"))
}
pub async fn ensure_redmine_config(config: &mut RedmineConfig) {
use std::io::Write;
let url_from_env = std::env::var("REDMINE_URL")
.ok()
.filter(|v| !v.trim().is_empty());
if url_from_env.is_none() && config.redmine_url.trim().is_empty() {
println!("🌐 No Redmine URL found in config or environment.");
println!(" Leave empty to disable Redmine integration.");
print!("Redmine URL: ");
let _ = std::io::stdout().flush();
let mut input = String::new();
if std::io::stdin().read_line(&mut input).is_ok() {
let value = input.trim().to_string();
if !value.is_empty() {
config.redmine_url = value;
save_config(config).await;
}
}
} else if let Some(url) = url_from_env {
config.redmine_url = url;
}
}
pub async fn load_or_create_config() -> RedmineConfig {
if let Some(path) = get_config_path() {
if let Ok(content) = tokio::fs::read_to_string(&path).await {
match serde_yaml::from_str::<RedmineConfig>(&content) {
Ok(cfg) => return cfg,
Err(e) => {
tracing::warn!(error = %e, path = ?path, "Failed to parse redmine.yaml — using defaults");
}
}
}
let default = RedmineConfig::default();
if let Ok(yaml) = serde_yaml::to_string(&default) {
if let Some(parent) = path.parent() {
let _ = tokio::fs::create_dir_all(parent).await;
}
if let Err(e) = tokio::fs::write(&path, yaml).await {
tracing::error!(error = %e, path = ?path, "Failed to write default redmine.yaml");
}
}
default
} else {
RedmineConfig::default()
}
}
pub async fn save_config(config: &RedmineConfig) {
if let Some(path) = get_config_path() {
match serde_yaml::to_string(config) {
Ok(yaml) => {
if let Err(e) = tokio::fs::write(&path, yaml).await {
tracing::error!(error = %e, path = ?path, "Failed to save redmine.yaml");
}
}
Err(e) => {
tracing::error!(error = %e, "Failed to serialize RedmineConfig");
}
}
}
}