diary/ops/
open.rs

1//! # Open operations
2//!
3//! The open module contains functionality relating to the open command,
4//! independent of the CLI.
5
6use std::{io, path::PathBuf};
7
8use chrono::prelude::*;
9
10use crate::{errors::DiaryError, Diary};
11
12/// The options available to the open command.
13pub struct OpenFileOptions {
14    /// The date of the entry to open.
15    pub entry_date: DateTime<Local>,
16}
17
18/// Opens a specific diary entry for editing.
19///
20/// # Arguments
21///
22/// * `opts` - The options passed by the user at runtime.
23/// * `diary` - Struct representing the diary.
24/// * `user_input` - A function of type UserInput. A function that takes a file
25/// and adds content to it.
26pub fn open(
27    opts: &OpenFileOptions,
28    diary: &Diary,
29    user_input: UserInput,
30) -> Result<(), DiaryError> {
31    let entry_path = diary.get_entry_path(&opts.entry_date);
32
33    if !entry_path.exists() {
34        return Err(DiaryError::NoEntry { source: None });
35    }
36
37    match user_input(entry_path) {
38        Err(e) => Err(DiaryError::IOError(e)), // uncovered.
39        _ => Ok(()),
40    }
41}
42
43type UserInput = fn(P: PathBuf) -> io::Result<()>;
44
45#[cfg(test)]
46mod test {
47    use std::{
48        fs::{self, OpenOptions},
49        io::{self, Write},
50        path::PathBuf,
51    };
52
53    use chrono::{Local, TimeZone};
54
55    use super::{open, OpenFileOptions};
56    use crate::{
57        config::Config,
58        ops::{
59            new::{new, NewOptions},
60            testing,
61        },
62        utils::editing::test::test_string_getter,
63        Diary,
64    };
65
66    fn test_user_input(filepath: PathBuf) -> io::Result<()> {
67        let mut file = OpenOptions::new().append(true).open(filepath)?;
68
69        let buf = "Test content";
70
71        file.write_all(buf.as_bytes())
72    }
73
74    #[test]
75    fn open_success() {
76        let config = testing::temp_config();
77        testing::default_init(config.diary_path());
78        let diary = Diary::from_config(&config).unwrap();
79
80        let new_opts = NewOptions { open: false };
81        let entry_date = Local.with_ymd_and_hms(2021, 11, 6, 0, 0, 0).unwrap();
82
83        new(&new_opts, &diary, &entry_date, test_string_getter).unwrap();
84
85        let opts = OpenFileOptions { entry_date };
86        open(&opts, &diary, test_user_input).unwrap();
87
88        let entry_path = diary.get_entry_path(&entry_date);
89
90        let content = fs::read_to_string(entry_path).unwrap();
91
92        assert!(content.contains("Test content"));
93    }
94
95    #[test]
96    #[should_panic(expected = "value: NoEntry")]
97    fn open_no_entry() {
98        let config = testing::temp_config();
99        testing::default_init(config.diary_path());
100        let diary = Diary::from_config(&config).unwrap();
101
102        let entry_date = Local.with_ymd_and_hms(2021, 11, 6, 0, 0, 0).unwrap();
103        let opts = OpenFileOptions { entry_date };
104
105        open(&opts, &diary, test_user_input).unwrap();
106    }
107
108    #[test]
109    #[should_panic(expected = "value: UnInitialised")]
110    fn open_bad_config() {
111        let config = Config::default();
112        let diary = Diary::from_config(&config).unwrap();
113
114        let entry_date = Local.with_ymd_and_hms(2021, 11, 6, 0, 0, 0).unwrap();
115        let opts = OpenFileOptions { entry_date };
116
117        open(&opts, &diary, test_user_input).unwrap();
118    }
119}