use std::{collections::HashMap, sync::LazyLock};
use regex::Regex;
use strum::IntoEnumIterator;
use crate::{
environment::Environment,
sources::VarSource,
var::{self, Confidence, Key, Variable},
};
pub type Value<'a> = (Key, &'static Variable, &'a (Confidence, String));
#[derive(Clone)]
pub struct Storage {
key_values: HashMap<Key, HashMap<usize, (Confidence, String)>>,
key_primary: HashMap<Key, (Confidence, String)>,
}
macro_rules! quote_empty {
($value:expr) => {
if $value.is_empty() {
"\"\""
} else {
$value
}
};
}
impl Storage {
pub fn new() -> Self {
Self {
key_values: HashMap::new(),
key_primary: HashMap::new(),
}
}
pub fn to_table(&self, environment: &Environment, sources: &[Box<dyn VarSource>]) -> String {
static R_COMMON_SOURCE_PREFIX: LazyLock<Regex> =
LazyLock::new(|| Regex::new(r"^projvar::sources::").unwrap());
static R_COMMON_SOURCE_NAME: LazyLock<Regex> =
LazyLock::new(|| Regex::new(r"::VarSource").unwrap());
static R_EMPTY_PROPERTIES: LazyLock<Regex> =
LazyLock::new(|| Regex::new(r"\[\]$").unwrap());
static HEADER_PREFIX: &str = "| Property | Env-Key |";
static HEADER_SUFFIX: &str = " Final Value |";
static SOURCE_NAME_ESTIMATE: usize = 32;
static CONTENT_LINE_PREFIX_EST: usize = 40;
static CONTENT_LINE_PART_EST: usize = 10;
let table_chars_estimate = (HEADER_PREFIX.len() + (sources.len() * (3 + SOURCE_NAME_ESTIMATE)) + 1) + (1 + (sources.len() * 6) + 1) + self.key_values.len() * (CONTENT_LINE_PREFIX_EST + sources.len() * CONTENT_LINE_PART_EST) + 1; let mut table = String::with_capacity(table_chars_estimate);
table.push_str(HEADER_PREFIX);
for source in sources {
let display = source.display();
let display = R_COMMON_SOURCE_PREFIX.replace(&display, "");
let display = R_COMMON_SOURCE_NAME.replace(&display, "");
let display = R_EMPTY_PROPERTIES.replace(&display, "");
table.push(' ');
table.push_str(&display);
table.push_str(" |");
}
table.push_str(HEADER_SUFFIX);
table.push('\n');
table.push('|');
for _table_sep_index in 0..(sources.len() + 3) {
table.push_str(" --- |");
}
table.push('\n');
for key in Key::iter() {
if let Some(values) = self.key_values.get(&key) {
let variable = var::get(key);
table.push_str("| ");
table.push_str(key.into());
table.push_str(" | `");
table.push_str(&variable.key(environment));
table.push_str("` |");
for source_index in 0..sources.len() {
table.push(' ');
table.push_str(
values
.get(&source_index)
.map_or("", |(_c, v)| quote_empty!(v)),
);
table.push_str(" |");
}
table.push_str(" **");
table.push_str(self.get(key).map_or("", |(_c, v)| v));
table.push_str("** |");
table.push('\n');
}
}
log::trace!("Table size (in chars), estimated: {table_chars_estimate}");
log::trace!("Table size (in chars), actual: {}", table.len());
table
}
pub fn to_list(&self, environment: &Environment) -> String {
let values = self.get_wrapup();
let mut key_strs: HashMap<Key, String> = HashMap::with_capacity(values.len());
for (key, variable, _value) in &values {
let key_str = variable.key(environment);
key_strs.insert(*key, key_str.as_ref().to_owned());
}
let mut list = Vec::with_capacity(values.len() * 7);
for (key, _variable, (_confidence, value)) in &values {
list.push("* ");
list.push(key.into());
list.push(" - `");
list.push(quote_empty!(&key_strs[key]));
list.push("` - ");
list.push(value);
list.push("\n");
}
list.concat()
}
pub fn get(&self, key: Key) -> Option<&(Confidence, String)> {
self.key_primary.get(&key)
}
pub fn get_all(&self, key: Key) -> Option<&HashMap<usize, (Confidence, String)>> {
self.key_values.get(&key)
}
pub fn get_wrapup(&self) -> Vec<Value<'_>> {
let mut wrapup: Vec<Value> = self
.key_primary
.iter()
.map(|key_value| {
let key = *key_value.0;
let variable = var::get(*key_value.0);
let value = key_value.1;
(key, variable, value)
})
.collect();
wrapup.sort_unstable_by_key(|entry| entry.0);
wrapup
}
pub fn add(&mut self, key: Key, source_index: usize, confidence: Confidence, value: String) {
(*self.key_values.entry(key).or_default())
.insert(source_index, (confidence, value.clone()));
self.key_primary.insert(key, (confidence, value));
}
pub fn remove(&mut self, key: Key) -> Option<(Confidence, String)> {
if self.key_values.remove(&key).is_some() {
log::info!("Removing key from storage: {key:?}");
}
self.key_primary.remove(&key)
}
}
impl Default for Storage {
fn default() -> Self {
Self::new()
}
}