cr_program_settings/lib.rs
1//! `cr_program_state` is a library that simplifies saving a settings file for the program.
2#![warn(missing_docs)]
3
4/// Global settings file path list, paths are added when successfully loaded, or when successfully saved.
5pub static SETTINGS_PATHS: RwLock<Vec<PathBuf>> = RwLock::new(vec![]);
6
7use crate::LoadSettingsError::{DeserializationError, IOError};
8use serde::{Deserialize, Serialize};
9use std::fs::File;
10use std::io::{Error, Read, Write};
11use std::path::PathBuf;
12use std::sync::RwLock;
13use std::{fs, io};
14
15/// Prelude module that contains all the imports for `cr_program_settings`;
16pub mod prelude {
17 pub use crate::{
18 delete_setting_file, delete_settings, get_user_home, load_settings,
19 load_settings_with_filename, save_settings, save_settings_with_filename,
20 settings_container, SETTINGS_PATHS,
21 };
22}
23
24/// Source code for the settings container.
25pub mod settings_container;
26
27/// Returns the users home as an optional using the "home" crate
28pub fn get_user_home() -> Option<PathBuf> {
29 home::home_dir()
30}
31
32#[macro_export]
33/// Saves settings given a struct to save, to the home directory with a name matching the crate name
34///
35/// Syntax:
36/// save_settings!(settings_struct)
37/// save_settings!(settings_struct, file_name)
38/// save_settings!(settings_struct, file_name, folder_name)
39///
40/// ```
41/// use serde::{Deserialize, Serialize};
42/// use cr_program_settings::prelude::*;
43///
44/// // create a struct we want to save, it needs to implement at a minimum of Serialize and Deserialize
45/// #[derive(Serialize,Deserialize, PartialEq, Debug)]
46/// struct Settings{
47/// setting1: u32,
48/// setting2: String,
49/// setting3: Vec<bool>,
50/// }
51///
52/// let settings = Settings{
53/// setting1: 128,
54/// setting2: "this is a cool setting struct".to_string(),
55/// setting3: vec![false,true,false,false]
56/// };
57///
58/// save_settings!(settings).expect("Settings were unable to be saved");
59///
60/// // -- snip --
61///
62/// let loaded_settings = load_settings!(Settings).expect("Unable to read settings file");
63///
64/// assert_eq!(settings,loaded_settings);
65///
66/// save_settings!(settings,"cool_filename.ser").expect("Unable to save settings with specific filename");
67///
68/// // -- snip --
69///
70/// let specific_settings_loaded = load_settings!(Settings,"cool_filename.ser").expect("Unable to load settings with specific filename");
71///
72/// assert_eq!(settings,specific_settings_loaded);
73/// ```
74macro_rules! save_settings {
75 ($settings:expr) => {
76 save_settings(env!("CARGO_CRATE_NAME"), &$settings)
77 };
78 ($settings: expr, $file_name: expr) => {
79 save_settings_with_filename(env!("CARGO_CRATE_NAME"), &$file_name, &$settings)
80 };
81 ($settings: expr, $file_name: expr, $folder_name: expr) => {
82 save_settings_with_filename($folder_name, &$file_name, &$settings)
83 };
84}
85
86#[macro_export]
87/// Loads settings given a type to load, from the home directory with a name matching the crate name
88///
89/// Syntax:
90/// load_settings!(SETTINGS_TYPE)
91/// load_settings!(SETTINGS_TYPE, file_name)
92/// load_settings!(SETTINGS_TYPE, file_name,folder_name)
93///
94/// For more usage examples, see save_settings!() documentation.
95/// ```
96/// use serde::{Deserialize, Serialize};
97/// use cr_program_settings::prelude::*;
98///
99/// // create a struct we want to save, it needs to implement at a minimum of Serialize and Deserialize
100/// #[derive(Serialize,Deserialize, PartialEq, Debug)]
101/// struct Settings{
102/// setting1: u32,
103/// setting2: String,
104/// setting3: Vec<bool>,
105/// }
106///
107/// let settings = Settings{
108/// setting1: 128,
109/// setting2: "this is a cool setting struct".to_string(),
110/// setting3: vec![false,true,false,false]
111/// };
112///
113/// save_settings!(settings,"odd_file_name.ser","unit_test_temp").expect("Unable to save settings to file");
114///
115/// let loaded_settings = load_settings!(Settings, "odd_file_name.ser","unit_test_temp").expect("Failed to load settings file");
116///
117/// assert_eq!(settings,loaded_settings);
118/// ```
119macro_rules! load_settings {
120 ($setting_type:ty) => {
121 load_settings::<$setting_type>(env!("CARGO_CRATE_NAME"))
122 };
123 ($setting_type:ty,$file_name: expr) => {
124 load_settings_with_filename::<$setting_type>(env!("CARGO_CRATE_NAME"), $file_name)
125 };
126 ($setting_type:ty,$file_name: expr,$folder_name: expr) => {
127 load_settings_with_filename::<$setting_type>($folder_name, $file_name)
128 };
129}
130
131#[macro_export]
132/// Deletes settings located at the home directory with a name matching the crate name
133/// Syntax:
134/// delete_settings!() // deletes file named: env!("CARGO_CRATE_NAME") file stored in the folder named: env!("CARGO_CRATE_NAME")
135/// delete_settings!(file_name) // deletes the file named: file_name stored in the folder named: env!("CARGO_CRATE_NAME")
136/// delete_settings!(file_name, folder_name) // deletes the file named: file_name stored in the folder named: folder_name
137macro_rules! delete_settings {
138 () => {
139 delete_settings(env!("CARGO_CRATE_NAME"))
140 };
141 ($file_name: expr) => {
142 delete_setting_file(env!("CARGO_CRATE_NAME"), $file_name)
143 };
144 ($file_name: expr,$folder_name: expr) => {
145 delete_setting_file($folder_name, $file_name)
146 };
147}
148
149#[derive(Debug)]
150/// An enum state representing the kinds of errors that saving settings has
151pub enum SaveSettingsError {
152 /// The library was unable to find the users home directory
153 FailedToGetUserHome,
154 /// The library encountered an io error when saving or creating the file or directory
155 IOError(Error),
156 /// The library encountered an error while serializing the struct
157 SerializationError(toml::ser::Error),
158}
159
160/// Saves a serializable settings object to a given filename in `USER_HOME/crate_name/file_name`
161pub fn save_settings_with_filename<T>(
162 crate_name: &str,
163 file_name: &str,
164 settings: &T,
165) -> Result<(), SaveSettingsError>
166where
167 T: Serialize,
168{
169 match get_user_home() {
170 None => Err(SaveSettingsError::FailedToGetUserHome),
171 Some(home_dir) => {
172 let settings_path = home_dir.join(PathBuf::from(crate_name));
173 let settings_file_path = settings_path.join(PathBuf::from(file_name));
174 match fs::create_dir_all(&settings_path) {
175 Ok(_) => match File::create(&settings_file_path) {
176 Ok(mut file) => match toml::to_string_pretty(&settings) {
177 Ok(serialized_data) => match file.write_all(serialized_data.as_bytes()) {
178 Ok(_) => {
179 {
180 let mut lock = SETTINGS_PATHS.write().unwrap();
181 lock.push(settings_file_path);
182 }
183 Ok(())
184 }
185 Err(err) => Err(SaveSettingsError::IOError(err)),
186 },
187 Err(err) => Err(SaveSettingsError::SerializationError(err)),
188 },
189 Err(err) => Err(SaveSettingsError::IOError(err)),
190 },
191 Err(err) => Err(SaveSettingsError::IOError(err)),
192 }
193 }
194 }
195}
196
197/// Saves the settings file given in a directory named using the crate name
198/// Given a struct and a crate name of `my_cool_rust_project`, the program
199/// would save it to `/home/username/my_cool_rust_project/my_cool_rust_project.ser`
200pub fn save_settings<T>(crate_name: &str, settings: &T) -> Result<(), SaveSettingsError>
201where
202 T: Serialize,
203{
204 save_settings_with_filename(crate_name, format!("{}.ser", crate_name).as_str(), settings)
205}
206
207#[derive(Debug)]
208/// Enum state representing the possible errors that can occur when loading settings
209pub enum LoadSettingsError {
210 /// The library was unable to find the users home directory
211 FailedToGetUserHome,
212 /// The library encountered an io error while reading the file or accessing the directory
213 IOError(Error),
214 /// The library encountered an error while deserializing the settings file
215 DeserializationError(toml::de::Error),
216}
217
218/// Loads a settings serialized file from `USER_HOME/crate_name/file_name`
219pub fn load_settings_with_filename<T>(
220 crate_name: &str,
221 file_name: &str,
222) -> Result<T, LoadSettingsError>
223where
224 for<'a> T: Deserialize<'a>,
225{
226 match get_user_home() {
227 None => Err(LoadSettingsError::FailedToGetUserHome),
228 Some(home_dir) => {
229 let settings_path = home_dir.join(PathBuf::from(crate_name));
230 let settings_file_path = settings_path.join(PathBuf::from(file_name));
231 match File::open(&settings_file_path) {
232 Ok(mut file) => {
233 let mut file_data = String::new();
234 match file.read_to_string(&mut file_data) {
235 Ok(_) => match toml::from_str::<T>(&file_data) {
236 Ok(thing) => {
237 {
238 let mut lock = SETTINGS_PATHS.write().unwrap();
239 if !lock.contains(&settings_file_path) {
240 lock.push(settings_file_path);
241 }
242 }
243 Ok(thing)
244 }
245 Err(err) => Err(DeserializationError(err)),
246 },
247 Err(err) => Err(IOError(err)),
248 }
249 }
250 Err(err) => Err(IOError(err)),
251 }
252 }
253 }
254}
255
256/// Loads a given settings file from the home directory and the given crate name.
257/// Given `my_cool_rust_project`, the program would search in `/home/username/my_cool_rust_project` for a settings file
258pub fn load_settings<T>(crate_name: &str) -> Result<T, LoadSettingsError>
259where
260 for<'a> T: Deserialize<'a>,
261{
262 load_settings_with_filename(crate_name, format!("{}.ser", crate_name).as_str())
263}
264
265/// Deletes the settings directory found in the `<user home>/crate_name`
266/// e.g. `/home/username/my_cool_project`
267pub fn delete_settings(crate_name: &str) -> io::Result<()> {
268 let home_dir = get_user_home().unwrap();
269 let settings_path = home_dir.join(PathBuf::from(crate_name));
270 fs::remove_dir_all(&settings_path)?;
271 SETTINGS_PATHS
272 .write()
273 .unwrap()
274 .retain(|path| match path.parent() {
275 None => true,
276 Some(parent) => parent != settings_path,
277 });
278 Ok(())
279}
280
281/// Deletes a specific settings file
282/// ```
283/// use std::ffi::OsStr;
284/// use serde::{Deserialize, Serialize};
285/// use cr_program_settings::prelude::*;
286/// #[derive(Serialize,Deserialize)]
287/// struct TestStruct {field1: u32}
288///
289/// let s = TestStruct{field1: 6};
290///
291/// let sn = "settings_file_978.ser";
292/// save_settings!(s,sn);
293/// assert!(SETTINGS_PATHS.read().unwrap().iter().any(|path| {
294/// match path.file_name() {
295/// None => { false }
296/// Some(file_name) => { file_name == sn }
297/// }
298/// }));
299///
300/// delete_settings!(sn);
301/// assert!(!SETTINGS_PATHS.read().unwrap().iter().any(|path| {
302/// match path.file_name() {
303/// None => { false }
304/// Some(file_name) => { file_name == sn }
305/// }
306/// }));
307///
308///
309/// ```
310pub fn delete_setting_file(crate_name: &str, file_name: &str) -> io::Result<()> {
311 let home_dir = get_user_home().unwrap();
312 let settings_path = home_dir.join(PathBuf::from(crate_name));
313 let settings_file = settings_path.join(file_name);
314 fs::remove_file(&settings_file)?;
315 SETTINGS_PATHS
316 .write()
317 .unwrap()
318 .retain(|path| path != &settings_file);
319 Ok(())
320}