use std::fs;
use std::path::PathBuf;
use anyhow::{Context, Result};
use serde::{Deserialize, Serialize};
use crate::i18n::Language;
pub const PRESET_SLOT_COUNT: usize = 5;
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct FilterPreset {
pub name: String,
pub query: String,
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct FilterPresets {
#[serde(default)]
pub slot1: Option<FilterPreset>,
#[serde(default)]
pub slot2: Option<FilterPreset>,
#[serde(default)]
pub slot3: Option<FilterPreset>,
#[serde(default)]
pub slot4: Option<FilterPreset>,
#[serde(default)]
pub slot5: Option<FilterPreset>,
}
impl FilterPresets {
pub fn config_path() -> Option<PathBuf> {
dirs::config_dir().map(|p| p.join("gitstack").join("filters.toml"))
}
pub fn load() -> Self {
Self::config_path()
.and_then(|path| fs::read_to_string(&path).ok())
.and_then(|content| toml::from_str(&content).ok())
.unwrap_or_default()
}
pub fn save(&self) -> Result<()> {
let path = Self::config_path().context("Config directory not found")?;
if let Some(parent) = path.parent() {
fs::create_dir_all(parent).context("Failed to create config directory")?;
}
let content = toml::to_string_pretty(self).context("Failed to serialize TOML")?;
#[cfg(unix)]
{
use std::io::Write;
use std::os::unix::fs::OpenOptionsExt;
let mut file = std::fs::OpenOptions::new()
.create(true)
.write(true)
.truncate(true)
.mode(0o600)
.open(&path)
.context("Failed to open config file")?;
file.write_all(content.as_bytes())
.context("Failed to write config file")?;
}
#[cfg(not(unix))]
{
fs::write(&path, content).context("Failed to write config file")?;
}
Ok(())
}
pub fn get(&self, slot: usize) -> Option<&FilterPreset> {
match slot {
1 => self.slot1.as_ref(),
2 => self.slot2.as_ref(),
3 => self.slot3.as_ref(),
4 => self.slot4.as_ref(),
5 => self.slot5.as_ref(),
_ => None,
}
}
pub fn set(&mut self, slot: usize, preset: FilterPreset) {
match slot {
1 => self.slot1 = Some(preset),
2 => self.slot2 = Some(preset),
3 => self.slot3 = Some(preset),
4 => self.slot4 = Some(preset),
5 => self.slot5 = Some(preset),
_ => {}
}
}
pub fn iter(&self) -> impl Iterator<Item = (usize, &FilterPreset)> {
[
(1, &self.slot1),
(2, &self.slot2),
(3, &self.slot3),
(4, &self.slot4),
(5, &self.slot5),
]
.into_iter()
.filter_map(|(slot, opt)| opt.as_ref().map(|p| (slot, p)))
}
pub fn count(&self) -> usize {
self.iter().count()
}
pub fn next_empty_slot(&self) -> Option<usize> {
(1..=PRESET_SLOT_COUNT).find(|&slot| self.get(slot).is_none())
}
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct LanguageConfig {
#[serde(default)]
pub language: Language,
}
impl LanguageConfig {
pub fn config_path() -> Option<PathBuf> {
dirs::config_dir().map(|p| p.join("gitstack").join("config.toml"))
}
pub fn load() -> Self {
Self::config_path()
.and_then(|path| fs::read_to_string(&path).ok())
.and_then(|content| toml::from_str(&content).ok())
.unwrap_or_default()
}
pub fn save(&self) -> Result<()> {
let path = Self::config_path().context("Config directory not found")?;
if let Some(parent) = path.parent() {
fs::create_dir_all(parent).context("Failed to create config directory")?;
}
let content = toml::to_string_pretty(self).context("Failed to serialize TOML")?;
#[cfg(unix)]
{
use std::io::Write;
use std::os::unix::fs::OpenOptionsExt;
let mut file = std::fs::OpenOptions::new()
.create(true)
.write(true)
.truncate(true)
.mode(0o600)
.open(&path)
.context("Failed to open config file")?;
file.write_all(content.as_bytes())
.context("Failed to write config file")?;
}
#[cfg(not(unix))]
{
fs::write(&path, content).context("Failed to write config file")?;
}
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_filter_presets_get_set() {
let mut presets = FilterPresets::default();
assert!(presets.get(1).is_none());
assert_eq!(presets.count(), 0);
presets.set(
1,
FilterPreset {
name: "My commits".to_string(),
query: "/author:john".to_string(),
},
);
assert!(presets.get(1).is_some());
assert_eq!(presets.get(1).unwrap().name, "My commits");
assert_eq!(presets.count(), 1);
presets.set(
3,
FilterPreset {
name: "Recent".to_string(),
query: "/since:1week".to_string(),
},
);
assert_eq!(presets.count(), 2);
assert_eq!(presets.next_empty_slot(), Some(2));
}
#[test]
fn test_filter_presets_iter() {
let mut presets = FilterPresets::default();
presets.set(
2,
FilterPreset {
name: "Test".to_string(),
query: "/test".to_string(),
},
);
presets.set(
5,
FilterPreset {
name: "Test2".to_string(),
query: "/test2".to_string(),
},
);
let slots: Vec<usize> = presets.iter().map(|(s, _)| s).collect();
assert_eq!(slots, vec![2, 5]);
}
}