about summary refs log blame commit diff
path: root/src/messages.rs
blob: 948787f1393bbead3b7cabd60daa00a3c5e769a4 (plain) (tree)






































































































































































                                                                                  

                                                                         




                                                                            







                                                  
use rand::seq::SliceRandom;
use rand::Rng;
use serde::de::MapAccess;
use serde::de::SeqAccess;
use serde::de::Visitor;
use std::collections::HashMap;
use std::fmt;
use std::marker::PhantomData;

#[derive(Deserialize, Debug, PartialEq, Eq)]
#[serde(untagged)]
enum Message<'a> {
    Single(&'a str),
    Choice(Vec<&'a str>),
}

impl<'a> Message<'a> {
    fn resolve<R: Rng + ?Sized>(&self, rng: &mut R) -> Option<&'a str> {
        use Message::*;
        match self {
            Single(msg) => Some(*msg),
            Choice(msgs) => msgs.choose(rng).map(|msg| *msg),
        }
    }
}

#[derive(Debug, PartialEq, Eq)]
enum NestedMap<'a> {
    Direct(Message<'a>),
    Nested(HashMap<&'a str, NestedMap<'a>>),
}

impl<'a> NestedMap<'a> {
    fn lookup(&'a self, path: &str) -> Option<&'a Message<'a>> {
        use NestedMap::*;
        let leaf =
            path.split(".")
                .fold(Some(self), |current, key| match current {
                    Some(Nested(m)) => m.get(key),
                    _ => None,
                });
        match leaf {
            Some(Direct(msg)) => Some(msg),
            _ => None,
        }
    }
}

struct NestedMapVisitor<'a> {
    marker: PhantomData<fn() -> NestedMap<'a>>,
}

impl<'a> NestedMapVisitor<'a> {
    fn new() -> Self {
        NestedMapVisitor {
            marker: PhantomData,
        }
    }
}

impl<'de> Visitor<'de> for NestedMapVisitor<'de> {
    type Value = NestedMap<'de>;

    fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
        formatter.write_str(
            "A message, a list of messages, or a nested map of messages",
        )
    }

    fn visit_borrowed_str<E>(self, v: &'de str) -> Result<Self::Value, E> {
        Ok(NestedMap::Direct(Message::Single(v)))
    }

    fn visit_seq<A>(self, mut seq: A) -> Result<Self::Value, A::Error>
    where
        A: SeqAccess<'de>,
    {
        let mut choices = Vec::with_capacity(seq.size_hint().unwrap_or(0));
        while let Some(choice) = seq.next_element()? {
            choices.push(choice);
        }
        Ok(NestedMap::Direct(Message::Choice(choices)))
    }

    fn visit_map<A>(self, mut map: A) -> Result<Self::Value, A::Error>
    where
        A: MapAccess<'de>,
    {
        let mut nested = HashMap::with_capacity(map.size_hint().unwrap_or(0));
        while let Some((k, v)) = map.next_entry()? {
            nested.insert(k, v);
        }
        Ok(NestedMap::Nested(nested))
    }
}

impl<'de> serde::Deserialize<'de> for NestedMap<'de> {
    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
    where
        D: serde::Deserializer<'de>,
    {
        deserializer.deserialize_any(NestedMapVisitor::new())
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_deserialize_nested_map() {
        let src = r#"
[global]
hello = "Hello World!"

[foo.bar]
single = "Single"
choice = ["Say this", "Or this"]
"#;
        let result = toml::from_str(src);
        assert_eq!(
            result,
            Ok(NestedMap::Nested(hashmap! {
                "global" => NestedMap::Nested(hashmap!{
                    "hello" => NestedMap::Direct(Message::Single("Hello World!")),
                }),
                "foo" => NestedMap::Nested(hashmap!{
                    "bar" => NestedMap::Nested(hashmap!{
                        "single" => NestedMap::Direct(Message::Single("Single")),
                        "choice" => NestedMap::Direct(Message::Choice(
                            vec!["Say this", "Or this"]
                        ))
                    })
                })
            }))
        )
    }

    #[test]
    fn test_lookup() {
        let map: NestedMap<'static> = toml::from_str(
            r#"
[global]
hello = "Hello World!"

[foo.bar]
single = "Single"
choice = ["Say this", "Or this"]
"#,
        )
        .unwrap();

        assert_eq!(
            map.lookup("global.hello"),
            Some(&Message::Single("Hello World!"))
        );
        assert_eq!(
            map.lookup("foo.bar.single"),
            Some(&Message::Single("Single"))
        );
        assert_eq!(
            map.lookup("foo.bar.choice"),
            Some(&Message::Choice(vec!["Say this", "Or this"]))
        );
    }
}

static_cfg! {
    static ref MESSAGES: NestedMap<'static> = toml_file("messages.toml");
}

/// Look up a game message based on the given (dot-separated) name, with the
/// given random generator used to select from choice-based messages
pub fn message<R: Rng + ?Sized>(name: &str, rng: &mut R) -> &'static str {
    MESSAGES
        .lookup(name)
        .and_then(|msg| msg.resolve(rng))
        .unwrap_or_else(|| {
            error!("Message not found: {}", name);
            "Message not found"
        })
}