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
/* Copyright © 2019-2021 Randy Barlow
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, version 3 of the License.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.*/
//! # Configuration
//!
//! This module defines the rpick configuration.
//!
//! The configuration defines the pick categories, their algorithms, and their choices.
use std::collections::BTreeMap;
use std::error;
use std::fs::{File, OpenOptions};
use std::io::{BufReader, Write};
use serde::{Deserialize, Serialize};
/// Return the user's config as a BTreeMap.
///
/// # Arguments
///
/// * `config_file_path` - A filesystem path to a YAML file that should be read.
///
/// # Returns
///
/// Returns a mapping of YAML to [`ConfigCategory`]'s, or an Error.
pub fn read_config(
config_file_path: &str,
) -> Result<BTreeMap<String, ConfigCategory>, Box<dyn error::Error>> {
let f = File::open(&config_file_path)?;
let reader = BufReader::new(f);
let config: BTreeMap<String, ConfigCategory> = serde_yaml::from_reader(reader)?;
Ok(config)
}
/// Save the data from the given BTreeMap to the user's config file.
///
/// # Arguments
///
/// * `config_file_path` - A filesystem path that the config should be written to.
/// * `config` - The config that should be serialized as YAML.
pub fn write_config(
config_file_path: &str,
config: BTreeMap<String, ConfigCategory>,
) -> Result<(), Box<dyn error::Error>> {
let mut f = OpenOptions::new()
.write(true)
.create(true)
.truncate(true)
.open(&config_file_path)?;
let yaml = serde_yaml::to_string(&config).unwrap();
f.write_all(&yaml.into_bytes())?;
Ok(())
}
/// A category of items that can be chosen from.
///
/// Each variant of this Enum maps to one of the supported algorithms.
#[derive(Debug, PartialEq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
#[serde(rename_all = "snake_case")]
#[serde(tag = "model")]
pub enum ConfigCategory {
/// The Even variant picks from its choices with even distribution.
///
/// # Attributes
///
/// * `choices` - The list of choices to pick from.
Even { choices: Vec<String> },
/// The Gaussian variant uses a
/// [Gaussian distribution](https://en.wikipedia.org/wiki/Normal_distribution) to prefer choices
/// near the beginning of the list of choices over those at the end. Once a choice has been
/// accepted, it is moved to the end of the list.
///
/// # Attributes
///
/// * `stddev_scaling_factor` - This is used to derive the standard deviation; the standard
/// deviation is the length of the list of choices, divided by this scaling factor.
/// * `choices` - The list of choices to pick from.
Gaussian {
#[serde(default = "default_stddev_scaling_factor")]
stddev_scaling_factor: f64,
choices: Vec<String>,
},
/// The Inventory variant uses a weighted distribution to pick items, with each items chances
/// being tied to how many tickets it has. When a choice is accepted, that choice's ticket
/// count is reduced by 1.
///
/// # Attributes
///
/// * `choices` - The list of choices to pick from.
Inventory { choices: Vec<InventoryChoice> },
/// The Lru variant picks the Least Recently Used item from the list of choices. The least
/// recently used choice is found at the beginning of the list. Once a choice has been
/// accepted, it is moved to the end of the list.
///
/// # Attributes
///
/// * `choices` - The list of choices to pick from.
#[serde(rename = "lru")]
Lru { choices: Vec<String> },
/// The Lottery variant uses a weighted distribution to pick items, with each items chances
/// being tied to how many tickets it has. When a choice is accepted, that choice's ticket
/// count is set to 0, and every choice not chosen receives its weight in additional tickets.
///
/// # Attributes
///
/// * `choices` - The list of choices to pick from.
Lottery { choices: Vec<LotteryChoice> },
/// The Weighted variant is a simple weighted distribution.
///
/// # Attributes
///
/// * `choices` - The list of choices to pick from.
Weighted { choices: Vec<WeightedChoice> },
}
/// Represents an individual choice for the inventory model.
///
/// # Attributes
///
/// * `name` - The name of the choice.
/// * `tickets` - The current number of tickets the choice has.
#[derive(Debug, PartialEq, Serialize, Deserialize)]
pub struct InventoryChoice {
pub name: String,
#[serde(default = "default_weight")]
pub tickets: u64,
}
/// Represents an individual choice for the lottery model.
///
/// # Attributes
///
/// * `name` - The name of the choice.
/// * `tickets` - The current number of tickets the choice has.
/// * `weight` - The number of tickets that will be added to `tickets` each time this choice is not
/// picked.
#[derive(Debug, PartialEq, Serialize, Deserialize)]
pub struct LotteryChoice {
pub name: String,
#[serde(default = "default_weight")]
pub tickets: u64,
#[serde(default = "default_weight")]
pub weight: u64,
}
/// Represents an individual choice for the weighted model.
///
/// # Attributes
///
/// * `name` - The name of the choice
/// * `weight` - How much chance this choice has of being chosen, relative to the other choices.
#[derive(Debug, PartialEq, Serialize, Deserialize)]
pub struct WeightedChoice {
pub name: String,
#[serde(default = "default_weight")]
pub weight: u64,
}
/// Define the default for the stddev_scaling_factor setting as 3.0.
fn default_stddev_scaling_factor() -> f64 {
3.0
}
/// Define the default for the weight setting as 1.
fn default_weight() -> u64 {
1
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_defaults() {
assert!((default_stddev_scaling_factor() - 3.0).abs() < 0.000_001);
assert_eq!(default_weight(), 1);
}
}