use std::{
io::Read,
path::{Path, PathBuf},
};
use regex::Regex;
use serde::Deserialize;
use crate::{Error, ResourceReader};
#[derive(Deserialize, PartialEq, Clone, Debug)]
pub struct World {
#[serde(skip_deserializing)]
pub source: PathBuf,
#[serde(skip_serializing_if = "Vec::is_empty", default)]
pub maps: Vec<WorldMap>,
#[serde(skip_serializing_if = "Vec::is_empty", default)]
pub patterns: Vec<WorldPattern>,
}
impl World {
pub fn match_path(&self, path: impl AsRef<Path>) -> Result<WorldMap, Error> {
let path_str = path.as_ref().to_str().expect("obtaining valid UTF-8 path");
for pattern in self.patterns.iter() {
match pattern.match_path_impl(path_str) {
Ok(world_map) => return Ok(world_map),
Err(Error::NoMatchFound { .. }) => continue,
Err(err) => return Err(err),
}
}
Err(Error::NoMatchFound {
path: path_str.to_owned(),
})
}
pub fn match_paths<P: AsRef<Path>>(&self, paths: &[P]) -> Vec<Result<WorldMap, Error>> {
paths
.into_iter()
.map(|path| self.match_path(path))
.collect()
}
}
#[derive(Deserialize, PartialEq, Clone, Debug)]
pub struct WorldMap {
#[serde(rename = "fileName")]
pub filename: String,
pub x: i32,
pub y: i32,
pub width: Option<i32>,
pub height: Option<i32>,
}
#[derive(Deserialize, Clone, Debug)]
#[serde(rename_all = "camelCase")]
pub struct WorldPattern {
#[serde(with = "serde_regex")]
pub regexp: Regex,
pub multiplier_x: i32,
pub multiplier_y: i32,
pub offset_x: i32,
pub offset_y: i32,
}
impl PartialEq for WorldPattern {
fn eq(&self, other: &Self) -> bool {
self.multiplier_x == other.multiplier_x
&& self.multiplier_y == other.multiplier_y
&& self.offset_x == other.offset_x
&& self.offset_y == other.offset_y
&& self.regexp.to_string() == other.regexp.to_string()
}
}
impl WorldPattern {
pub fn match_path(&self, path: impl AsRef<Path>) -> Result<WorldMap, Error> {
let path_str = path.as_ref().to_str().expect("obtaining valid UTF-8 path");
self.match_path_impl(path_str)
}
pub(crate) fn match_path_impl(&self, path: &str) -> Result<WorldMap, Error> {
let captures = match self.regexp.captures(path) {
Some(captures) => captures,
None => {
return Err(Error::NoMatchFound {
path: path.to_owned(),
})
}
};
let x = match captures.get(1) {
Some(x) => x.as_str().parse::<i32>().unwrap(),
None => {
return Err(Error::NoMatchFound {
path: path.to_owned(),
})
}
};
let y = match captures.get(2) {
Some(y) => y.as_str().parse::<i32>().unwrap(),
None => {
return Err(Error::NoMatchFound {
path: path.to_owned(),
})
}
};
let x = x
.checked_mul(self.multiplier_x)
.ok_or(Error::RangeError(
"Capture x * multiplierX causes overflow".to_string(),
))?
.checked_add(self.offset_x)
.ok_or(Error::RangeError(
"Capture x * multiplierX + offsetX causes overflow".to_string(),
))?;
let y = y
.checked_mul(self.multiplier_y)
.ok_or(Error::RangeError(
"Capture y * multiplierY causes overflow".to_string(),
))?
.checked_add(self.offset_y)
.ok_or(Error::RangeError(
"Capture y * multiplierY + offsetY causes overflow".to_string(),
))?;
Ok(WorldMap {
filename: path.to_owned(),
x,
y,
width: None,
height: None,
})
}
}
pub(crate) fn parse_world(
world_path: &Path,
reader: &mut impl ResourceReader,
) -> Result<World, Error> {
let mut path = reader
.read_from(&world_path)
.map_err(|err| Error::ResourceLoadingError {
path: world_path.to_owned(),
err: Box::new(err),
})?;
let mut world_string = String::new();
path.read_to_string(&mut world_string)
.map_err(|err| Error::ResourceLoadingError {
path: world_path.to_owned(),
err: Box::new(err),
})?;
let mut world: World =
serde_json::from_str(&world_string).map_err(|err| Error::JsonDecodingError(err))?;
world.source = world_path.to_owned();
Ok(world)
}