flashed 0.10.1

A flashcard TUI
Documentation
//! # Decks
//!
//! This module contains types to do with Decks.

use std::{
    collections::HashMap,
    fs::{self, File},
    io,
    path::{Path, PathBuf},
    vec,
};

use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
use serde_json::Error;
use thiserror::Error;

use crate::{CardInner, Card, CardIdx};

/// A deck type for storing cards, their scores and their due dates
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct Deck {
    /// The path of the file or folder of origin of the deck
    pub path: PathBuf,
    /// The cards in the deck
    pub cards: Vec<Card>,
}

impl Deck {
    /// Create a new deck from a given path
    pub fn new_from_path(path: &Path) -> Result<Self, DeckError> {
        if let Some(cards) = Deck::gather_cards(path.to_path_buf())? {
            Ok(Deck {
                path: path.to_path_buf(),
                cards,
            })
        } else {
            Err(DeckError::ContentError { path: path.to_path_buf() })
        }

    }

    /// Recursively searches directories and builds a deck from found cards
    fn gather_cards(path: PathBuf) -> Result<Option<Vec<Card>>, DeckError> {
        if path.is_dir() {
            let mut cards = vec![];
            for ancestor in fs::read_dir(path)? {
                let ancestor_path = ancestor?.path();
                if let Some(mut c) = Deck::gather_cards(ancestor_path)? {
                    cards.append(&mut c);
                }
            }
            Ok(Some(cards))
        } else {
            let mut f = File::open(&path)?;

            match serde_json::from_reader::<&mut File, Vec<CardInner>>(&mut f) {
                Ok(f) => {
                    Ok(Some(f.into_iter()
                        .map(|x| Card {
                            inner: x,
                            path: path.clone(),
                            score: 0,
                            due_date: None,
                        })
                        .collect()))
                },
                Err(e) => {
                    if path.file_stem().unwrap().to_str().unwrap().ends_with(".score") {
                        Ok(None)
                    } else {
                        Err(DeckError::ParseError { path, source: e })
                    }
                },
            }
            
        }
    }

    /// Returns a hashmap of the due dates of each card
    pub fn dues(&self) -> HashMap<CardIdx, Option<DateTime<Utc>>> {
        self.cards
            .iter()
            .enumerate()
            .map(|x| (x.0 as CardIdx, x.1.due_date))
            .collect()
    }

    /// Returns a hashmap of the scores of each card
    pub fn scores(&self) -> HashMap<CardIdx, usize> {
        self.cards
            .iter()
            .enumerate()
            .map(|x| (x.0 as CardIdx, x.1.score))
            .collect()
    }
}

impl From<Vec<CardInner>> for Deck {
    fn from(cards: Vec<CardInner>) -> Self {
        Deck {
            cards: cards
                .iter()
                .map(|x| <CardInner as std::convert::Into<Card>>::into(x.clone()))
                .collect(),
            ..Default::default()
        }
    }
}

impl From<Deck> for Vec<CardInner> {
    fn from(value: Deck) -> Self {
        let mut cards = vec![];
        for card in value.cards {
            cards.push(card.inner);
        }
        cards
    }
}

impl From<Vec<Card>> for Deck {
    fn from(cards: Vec<Card>) -> Self {
        Deck {
            cards,
            ..Default::default()
        }
    }
}

#[derive(Debug, Error)]
pub enum DeckError{
    #[error("IO Error: {source}")]
    IoError { 
        #[from] #[source] source: io::Error
    },
    #[error("Error parsing file {path}: {source}")]
    ParseError {
        path: PathBuf,
        #[source]
        source: Error,
    },
    #[error("Directory {path} contains only score files. How did you acomplish this?")]
    ContentError {
        path: PathBuf
    }
}