1use crate::{
3 state::{
4 error::ParseError,
5 ssb_state::Section
6 },
7 objects::ssb_objects::{View,Event,EventTrigger,FontFace,FontStyle,FontData,TextureId,TextureDataVariant},
8 utils::{
9 pattern::*,
10 functions::convert::parse_timestamp
11 }
12};
13use std::{
14 collections::HashMap,
15 io::BufRead,
16 convert::TryFrom
17};
18
19
20#[derive(Debug, PartialEq, Clone)]
22#[cfg_attr(feature = "serialization", derive(serde::Serialize,serde::Deserialize))]
23pub struct Ssb {
24 pub info_title: Option<String>,
26 pub info_author: Option<String>,
27 pub info_description: Option<String>,
28 pub info_version: Option<String>,
29 pub info_custom: HashMap<String, String>,
30 pub target_width: Option<u16>,
32 pub target_height: Option<u16>,
33 pub target_depth: u16,
34 pub target_view: View,
35 pub macros: HashMap<String, String>,
37 pub events: Vec<Event>,
39 pub fonts: HashMap<FontFace, FontData>,
41 pub textures: HashMap<TextureId, TextureDataVariant>
42}
43impl Default for Ssb {
44 fn default() -> Self {
45 Self {
46 info_title: None,
47 info_author: None,
48 info_description: None,
49 info_version: None,
50 info_custom: HashMap::default(),
51 target_width: None,
52 target_height: None,
53 target_depth: 1000,
54 target_view: View::Perspective,
55 macros: HashMap::default(),
56 events: Vec::default(),
57 fonts: HashMap::default(),
58 textures: HashMap::default()
59 }
60 }
61}
62impl Ssb {
63 pub fn parse_owned<R>(mut self, reader: R) -> Result<Self, ParseError>
65 where R: BufRead {
66 self.parse(reader)?;
67 Ok(self)
68 }
69 pub fn parse<R>(&mut self, reader: R) -> Result<&mut Self, ParseError>
71 where R: BufRead {
72 let mut section: Option<Section> = None;
74 for (line_index, line) in reader.lines().enumerate() {
76 let mut line = line?;
78 if line.ends_with('\r') {line.pop();}
79 if !(line.is_empty() || line.starts_with("//")) {
81 if let Ok(parsed_section) = Section::try_from(line.as_ref()) {
83 section = Some(parsed_section);
84 } else {
85 match section {
86 Some(Section::Info) => {
88 if line.starts_with(INFO_TITLE_KEY) {
90 self.info_title = Some(line[INFO_TITLE_KEY.len()..].to_owned());
91 }
92 else if line.starts_with(INFO_AUTHOR_KEY) {
94 self.info_author = Some(line[INFO_AUTHOR_KEY.len()..].to_owned());
95 }
96 else if line.starts_with(INFO_DESCRIPTION_KEY) {
98 self.info_description = Some(line[INFO_DESCRIPTION_KEY.len()..].to_owned());
99 }
100 else if line.starts_with(INFO_VERSION_KEY) {
102 self.info_version = Some(line[INFO_VERSION_KEY.len()..].to_owned());
103 }
104 else if let Some(separator_pos) = line.find(KEY_SUFFIX).filter(|pos| *pos > 0) {
106 self.info_custom.insert(
107 line[..separator_pos].to_owned(),
108 line[separator_pos + KEY_SUFFIX.len()..].to_owned()
109 );
110 }
111 else {
113 return Err(ParseError::new_with_pos("Invalid info entry!", (line_index, 0)));
114 }
115 }
116 Some(Section::Target) => {
118 if line.starts_with(TARGET_WIDTH_KEY) {
120 self.target_width = Some(
121 line[TARGET_WIDTH_KEY.len()..].parse().map_err(|_| ParseError::new_with_pos("Invalid target width value!", (line_index, TARGET_WIDTH_KEY.len())) )?
122 );
123 }
124 else if line.starts_with(TARGET_HEIGHT_KEY) {
126 self.target_height = Some(
127 line[TARGET_HEIGHT_KEY.len()..].parse().map_err(|_| ParseError::new_with_pos("Invalid target height value!", (line_index, TARGET_HEIGHT_KEY.len())) )?
128 );
129 }
130 else if line.starts_with(TARGET_DEPTH_KEY) {
132 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())) )?;
133 }
134 else if line.starts_with(TARGET_VIEW_KEY) {
136 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())) )?;
137 }
138 else {
140 return Err(ParseError::new_with_pos("Invalid target entry!", (line_index, 0)));
141 }
142 }
143 Some(Section::Macros) => {
145 if let Some(separator_pos) = line.find(KEY_SUFFIX).filter(|pos| *pos > 0) {
147 self.macros.insert(
148 line[..separator_pos].to_owned(),
149 line[separator_pos + KEY_SUFFIX.len()..].to_owned()
150 );
151 }
152 else {
154 return Err(ParseError::new_with_pos("Invalid macros entry!", (line_index, 0)));
155 }
156 }
157 Some(Section::Events) => {
159 let mut event_tokens = line.splitn(4, EVENT_SEPARATOR);
160 if let (Some(trigger), Some(macro_name), Some(note), Some(data)) = (event_tokens.next(), event_tokens.next(), event_tokens.next(), event_tokens.next()) {
161 self.events.push(
163 Event {
164 trigger: {
165 if trigger.starts_with('\'') && trigger.len() >= 2 && trigger.ends_with('\'') {
167 EventTrigger::Id(trigger[1..trigger.len()-1].to_owned())
168 } else if let Some(seperator_pos) = trigger.find(TRIGGER_SEPARATOR) {
170 let start_time = parse_timestamp(&trigger[..seperator_pos]).map_err(|_| ParseError::new_with_pos("Start timestamp invalid!", (line_index, 0)) )?;
171 let end_time = parse_timestamp(&trigger[seperator_pos + 1 ..]).map_err(|_| ParseError::new_with_pos("End timestamp invalid!", (line_index, seperator_pos + 1 ) ))?;
172 if start_time > end_time {
173 return Err(ParseError::new_with_pos("Start time greater than end time!", (line_index, 0)));
174 }
175 EventTrigger::Time((start_time, end_time))
176 } else {
178 return Err(ParseError::new_with_pos("Invalid trigger format!", (line_index, 0)));
179 }
180 },
181 macro_name: Some(macro_name.to_owned()).filter(|s| !s.is_empty()),
182 note: Some(note.to_owned()).filter(|s| !s.is_empty()),
183 data: data.to_owned(),
184 data_location: (line_index, trigger.len() + macro_name.len() + note.len() + 3 )
185 }
186 );
187 }
188 else {
190 return Err(ParseError::new_with_pos("Invalid events entry!", (line_index, 0)));
191 }
192 }
193 Some(Section::Resources) => {
195 if line.starts_with(RESOURCES_FONT_KEY) {
197 let mut font_tokens = line[RESOURCES_FONT_KEY.len()..].splitn(3, VALUE_SEPARATOR);
199 if let (Some(family), Some(style), Some(data)) = (font_tokens.next(), font_tokens.next(), font_tokens.next()) {
200 self.fonts.insert(
202 FontFace {
203 family: family.to_owned(),
204 style: FontStyle::try_from(style).map_err(|_| ParseError::new_with_pos("Font style invalid!", (line_index, RESOURCES_FONT_KEY.len() + family.len() + 1 ) ))?
205 },
206 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))) )?
207 );
208 } else {
209 return Err(ParseError::new_with_pos("Font family, style and data expected!", (line_index, RESOURCES_FONT_KEY.len())));
210 }
211 }
212 else if line.starts_with(RESOURCES_TEXTURE_KEY) {
214 let mut texture_tokens = line[RESOURCES_TEXTURE_KEY.len()..].splitn(3, VALUE_SEPARATOR);
216 if let (Some(id), Some(data_type), Some(data)) = (texture_tokens.next(), texture_tokens.next(), texture_tokens.next()) {
217 self.textures.insert(
219 id.to_owned(),
220 match data_type {
221 "data" => TextureDataVariant::Raw(
223 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))) )?
224 ),
225 "url" => TextureDataVariant::Url(
227 data.to_owned()
228 ),
229 _ => return Err(ParseError::new_with_pos("Texture data type invalid!", (line_index, RESOURCES_TEXTURE_KEY.len() + id.len() + 1 )))
230 }
231 );
232 } else {
233 return Err(ParseError::new_with_pos("Texture id, data type and data expected!", (line_index, RESOURCES_TEXTURE_KEY.len())));
234 }
235 }
236 else {
238 return Err(ParseError::new_with_pos("Invalid resources entry!", (line_index, 0)));
239 }
240 }
241 None => return Err(ParseError::new_with_pos("No section set!", (line_index, 0)))
243 }
244 }
245 }
246 }
247 Ok(self)
249 }
250}