1use std::{collections::HashMap, fs, path::PathBuf};
2
3use chrono::{Datelike, NaiveDate};
4use markdown::{to_html_with_options, Options};
5use serde::{Deserialize, Serialize};
6
7use crate::{
8 common::{get_json_data, preview::get_preview, toc, BlogError, BlogJson},
9 high::HighBlogEntry,
10 types::Blog,
11};
12
13#[derive(Serialize, Deserialize)]
25pub struct MediumBlog {
26 pub hash: HashMap<String, MediumBlogEntry>,
30 pub entries: Vec<MediumBlogEntry>,
34 pub tags: Vec<String>,
38 pub sitemap: String,
40}
41
42#[derive(Debug, Serialize, Deserialize, Clone)]
44pub struct MediumBlogEntry {
45 title: String,
47 date: NaiveDate,
49 desc: Option<String>,
51 slug: String,
53 tags: Vec<String>,
55 toc: Option<String>,
57 keywords: Option<Vec<String>>,
59 canonical_link: Option<String>,
61 author_name: Option<String>,
63 author_webpage: Option<String>,
65 preview: String,
67 file_name: String, last_modified: Option<NaiveDate>,
69 priority: Option<f64>,
70}
71
72impl Blog for MediumBlogEntry {
73 fn create<T: AsRef<std::path::Path>>(
74 blog: T,
75 toc_generation_func: Option<&dyn Fn(&markdown::mdast::Node) -> String>,
76 preview_chars: Option<usize>,
77 ) -> Result<Self, BlogError> {
78 let json = get_json_data(&blog)?;
79
80 let markdown = match fs::read_to_string(&blog) {
81 Ok(x) => x,
82 Err(y) => return Err(BlogError::File(y)),
83 };
84
85 let html = match to_html_with_options(
86 &markdown,
87 &Options {
88 compile: markdown::CompileOptions {
89 allow_dangerous_html: true,
90 allow_dangerous_protocol: true,
91
92 ..markdown::CompileOptions::default()
93 },
94 ..markdown::Options::default()
95 },
96 ) {
97 Ok(x) => x,
98 Err(y) => return Err(BlogError::Markdown(y.to_string())),
99 };
100
101 let preview: String = get_preview(&html, preview_chars);
102
103 let toc = toc(&markdown, toc_generation_func)?;
104
105 let file_name = match blog.as_ref().file_name() {
106 Some(x) => x.to_str().unwrap().to_string(),
107 None => return Err(BlogError::FileNotFound),
108 };
109
110 return Ok(MediumBlogEntry::new(json, toc, preview, file_name));
111 }
112
113 fn get_title(&self) -> String {
114 return self.title.clone();
115 }
116
117 fn get_date_listed(&self) -> NaiveDate {
118 return self.date.clone();
119 }
120
121 fn get_description(&self) -> Option<String> {
122 return self.desc.clone();
123 }
124
125 fn get_html(&self) -> String {
126 todo!();
127 }
128
129 fn get_full_slug(&self) -> String {
130 return format!("{}/{}", self.get_date_listed(), self.get_part_slug());
131 }
132
133 fn get_part_slug(&self) -> String {
134 return self.slug.clone();
135 }
136
137 fn get_tags(&self) -> Vec<String> {
138 return self.tags.clone();
139 }
140
141 fn get_table_of_contents(&self) -> Option<String> {
142 return self.toc.clone();
143 }
144
145 fn get_keywords(&self) -> Option<Vec<String>> {
146 return self.keywords.clone();
147 }
148
149 fn get_canonicle_link(&self) -> Option<String> {
150 return self.canonical_link.clone();
151 }
152
153 fn get_author_name(&self) -> Option<String> {
154 return self.author_name.clone();
155 }
156
157 fn get_author_webpage(&self) -> Option<String> {
158 return self.author_webpage.clone();
159 }
160
161 fn get_preview(&self) -> String {
162 return self.preview.clone();
163 }
164
165 fn get_last_modified(&self) -> Option<NaiveDate> {
166 return self.last_modified.clone();
167 }
168
169 fn get_priority(&self) -> Option<f64> {
170 return self.priority.clone();
171 }
172}
173
174impl MediumBlogEntry {
175 pub(crate) fn new(
176 json: BlogJson,
177 toc: Option<String>,
178 preview: String,
179 file_name: String,
180 ) -> Self {
181 return MediumBlogEntry {
182 title: json.title,
183 date: json.date,
184 desc: json.desc,
185 slug: json.slug,
186 tags: json.tags,
187 toc: toc,
188 keywords: json.keywords,
189 canonical_link: json.canonical_link,
190 author_name: json.author_name,
191 author_webpage: json.author_webpage,
192 preview: preview,
193 file_name,
194 last_modified: json.last_modified,
195 priority: json.priority,
196 };
197 }
198
199 pub fn render(&self, base: PathBuf) -> Result<HighBlogEntry, BlogError> {
202 let year = self.date.year();
203 let path = base
204 .join(format!("{}", year))
205 .join(format!("{}", self.date))
206 .join(self.file_name.clone());
207
208 let md = match fs::read_to_string(path) {
209 Ok(x) => x,
210 Err(y) => return Err(BlogError::File(y)),
211 };
212
213 let html = match to_html_with_options(
214 &md,
215 &Options {
216 compile: markdown::CompileOptions {
217 allow_dangerous_html: true,
218 allow_dangerous_protocol: true,
219
220 ..markdown::CompileOptions::default()
221 },
222 ..markdown::Options::default()
223 },
224 ) {
225 Ok(x) => x,
226 Err(y) => return Err(BlogError::Markdown(y.to_string())),
227 };
228
229 let high = HighBlogEntry::new_from_medium(self, html);
230
231 return Ok(high);
232 }
233}