bevy_etcetera 0.4.0

Tiny Bevy wrapper for etcetera: An unopinionated library for obtaining configuration, data, cache, & other directories
Documentation
//! A very small bevy wrapper over [`etcetera`](https://docs.rs/etcetera/latest/etcetera/). It allows you to
//! access common directories across MacOS, Windows, and Linux.
//!
//! # Basic usage
//!
//! ```
//! use bevy::prelude::*;
//! use bevy_etcetera::Directories;
//!
//! fn my_setup_system(mut cmd: Commands) -> Result<(), BevyError> {
//!     let directories = Directories::new("com", "doomy", "Cool Bevy Game")?;
//!     cmd.insert_resource(directories);
//!     Ok(())
//! }
//!
//! fn my_system(directories: Res<Directories>) {
//!     // Path dependent on OS
//!     let path = directories
//!         .data_dir()
//!         .join("some_file")
//!         .with_extension("item.ron");
//! }
//! ```

use std::path::{Path, PathBuf};

use bevy::prelude::*;
use etcetera::{AppStrategy, AppStrategyArgs, app_strategy::choose_native_strategy};

#[cfg(target_os = "windows")]
type Strategy = etcetera::app_strategy::Windows;

#[cfg(target_os = "linux")]
type Strategy = etcetera::app_strategy::Xdg;

#[cfg(any(target_os = "macos", target_os = "ios"))]
type Strategy = etcetera::app_strategy::Apple;

#[derive(Resource)]
pub struct Directories(Strategy);

impl Directories {
    /// Creates a new [`Directories`] resource
    ///
    /// # Parameters
    ///
    /// * `top_level_domain` - The top level domain of the application, e.g.
    ///   `com`, `org`, or `io.github`.
    /// * `author` - The name of the author of the application.
    /// * `app_name` - The application’s name. This should be capitalised if
    ///   appropriate.
    ///
    /// # Returns
    ///
    /// Returns a `Result` containing the `Directories` struct, or a `BevyError`
    /// (from [`etcetera::HomeDirError`] if the home directory cannot be found.
    ///
    /// # Examples
    ///
    /// ```
    /// use bevy::prelude::*;
    /// use bevy_etcetera::Directories;
    ///
    /// let mut world = World::new();
    /// if let Ok(directories) = Directories::from_string("com.doomy.Cool Bevy Game") {
    ///     world.insert_resource(directories);
    /// }
    /// ```
    pub fn new(
        top_level_domain: impl Into<String>,
        author: impl Into<String>,
        app_name: impl Into<String>,
    ) -> Result<Self, BevyError> {
        choose_native_strategy(AppStrategyArgs {
            top_level_domain: top_level_domain.into(),
            author: author.into(),
            app_name: app_name.into(),
        })
        .map(Self)
        .map_err(BevyError::from)
    }

    /// Creates a new [`Directories`] resource given a valid string. Valid
    /// identifiers should contain the top level domain, author, and then app
    /// name, separated by periods.
    ///
    /// # Examples
    ///
    /// "com.example.My App"
    /// "My.App.Name"
    ///
    /// # Parameters
    ///
    /// * `value` - the string identifier to parse
    ///
    /// # Returns
    ///
    /// Returns [`Directories`] if successful, or an error if an invalid string,
    /// or the home directory was not found
    pub fn from_string<S>(value: S) -> Result<Self, BevyError>
    where
        S: AsRef<str>,
    {
        let values: Vec<&str> = value.as_ref().splitn(3, '.').collect();
        match values.len() == 3 {
            false => Err("Invalid string provided.".into()),
            true => Ok(Self::new(values[0], values[1], values[2])?),
        }
    }

    /// Gets the home directory of the current user
    ///
    /// * MacOS - `~/`
    /// * Linux - `~/`
    /// * Windows - `%USERPROFILE%`
    pub fn home_dir(&self) -> &Path {
        self.0.home_dir()
    }

    /// Gets the configuration directory for your application.
    ///
    /// * MacOS - `~/Library/Preferences/`
    /// * Linux - `~/.config/`
    /// * Windows - `%APPDATA%`
    pub fn config_dir(&self) -> PathBuf {
        self.0.config_dir()
    }

    /// Gets the data directory for your application.
    ///
    /// * MacOS - `~/Library/Application Support/`
    /// * Linux - `~/.local/share/`
    /// * Windows - `%APPDATA%`
    pub fn data_dir(&self) -> PathBuf {
        self.0.data_dir()
    }

    /// Gets the cache directory for your application.
    ///
    /// * MacOS - `~/Library/Caches/`
    /// * Linux - `~/.cache/`
    /// * Windows - `%LOCALAPPDATA%`
    pub fn cache_dir(&self) -> PathBuf {
        self.0.cache_dir()
    }

    /// Gets the state directory for your application. State directory may not
    /// to exist for all conventions.
    ///
    /// * Linux - `~/.local/state`
    pub fn state_dir(&self) -> Option<PathBuf> {
        self.0.state_dir()
    }

    /// Gets the runtime directory for your application. Runtime directory may
    /// not to exist for all conventions.
    pub fn runtime_dir(&self) -> Option<PathBuf> {
        self.0.runtime_dir()
    }

    /// Gets the underlying [`Strategy`] used to obtain directories
    pub fn strategy(&self) -> &Strategy {
        &self.0
    }
}

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

    #[test]
    fn create_valid_directories_from_string_literals() {
        [
            "a.b.c",
            "1.2.3",
            "Testing One.Mycool Bundle.App Name",
            "APP$1.90))3,.(DJHG",
        ]
        .iter()
        .for_each(|s| {
            matches!(Directories::from_string(s), Ok(_));
        })
    }

    #[test]
    fn create_invalid_directories_from_string_literals() {
        [
            "a.b.c.d",
            "1.2",
            "Testing One..Mycool Bundle.App Name",
            "ABCDEFG",
        ]
        .iter()
        .for_each(|s| {
            matches!(Directories::from_string(s), Err(_));
        })
    }
}