tabiew 0.15.1

A lightweight TUI application to view and query tabular data files, such as CSV, TSV, and parquet.
use std::{
    fs,
    ops::Deref,
    sync::{
        OnceLock, RwLock,
        atomic::{AtomicBool, AtomicI8, Ordering},
    },
};

use serde::{Deserialize, Serialize};

use crate::{
    AppResult,
    misc::{http::HttpConfig, paths::config_path},
    tui::themes::theme::LoadedTheme,
};

use super::type_ext::UnwrapOrGracefulShutdown;

#[derive(Debug, Serialize, Deserialize)]
#[serde(default)]
pub struct Config {
    theme: RwLock<LoadedTheme>,
    http: RwLock<HttpConfig>,
    show_table_borders: AtomicBool,
    show_table_row_numbers: AtomicBool,
    use_nerd_font: AtomicBool,
    fp_precision: AtomicI8,
}

impl Config {
    pub fn reload(&self) -> AppResult<()> {
        let path = config_path()?;
        let contents = fs::read_to_string(path)?;
        let Config {
            theme,
            http,
            show_table_borders: table_borders,
            show_table_row_numbers: table_row_numbers,
            use_nerd_font: nerd_font,
            fp_precision,
        } = toml::from_str(&contents)?;
        self.set_theme(theme.into_inner()?);
        self.set_http_config(http.into_inner()?);
        self.show_table_borders
            .swap(table_borders.into_inner(), Ordering::Relaxed);
        self.show_table_row_numbers
            .swap(table_row_numbers.into_inner(), Ordering::Relaxed);
        self.use_nerd_font
            .swap(nerd_font.into_inner(), Ordering::Relaxed);
        self.fp_precision
            .swap(fp_precision.into_inner(), Ordering::Relaxed);
        Ok(())
    }

    pub fn store(&self) -> AppResult<()> {
        let config_path = config_path()?;
        if let Some(parent) = config_path.parent() {
            fs::create_dir_all(parent)?;
        }
        let contents = toml::to_string_pretty(self)?;
        Ok(fs::write(config_path, contents)?)
    }

    pub fn theme(&self) -> impl Deref<Target = LoadedTheme> {
        self.theme.read().unwrap_or_graceful_shutdown()
    }

    pub fn set_theme(&self, theme: impl Into<LoadedTheme>) {
        *self.theme.write().unwrap_or_graceful_shutdown() = theme.into();
    }

    pub fn http_config(&self) -> impl Deref<Target = HttpConfig> {
        self.http.read().unwrap_or_graceful_shutdown()
    }

    pub fn set_http_config(&self, http_config: impl Into<HttpConfig>) {
        *self.http.write().unwrap_or_graceful_shutdown() = http_config.into();
    }

    pub fn show_table_borders(&self) -> bool {
        self.show_table_borders.load(Ordering::Relaxed)
    }

    pub fn toggle_show_table_borders(&self) {
        self.show_table_borders.fetch_xor(true, Ordering::Relaxed);
    }

    pub fn fp_precision(&self) -> Option<usize> {
        match self.fp_precision.load(Ordering::Relaxed) {
            precision if precision < 0 => None,
            precision => Some(precision as usize),
        }
    }

    pub fn set_fp_precision(&self, precision: i8) {
        self.fp_precision.swap(precision, Ordering::Relaxed);
    }

    pub fn show_table_row_numbers(&self) -> bool {
        self.show_table_row_numbers.load(Ordering::Relaxed)
    }

    pub fn toggle_show_table_row_numbers(&self) {
        self.show_table_row_numbers
            .fetch_xor(true, Ordering::Relaxed);
    }

    pub fn use_nerd_font(&self) -> bool {
        self.use_nerd_font.load(Ordering::Relaxed)
    }

    pub fn toggle_use_nerd_font(&self) {
        self.use_nerd_font.fetch_xor(true, Ordering::Relaxed);
    }
}

impl Default for Config {
    fn default() -> Self {
        Self {
            theme: RwLock::new(LoadedTheme::default()),
            show_table_borders: AtomicBool::new(true),
            show_table_row_numbers: AtomicBool::new(true),
            use_nerd_font: AtomicBool::new(false),
            http: RwLock::new(HttpConfig::default()),
            fp_precision: AtomicI8::new(-1),
        }
    }
}

static CONFIG: OnceLock<Config> = OnceLock::new();
pub fn init() -> AppResult<()> {
    let config = if let Ok(path) = config_path()
        && path.exists()
    {
        toml::from_str(&fs::read_to_string(path)?)?
    } else {
        Default::default()
    };
    CONFIG.get_or_init(move || config);
    Ok(())
}

pub fn config() -> &'static Config {
    CONFIG.get_or_init(Config::default)
}

pub fn theme() -> impl Deref<Target = LoadedTheme> {
    config().theme()
}