ap_manual 0.2.0

A rust package to cannonically interact with manual AP worlds
Documentation
//! This crate exists for the purposes of creating, editing, and reading manual AP worlds
//! # Features
//! ## file_io
//! enables reading and writing apworlds either from files or readers
//! ## item_flags
//! enables configuring item details using flags
use serde::{Deserialize, Serialize};

pub mod category;
pub mod game;
pub mod item;
pub mod location;
pub mod region;
pub mod meta;
pub mod options;
#[cfg(feature="file_io")]
pub mod apworld_writer;
#[derive(Debug,Clone,Serialize,Deserialize,PartialEq,Eq)]
#[serde(untagged)]
/// A cannonical representation of a json value which is allowed to be T or U
pub enum Either<T,U>{
    Left(T),
    Right(U)
}
impl<T: Default,U> Default for Either<T,U>{
    fn default() -> Self {
        Either::Left(T::default())
    }
}
/// A cannonical representation of a value that can be represented as either a single T or a [T]
#[derive(Debug,PartialEq, Eq,Clone)]
pub struct  ItemOrList<T>{
    pub inner: Vec<T>
}
impl<T: Serialize> Serialize for ItemOrList<T>{
    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
        where
            S: serde::Serializer {
        if self.inner.len()==1{
            self.inner[0].serialize(serializer)
        } else {
            self.inner.serialize(serializer)
        }
    }
}

impl<'de,T: Deserialize<'de>> Deserialize<'de> for ItemOrList<T>{
    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
        where
            D: serde::Deserializer<'de> {
            #[derive(Debug,PartialEq,Clone,Serialize,Deserialize)]
            #[serde(untagged)]
            enum CITemOrList<V>{
                Item(V),
                List(Vec<V>)
            }
            let a: CITemOrList<T>= CITemOrList::deserialize(deserializer)?;
            match a{
                CITemOrList::Item(v)=>Ok(ItemOrList{inner: vec![v]}),
                CITemOrList::List(v)=>Ok(ItemOrList { inner: v })
            }
        }
}
impl<T> Default for ItemOrList<T>{
    fn default() -> Self {
        Self{inner: vec![]}
    }
}
impl<T> ItemOrList<T>{
    fn is_empty(&self)->bool{
        self.inner.is_empty()
    }
}
impl<T> ItemOrList<T>{
    pub fn iter<'a>(&'a self)->std::slice::Iter<'a,T>{
        self.inner.iter()
    }
    /// add an item to the collection
    pub fn push(&mut self, item: T){
        self.inner.push(item);
    }
    /// get the idx'th item from the collection, if it exists
    pub fn get(&self,idx: usize)->Option<&T>{
        self.inner.get(idx)
    }
    /// remove the idx'th item from the collection, returning it if it exists
    pub fn remove(&mut self, idx:usize)->Option<T>{
        if idx<self.inner.len(){
            Some(self.inner.remove(idx))
        } else {
            None
        }
    }
    /// get the number of items in the collection
    pub fn len(&self)->usize{
        self.inner.len()
    }
    /// construct the collection from a singular item
    pub fn from_item(item: T)->Self{
        Self { inner: vec![item] }
    }
    /// construct the collection from a list
    pub fn from_list(ls: Vec<T>)->Self{
        Self {inner: ls}
    }
}
#[derive(Debug,Serialize,Deserialize,PartialEq, Eq,Clone)]
#[serde(untagged)]
#[serde(expecting="boolean or integer")]
///A cannonical representation of a value which can be a Bool or Int. Usually used to represent an item quantity
///Definitionally equal to Either<bool,u32>
pub enum BoolInt{
    Bool(bool),
    Int(u32)
}
impl Default for BoolInt{
    fn default() -> Self {
        Self::Bool(false)
    }
}
impl BoolInt{
    pub fn is_false_or_zero(&self)->bool{
        match self{
            BoolInt::Bool(v)=>!v,
            BoolInt::Int(v)=>*v==0
        }
    }
}
fn bool_is_false(b: &bool)->bool{
    !*b
}
fn bool_is_true(b: &bool)->bool{
    *b
}
#[derive(Debug)]
/// A cannonical representation of a manual ap world
pub struct APWorld{
    pub category: category::Categories,
    pub game: game::Game,
    pub items: Vec<item::Item>,
    pub locations: Vec<location::Location>,
    pub meta: meta::Meta,
    pub options: options::Options,
    pub regions: region::Regions
}
impl APWorld{
    pub fn simple(g: game::Game)->Self{
        Self { category: category::Categories::default(),
            game: g, items: Vec::default(), locations: Vec::new(), meta: meta::Meta::default(),
            options: options::Options::default(), regions: region::Regions::default() }
    }
    pub fn with_category(mut self,name: String,c: category::Category)->Self{
        self.category.add(name, c);
        self
    }
    pub fn with_item(mut self,item: item::Item)->Self{
        self.items.push(item);
        self
    }
    pub fn with_location(mut self,location: location::Location)->Self{
        self.locations.push(location);
        self
    }
    pub fn with_region(self,name: String,r: region::Region)->Self{
        Self { regions:self.regions.with_region(name, r),..self}
    }
}
#[cfg(test)]
mod tests {
    use std::collections::HashMap;

