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
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
#![feature(uniform_paths)]

mod cfg;
mod data;
mod db;

use cfg::Config;
pub use data::{Id, Item};
use db::{Database, HashMapDb, PersistentDatabase};
use failure::Error;
use std::collections::HashMap;
use std::env;
use std::path::{Path, PathBuf};

const CONFIG_PATH: &str = ".skribe";
const SKRIBE_HOME: &str = "SKRIBE_HOME";

#[derive(Debug)]
pub struct Skribe {
    items: HashMapDb<Id, Item>,
    config: Config,
}

impl Skribe {
    pub fn new() -> Skribe {
        let mut items = HashMapDb::new();
        items.load();
        Skribe {
            items: items,
            config: Config::default(),
        }
    }

    pub fn get(&self, id: &Id) -> Option<&Item> {
        self.items.get(id)
    }

    pub fn get_mut(&mut self, id: &Id) -> Option<&mut Item> {
        self.items.get_mut(id)
    }

    pub fn get_items(&self) -> &HashMap<Id, Item> {
        &self.items.get_all()
    }

    // TODO decide to use instance method instead
    pub fn create_item() -> Item {
        Item::default()
    }

    pub fn save_item(&mut self, item: Item) -> Option<Item> {
        self.items.insert(item.id, item)
    }

    pub fn delete_item(&mut self, id: &Id) -> Option<Item> {
        self.items.remove(id)
    }

    pub fn init() -> Result<Self, Error> {
        let config = Config::init(Self::get_config_path())?;
        let items: HashMapDb<Id, Item> = HashMapDb::init();
        Ok(Self { config, items })
    }

    fn get_config_path() -> PathBuf {
        match env::var(SKRIBE_HOME) {
            Ok(path) => PathBuf::from(path),
            Err(_) => {
                let home = dirs::home_dir().unwrap_or(PathBuf::from("."));
                home.join(Path::new(CONFIG_PATH))
            }
        }
    }

    pub fn get_home(&self) -> &PathBuf {
        &self.config.path
    }
}

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

    #[test]
    fn crud_item() {
        let mut skribe = Skribe::new();
        let item = Skribe::create_item();
        let base_id = item.id;
        let cloned = item.clone();

        // Create
        {
            assert!(
                skribe.get(&base_id).is_none(),
                "There is nothing in the storage at the beginning"
            );

            skribe.save_item(item);
            assert_eq!(
                skribe.get(&base_id).unwrap(),
                &cloned,
                "The new item is saved successfully"
            );
        }

        // Read
        {
            // At the moment there must be only one item
            for (id, item) in skribe.get_items() {
                assert_eq!(&base_id, id);
                assert_eq!(&cloned, item);
            }
        }

        // Update
        {
            {
                let mut editable = skribe.get_mut(&base_id).unwrap();
                editable.title = String::from("Edited");
            }

            assert_eq!(
                skribe.get(&base_id).unwrap().title,
                String::from("Edited"),
                "The item was changed successfully"
            );
        }

        // Delete
        {
            let another_cloned = skribe.get(&base_id).unwrap().clone();
            let deleted = skribe.delete_item(&base_id).unwrap();
            assert_eq!(another_cloned, deleted);
            assert!(skribe.get(&base_id).is_none());
        }
    }
}