siena/providers/
local.rs

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            // Skip iteration when parser does not match file extension
65            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 we made it this far, continue with parsing
74            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            // Create dir if it doesnt exist
91            fs::create_dir_all(&directory).expect(&format!("Could not create {}", directory));
92
93            // Write to file
94            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            // yaml
113            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            // frontmatter
121            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}