    use crate::{region::Region};

    use super::*;
    #[test]
    fn category_test(){

    }
    #[test]
    fn game_test(){
        let gt = "{\"game\": \"CelesteStrawberryJam\",\"creator\": \"Gregovin\",\"filler_item_name\": \"Filler\",\"starting_items\": []}";
        let tst: game::Game = serde_json::from_str(gt).unwrap();
        assert_eq!(&tst.game,"CelesteStrawberryJam");
        assert_eq!(&tst.creator,"Gregovin");
        assert_eq!(&tst.filler_item_name,"Filler");
        assert_eq!(tst.death_link,false);
        assert_eq!(tst.starting_index,1);
        assert_eq!(tst.starting_items,vec![]);
    } 
    #[test]
    fn item_test(){
        let it = "{\"count\": 1,\"name\": \"Forest Path Access\",\"category\": [\"Beginner\",\"Access\"],\"progression\": true}";
        let tst: item::Item = serde_json::from_str(it).unwrap();
        assert_eq!(&tst.name,"Forest Path Access");
        assert_eq!(*tst.count,1);
        assert_eq!(tst.category,ItemOrList::from_list(vec![String::from("Beginner"),String::from("Access")]));
        assert!(tst.progression);
        assert_eq!(tst.trap,false);
        assert_eq!(tst.progression_skip_balancing,false);
        assert_eq!(tst.classification_count,HashMap::new());
        assert_eq!(tst.value,HashMap::new());
    }
    #[test]
    fn locations_test(){
        let it = "{\"name\": \"Forest Path Strawberry 1\",\"region\": \"Default\",\"category\": [\"Beginner\",\"Forest Path\",\"Strawberry\"],\"requires\": \"|Forest Path Access|\"}";
        let tst: location::Location = serde_json::from_str(it).unwrap();
        assert_eq!(&tst.name,"Forest Path Strawberry 1");
        assert_eq!(tst.region,Some("Default".to_string()));
        assert_eq!(tst.category,ItemOrList{ inner: vec!["Beginner".to_string(),"Forest Path".to_string(),"Strawberry".to_string()]});
        assert_eq!(tst.requires,ItemOrList{inner:vec!["|Forest Path Access|".to_owned()]});
    }
    #[test]
    fn region_test(){
        let it="{\"Default\": {\"starting\": true,\"connects_to\": [],\"requires\": []}}";
        let tst: region::Regions=serde_json::from_str(it).unwrap();

        assert_eq!(tst.regions.get("Default").cloned(),Some(
            Region{
                starting: true,
                requires: ItemOrList::from_list(vec![]),
                connects_to: vec![],
                exit_requires: ItemOrList::default(),
                entrance_requires: ItemOrList::default()
            }
        ));
    }
}