clock-rs 0.2.1

A modern, terminal-based digital clock
use std::{
    env::{self, VarError},
    fs,
    path::Path,
};

use serde::Deserialize;

use crate::{color::Color, error::Error, position::Position};

#[derive(Default, Deserialize)]
#[serde(default)]
pub struct Config {
    pub general: GeneralConfig,
    pub position: PositionConfig,
    pub date: DateConfig,
}

#[derive(Deserialize)]
#[serde(default)]
pub struct GeneralConfig {
    pub color: Color,
    pub interval: u64,
    pub blink: bool,
    pub bold: bool,
}

impl Default for GeneralConfig {
    fn default() -> Self {
        Self {
            interval: 200,
            color: Color::default(),
            blink: false,
            bold: false,
        }
    }
}

#[derive(Default, Deserialize)]
#[serde(default)]
pub struct PositionConfig {
    #[serde(rename = "horizontal")]
    pub x: Position,
    #[serde(rename = "vertical")]
    pub y: Position,
}

#[derive(Deserialize)]
#[serde(default)]
pub struct DateConfig {
    pub fmt: String,
    pub use_12h: bool,
    pub utc: bool,
    pub hide_seconds: bool,
}

impl Default for DateConfig {
    fn default() -> Self {
        Self {
            fmt: "%d-%m-%Y".to_string(),
            use_12h: false,
            utc: false,
            hide_seconds: false,
        }
    }
}

impl Config {
    pub fn parse() -> Result<Self, Error> {
        let path = match env::var("CONF_PATH") {
            Ok(path) => match path.as_str() {
                "None" => None,
                _ => Some(path),
            },
            Err(VarError::NotUnicode(path)) => {
                return Err(Error::NonUnicodePath(path.display().to_string()));
            }
            Err(VarError::NotPresent) => match dirs::config_local_dir() {
                Some(config_local_dir) => {
                    match config_local_dir.join("clock-rs").join("conf.toml").to_str() {
                        Some(path) if Path::new(path).exists() => Some(path.to_string()),
                        Some(_) => None,
                        None => {
                            return Err(Error::NonUnicodePath(
                                config_local_dir.display().to_string(),
                            ))
                        }
                    }
                }
                None => None,
            },
        };

        let Some(file_path) = path else {
            return Ok(Config::default());
        };

        let config_str = fs::read_to_string(&file_path).map_err(|err| Error::ReadFile {
            path: file_path.clone(),
            err: err.to_string(),
        })?;

        toml::from_str(&config_str).map_err(|err| Error::ParseToml {
            path: file_path,
            err: err.to_string(),
        })
    }
}