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
//! # Open operations
//!
//! The open module contains functionality relating to the open command,
//! independent of the CLI.

use std::{io, path::PathBuf};

use chrono::prelude::*;

use crate::{config::Config, entry::Entry, errors::DiaryError};

/// The options available to the open command.
pub struct OpenFileOptions {
    /// The date of the entry to open.
    pub entry_date: Date<Local>,
}

/// Opens a specific diary entry for editing.
///
/// # Arguments
///
/// * `opts` - The options passed by the user at runtime.
/// * `config` - The contents of the config file.
/// * `user_input` - A function of type UserInput. A function that takes a file
/// and adds content to it.
pub fn open(
    opts: &OpenFileOptions,
    config: &Config,
    user_input: UserInput,
) -> Result<(), DiaryError> {
    config.initialised()?;

    let diary_entry = Entry::from_config(config)?;

    let entry_path = diary_entry.get_entry_path(&opts.entry_date);

    if !entry_path.exists() {
        return Err(DiaryError::NoEntry { source: None });
    }

    match user_input(entry_path) {
        Err(e) => Err(DiaryError::IOError(e)), // uncovered.
        _ => Ok(()),
    }
}

type UserInput = fn(P: PathBuf) -> io::Result<()>;

#[cfg(test)]
mod test {
    use std::{
        fs::{self, OpenOptions},
        io::{self, Write},
        path::PathBuf,
    };

    use chrono::{Local, TimeZone};
    use tempfile::tempdir;

    use super::{open, OpenFileOptions};
    use crate::{
        config::Config,
        entry::Entry,
        ops::new::{new, NewOptions},
        utils::editing::test::test_string_getter,
    };

    fn test_user_input(filepath: PathBuf) -> io::Result<()> {
        let mut file = OpenOptions::new().append(true).open(filepath)?;

        let buf = "Test content";

        file.write_all(buf.as_bytes())
    }

    #[test]
    fn open_success() {
        let entry_date = Local.ymd(2021, 11, 6);
        let opts = OpenFileOptions { entry_date };
        let temp_dir = tempdir().unwrap();
        let filepath = temp_dir.path().to_path_buf();

        let config = Config::builder().diary_path(filepath).build();

        let new_opts = NewOptions { open: false };

        new(&new_opts, &config, &entry_date, test_string_getter).unwrap();

        open(&opts, &config, test_user_input).unwrap();

        let diary_file = Entry::from_config(&config).unwrap();

        let entry_path = diary_file.get_entry_path(&entry_date);

        let content = fs::read_to_string(entry_path).unwrap();

        assert!(content.contains("Test content"));
    }

    #[test]
    #[should_panic(expected = "value: NoEntry")]
    fn open_no_entry() {
        let entry_date = Local.ymd(2021, 11, 6);
        let opts = OpenFileOptions { entry_date };
        let temp_dir = tempdir().unwrap();
        let filepath = temp_dir.path().to_path_buf();

        let config = Config::builder().diary_path(filepath).build();

        open(&opts, &config, test_user_input).unwrap();
    }

    #[test]
    #[should_panic(expected = "value: UnInitialised")]
    fn open_bad_config() {
        let entry_date = Local.ymd(2021, 11, 6);
        let opts = OpenFileOptions { entry_date };

        let config = Config::default();

        open(&opts, &config, test_user_input).unwrap();
    }
}