docopticon 0.1.2

An argument-parser based on the obligatory help-text
Documentation
use super::{
    home_path, load_all_from_path, load_from_path, load_home_env, save_to_path, system_paths,
};

use std::{
    env,
    path::{Path, PathBuf},
    str::Utf8Error,
    string::String,
};

use serde::{de, ser, Deserialize, Serialize};
/// Contains non-essential data that should persist between application restarts, but is not important
/// (or portable) enough to the user that it should be stored in XDG_DATA_HOME.
///
/// This **only** has user paths defined as per XDG specification, no system paths are defined,
/// so keep that in mind.
///
/// Example of things to store:
/// * non-essential state information
pub trait StateFile<E, T = Self>: for<'s> Deserialize<'s> + Serialize
where
    for<'s> T: Serialize + Deserialize<'s>,
    E: std::error::Error + std::convert::From<std::io::Error>,
{
    /// Name of the directory to-be-used for the application's storage. If no directory is desired, set this to "" (empty string).
    ///
    /// Defaults to "`$CARGO_CRATE_NAME`" which is the name of the crate at compile-time.
    const APP_DIR_NAME: &'static str = env!("CARGO_CRATE_NAME");
    /// Name of the directory to-be-used for more complicated setups, like organizing multiple
    /// configuration files into less of a mess, or allowing overrides to be separated from the
    /// actual config file.
    ///
    /// Defaults to "`$CARGO_CRATE_NAME`.d" which is the name of the crate at compile-time.
    const EXTRA_DIR: &'static str = concat!(env!("CARGO_CRATE_NAME"), ".d");
    /// Name of the environment variable which specifies the home path directory for storage.
    const HOME_PATH_ENV: &'static str = "XDG_STATE_HOME";
    /// Fallback path for `HOME_PATH_ENV`, assumes getting `$HOME` is valid.
    const HOME_PATH_FALLBACK: &'static str = ".local/state";
    /// Name of the file to store the implementor of this trait as.
    const FILENAME: &'static str = "state";

    /// Fully constructed home-path.
    fn home() -> PathBuf {
        home_path(
            Self::HOME_PATH_ENV,
            Self::HOME_PATH_FALLBACK,
            Self::APP_DIR_NAME,
            Self::FILENAME,
        )
    }

    /// Save [StateFile] to home.
    fn save(&self) -> Result<(), E> {
        self.save_to_path(&Self::home(), true)
    }

    /// Save [StateFile] to a custom-specified path, optionally creating all dirs needed on the way.
    fn save_to_path(&self, path: impl AsRef<Path>, create_dirs: bool) -> Result<(), E> {
        save_to_path(self, path.as_ref(), create_dirs)
    }

    /// Load [StateFile] from first available source in the following order:
    /// 1. `XDG_STATE_HOME/{Self::APP_DIR_NAME}/{Self::FILENAME}`
    /// 2. `$HOME/.local/state/{Self::APP_DIR_NAME}/{Self::FILENAME}`
    fn load() -> Result<T, E> {
        Self::load_from_home()
    }

    /// Load [StateFile] from home.
    fn load_from_home() -> Result<T, E> {
        Self::load_from_path(&Self::home())
    }

    /// Load all available [StateFile]s from home's [Self::EXTRA_DIR].
    fn load_from_home_extra_dirs() -> Result<Vec<T>, E> {
        load_all_from_path(&Self::home())
    }

    /// Load [StateFile] from a given path.
    fn load_from_path(path: impl AsRef<Path>) -> Result<T, E> {
        load_from_path(path.as_ref())
    }
}

mod tests {
    use super::*;
    use std::env::set_var;

    use serde::{Deserialize, Serialize};

    #[derive(Serialize, Deserialize)]
    struct State;
    impl StateFile<std::io::Error> for State {}

    #[test]
    fn home_path() {
        // regular test
        set_var("XDG_STATE_HOME", "/tmp/.local/state/");
        assert_eq!(
            State::home(),
            PathBuf::from("/tmp/.local/state/docopticon/state")
        );
        // relative test
        set_var("XDG_CONFIG_HOME", "relative/dir");
        set_var("HOME", "/tmp/");
        assert_eq!(
            State::home(),
            PathBuf::from("/tmp/.local/state/docopticon/state")
        );
    }
}