use super::{line_len, Bookmark, RawLine};
use crate::{error::Result, Map, Value};
use linear_map::LinearMap;
use serde::{Deserialize, Serialize};
const EMPTY_STRING: &String = &String::new();
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)]
#[serde(untagged)]
pub enum ChoiceTarget {
Lines(Vec<RawLine>),
PassageName(String),
None,
}
impl Default for ChoiceTarget {
fn default() -> Self {
Self::None
}
}
impl ChoiceTarget {
pub fn line_len(&self) -> usize {
match self {
Self::Lines(lines) => line_len(lines),
_ => 0,
}
}
}
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)]
#[serde(untagged)]
pub enum RawChoice {
Conditional(LinearMap<String, ChoiceTarget>),
Target(ChoiceTarget),
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Default)]
pub struct RawChoices {
choices: LinearMap<String, RawChoice>,
#[serde(default)]
pub timeout: f64,
#[serde(default)]
pub default: ChoiceTarget,
}
impl RawChoices {
pub fn len(&self) -> usize {
self.choices.len()
}
pub fn line_len(&self) -> usize {
let mut length = 1 + self.default.line_len();
for (_key, choice) in &self.choices {
match choice {
RawChoice::Target(ChoiceTarget::Lines(lines)) => {
length += line_len(lines) + 1;
}
RawChoice::Conditional(conditional) => {
for (_inner_key, target) in conditional {
if let ChoiceTarget::Lines(lines) = target {
length += line_len(lines) + 1;
}
}
}
_ => (),
}
}
if length > 1 && self.default.line_len() == 0 {
length -= 1;
}
length
}
}
impl<'a> IntoIterator for &'a RawChoices {
type Item = (&'a String, &'a RawChoice);
type IntoIter = linear_map::Iter<'a, String, RawChoice>;
fn into_iter(self) -> Self::IntoIter {
self.choices.iter()
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Default)]
pub struct Choices {
pub choices: Vec<String>,
#[serde(default)]
pub timeout: f64,
}
impl Choices {
pub fn push(&mut self, choice: &str) {
self.choices.push(choice.to_string());
}
pub fn clear(&mut self) {
self.choices.clear()
}
pub fn reserve(&mut self, additional: usize) {
self.choices.reserve(additional)
}
pub fn reverse(&mut self) {
self.choices.reverse()
}
pub fn len(&self) -> usize {
self.choices.len()
}
pub fn is_empty(&self) -> bool {
self.choices.is_empty()
}
pub fn from_raw<'r>(
choice_to_passage: &mut Map<&'r str, &'r str>,
choice_to_line_num: &mut Map<&'r str, usize>,
raw: &'r RawChoices,
bookmark: &Bookmark,
) -> Result<Self> {
let mut choices = Self {
timeout: raw.timeout,
..Default::default()
};
choices.reserve(raw.len());
choice_to_passage.clear();
choice_to_passage.reserve(raw.len());
let mut passage: &String = EMPTY_STRING;
let mut line_num = raw.line_len() - raw.default.line_len();
let mut add_target = |key: &'r str, target: &'r ChoiceTarget| {
match target {
ChoiceTarget::PassageName(passage_name) => {
passage = passage_name;
choices.push(key);
choice_to_passage.insert(key, passage);
}
ChoiceTarget::None => {
choices.push(key);
choice_to_passage.insert(key, passage);
}
ChoiceTarget::Lines(lines) => {
choices.push(key);
line_num -= line_len(lines) + 1;
choice_to_line_num.insert(key, line_num);
}
}
};
for (key, choice) in raw.into_iter().rev() {
match choice {
RawChoice::Target(target) => add_target(key, target),
RawChoice::Conditional(conditional) => {
if !Value::from_conditional(key, bookmark)? {
continue;
}
for (inner_key, target) in conditional.iter().rev() {
add_target(inner_key, target);
}
}
}
}
choices.reverse();
Ok(choices)
}
}
impl<'a> IntoIterator for &'a Choices {
type Item = &'a String;
type IntoIter = std::slice::Iter<'a, String>;
fn into_iter(self) -> Self::IntoIter {
self.choices.iter()
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::FromYaml;
impl FromYaml for RawChoices {}
#[test]
fn test_choices_order() {
let bookmark = Bookmark::new(hashmap! {});
let choices_str = r#"
choices:
a: A
b: B
c:
d: D
e:
- E1
f:
- F1
- F2
default:
- default1
- default2
"#;
let raw = RawChoices::from_yml(choices_str).unwrap();
assert_eq!(raw.line_len(), 8);
let mut choice_to_passage = Map::default();
let mut choice_to_line_num = Map::default();
let choices = Choices::from_raw(
&mut choice_to_passage,
&mut choice_to_line_num,
&raw,
&bookmark,
)
.unwrap();
assert_eq!(
choices.choices,
vec![
"a".to_string(),
"b".to_string(),
"c".to_string(),
"d".to_string(),
"e".to_string(),
"f".to_string()
]
);
assert_eq!(
choice_to_passage,
hashmap! {
"a" => "A",
"b" => "B",
"c" => "D",
"d" => "D",
}
);
assert_eq!(
choice_to_line_num,
hashmap! {
"e" => 1,
"f" => 3,
}
);
}
#[test]
fn test_choices_length() {
let choices_str = r#"
choices:
if $var1 > 0:
yes:
- A: "2"
no:
- A: "4"
- "5"
maybe:
- A: "6"
"#;
let raw = RawChoices::from_yml(choices_str).unwrap();
assert_eq!(raw.line_len(), 7); }
}