extern crate alloc;
use alloc::borrow::Cow;
use alloc::string::{String, ToString};
use alloc::vec::Vec;
use core::fmt::{Display, Formatter};
use core::str::FromStr;
use crate::engine::pointers::OptionParameterPointer;
use crate::errors::MessageParseError;
use crate::dev_macros::{from_str_parts, impl_message, message_from_impl};
use crate::{parsing, MessageParseErrorKind, OptionReplaceIf};
use super::{pointers, traits};
enum BlankOptionType {
Check,
Spin,
Combo,
Button,
String
}
impl FromStr for BlankOptionType {
type Err = ();
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s {
"check" => Ok(Self::Check),
"spin" => Ok(Self::Spin),
"combo" => Ok(Self::Combo),
"button" => Ok(Self::Button),
"string" => Ok(Self::String),
_ => Err(())
}
}
}
impl Display for BlankOptionType {
fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
f.write_str(match self {
Self::Check => "check",
Self::Spin => "spin",
Self::Combo => "combo",
Self::Button => "button",
Self::String => "string"
})
}
}
#[allow(clippy::module_name_repetitions)]
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum OptionType<'a> {
Check {
default: StdOption<bool>
},
Spin {
default: StdOption<i64>,
min: StdOption<i64>,
max: StdOption<i64>,
},
Combo {
default: StdOption<Cow<'a, str>>,
var: Cow<'a, [Cow<'a, str>]>,
},
Button,
String {
default: StdOption<Cow<'a, str>>,
},
}
impl Display for OptionType<'_> {
fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
f.write_str("type ")?;
match self {
Self::Check { default } => {
f.write_str("check")?;
if let Some(default) = default {
write!(f, " default {default}")?;
}
}
Self::Spin { default, min, max } => {
f.write_str("spin")?;
if let Some(default) = default {
write!(f, " default {default}")?;
}
if let Some(min) = min {
write!(f, " min {min}")?;
}
if let Some(max) = max {
write!(f, " max {max}")?;
}
}
Self::Combo { default, var } => {
f.write_str("combo")?;
if let Some(default) = default {
write!(f, " default {default}")?;
}
for variation in var.iter() {
write!(f, " var {variation}")?;
}
}
Self::Button => f.write_str("button")?,
Self::String { default } => {
f.write_str("string")?;
if let Some(default) = default {
write!(f, " default {default}")?;
}
},
}
Ok(())
}
}
type StdOption<T> = core::option::Option<T>;
#[allow(clippy::module_name_repetitions)]
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct Option<'a> {
pub name: Cow<'a, str>,
pub r#type: OptionType<'a>,
}
impl_message!(Option<'_>);
message_from_impl!(engine Option<'a>);
from_str_parts!(impl Option<'_> for parts -> Result {
let mut name = None::<String>;
let mut r#type = None::<BlankOptionType>;
let mut default = None::<String>;
let mut min = None::<i64>;
let mut max = None::<i64>;
let mut var = Vec::new();
let parameter_fn = |parameter, _, value: &str, parts| {
match parameter {
OptionParameterPointer::Name => name = Some(value.to_string()),
OptionParameterPointer::Type => r#type.replace_if(value.parse().ok()),
OptionParameterPointer::Default => default = Some(value.to_string()),
OptionParameterPointer::Min => min.replace_if(value.parse().ok()),
OptionParameterPointer::Max => max.replace_if(value.parse().ok()),
OptionParameterPointer::Var => var.push(Cow::Owned(value.to_string())),
}
Some(parts)
};
let mut value = String::with_capacity(200);
parsing::apply_parameters(parts, &mut value, parameter_fn);
let Some(name) = name else {
return Err(MessageParseError {
expected: "name",
kind: MessageParseErrorKind::MissingParameters
});
};
let Some(r#type) = r#type else {
return Err(MessageParseError {
expected: "a type parameter; check, spin, combo, button or string",
kind: MessageParseErrorKind::MissingParameters
});
};
let name = Cow::Owned(name);
match r#type {
BlankOptionType::Check => {
Ok(Self { name, r#type: OptionType::Check { default: default.and_then(|d| d.parse().ok()) } })
},
BlankOptionType::Spin => {
Ok(Self { name, r#type: OptionType::Spin {
default: default.and_then(|d| d.parse().ok()),
min,
max
} })
},
BlankOptionType::Combo => {
Ok(Self { name, r#type: OptionType::Combo { default: default.map(Cow::Owned), var: Cow::Owned(var) } })
},
BlankOptionType::Button => Ok(Self { name, r#type: OptionType::Button }),
BlankOptionType::String => {
Ok(Self { name, r#type: OptionType::String { default: default.map(Cow::Owned) } })
},
}
});
impl Display for Option<'_> {
fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
write!(f, "option name {} {}", self.name, self.r#type)
}
}
#[cfg(test)]
mod tests {
use alloc::borrow::Cow;
use alloc::string::ToString;
use crate::dev_macros::{assert_from_str_message, assert_message_to_from_str, assert_message_to_str};
use crate::engine::OptionType;
use super::Option;
#[test]
fn to_from_str_min_max() {
let m = Option {
name: Cow::Borrowed("Skill Level"),
r#type: OptionType::Spin {
default: Some(20),
min: Some(-10),
max: Some(20),
}
};
assert_from_str_message!(
engine
"option name Skill Level type spin type INVALID default 20 min -10 max 20",
Ok(m.clone())
);
assert_message_to_str!(engine m, "option name Skill Level type spin default 20 min -10 max 20");
let m = Option {
name: Cow::Borrowed("Personality"),
r#type: OptionType::String { default: Some(Cow::Borrowed("Aggressive")) }
};
assert_from_str_message!(
engine
"option name Personality type spin type string default Aggressive",
Ok(m.clone())
);
assert_message_to_str!(engine m, "option name Personality type string default Aggressive");
}
#[test]
fn to_from_str_var() {
let var = &[Cow::Borrowed("Foo bar fighter"), Cow::Borrowed("Aggressive p"), Cow::Borrowed("Defensive p"), Cow::Borrowed("Positional"), Cow::Borrowed("Endgame")];
let m = Option {
name: Cow::Borrowed("K Personality"),
r#type: OptionType::Combo {
default: Some(Cow::Borrowed("Default p")),
var: Cow::Borrowed(var),
}
};
let str_in = "option var Foo bar fighter name K Personality type combo default Default p var Aggressive p var Defensive p var Positional var Endgame";
let str_out = "option name K Personality type combo default Default p var Foo bar fighter var Aggressive p var Defensive p var Positional var Endgame";
assert_from_str_message!(
engine
str_in,
Ok(m.clone())
);
assert_message_to_str!(engine m, str_out);
}
#[test]
fn button() {
let m = Option {
name: "Execute order\t 66".into(),
r#type: OptionType::Button
};
assert_from_str_message!(
engine
"option name Execute order\t 66 type button",
Ok(m.clone())
);
assert_message_to_from_str!(
engine
m,
"option name Execute order\t 66 type button"
);
}
}