1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367
//! ## `HashMap` wrapper for layered settings
//!
//! This crate facilitates the management of settings, with the goal of allowing developers to turn previously needlessly set in stone
//! values into a setting a user can change, as well as making it easier for a developer to create multiple priority levels of settings,
//! allowing users to have greater control and customization including across devices.
//!
//! This crate allows a developer to store and access all program settings on a [`Account`],
//! a wrapper around a [`HashMap`](std::collections::HashMap).
//!
//! This crate is intended to be used with some sort of type abstraction so that settings of distinct types can be stored in
//! a single `Account`. This crate provides the [`Stg`] type abstraction for this.
//!
//! An `Account` can also hold other [Accounts](crate::account::Account#accounts), allowing the existence of layered settings,
//! that permit the creation complex systems that have the:
//!
//! ### Benefits
//!
//! 1. Having multiple places changing the same setting with the value being taken from the place that is deemed
//! to have the most importance.
//! (eg: Default, Themes, Extensions, Global Settings, OS Settings, Device Settings, Temporary Settings )
//!
//! 2. Organization of Settings. Given that an `Account` can hold accounts, and they can hold accounts of they own, its possible for
//! small groups of settings to be organized in an `Account`, making it more convenient to locate a setting, or display a group of settings.
//! Important to notice that this organization doesn't need to be (but could be) enforced in all held accounts equally.
//!
//! 3. `Account`s can be individually [deactivated](crate::account::Account#active) allowing for a developer (or a user)
//! to group settings in an `Account` and easily ignore them under certain conditions.
//!
//! ### Drawbacks
//!
//! 1. Each `Account` holds a copy of the settings present in it's child Accounts, so there is a memory cost, but its
//! [planned](https://github.com/OxidizedLoop/HashMapSettings/issues/28) for it to be changed to a reference to the value instead.
//!
//! 2. Having to internally do a [`HashMap`](std::collections::HashMap)'s .get() will most likely be slower than alternatives.
//!
//! ## Example
//!
//! ```rust
/*!
// imports
use hashmap_settings::prelude::*;
use std::collections::HashMap;
//creating the Parent Account
let mut account = Account::<
String, //Account's name
&str, //HashMap<K,V> K key
Stg //HashMap<K,v> V value
>::default();
// inserting child Accounts
account.push(
Account::new(
"Default".to_string(), //Name of the Account
true,//is the account active
HashMap::from([
("lines", 3.stg()), // .stg() turns a type into the type abstraction Stg
("word_repetition", 10.stg()),
("word", "default".to_string().stg()),
]), //settings
vec![], //child Accounts
),
Valid::new_true(), // not relevant for this example and can be ignored.
);
account.push(
Account::new(
"Global Settings".to_string(),
true,
HashMap::from([
("word_repetition", 2.stg()),
("word", "global".to_string().stg()),
]), //this account is in a layer above the "Default" Account, so it's values will have priority
vec![],
),
Valid::new_true(),
);// we could be getting this from a database
account.push(
Account::new(
"Local Settings".to_string(),
true,
HashMap::from([("word", "local".to_string().stg())]),
//this account is in a layer that's above "Default" and "Global Settings" Accounts,
//so it's values will have priority over it
vec![],
),
Valid::new_true(),
);// we could be getting this Account from a local file
account.push(
Account::new(
"Inactive Account".to_string(),
false, //this account is inactive so its settings will be ignored.
HashMap::from([("word", "inactive".to_string().stg())]),
vec![],
),
Valid::new_true(),
);
//getting values from the account
let word: String = account.get(&"word").unstg()?;
let word_repetition = account.get(&"word_repetition").unstg()?;
let lines =account.get(&"lines").unstg()?;
//example of using the values
let mut sentence = String::new();
for _ in 0..word_repetition {
sentence.push_str(&word);
sentence.push(' ');
}
sentence.pop();
for _ in 0..lines {
println!("{sentence}");
}
//this will print the following:
/*
local local
local local
local local
*/
//values in child accounts are still accessible
let ref_child_account: &Account<_, _, _> = account
.deep(&mut vec![&"Default".to_string()])
.unwrap();
let inactive_word: String = ref_child_account.get(&"word").unstg()?;
println!("{inactive_word}");
//this will print "default"
//this includes inactive accounts
let ref_child_account: &Account<_, _, _> = account
.deep(&mut vec![&"Inactive Account".to_string()])
.unwrap();
let inactive_word: String = ref_child_account.get(&"word").unstg()?;
println!("{inactive_word}");
//this will print "inactive"
# Ok::<(), StgError>(())
*/
//!```
//! ## How to use
//!
//! This crate relies on the nightly feature [dyn trait upcasting](https://github.com/rust-lang/rust/issues/65991)
//! that was supposed to be stable in rust 1.76.0, unfortunately it has been [delayed](https://github.com/rust-lang/rust/pull/120233)
//! so currently the nightly compiler is required.
//!
//! Add the following line to your Cargo.toml:
//!
//! ```toml
//! [dependencies]
//! hashmap_settings = "0.5"
//! ```
//!
//! Add the following line to your .rs file:
//!
//! ```rust
//! # #[allow(warnings)]
//! use hashmap_settings::prelude::*;
//! ```
#![feature(trait_upcasting)]
#![doc(test(attr(deny(warnings))))] //no warnings in tests
/// [`Account`] and other related elements.
pub mod account;
pub mod stg;
pub mod prelude {
//! Prelude containing everything that will likely be needed while using `Account`
//!
//! This includes everything in the crate except the trait [`Incrementable`](crate::account::Incrementable)
#[doc(inline)]
pub use crate::account::{Account, DeepError, Valid};
#[doc(inline)]
pub use crate::stg::{Setting, Stg, StgError, StgTrait};
}
// inline for docs
#[doc(inline)]
pub use self::{account::Account, stg::Stg};
#[cfg(test)]
mod tests {
use std::collections::HashMap;
use crate::{
account::Account,
prelude::Valid,
stg::{Setting, Stg, StgError, StgTrait},
};
#[test]
fn stg_example() -> Result<(), StgError> {
// # use hashmap_settings::Account;
//creating a basic account
let mut account = Account::<(), &str, Stg>::default();
//inserting values of distinct types
account.insert("Number of trees", 5.stg());
account.insert("Grass color", "green".to_string().stg());
account.insert("Today is good", true.stg());
//getting values from the account
let today_bool: bool = account.get(&"Today is good").unstg()?;
let grass_color: String = account.get(&"Grass color").unstg_panic();
let trees: i32 = account.get(&"Number of trees").unstg()?;
//example of using the values
print!(
"It's {today_bool} that today is a wonderful day,
the grass is {grass_color} and I can see {trees} trees in the distance"
);
Ok(())
}
#[test]
fn doc_example() -> Result<(), StgError> {
//creating the Parent Account
let mut account = Account::<String, &str, Stg>::default();
//inserting child Accounts
account.push(
Account::new(
"Default".to_string(), //Name of the Account
true, //is the account active
HashMap::from([
("lines", 3.stg()),
("word_repetition", 10.stg()),
("word", "default".to_string().stg()),
]), //settings
vec![], //child Accounts
),
Valid::new_true(),
);
account.push(
Account::new(
"Global Settings".to_string(),
true,
HashMap::from([
("word_repetition", 2.stg()),
("word", "global".to_string().stg()),
]), //this account is in a layer above the "Default" Account, so it's values will have priority
vec![],
),
Valid::new_true(),
); // we could be getting this from a database
account.push(
Account::new(
"Local Settings".to_string(),
true,
HashMap::from([("word", "local".to_string().stg())]),
//this account is in a layer that's above "Default" and "Global Settings" Accounts,
//so it's values will have priority over it
vec![],
),
Valid::new_true(),
); // we could be getting this Account from a local file
account.push(
Account::new(
"Inactive Account".to_string(),
false, //this account is inactive so its settings will be ignored.
HashMap::from([("word", "inactive".to_string().stg())]),
vec![],
),
Valid::new_true(),
);
//getting values from the account
let word: String = account.get(&"word").unstg()?;
let word_repetition = account.get(&"word_repetition").unstg()?;
let lines = account.get(&"lines").unstg()?;
//example of using the values
let mut sentence = String::new();
for _ in 0..word_repetition {
sentence.push_str(&word);
sentence.push(' ');
}
sentence.pop();
for _ in 0..lines {
println!("{sentence}");
}
//this will print the following:
/*
local local
local local
local local
*/
//values in child accounts are still accessible
let ref_child_account: &Account<_, _, _> =
account.deep(&mut vec![&"Default".to_string()]).unwrap();
let inactive_word: String = ref_child_account.get(&"word").unstg()?;
println!("{inactive_word}");
//this will print "default"
//this includes inactive accounts
let ref_child_account: &Account<_, _, _> = account
.deep(&mut vec![&"Inactive Account".to_string()])
.unwrap();
let inactive_word: String = ref_child_account.get(&"word").unstg()?;
println!("{inactive_word}");
//this will print "inactive"
Ok(())
}
#[test]
fn account_test() {
let bool_setting = true;
let i32_setting = 42;
let mut account = Account::<(), String, Stg>::default();
account.insert("bool_setting".to_string(), bool_setting.stg());
account.insert("i32_setting".to_string(), i32_setting.stg());
let i32s: i32 = account
.get(&"i32_setting".to_string())
.unwrap()
.clone()
.unstg_panic();
assert_eq!(i32s, 42);
let stg: Stg = account.get(&"bool_setting".to_string()).unwrap().clone();
assert!(stg.unstg_panic::<bool>());
}
#[test]
fn partialeq_test() {
assert!(true.stg() == true.stg());
}
#[test]
const fn setting_example() {
use crate::stg::Setting;
#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, Debug, PartialEq)]
pub struct MyType {}
#[cfg_attr(feature = "serde", typetag::serde)]
// add #[typetag::serde] if serde feature is activated
impl Setting for MyType {}
}
#[test]
fn account_new() {
let mut account1 = Account::new(
"name".to_string(),
Default::default(),
HashMap::default(),
Vec::default(),
);
account1.insert("answer to everything", 42.stg());
account1.insert("true is true", true.stg());
let account2 = Account::new(
"name".to_string(),
Default::default(),
[
("answer to everything", 42.stg()),
("true is true", true.stg()),
]
.into(),
Vec::default(),
);
assert!(account1 == account2);
}
}