use std::{
fmt::{Debug, Display},
path::{Path, PathBuf},
str::FromStr,
};
use either::Either;
use rust_decimal_macros::dec;
use thiserror::Error;
pub type Integer = i32;
pub const LATEST_VERSION: Version = 14;
pub const MIN_VERSION: Version = 3;
pub type Version = u8;
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct Position {
pub x: Decimal,
pub y: Decimal,
}
impl Default for Position {
fn default() -> Self {
Self {
x: dec!(256).into(),
y: dec!(192).into(),
}
}
}
#[derive(Debug)]
pub struct Error<E> {
line_index: usize,
error: E,
}
impl<E: std::error::Error + 'static> std::error::Error for Error<E> {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
self.error.source()
}
}
impl<E> Error<E> {
pub fn display_error_with_line(&self, file_input: &str) -> String
where
E: std::fmt::Display,
{
let line = file_input.lines().nth(self.line_index).unwrap_or_default();
format!("Line {}: {}, {}", self.line_index + 1, line, self.error)
}
pub fn new(error: E, line_index: usize) -> Self {
Self { line_index, error }
}
pub fn new_into<E2>(error: E, line_index: usize) -> Error<E2>
where
E2: From<E>,
{
Error {
line_index,
error: error.into(),
}
}
pub fn new_from_result<T>(result: Result<T, E>, line_index: usize) -> Result<T, Error<E>> {
result.map_err(|err| Error {
line_index,
error: err,
})
}
pub fn new_from_result_into<T, E2>(
result: Result<T, E>,
line_index: usize,
) -> Result<T, Error<E2>>
where
E2: From<E>,
{
result.map_err(|err| Error {
line_index,
error: err.into(),
})
}
pub fn error_into<E2>(self) -> Error<E2>
where
E2: From<E>,
{
Error {
line_index: self.line_index,
error: self.error.into(),
}
}
pub fn error_result_into<T, E2>(result: Result<T, Error<E>>) -> Result<T, Error<E2>>
where
E2: From<E>,
{
result.map_err(|err| Error {
line_index: err.line_index,
error: err.error.into(),
})
}
pub fn processing_line<T, E2>(
result: Result<T, Error<E>>,
line_index: usize,
) -> Result<T, Error<E2>>
where
E2: From<E>,
{
result.map_err(|err| Error {
line_index: err.line_index + line_index,
error: err.error.into(),
})
}
pub fn line_index(&self) -> usize {
self.line_index
}
pub fn error(&self) -> &E {
&self.error
}
}
impl<E> Display for Error<E>
where
E: std::fmt::Display,
{
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "Line {}, {}", self.line_index + 1, self.error)
}
}
impl<E> From<E> for Error<E> {
fn from(error: E) -> Self {
Self {
line_index: 0,
error,
}
}
}
pub trait VersionedToString {
fn to_string(&self, version: Version) -> Option<String>;
}
pub trait VersionedFromStr: Sized {
type Err;
fn from_str(s: &str, version: Version) -> std::result::Result<Option<Self>, Self::Err>;
}
pub trait VersionedDefault: Sized {
fn default(version: Version) -> Option<Self>;
}
#[derive(Clone, Debug, Hash, PartialEq, Eq)]
pub struct FilePath(PathBuf);
impl FilePath {
pub fn get(&self) -> &Path {
&self.0
}
pub fn set<P>(&mut self, path: P)
where
P: AsRef<Path>,
{
let path = path.as_ref().to_owned();
self.0 = path;
}
}
impl VersionedToString for FilePath {
fn to_string(&self, _: Version) -> Option<String> {
let quotes = {
let path = self.0.to_string_lossy();
path.contains(' ') && !(path.starts_with('"') && path.ends_with('"'))
};
let path = self.0.display();
let path = if quotes {
format!("\"{path}\"")
} else {
path.to_string()
};
Some(path)
}
}
impl<P: AsRef<Path>> From<P> for FilePath {
fn from(path: P) -> Self {
let path = path.as_ref().to_owned();
FilePath(path)
}
}
#[derive(Debug, Error)]
#[error("Invalid repr value")]
pub struct InvalidRepr;
pub trait VersionedFromRepr: Sized {
fn from_repr(repr: usize, version: Version) -> Result<Option<Self>, InvalidRepr>;
}
pub trait VersionedFrom<T>: Sized {
fn from(value: T, version: Version) -> Option<Self>;
}
pub trait VersionedTryFrom<T>: Sized {
type Error;
fn try_from(value: T, version: Version) -> Result<Option<Self>, Self::Error>;
}
#[derive(Debug, Hash, Clone, PartialEq, Eq)]
pub struct Decimal(Either<rust_decimal::Decimal, String>);
impl Decimal {
pub fn new(value: rust_decimal::Decimal) -> Self {
Self(Either::Left(value))
}
pub fn new_from_str(value: &str) -> Self {
Self(Either::Right(value.to_owned()))
}
pub fn try_make_decimal(&mut self) -> Result<(), rust_decimal::Error> {
if let Either::Right(value) = &mut self.0 {
let value = rust_decimal::Decimal::from_str(value)?;
self.0 = Either::Left(value);
}
Ok(())
}
pub fn get(&self) -> &Either<rust_decimal::Decimal, String> {
&self.0
}
pub fn get_mut(&mut self) -> &mut Either<rust_decimal::Decimal, String> {
&mut self.0
}
}
impl FromStr for Decimal {
type Err = ();
fn from_str(s: &str) -> Result<Self, Self::Err> {
Ok(Decimal::from(s))
}
}
impl Display for Decimal {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.0)
}
}
impl From<rust_decimal::Decimal> for Decimal {
fn from(value: rust_decimal::Decimal) -> Self {
Self(Either::Left(value))
}
}
impl From<i32> for Decimal {
fn from(value: i32) -> Self {
Self(Either::Left(rust_decimal::Decimal::from(value)))
}
}
impl From<&str> for Decimal {
fn from(value: &str) -> Self {
let value = match value.parse() {
Ok(value) => Either::Left(value),
Err(_) => Either::Right(value.to_owned()),
};
Self(value)
}
}
impl Default for Decimal {
fn default() -> Self {
Self(Either::Left(rust_decimal::Decimal::default()))
}
}