use std::collections::HashMap;
use crate::template::ValueDef;
use crate::types::{ListItem, Value, ValueOption};
#[derive(Debug, Clone)]
pub struct ValueState {
pub def: ValueDef,
pub index: usize,
current: Value,
filldown_cache: Option<Value>,
}
impl ValueState {
pub fn new(def: ValueDef, index: usize) -> Self {
let initial = if def.has_option(ValueOption::List) {
Value::List(Vec::new())
} else {
Value::Empty
};
Self {
def,
index,
current: initial,
filldown_cache: None,
}
}
pub fn assign(&mut self, value: String, all_results: &mut [Vec<Value>]) {
if self.def.has_option(ValueOption::List) {
self.assign_list(value.clone());
} else {
self.current = Value::Single(value.clone());
}
if self.def.has_option(ValueOption::Filldown) {
self.filldown_cache = Some(self.current.clone());
}
if self.def.has_option(ValueOption::Fillup) && !value.is_empty() {
self.backfill(&value, all_results);
}
}
fn assign_list(&mut self, value: String) {
let item = if let Some(ref regex) = self.def.compiled_regex {
if let Ok(Some(caps)) = regex.captures(&value) {
let dict: HashMap<String, String> = regex
.capture_names()
.flatten()
.filter_map(|name| {
caps.name(name)
.map(|m| (name.to_string(), m.as_str().to_string()))
})
.collect();
if !dict.is_empty() {
ListItem::Dict(dict)
} else {
ListItem::String(value)
}
} else {
ListItem::String(value)
}
} else {
ListItem::String(value)
};
if let Value::List(ref mut list) = self.current {
list.push(item);
}
}
fn backfill(&self, value: &str, results: &mut [Vec<Value>]) {
for record in results.iter_mut().rev() {
if self.index < record.len() {
if record[self.index].is_empty() {
record[self.index] = Value::Single(value.to_string());
} else {
break;
}
}
}
}
pub fn assign_none(&mut self) {
if self.def.has_option(ValueOption::List) {
return;
}
self.current = Value::Empty;
if self.def.has_option(ValueOption::Filldown) {
self.filldown_cache = Some(Value::Empty);
}
}
pub fn clear(&mut self) {
if self.def.has_option(ValueOption::Filldown) {
if let Some(ref cached) = self.filldown_cache {
self.current = cached.clone();
return;
}
}
if self.def.has_option(ValueOption::List) && !self.def.has_option(ValueOption::Filldown) {
self.current = Value::List(Vec::new());
} else if !self.def.has_option(ValueOption::Filldown) {
self.current = Value::Empty;
}
}
pub fn clear_all(&mut self) {
self.filldown_cache = None;
self.current = if self.def.has_option(ValueOption::List) {
Value::List(Vec::new())
} else {
Value::Empty
};
}
pub fn satisfies_required(&self) -> bool {
if !self.def.has_option(ValueOption::Required) {
return true;
}
!self.current.is_empty()
}
pub fn take_for_record(&mut self) -> Value {
if self.def.has_option(ValueOption::List) {
self.current.clone()
} else {
std::mem::take(&mut self.current)
}
}
pub fn current(&self) -> &Value {
&self.current
}
}
#[cfg(test)]
mod tests {
use super::*;
fn make_def(name: &str, options: &[ValueOption]) -> ValueDef {
ValueDef {
name: name.to_string(),
pattern: "(\\S+)".to_string(),
options: options.iter().cloned().collect(),
template_pattern: format!("(?P<{}>\\S+)", name),
compiled_regex: None,
}
}
#[test]
fn test_simple_assign() {
let def = make_def("Test", &[]);
let mut state = ValueState::new(def, 0);
let mut results = Vec::new();
state.assign("hello".to_string(), &mut results);
assert_eq!(state.current(), &Value::Single("hello".into()));
}
#[test]
fn test_filldown() {
let def = make_def("Test", &[ValueOption::Filldown]);
let mut state = ValueState::new(def, 0);
let mut results = Vec::new();
state.assign("cached".to_string(), &mut results);
assert_eq!(state.current(), &Value::Single("cached".into()));
state.clear();
assert_eq!(state.current(), &Value::Single("cached".into()));
state.clear_all();
assert_eq!(state.current(), &Value::Empty);
}
#[test]
fn test_list() {
let def = make_def("Items", &[ValueOption::List]);
let mut state = ValueState::new(def, 0);
let mut results = Vec::new();
state.assign("one".to_string(), &mut results);
state.assign("two".to_string(), &mut results);
state.assign("three".to_string(), &mut results);
match state.current() {
Value::List(items) => {
assert_eq!(items.len(), 3);
}
_ => panic!("Expected List value"),
}
}
#[test]
fn test_required() {
let def = make_def("Required", &[ValueOption::Required]);
let mut state = ValueState::new(def, 0);
assert!(!state.satisfies_required());
let mut results = Vec::new();
state.assign("value".to_string(), &mut results);
assert!(state.satisfies_required()); }
#[test]
fn test_assign_none_clears_value() {
let def = make_def("Test", &[]);
let mut state = ValueState::new(def, 0);
let mut results = Vec::new();
state.assign("hello".to_string(), &mut results);
assert_eq!(state.current(), &Value::Single("hello".into()));
state.assign_none();
assert_eq!(state.current(), &Value::Empty);
}
#[test]
fn test_assign_none_clears_filldown_cache() {
let def = make_def("Test", &[ValueOption::Filldown]);
let mut state = ValueState::new(def, 0);
let mut results = Vec::new();
state.assign("cached".to_string(), &mut results);
assert_eq!(state.current(), &Value::Single("cached".into()));
state.clear();
assert_eq!(state.current(), &Value::Single("cached".into()));
state.assign_none();
assert_eq!(state.current(), &Value::Empty);
state.clear();
assert_eq!(state.current(), &Value::Empty);
}
#[test]
fn test_assign_none_skips_list() {
let def = make_def("Items", &[ValueOption::List]);
let mut state = ValueState::new(def, 0);
let mut results = Vec::new();
state.assign("one".to_string(), &mut results);
state.assign_none();
match state.current() {
Value::List(items) => assert_eq!(items.len(), 1),
_ => panic!("Expected List value"),
}
}
}