use std::io::ErrorKind;
use std::borrow::Cow;
use std::fs::File;
use std::path::{Path, PathBuf};
use serde::Deserialize;
use directories::{UserDirs, ProjectDirs};
use ratatui::style::{Style, Color};
use crate::error::{Error, Result, ResultExt};
#[derive(Clone, Default, Debug, Deserialize)]
pub struct Config {
#[serde(default)]
pub theme: Theme,
#[serde(default)]
pub database: Option<PathBuf>,
}
impl Config {
pub fn from_rc_file() -> Result<Self> {
if let Ok(project_dirs) = Self::project_dirs() {
let config_path = project_dirs.config_dir().join(".steelsaferc");
if let Some(config_file) = Self::open_file_if_exists(&config_path)? {
return serde_json::from_reader(config_file).context("Invalid .steelsaferc");
}
}
if let Some(user_dirs) = UserDirs::new() {
let config_path = user_dirs.home_dir().join(".steelsaferc");
if let Some(config_file) = Self::open_file_if_exists(&config_path)? {
return serde_json::from_reader(config_file).context("Invalid .steelsaferc");
}
}
Ok(Config::default())
}
fn project_dirs() -> Result<ProjectDirs> {
ProjectDirs::from("org", "h2co3", "steelsafe").ok_or(Error::MissingDatabaseDir)
}
fn open_file_if_exists(path: &Path) -> Result<Option<File>> {
match File::open(path) {
Ok(file) => Ok(Some(file)),
Err(error) => {
if [ErrorKind::NotFound, ErrorKind::PermissionDenied].contains(&error.kind()) {
Ok(None)
} else {
Err(Error::context(error, "Found .steelsaferc but cannot open"))
}
}
}
}
pub fn ensure_db_dir(&self) -> Result<Cow<'_, Path>> {
if let Some(path) = self.database.as_ref() {
std::fs::create_dir_all(path)?;
return Ok(path.into());
}
let dirs = Self::project_dirs()?;
let db_dir = dirs.data_dir();
std::fs::create_dir_all(db_dir)?;
Ok(db_dir.to_owned().into())
}
}
#[derive(Clone, Default, Debug, Deserialize)]
pub struct ColorPair {
#[serde(default)]
pub bg: Option<Color>,
#[serde(default)]
pub fg: Option<Color>,
}
#[derive(Clone, Default, Debug, Deserialize)]
pub struct Theme {
#[serde(default)]
pub default: ColorPair,
#[serde(default)]
pub highlight: ColorPair,
#[serde(default)]
pub border: ColorPair,
#[serde(default)]
pub border_highlight: ColorPair,
#[serde(default)]
pub error: ColorPair,
}
impl Theme {
pub fn default(&self) -> Style {
Style::default()
.bg(self.default.bg.unwrap_or(Color::Black))
.fg(self.default.fg.unwrap_or(Color::LightYellow))
}
pub fn highlight(&self) -> Style {
Style::default()
.bg(self.highlight.bg.unwrap_or(Color::LightYellow))
.fg(self.highlight.fg.unwrap_or(Color::Black))
}
pub fn border(&self) -> Style {
Style::default()
.bg(self.border.bg.unwrap_or(Color::Black))
.fg(self.border.fg.unwrap_or(Color::LightCyan))
}
pub fn border_highlight(&self) -> Style {
Style::default()
.bg(self.border_highlight.bg.unwrap_or(Color::LightYellow))
.fg(self.border_highlight.fg.unwrap_or(Color::Cyan))
}
pub fn error(&self) -> Style {
Style::default()
.bg(self.error.bg.unwrap_or(Color::LightYellow))
.fg(self.error.fg.unwrap_or(Color::LightRed))
}
}