synd-term 0.3.2

terminal feed viewer
Documentation
use std::{collections::HashMap, io, path::PathBuf, time::Duration};

use serde::{Deserialize, Serialize};
use thiserror::Error;
use url::Url;

use crate::{cli::Palette, config::categories};

#[derive(Debug, Serialize, Deserialize)]
pub struct CacheEntry {
    pub(super) directory: Option<PathBuf>,
}

#[derive(Debug, Serialize, Deserialize)]
pub struct LogEntry {
    pub(super) path: Option<PathBuf>,
}

#[derive(Debug, Serialize, Deserialize)]
pub struct ThemeEntry {
    pub(super) name: Option<Palette>,
}

#[derive(Debug, Serialize, Deserialize)]
pub struct FeedEntry {
    pub(super) entries_limit: Option<usize>,
    pub(super) browser: Option<FeedBrowserEntry>,
}

#[derive(Debug, Serialize, Deserialize)]
pub struct FeedBrowserEntry {
    pub(super) command: Option<PathBuf>,
    pub(super) args: Option<Vec<String>>,
}

#[derive(Debug, Serialize, Deserialize)]
pub struct ApiEntry {
    pub(super) endpoint: Option<Url>,
    #[serde(
        default,
        deserialize_with = "synd_stdx::time::humantime::de::parse_duration_opt"
    )]
    pub(super) timeout: Option<Duration>,
}

#[derive(Debug, Serialize, Deserialize)]
pub struct GithubEntry {
    pub(super) enable: Option<bool>,
    pub(super) pat: Option<String>,
}

#[derive(Error, Debug)]
pub enum ConfigFileError {
    #[error(transparent)]
    Io(#[from] io::Error),
    #[error(transparent)]
    Deserialize(#[from] toml::de::Error),
}

#[derive(Debug, Serialize, Deserialize)]
pub struct ConfigFile {
    pub(super) cache: Option<CacheEntry>,
    pub(super) log: Option<LogEntry>,
    pub(super) theme: Option<ThemeEntry>,
    pub(super) api: Option<ApiEntry>,
    pub(super) feed: Option<FeedEntry>,
    pub(super) github: Option<GithubEntry>,
    pub(super) categories: Option<HashMap<String, categories::Entry>>,
}

impl ConfigFile {
    pub(super) fn new<R: io::Read>(mut src: R) -> Result<Self, ConfigFileError> {
        let mut buf = String::new();
        src.read_to_string(&mut buf)?;
        toml::from_str(&buf).map_err(ConfigFileError::from)
    }
}

pub static INIT_CONFIG: &str = r#"
[cache]
# Cache directory
# directory = "path/to/dir"

[log]
# Log file path
# path = "path/to/log"

[theme]
# Theme name 
# The available themes can be found by `synd --help`
# name = "ferra"

[api]
# Backend api endpoint
# endpoint = "https://api.syndicationd.ymgyt.io"

# Client timeout duration 
# timeout = "30s"

[feed]
# Feed entries to fetch
# entries_limit = 200 

# Command to browse feed
# browser = { command = "", args = [] }

[github]
# Enable github notification feature
# enable = true

# Github Personal access token(PAT) to browse notifications
# pat = "ghp_xxxx"
"#;

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn deserialize() {
        let src = r#"
[cache]
directory = "/tmp/synd/cache"

[log]
path = "/tmp/synd/synd.log"

[theme]
name = "ferra"

[api]
endpoint = "https://api.syndicationd.ymgyt.io"
timeout = "30s"

[feed]
entries_limit = 100
browser = { command = "w3m", args = ["--foo", "--bar"] }

[github]
enable = true
pat = "ghp_xxxx"

[categories.rust]
icon = { symbol = "S", color = { rgb = 0xF74C00 }}
aliases = ["rs"]
"#;

        let config = ConfigFile::new(src.as_bytes()).unwrap();

        insta::assert_debug_snapshot!("deserialized_config", config);
    }
}