1use std::{fs::File, io::Write};
6
7use chrono::prelude::*;
8
9use crate::{errors::DiaryError, utils::editing, Diary, EntryContent};
10
11pub struct AddOptions {
13 pub tag: Option<String>,
15 pub content: Option<String>,
17}
18
19fn add_content(mut file: File, content: String, tag: Option<String>) -> Result<(), DiaryError> {
31 if content.is_empty() {
32 return Err(DiaryError::NoContent);
33 }
34
35 if let Some(tag) = tag {
36 file.write_all(tag.as_bytes())?;
37 }
38 editing::add_user_content_to_file(&mut file, content)?;
39 Ok(())
40}
41
42pub fn add(
55 opts: &AddOptions,
56 diary: &Diary,
57 date: &DateTime<Local>,
58 string_getter: editing::StringGetter,
59) -> Result<(), DiaryError> {
60 let file = match diary.get_entry_file(date) {
61 Ok(file) => file,
62 Err(e) if e.kind() == std::io::ErrorKind::NotFound => {
63 return Err(DiaryError::NoEntry { source: Some(e) })
64 }
65 Err(e) => return Err(e.into()), };
67
68 let content = match &opts.content {
69 Some(content) => content.to_owned() + "\n",
70 None => string_getter("".to_owned())?,
71 };
72
73 let tag_result = opts
74 .tag
75 .as_ref()
76 .map(|tag| diary.file_type().tag(tag.to_string()));
77
78 add_content(file, content, tag_result)
79}
80
81#[cfg(test)]
82mod test {
83 use std::fs;
84
85 use chrono::{Local, TimeZone};
86
87 use crate::{
88 ops::{
89 add::{add, AddOptions},
90 testing,
91 },
92 utils::editing::test::{test_empty_string_getter, test_string_getter},
93 Diary,
94 };
95
96 #[test]
97 fn quick_add() {
98 let config = testing::temp_config();
99
100 testing::default_init(config.diary_path());
101
102 let entry_date = Local.with_ymd_and_hms(2021, 11, 6, 0, 0, 0).unwrap();
103 testing::new_entry(&config, &entry_date);
104
105 let diary = Diary::from_config(&config).unwrap();
106
107 let opts = AddOptions {
108 tag: None,
109 content: Some("testing quick add".to_owned()),
110 };
111 add(&opts, &diary, &entry_date, test_string_getter).unwrap();
112
113 let diary_file = Diary::from_config(&config).unwrap();
114
115 let entry_path = diary_file.get_entry_path(&entry_date);
116
117 let content = fs::read_to_string(entry_path).unwrap();
118
119 assert!(content.contains("testing quick add"));
120 }
121
122 #[test]
123 fn add_no_tag() {
124 let config = testing::temp_config();
125
126 testing::default_init(config.diary_path());
127
128 let entry_date = Local.with_ymd_and_hms(2021, 11, 6, 0, 0, 0).unwrap();
129 testing::new_entry(&config, &entry_date);
130
131 let diary = Diary::from_config(&config).unwrap();
132
133 let opts = AddOptions {
134 tag: None,
135 content: None,
136 };
137 add(&opts, &diary, &entry_date, test_string_getter).unwrap();
138
139 let diary_file = Diary::from_config(&config).unwrap();
140
141 let entry_path = diary_file.get_entry_path(&entry_date);
142
143 let content = fs::read_to_string(entry_path).unwrap();
144
145 assert!(content.contains("Test content"));
146 assert!(!content.contains("\n\n\n"))
147 }
148
149 #[test]
150 fn add_with_tag() {
151 let config = testing::temp_config();
152 testing::default_init(config.diary_path());
153
154 let entry_date = Local.with_ymd_and_hms(2021, 11, 6, 0, 0, 0).unwrap();
155 testing::new_entry(&config, &entry_date);
156
157 let diary = Diary::from_config(&config).unwrap();
158 let opts = AddOptions {
159 tag: Some("Tag".to_owned()),
160 content: None,
161 };
162 add(&opts, &diary, &entry_date, test_string_getter).unwrap();
163
164 let entry_path = diary.get_entry_path(&entry_date);
165
166 let content = fs::read_to_string(entry_path).unwrap();
167
168 assert!(content.contains("Test content"));
169 assert!(content.contains("Tag"));
170 }
171
172 #[test]
173 #[should_panic(expected = "value: NoContent")]
174 fn add_empty_string() {
175 let config = testing::temp_config();
176 testing::default_init(config.diary_path());
177
178 let entry_date = Local.with_ymd_and_hms(2021, 11, 6, 0, 0, 0).unwrap();
179 testing::new_entry(&config, &entry_date);
180
181 let diary = Diary::from_config(&config).unwrap();
182 let opts = AddOptions {
183 tag: Some("Tag".to_owned()),
184 content: None,
185 };
186 add(&opts, &diary, &entry_date, test_empty_string_getter).unwrap();
187 }
188
189 #[test]
190 #[should_panic(expected = "value: NoEntry")]
191 fn add_to_nonexistent_file() {
192 let config = testing::temp_config();
193 testing::default_init(config.diary_path());
194
195 let diary = Diary::from_config(&config).unwrap();
196
197 let entry_date = Local.with_ymd_and_hms(2021, 11, 6, 0, 0, 0).unwrap();
198 let opts = AddOptions {
199 tag: Some("Tag".to_owned()),
200 content: None,
201 };
202 add(&opts, &diary, &entry_date, test_string_getter).unwrap();
203 }
204}