bevy_etcetera/lib.rs
1//! A very small bevy wrapper over [`etcetera`](https://docs.rs/etcetera/latest/etcetera/). It allows you to
2//! access common directories across MacOS, Windows, and Linux.
3//!
4//! # Basic usage
5//!
6//! ```
7//! use bevy::prelude::*;
8//! use bevy_etcetera::Directories;
9//!
10//! fn my_setup_system(mut cmd: Commands) -> Result<(), BevyError> {
11//! let directories = Directories::new("com", "doomy", "Cool Bevy Game")?;
12//! cmd.insert_resource(directories);
13//! Ok(())
14//! }
15//!
16//! fn my_system(directories: Res<Directories>) {
17//! // Path dependent on OS
18//! let path = directories
19//! .data_dir()
20//! .join("some_file")
21//! .with_extension("item.ron");
22//! }
23//! ```
24
25use std::path::{Path, PathBuf};
26
27use bevy::prelude::*;
28use etcetera::{AppStrategy, AppStrategyArgs, app_strategy::choose_native_strategy};
29
30#[cfg(target_os = "windows")]
31type Strategy = etcetera::app_strategy::Windows;
32
33#[cfg(target_os = "linux")]
34type Strategy = etcetera::app_strategy::Xdg;
35
36#[cfg(any(target_os = "macos", target_os = "ios"))]
37type Strategy = etcetera::app_strategy::Apple;
38
39#[derive(Resource)]
40pub struct Directories(Strategy);
41
42impl Directories {
43 /// Creates a new [`Directories`] resource
44 ///
45 /// # Parameters
46 ///
47 /// * `top_level_domain` - The top level domain of the application, e.g.
48 /// `com`, `org`, or `io.github`.
49 /// * `author` - The name of the author of the application.
50 /// * `app_name` - The application’s name. This should be capitalised if
51 /// appropriate.
52 ///
53 /// # Returns
54 ///
55 /// Returns a `Result` containing the `Directories` struct, or a `BevyError`
56 /// (from [`etcetera::HomeDirError`] if the home directory cannot be found.
57 ///
58 /// # Examples
59 ///
60 /// ```
61 /// use bevy::prelude::*;
62 /// use bevy_etcetera::Directories;
63 ///
64 /// let mut world = World::new();
65 /// if let Ok(directories) = Directories::from_string("com.doomy.Cool Bevy Game") {
66 /// world.insert_resource(directories);
67 /// }
68 /// ```
69 pub fn new(
70 top_level_domain: impl Into<String>,
71 author: impl Into<String>,
72 app_name: impl Into<String>,
73 ) -> Result<Self, BevyError> {
74 choose_native_strategy(AppStrategyArgs {
75 top_level_domain: top_level_domain.into(),
76 author: author.into(),
77 app_name: app_name.into(),
78 })
79 .map(Self)
80 .map_err(BevyError::from)
81 }
82
83 /// Creates a new [`Directories`] resource given a valid string. Valid
84 /// identifiers should contain the top level domain, author, and then app
85 /// name, separated by periods.
86 ///
87 /// # Examples
88 ///
89 /// "com.example.My App"
90 /// "My.App.Name"
91 ///
92 /// # Parameters
93 ///
94 /// * `value` - the string identifier to parse
95 ///
96 /// # Returns
97 ///
98 /// Returns [`Directories`] if successful, or an error if an invalid string,
99 /// or the home directory was not found
100 pub fn from_string<S>(value: S) -> Result<Self, BevyError>
101 where
102 S: AsRef<str>,
103 {
104 let values: Vec<&str> = value.as_ref().splitn(3, '.').collect();
105 match values.len() == 3 {
106 false => Err("Invalid string provided.".into()),
107 true => Ok(Self::new(values[0], values[1], values[2])?),
108 }
109 }
110
111 /// Gets the home directory of the current user
112 ///
113 /// * MacOS - `~/`
114 /// * Linux - `~/`
115 /// * Windows - `%USERPROFILE%`
116 pub fn home_dir(&self) -> &Path {
117 self.0.home_dir()
118 }
119
120 /// Gets the configuration directory for your application.
121 ///
122 /// * MacOS - `~/Library/Preferences/`
123 /// * Linux - `~/.config/`
124 /// * Windows - `%APPDATA%`
125 pub fn config_dir(&self) -> PathBuf {
126 self.0.config_dir()
127 }
128
129 /// Gets the data directory for your application.
130 ///
131 /// * MacOS - `~/Library/Application Support/`
132 /// * Linux - `~/.local/share/`
133 /// * Windows - `%APPDATA%`
134 pub fn data_dir(&self) -> PathBuf {
135 self.0.data_dir()
136 }
137
138 /// Gets the cache directory for your application.
139 ///
140 /// * MacOS - `~/Library/Caches/`
141 /// * Linux - `~/.cache/`
142 /// * Windows - `%LOCALAPPDATA%`
143 pub fn cache_dir(&self) -> PathBuf {
144 self.0.cache_dir()
145 }
146
147 /// Gets the state directory for your application. State directory may not
148 /// to exist for all conventions.
149 ///
150 /// * Linux - `~/.local/state`
151 pub fn state_dir(&self) -> Option<PathBuf> {
152 self.0.state_dir()
153 }
154
155 /// Gets the runtime directory for your application. Runtime directory may
156 /// not to exist for all conventions.
157 pub fn runtime_dir(&self) -> Option<PathBuf> {
158 self.0.runtime_dir()
159 }
160
161 /// Gets the underlying [`Strategy`] used to obtain directories
162 pub fn strategy(&self) -> &Strategy {
163 &self.0
164 }
165}
166
167#[cfg(test)]
168mod tests {
169 use super::*;
170
171 #[test]
172 fn create_valid_directories_from_string_literals() {
173 [
174 "a.b.c",
175 "1.2.3",
176 "Testing One.Mycool Bundle.App Name",
177 "APP$1.90))3,.(DJHG",
178 ]
179 .iter()
180 .for_each(|s| {
181 matches!(Directories::from_string(s), Ok(_));
182 })
183 }
184
185 #[test]
186 fn create_invalid_directories_from_string_literals() {
187 [
188 "a.b.c.d",
189 "1.2",
190 "Testing One..Mycool Bundle.App Name",
191 "ABCDEFG",
192 ]
193 .iter()
194 .for_each(|s| {
195 matches!(Directories::from_string(s), Err(_));
196 })
197 }
198}