use crate::{
error::{parse::validate::ValidationError, utils::MetaData},
knot::Address,
line::{Alternative, Condition, Expression},
story::validate::{ValidateContent, ValidationData},
};
#[cfg(feature = "serde_support")]
use serde::{Deserialize, Serialize};
#[derive(Clone, Debug, PartialEq)]
#[cfg_attr(feature = "serde_support", derive(Deserialize, Serialize))]
pub struct InternalLine {
pub chunk: LineChunk,
pub tags: Vec<String>,
pub glue_begin: bool,
pub glue_end: bool,
pub meta_data: MetaData,
}
#[derive(Clone, Debug, PartialEq)]
#[cfg_attr(feature = "serde_support", derive(Deserialize, Serialize))]
pub struct LineChunk {
pub condition: Option<Condition>,
pub items: Vec<Content>,
pub else_items: Vec<Content>,
}
#[derive(Clone, Debug, PartialEq)]
#[cfg_attr(feature = "serde_support", derive(Deserialize, Serialize))]
pub enum Content {
Alternative(Alternative),
Divert(Address),
Empty,
Expression(Expression),
Nested(LineChunk),
Text(String),
}
impl InternalLine {
pub fn from_chunk(chunk: LineChunk) -> Self {
InternalLine {
chunk,
tags: Vec::new(),
glue_begin: false,
glue_end: false,
meta_data: MetaData { line_index: 0 },
}
}
pub fn text(&self) -> String {
let mut buffer = String::new();
for item in &self.chunk.items {
match item {
Content::Text(string) => {
buffer.push_str(&string);
}
_ => (),
}
}
buffer
}
#[cfg(test)]
pub fn from_string(line: &str) -> Self {
use builders::LineChunkBuilder;
let chunk = LineChunkBuilder::from_string(line).build();
Self::from_chunk(chunk)
}
}
impl ValidateContent for InternalLine {
fn validate(
&mut self,
error: &mut ValidationError,
current_location: &Address,
_: &MetaData,
data: &ValidationData,
) {
self.chunk
.validate(error, current_location, &self.meta_data, data);
}
}
impl ValidateContent for LineChunk {
fn validate(
&mut self,
error: &mut ValidationError,
current_location: &Address,
meta_data: &MetaData,
data: &ValidationData,
) {
if let Some(condition) = self.condition.as_mut() {
condition.validate(error, current_location, meta_data, data);
}
self.items
.iter_mut()
.chain(self.else_items.iter_mut())
.for_each(|item| item.validate(error, current_location, meta_data, data));
}
}
impl ValidateContent for Content {
fn validate(
&mut self,
error: &mut ValidationError,
current_location: &Address,
meta_data: &MetaData,
data: &ValidationData,
) {
match self {
Content::Alternative(alternative) => {
alternative.validate(error, current_location, meta_data, data)
}
Content::Divert(address) => address.validate(error, current_location, meta_data, data),
Content::Empty | Content::Text(..) => (),
Content::Expression(expression) => {
expression.validate(error, current_location, meta_data, data)
}
Content::Nested(chunk) => chunk.validate(error, current_location, meta_data, data),
}
}
}
#[cfg(test)]
pub mod builders {
use super::*;
pub struct InternalLineBuilder {
chunk: LineChunk,
tags: Vec<String>,
glue_begin: bool,
glue_end: bool,
}
impl InternalLineBuilder {
pub fn from_chunk(chunk: LineChunk) -> Self {
InternalLineBuilder {
chunk,
tags: Vec::new(),
glue_begin: false,
glue_end: false,
}
}
pub fn build(self) -> InternalLine {
InternalLine {
chunk: self.chunk,
tags: self.tags,
glue_begin: self.glue_begin,
glue_end: self.glue_end,
meta_data: MetaData { line_index: 0 },
}
}
}
pub struct LineChunkBuilder {
items: Vec<Content>,
}
impl LineChunkBuilder {
pub fn new() -> Self {
LineChunkBuilder { items: Vec::new() }
}
pub fn build(mut self) -> LineChunk {
if self.items.is_empty() {
self.items.push(Content::Empty);
}
LineChunk {
condition: None,
items: self.items,
else_items: Vec::new(),
}
}
pub fn with_alternative(self, alternative: Alternative) -> Self {
self.with_item(Content::Alternative(alternative))
}
pub fn with_divert(self, address: &str) -> Self {
self.with_item(Content::Divert(Address::Raw(address.to_string())))
}
pub fn with_item(mut self, item: Content) -> Self {
self.items.push(item);
self
}
pub fn with_text(self, text: &str) -> Self {
self.with_item(Content::Text(text.to_string()))
}
pub fn from_string(line: &str) -> Self {
LineChunkBuilder::new().with_text(line)
}
}
}