use crate::{
state::{
error::ParseError,
ssb_state::Section
},
objects::ssb_objects::{View,Event,EventTrigger,FontFace,FontStyle,FontData,TextureId,TextureDataVariant},
utils::{
pattern::*,
functions::convert::parse_timestamp
}
};
use std::{
collections::HashMap,
io::BufRead,
convert::TryFrom
};
#[derive(Debug, PartialEq, Clone)]
#[cfg_attr(feature = "serialization", derive(serde::Serialize,serde::Deserialize))]
pub struct Ssb {
pub info_title: Option<String>,
pub info_author: Option<String>,
pub info_description: Option<String>,
pub info_version: Option<String>,
pub info_custom: HashMap<String, String>,
pub target_width: Option<u16>,
pub target_height: Option<u16>,
pub target_depth: u16,
pub target_view: View,
pub macros: HashMap<String, String>,
pub events: Vec<Event>,
pub fonts: HashMap<FontFace, FontData>,
pub textures: HashMap<TextureId, TextureDataVariant>
}
impl Default for Ssb {
fn default() -> Self {
Self {
info_title: None,
info_author: None,
info_description: None,
info_version: None,
info_custom: HashMap::default(),
target_width: None,
target_height: None,
target_depth: 1000,
target_view: View::Perspective,
macros: HashMap::default(),
events: Vec::default(),
fonts: HashMap::default(),
textures: HashMap::default()
}
}
}
impl Ssb {
pub fn parse_owned<R>(mut self, reader: R) -> Result<Self, ParseError>
where R: BufRead {
self.parse(reader)?;
Ok(self)
}
pub fn parse<R>(&mut self, reader: R) -> Result<&mut Self, ParseError>
where R: BufRead {
let mut section: Option<Section> = None;
for (line_index, line) in reader.lines().enumerate() {
let mut line = line?;
if line.ends_with('\r') {line.pop();}
if !(line.is_empty() || line.starts_with("//")) {
if let Ok(parsed_section) = Section::try_from(line.as_ref()) {
section = Some(parsed_section);
} else {
match section {
Some(Section::Info) => {
if line.starts_with(INFO_TITLE_KEY) {
self.info_title = Some(line[INFO_TITLE_KEY.len()..].to_owned());
}
else if line.starts_with(INFO_AUTHOR_KEY) {
self.info_author = Some(line[INFO_AUTHOR_KEY.len()..].to_owned());
}
else if line.starts_with(INFO_DESCRIPTION_KEY) {
self.info_description = Some(line[INFO_DESCRIPTION_KEY.len()..].to_owned());
}
else if line.starts_with(INFO_VERSION_KEY) {
self.info_version = Some(line[INFO_VERSION_KEY.len()..].to_owned());
}
else if let Some(separator_pos) = line.find(KEY_SUFFIX).filter(|pos| *pos > 0) {
self.info_custom.insert(
line[..separator_pos].to_owned(),
line[separator_pos + KEY_SUFFIX.len()..].to_owned()
);
}
else {
return Err(ParseError::new_with_pos("Invalid info entry!", (line_index, 0)));
}
}
Some(Section::Target) => {
if line.starts_with(TARGET_WIDTH_KEY) {
self.target_width = Some(
line[TARGET_WIDTH_KEY.len()..].parse().map_err(|_| ParseError::new_with_pos("Invalid target width value!", (line_index, TARGET_WIDTH_KEY.len())) )?
);
}
else if line.starts_with(TARGET_HEIGHT_KEY) {
self.target_height = Some(
line[TARGET_HEIGHT_KEY.len()..].parse().map_err(|_| ParseError::new_with_pos("Invalid target height value!", (line_index, TARGET_HEIGHT_KEY.len())) )?
);
}
else if line.starts_with(TARGET_DEPTH_KEY) {
self.target_depth = line[TARGET_DEPTH_KEY.len()..].parse().map_err(|_| ParseError::new_with_pos("Invalid target depth value!", (line_index, TARGET_DEPTH_KEY.len())) )?;
}
else if line.starts_with(TARGET_VIEW_KEY) {
self.target_view = View::try_from(&line[TARGET_VIEW_KEY.len()..]).map_err(|_| ParseError::new_with_pos("Invalid target view value!", (line_index, TARGET_VIEW_KEY.len())) )?;
}
else {
return Err(ParseError::new_with_pos("Invalid target entry!", (line_index, 0)));
}
}
Some(Section::Macros) => {
if let Some(separator_pos) = line.find(KEY_SUFFIX).filter(|pos| *pos > 0) {
self.macros.insert(
line[..separator_pos].to_owned(),
line[separator_pos + KEY_SUFFIX.len()..].to_owned()
);
}
else {
return Err(ParseError::new_with_pos("Invalid macros entry!", (line_index, 0)));
}
}
Some(Section::Events) => {
let mut event_tokens = line.splitn(4, EVENT_SEPARATOR);
if let (Some(trigger), Some(macro_name), Some(note), Some(data)) = (event_tokens.next(), event_tokens.next(), event_tokens.next(), event_tokens.next()) {
self.events.push(
Event {
trigger: {
if trigger.starts_with('\'') && trigger.len() >= 2 && trigger.ends_with('\'') {
EventTrigger::Id(trigger[1..trigger.len()-1].to_owned())
} else if let Some(seperator_pos) = trigger.find(TRIGGER_SEPARATOR) {
let start_time = parse_timestamp(&trigger[..seperator_pos]).map_err(|_| ParseError::new_with_pos("Start timestamp invalid!", (line_index, 0)) )?;
let end_time = parse_timestamp(&trigger[seperator_pos + 1 ..]).map_err(|_| ParseError::new_with_pos("End timestamp invalid!", (line_index, seperator_pos + 1 ) ))?;
if start_time > end_time {
return Err(ParseError::new_with_pos("Start time greater than end time!", (line_index, 0)));
}
EventTrigger::Time((start_time, end_time))
} else {
return Err(ParseError::new_with_pos("Invalid trigger format!", (line_index, 0)));
}
},
macro_name: Some(macro_name.to_owned()).filter(|s| !s.is_empty()),
note: Some(note.to_owned()).filter(|s| !s.is_empty()),
data: data.to_owned(),
data_location: (line_index, trigger.len() + macro_name.len() + note.len() + 3 )
}
);
}
else {
return Err(ParseError::new_with_pos("Invalid events entry!", (line_index, 0)));
}
}
Some(Section::Resources) => {
if line.starts_with(RESOURCES_FONT_KEY) {
let mut font_tokens = line[RESOURCES_FONT_KEY.len()..].splitn(3, VALUE_SEPARATOR);
if let (Some(family), Some(style), Some(data)) = (font_tokens.next(), font_tokens.next(), font_tokens.next()) {
self.fonts.insert(
FontFace {
family: family.to_owned(),
style: FontStyle::try_from(style).map_err(|_| ParseError::new_with_pos("Font style invalid!", (line_index, RESOURCES_FONT_KEY.len() + family.len() + 1 ) ))?
},
base64::decode(data).map_err(|_| ParseError::new_with_pos("Font data not in base64 format!", (line_index, RESOURCES_FONT_KEY.len() + family.len() + style.len() + (1 << 1))) )?
);
} else {
return Err(ParseError::new_with_pos("Font family, style and data expected!", (line_index, RESOURCES_FONT_KEY.len())));
}
}
else if line.starts_with(RESOURCES_TEXTURE_KEY) {
let mut texture_tokens = line[RESOURCES_TEXTURE_KEY.len()..].splitn(3, VALUE_SEPARATOR);
if let (Some(id), Some(data_type), Some(data)) = (texture_tokens.next(), texture_tokens.next(), texture_tokens.next()) {
self.textures.insert(
id.to_owned(),
match data_type {
"data" => TextureDataVariant::Raw(
base64::decode(data).map_err(|_| ParseError::new_with_pos("Texture data not in base64 format!", (line_index, RESOURCES_TEXTURE_KEY.len() + id.len() + data_type.len() + (1 << 1))) )?
),
"url" => TextureDataVariant::Url(
data.to_owned()
),
_ => return Err(ParseError::new_with_pos("Texture data type invalid!", (line_index, RESOURCES_TEXTURE_KEY.len() + id.len() + 1 )))
}
);
} else {
return Err(ParseError::new_with_pos("Texture id, data type and data expected!", (line_index, RESOURCES_TEXTURE_KEY.len())));
}
}
else {
return Err(ParseError::new_with_pos("Invalid resources entry!", (line_index, 0)));
}
}
None => return Err(ParseError::new_with_pos("No section set!", (line_index, 0)))
}
}
}
}
Ok(self)
}
}