1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
use std::collections::HashMap;

use anyhow::Result;
use rhai::Dynamic;
use serde::{Deserialize, Serialize};
use uuid::Uuid;

/// A single key-value pair that represents an element in the set.
#[derive(Serialize, Deserialize)]
pub struct Card {
    /// The prompt the user will be given for this card.
    pub question: String,
    /// The answer this card has (which will be shown to the user).
    pub answer: String,
    /// Whether or not this card has been seen yet in the active test.
    pub seen_in_test: bool,
    /// Whether or not this card has been marked as difficult. Difficult cards are intended to
    /// be identified during the learning process, and the marking of them as such should be
    /// automated.
    pub difficult: bool,
    /// Whether or not this card has been starred. Cards are automatically starred if a user gets
    /// them wrong in a test, and they will be unstarred if the user later gets them right in a test. This
    /// behaviour can be customised with flags.
    pub starred: bool,
    /// Data about this card stored by the current method. This can be serialized and deserialized, but
    /// is completely arbitrary, and different cards may store completely different data here. This should
    /// be passed to and from method scripts with no intervention from Rust.
    pub method_data: Dynamic,
}

/// A slim representation of a card without internal metadata, which will be returned when polling a
/// [`crate::Driver`].
#[derive(Clone)]
pub struct SlimCard {
    /// The question on the card.
    pub question: String,
    /// The answer on the 'other side' of the card.
    pub answer: String,
    /// Whether or not the card has been automatically marked as difficult. Callers may wish to highlight this
    /// to users when a question is displayed, or not.
    pub difficult: bool,
    /// Whether or not the card has been starred, which, likewise, callers may wish to highlight or not when
    /// displaying this card.
    pub starred: bool,
}

/// The different card categories that operations on sets can be classed into.
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[cfg_attr(feature = "cli", derive(clap::ValueEnum))]
pub enum CardType {
    /// All the cards in the set.
    All,
    /// Only cards that have been automatically marked as difficult.
    Difficult,
    /// Only cards that have been automatically starred when the user got them wrong in a test.
    Starred,
}

/// A set of cards with associated data about how learning this set has progressed.
#[derive(Serialize, Deserialize)]
pub struct Set {
    /// The name of the method used on this set. As methods provide their own custom metadata for each card, it
    /// is not generally possible to transition a set from one learning method to another while keeping your
    /// progress, unless a transformer is provided by the methods to do so. This acts as a guard to prevent
    /// the user from accidentally deleting all their hard work!
    pub method: String,
    /// A list of all the cards in the set.
    pub cards: HashMap<Uuid, Card>,
    /// The state of the set in terms of tests. This will be `Some(..)` if there was a previous
    /// test, and the attached string will be the name of the method used. Runs on different targets
    /// will not interfere with each other, and this program is built to support them.
    pub run_state: Option<String>,
    /// Whether or not there is a test currently in progress. Card weightings are calculated with an
    /// internal system in tests, but no internal card metadata will be modified, this is instead used to keep
    /// track of which cards have already been shown to the user.
    ///
    /// Note that, if a test is started on one target, and a later test is begun on a different subset target,
    /// it is possible that the latter will cause the prior to be forgotten about (since this will be set back
    /// to `false` once the active test is finished). This kind of issue does not affect learn mode, because there
    /// is no such thing as a finished learn mode, until all weightings are set to zero, meaning things are kept
    /// track of on a card-by-card basis, unlike in tests.
    pub test_in_progress: bool,
}
impl Set {
    /// Saves this set to the given JSON file, preserving all progress.
    pub fn save(&self) -> Result<String> {
        let json = serde_json::to_string(&self)?;
        Ok(json)
    }
    /// Loads this set from the given JSON.
    pub fn from_json(json: &str) -> Result<Self> {
        let set = serde_json::from_str(json)?;
        Ok(set)
    }
    /// Resets all cards in a learn back to the default metadata values prescribed by the learning method.
    pub(crate) fn reset_learn(&mut self, default_data: Dynamic) {
        for card in self.cards.values_mut() {
            card.method_data = default_data.clone();
        }
    }
    /// Resets all test progress for this set. This is irreversible!
    ///
    /// This will not change whether or not cards are starred.
    pub(crate) fn reset_test(&mut self) {
        for card in self.cards.values_mut() {
            card.seen_in_test = false;
        }
    }
    /// Resets all stars for this set. This is irreversible!
    pub fn reset_stars(&mut self) {
        for card in self.cards.values_mut() {
            card.starred = false;
        }
    }
}