1use std::fs;
4use std::path::PathBuf;
5use std::{
6 fs::{File, ReadDir, create_dir_all, read_dir},
7 io,
8 io::{Read, Write},
9 path::Path,
10};
11
12use dirs::config_dir;
13use log::{error, info};
14
15use crate::config::Config;
16use crate::html_generator::generate_default_css;
17
18pub fn read_input_dir(
27 input_dir: &str,
28 run_recursively: &bool,
29) -> Result<Vec<(String, String)>, io::Error> {
30 if *run_recursively {
31 let mut file_contents: Vec<(String, String)> = Vec::new();
33 let input_dir = Path::new(input_dir);
34 visit_dir(Path::new(input_dir), input_dir, &mut file_contents).map_err(|e| {
35 error!(
36 "Failed to read input directory '{}': {}",
37 input_dir.display(),
38 e
39 );
40 e
41 })?;
42
43 Ok(file_contents)
44 } else {
45 let entries: ReadDir = read_dir(input_dir).map_err(|e| {
46 error!("Failed to read input directory '{input_dir}': {e}");
47 e
48 })?;
49
50 let mut file_contents: Vec<(String, String)> = Vec::new();
52 for entry in entries {
53 let entry = entry?;
54
55 let file_path = entry.path();
56 let file_name = file_path
57 .file_name()
58 .and_then(|s| s.to_str())
59 .ok_or_else(|| {
60 io::Error::new(
61 io::ErrorKind::InvalidData,
62 format!(
63 "Failed to extract file name from path '{}'",
64 file_path.display()
65 ),
66 )
67 })?
68 .to_string();
69
70 if file_path.extension().and_then(|s| s.to_str()) == Some("md") {
71 let contents = read_file(file_path.to_str().unwrap()).map_err(|e| {
72 io::Error::other(format!(
73 "Failed to read file '{}': {}",
74 file_path.display(),
75 e
76 ))
77 })?;
78 file_contents.push((file_name, contents));
79 }
80 }
81
82 Ok(file_contents)
83 }
84}
85
86fn visit_dir(
88 dir: &Path,
89 base: &Path,
90 file_contents: &mut Vec<(String, String)>,
91) -> Result<(), std::io::Error> {
92 for entry in read_dir(dir)? {
93 let entry = entry?;
94 let path = entry.path();
95
96 if path.is_dir() {
97 visit_dir(&path, base, file_contents)?;
98 } else if path.extension().and_then(|s| s.to_str()) == Some("md") {
99 let rel_path = path
100 .strip_prefix(base)
101 .map_err(|e| io::Error::other(format!("Failed to strip base path: {e}")))?
102 .to_string_lossy()
103 .to_string();
104 let contents = read_file(path.to_str().unwrap()).map_err(|e| {
105 io::Error::other(format!("Failed to read file '{}': {}", path.display(), e))
106 })?;
107
108 file_contents.push((rel_path, contents));
109 }
110 }
111
112 Ok(())
113}
114
115pub fn read_file(file_path: &str) -> Result<String, std::io::Error> {
124 let mut md_file: File = File::open(file_path)?;
125
126 let mut contents = String::new();
127 md_file.read_to_string(&mut contents)?;
128
129 Ok(contents)
130}
131
132pub fn write_html_to_file(
142 html: &str,
143 output_dir: &str,
144 input_filepath: &str,
145) -> Result<(), io::Error> {
146 info!("Writing output to directory: {}", output_dir);
147 let output_dir = Path::new(output_dir).join(input_filepath);
148
149 if let Some(parent) = output_dir.parent() {
150 create_dir_all(parent)?;
151 }
152
153 let mut output_file = File::create(&output_dir)?;
154
155 output_file.write_all(html.as_bytes())?;
156
157 info!("HTML written to: {}", output_dir.display());
158 Ok(())
159}
160
161pub fn copy_file_to_output_dir(
174 input_file_path: &str,
175 output_dir: &str,
176 subdir: Option<&str>,
177 base_dir: Option<&str>,
178) -> Result<(), io::Error> {
179 use std::path::PathBuf;
180
181 let abs_input_path = if let Some(base) = base_dir {
182 let input_path = Path::new(input_file_path);
183 if input_path.is_absolute() {
184 input_path.to_path_buf()
185 } else {
186 Path::new(base).join(input_file_path)
187 }
188 } else {
189 PathBuf::from(input_file_path)
190 };
191
192 let file_name = abs_input_path.file_name().ok_or_else(|| {
193 io::Error::new(
194 io::ErrorKind::InvalidInput,
195 format!(
196 "Failed to extract file name from path '{}'",
197 abs_input_path.display()
198 ),
199 )
200 })?;
201
202 let mut output_file_path = PathBuf::from(output_dir);
203 if let Some(sub) = subdir {
204 output_file_path.push(sub);
205 create_dir_all(&output_file_path)?;
206 } else {
207 create_dir_all(&output_file_path)?
208 }
209 output_file_path.push(file_name);
210
211 fs::copy(&abs_input_path, &output_file_path)?;
212
213 Ok(())
214}
215
216pub fn copy_favicon_to_output_dir(
218 input_file_path: &str,
219 output_dir: &str,
220) -> Result<(), io::Error> {
221 copy_file_to_output_dir(input_file_path, output_dir, Some("media"), None)
222}
223
224pub fn copy_image_to_output_dir(
226 input_file_path: &str,
227 output_dir: &str,
228 md_dir: &str,
229) -> Result<(), io::Error> {
230 copy_file_to_output_dir(input_file_path, output_dir, Some("media"), Some(md_dir))
231}
232
233pub fn copy_css_to_output_dir(input_file_path: &str, output_dir: &str) -> Result<(), io::Error> {
235 copy_file_to_output_dir(input_file_path, output_dir, None, None)
236}
237
238pub fn write_default_css_file(output_dir: &str) -> Result<(), io::Error> {
240 let css_content = generate_default_css();
241 let css_file_path = format!("{}/styles.css", output_dir);
242
243 let mut file = File::create(&css_file_path)?;
244
245 file.write_all(css_content.as_bytes())?;
246
247 Ok(())
248}
249
250pub fn get_config_path() -> Result<PathBuf, io::Error> {
254 let mut config_path = config_dir().unwrap_or_else(|| PathBuf::from("."));
255
256 config_path.push("markrs");
257 create_dir_all(&config_path)?;
258 config_path.push("config.toml");
259
260 Ok(config_path)
261}
262
263pub fn does_config_exist() -> Result<bool, io::Error> {
265 let config_path = get_config_path()?;
266
267 let config_exists = config_path.exists();
268 Ok(config_exists)
269}
270
271pub fn write_default_config() -> Result<Config, crate::config::Error> {
274 let config_path = get_config_path()?;
275
276 info!(
277 "Config file does not exist, creating default config at: {}",
278 config_path.display()
279 );
280
281 let mut file = File::create(&config_path)?;
282
283 let default_config = Config::default();
284
285 let default_config_content = toml_edit::ser::to_string_pretty(&default_config)
286 .map_err(crate::config::Error::TomlSerialization)?;
287
288 file.write_all(default_config_content.as_bytes())?;
289
290 info!("Default config file created at: {}", config_path.display());
291
292 Ok(default_config)
293}