use std::{collections::HashMap, fmt::Display, io::Read};
use anyhow::{Context, anyhow};
#[cfg(feature = "schema")]
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use nitro_shared::util::ToInt;
use super::Options;
pub fn parse_options<R: Read>(reader: &mut R) -> anyhow::Result<Options> {
serde_json::from_reader(reader).context("Failed to parse options")
}
#[cfg(test)]
pub fn parse_options_str(string: &str) -> anyhow::Result<Options> {
serde_json::from_str(string).context("Failed to parse options")
}
pub fn read_options_file(
contents: &str,
separator: char,
) -> anyhow::Result<HashMap<String, String>> {
let mut out = HashMap::with_capacity(contents.lines().count());
for (i, line) in contents.lines().enumerate() {
if !line.contains(separator) {
continue;
}
let index = line
.find(separator)
.ok_or_else(|| anyhow!("Options line {i} does not have a separator!"))?;
let (key, value) = line.split_at(index);
out.insert(key.to_string(), String::from(&value[1..]));
}
Ok(out)
}
#[derive(Deserialize, Serialize, Debug, Clone, PartialEq)]
#[cfg_attr(feature = "schema", derive(JsonSchema))]
#[serde(untagged)]
pub enum EnumOrNumber<T> {
Enum(T),
Num(i32),
}
impl<T: Display> Display for EnumOrNumber<T> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"{}",
match self {
Self::Enum(e) => e.to_string(),
Self::Num(num) => num.to_string(),
}
)
}
}
impl<T: ToInt> ToInt for EnumOrNumber<T> {
fn to_int(&self) -> i32 {
match self {
Self::Enum(e) => e.to_int(),
Self::Num(num) => *num,
}
}
}
#[derive(Deserialize, Serialize, Debug, Clone, PartialEq)]
#[cfg_attr(feature = "schema", derive(JsonSchema))]
#[serde(untagged)]
pub enum EnumOrString<T> {
Enum(T),
String(String),
}
impl<T: Display> Display for EnumOrString<T> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"{}",
match self {
Self::Enum(e) => e.to_string(),
Self::String(string) => string.clone(),
}
)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_default() {
parse_options_str("{}").unwrap();
}
#[test]
fn test_enums() {
#[derive(Clone)]
enum TestEnum {
Foo,
Bar,
}
impl Display for TestEnum {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"{}",
match self {
Self::Foo => "foo",
Self::Bar => "bar",
}
)
}
}
impl ToInt for TestEnum {
fn to_int(&self) -> i32 {
self.clone() as i32
}
}
assert_eq!(EnumOrNumber::Enum(TestEnum::Foo).to_int(), 0);
assert_eq!(EnumOrNumber::Enum(TestEnum::Bar).to_string(), "bar");
assert_eq!(EnumOrNumber::Enum(TestEnum::Foo).to_int().to_string(), "0");
assert_eq!(EnumOrString::Enum(TestEnum::Bar).to_string(), "bar");
}
#[test]
fn test_read_options_file() -> anyhow::Result<()> {
let text = r#"
fov=12
hello=world
yes=false
"#;
let options = read_options_file(text, '=')?;
assert_eq!(options.get("fov").unwrap(), "12");
assert_eq!(options.get("hello").unwrap(), "world");
assert_eq!(options.get("yes").unwrap(), "false");
Ok(())
}
}