1use thiserror::Error;
2
3use crate::{
4 frontmatter,
5 siena::{Record, RecordData, StoreProvider},
6 utils::str_ends_with_any,
7};
8use std::fs;
9use std::{collections::HashMap, fs::DirEntry};
10
11#[derive(Error, Debug)]
12pub enum ParseError {
13 #[error("IO error: {0}")]
14 IoError(#[from] std::io::Error),
15}
16
17fn parse_file(file: &DirEntry, collection: &str) -> Result<Record, ParseError> {
18 let contents = fs::read_to_string(file.path())?;
19 let file_name = file.file_name();
20 let mut data = HashMap::new();
21 let id = file_name
22 .to_str()
23 .unwrap()
24 .replace(".yml", "")
25 .replace(".yaml", "")
26 .replace(".md", "")
27 .replace(".markdown", "");
28
29 if str_ends_with_any(file.path().to_str().unwrap(), Vec::from(["yml", "yaml"])) {
30 if let Ok(yaml) = serde_yaml::from_str(&contents) {
31 data = yaml;
32 }
33 }
34
35 if str_ends_with_any(file.path().to_str().unwrap(), Vec::from(["md", "markdown"])) {
36 if let Ok(fm) = frontmatter::parse(&contents) {
37 data = fm;
38 }
39 }
40
41 Ok(Record {
42 id,
43 collection: collection.to_string(),
44 file_name: file_name.to_str().unwrap().to_string(),
45 data,
46 })
47}
48
49#[derive(Clone)]
50pub struct LocalProvider {
51 pub directory: String,
52}
53
54impl StoreProvider for LocalProvider {
55 fn retrieve(&self, name: &str) -> Vec<Record> {
56 let mut records = Vec::new();
57 let dir = fs::read_dir(format!("{}{}{}", self.directory, "/", name));
58
59 if dir.is_err() {
60 return records;
61 }
62
63 for file in dir.unwrap() {
64 let file_path = file.as_ref().unwrap().path();
66 let file_path_str = file_path.to_str().clone().unwrap();
67 let allowed_exts = Vec::from(["yml", "yaml", "md", "markdown"]);
68
69 if !str_ends_with_any(file_path_str, allowed_exts) {
70 continue;
71 }
72
73 if file.as_ref().is_ok() {
75 if let Ok(record) = parse_file(&file.unwrap(), &name) {
76 records.push(record);
77 }
78 }
79 }
80
81 return records;
82 }
83
84 fn set(&self, records: Vec<Record>, data: Vec<(&str, &RecordData)>) -> Vec<Record> {
85 let mut updated_records: Vec<Record> = Vec::new();
86
87 for mut record in records {
88 let directory = format!("{}/{}", self.directory, record.collection);
89
90 fs::create_dir_all(&directory).expect(&format!("Could not create {}", directory));
92
93 let file = fs::OpenOptions::new()
95 .read(true)
96 .write(true)
97 .create(true)
98 .open(format!("{}/{}", directory, record.file_name))
99 .expect(&format!(
100 "Could not write to file {}/{}",
101 directory, record.file_name
102 ));
103
104 for data_item in data.clone() {
105 record
106 .data
107 .insert(data_item.0.to_string(), data_item.1.clone());
108 }
109
110 updated_records.push(record.clone());
111
112 if str_ends_with_any(record.file_name.as_ref(), Vec::from(["yml", "yaml"])) {
114 serde_yaml::to_writer(file, &record.data).expect(&format!(
115 "Could not write to file {}/{}",
116 directory, record.file_name
117 ));
118 }
119
120 if str_ends_with_any(record.file_name.as_ref(), Vec::from(["md", "markdown"])) {
122 let meta = record.data.clone();
123
124 match record.data.get("content_raw").unwrap() {
125 RecordData::Str(md) => {
126 let fm = serde_frontmatter::serialize(meta, md).unwrap_or(String::from(""));
127 let file_path = format!("{}/{}", directory, record.file_name);
128
129 fs::write(&file_path, fm)
130 .expect(&format!("Could not write to {}", file_path));
131 }
132 _ => (),
133 }
134 }
135 }
136
137 return updated_records;
138 }
139
140 fn delete(&self, records: Vec<Record>) {
141 for record in records {
142 let directory = format!("{}/{}", self.directory, record.collection);
143 let file = format!("{}/{}", directory, record.file_name);
144
145 fs::remove_file(file.clone()).expect(&format!("Cannot delete file: {}", file));
146 }
147 }
148}