website 0.2.0

A self contained website creator and server with zero conf simplicity.
use std::{path::PathBuf, str::FromStr};

use cached::proc_macro::cached;
use derive_getters::Getters;
use serde::{Deserialize, Serialize};
use tracing::Level;

#[cached]
pub fn load_config() -> Config {
    let config_path = dirs::config_local_dir()
        .unwrap_or(PathBuf::from_str("./config").unwrap())
        .join("website/config.toml");

    print!(
        "🔧 Loading config from {} (if exists)...",
        config_path.to_string_lossy()
    );

    let mut config = None;
    if let Ok(data) = std::fs::read_to_string(config_path) {
        match toml::from_str::<ConfigParsed>(&data) {
            Ok(parsed) => {
                println!(" found and loaded!");
                config = Some(Config::from(parsed));
            }
            Err(error) => {
                println!();
                eprintln!("💥 Failed to parse config: {error}");
            }
        };
    }

    if config.is_none() {
        config = Some(Config::from(ConfigParsed::default()));
        eprintln!("⚠️ Unable to find config file, falling back on default config");
    }

    config.unwrap()
}

#[derive(Default, Serialize, Deserialize)]
pub struct ConfigParsed {
    title: Option<String>,
    port: Option<u16>,
    data_path: Option<PathBuf>,
    log_level: Option<ConfigLogLevel>,
}

#[derive(Clone, Getters, Serialize)]
pub struct Config {
    title: String,
    port: u16,
    data_path: PathBuf,
    log_level: ConfigLogLevel,
}

impl Config {
    pub fn log_path(&self) -> PathBuf {
        self.data_path.join("logs")
    }

    pub fn pages_path(&self) -> PathBuf {
        self.data_path.join("pages")
    }
}

impl From<ConfigParsed> for Config {
    fn from(value: ConfigParsed) -> Self {
        Self {
            title: value.title.unwrap_or("Welcome".into()),
            port: 4000,
            data_path: value.data_path.unwrap_or(
                dirs::data_local_dir()
                    .unwrap_or(PathBuf::from_str("./data").unwrap())
                    .join("website/"),
            ),
            log_level: value.log_level.unwrap_or(ConfigLogLevel::Info),
        }
    }
}

#[repr(usize)]
#[derive(Default, Copy, Clone, Debug, Hash, Eq, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum ConfigLogLevel {
    #[default]
    Error,
    Warn,
    Info,
    Debug,
    Trace,
}

impl From<Level> for ConfigLogLevel {
    fn from(value: Level) -> Self {
        match value {
            Level::ERROR => ConfigLogLevel::Error,
            Level::WARN => ConfigLogLevel::Warn,
            Level::INFO => ConfigLogLevel::Info,
            Level::DEBUG => ConfigLogLevel::Debug,
            Level::TRACE => ConfigLogLevel::Trace,
        }
    }
}

impl From<ConfigLogLevel> for Level {
    fn from(value: ConfigLogLevel) -> Self {
        match value {
            ConfigLogLevel::Error => Level::ERROR,
            ConfigLogLevel::Warn => Level::WARN,
            ConfigLogLevel::Info => Level::INFO,
            ConfigLogLevel::Debug => Level::DEBUG,
            ConfigLogLevel::Trace => Level::TRACE,
        }
    }
}