valis_core/modules/notes/
markdown.rs1use std::error::Error;
2use std::fs;
3use std::fs::File;
4use std::io::Write;
5use std::path::{Path, PathBuf};
6use std::str::FromStr;
7
8use globmatch::Matcher;
9use lazy_static::lazy_static;
10use regex::Regex;
11use rlua::{Context, Lua, Table, ToLua, ToLuaMulti};
12
13use crate::modules::core::get_files;
14use crate::modules::notes::markdown::WikilinkType::TEXT;
15
16lazy_static! {
17 static ref WIKILINK_REGEX: Regex = Regex::new(r"(!)?\[\[(.*?)\]\]").unwrap();
18 static ref MEDIA_REGEX: Regex = Regex::new(r"!\[(.*)?\]\((.*)\)").unwrap();
19 static ref IMAGE_EXTENSIONS: Vec<String> = vec![
20 "jpg".to_string(),
21 "png".to_string(),
22 "svg".to_string(),
23 "jpeg".to_string(),
24 "gif".to_string()
25 ];
26}
27
28#[derive(Debug)]
29pub struct Page {
30 pub title: String,
31 pub path: PathBuf,
32 pub contents: String,
33 pub wikilinks: Vec<WikiLink>,
34}
35
36impl Clone for Page {
37 fn clone(&self) -> Self {
38 Page {
39 title: self.title.clone(),
40 path: self.path.clone(),
41 contents: self.contents.clone(),
42 wikilinks: self.wikilinks.clone(),
43 }
44 }
45}
46
47fn convert_wikilinks_to_hugo(contents: &str) -> String {
48 let re = Regex::new(r"(!?)\[\[(.*?)\]\]").unwrap();
50
51 let replace_func = |caps: ®ex::Captures| {
53 let is_image = &caps[1] == "!";
54 let link_and_name = &caps[2];
55
56 if is_image {
58 format!(
59 "{{{{< figure src=\"/assets/{}\" alt=\"{}\" >}}}}",
60 link_and_name, link_and_name
61 )
62 }
63 else if let Some(pipe_index) = link_and_name.find('|') {
65 let (link, name) = link_and_name.split_at(pipe_index);
67 let name = &name[1..]; format!("[{}]({{{{< ref \"{}\" >}}}})", name, link)
69 } else {
70 format!("[{}]({{{{< ref \"{}\" >}}}})", link_and_name, link_and_name)
72 }
73 };
74
75 re.replace_all(contents, replace_func).into_owned()
77}
78
79impl Page {
80 pub fn save_to_file(&self, directory: &PathBuf) -> std::io::Result<()> {
81 let mut file_path = directory.clone();
83 file_path.push(format!("{}.md", &self.title));
84
85 let mut file = File::create(&file_path)?;
87
88 let contents = convert_wikilinks_to_hugo(&self.contents);
90
91 file.write_all(contents.as_bytes())
93 }
94}
95
96pub trait PageLoader {
97 fn from_path(path: &PathBuf) -> Self;
98 fn title_from_path(path: &PathBuf) -> String;
99}
100
101impl PageLoader for Page {
102 fn from_path(path: &PathBuf) -> Self {
103 let contents = fs::read_to_string(path).ok().unwrap();
105 Self {
106 path: path.clone(),
107 title: Self::title_from_path(path),
108 contents: contents.clone(),
109 wikilinks: extract_links(&contents),
110 }
111 }
112
113 fn title_from_path(path: &PathBuf) -> String {
114 path.file_stem()
115 .and_then(|stem| stem.to_str())
116 .unwrap_or("")
117 .to_string()
118 }
119}
120
121#[derive(Debug, PartialEq, Copy, Clone)]
122pub enum WikilinkType {
123 IMAGE,
124 TEXT,
125}
126
127#[derive(Debug)]
128pub struct WikiLink {
129 pub name: String,
130 pub link: String,
131 pub anchor: String,
132 pub link_type: WikilinkType,
133 pub original: String,
134}
135
136impl Clone for WikiLink {
137 fn clone(&self) -> Self {
138 WikiLink {
139 name: self.name.to_string(),
140 link: self.link.to_string(),
141 anchor: self.anchor.to_string(),
142 link_type: self.link_type.clone(),
143 original: self.original.to_string(),
144 }
145 }
146}
147
148pub fn get_markdown_files<'a>(root: PathBuf) -> Result<Matcher<'a, PathBuf>, String> {
149 return get_files(root, &"**/*.md");
150}
151
152pub fn remove_code_blocks(s: &str) -> String {
153let re = Regex::new(r"```.*?```").unwrap();
155 re.replace_all(s, "").to_string()
156}
157
158
159pub fn extract_links(contents: &str) -> Vec<WikiLink> {
160 return WIKILINK_REGEX
161 .captures_iter(&remove_code_blocks(contents))
162 .map(|captures| parse_wikilink(captures.get(2).unwrap().as_str()))
163 .filter(|wikilink| wikilink.as_ref().ok().is_some())
164 .map(|wikilink| wikilink.unwrap())
165 .collect::<Vec<WikiLink>>();
166}
167
168impl FromStr for WikiLink {
169 type Err = Box<dyn Error>;
170
171 fn from_str(s: &str) -> Result<Self, Self::Err> {
172 let link_contents = s.trim_start_matches("[[").trim_end_matches("]]");
173
174 let (link, name) = if let Some(pipe_pos) = link_contents.find('|') {
175 let (link, name) = link_contents.split_at(pipe_pos);
176 (link.trim(), name.trim_start_matches('|'))
177 } else {
178 (link_contents, link_contents)
179 };
180
181 let (link_without_anchor, anchor) = if let Some(anchor_pos) = link.find('#') {
182 let (link, anchor) = link.split_at(anchor_pos);
183 (link.trim(), anchor.trim_start_matches('#'))
184 } else {
185 (link, "")
186 };
187
188 let is_just_anchor = link.starts_with('#');
189 let final_link = if is_just_anchor {
190 ""
191 } else {
192 link_without_anchor
193 };
194 let final_anchor = if is_just_anchor {
195 link_contents.trim_start_matches('#')
196 } else {
197 anchor
198 };
199 let final_name = if name == link {
200 final_link.to_string()
201 } else {
202 name.to_string()
203 };
204
205 let link_type = filename_type(final_link);
206
207 Ok(WikiLink {
208 name: final_name,
209 link: String::from(final_link),
210 anchor: String::from(final_anchor),
211 link_type,
212 original: "".to_string(),
213 })
214 }
215}
216
217pub fn parse_wikilink(s: &str) -> Result<WikiLink, Box<dyn Error>> {
218 WikiLink::from_str(s)
219}
220
221pub fn filename_type(filename: &str) -> WikilinkType {
222 let lower_ext = filename.split('.').last().unwrap_or("").to_lowercase();
223 match lower_ext.as_str() {
224 "jpg" | "jpeg" | "png" | "gif" => WikilinkType::IMAGE,
225 _ => WikilinkType::TEXT,
226 }
227}
228
229#[cfg(test)]
230mod tests {
231 use crate::modules::notes::markdown::WikilinkType::IMAGE;
232
233 use super::*;
235
236 #[test]
237 fn test_simple() {
238 let wikilink = parse_wikilink("[[Test]]").unwrap();
239 assert_eq!(wikilink.name, "Test");
240 assert_eq!(wikilink.link, "Test");
241 assert_eq!(wikilink.anchor, "");
242 assert_eq!(wikilink.link_type, TEXT);
243 }
244
245 #[test]
246 fn test_alternate_name() {
247 let wikilink = parse_wikilink("[[Test|Another]]").unwrap();
248 assert_eq!(wikilink.name, "Another");
249 assert_eq!(wikilink.link, "Test");
250 assert_eq!(wikilink.anchor, "");
251 assert_eq!(wikilink.link_type, TEXT);
252 }
253
254 #[test]
255 fn test_anchor() {
256 let wikilink = parse_wikilink("[[Test#Anchor]]").unwrap();
257 assert_eq!(wikilink.name, "Test");
258 assert_eq!(wikilink.link, "Test");
259 assert_eq!(wikilink.anchor, "Anchor");
260 assert_eq!(wikilink.link_type, TEXT);
261 }
262
263 #[test]
264 fn test_just_anchor() {
265 let wikilink = parse_wikilink("[[#Anchor Test]]").unwrap();
267 assert_eq!(wikilink.name, "");
268 assert_eq!(wikilink.link, "");
269 assert_eq!(wikilink.anchor, "Anchor Test");
270 assert_eq!(wikilink.link_type, TEXT);
271 }
272
273 #[test]
274 fn test_anchor_and_name() {
275 let wikilink = parse_wikilink("[[Test#Anchor2|Another]]").unwrap();
276 assert_eq!(wikilink.name, "Another");
277 assert_eq!(wikilink.link, "Test");
278 assert_eq!(wikilink.anchor, "Anchor2");
279 assert_eq!(wikilink.link_type, TEXT);
280 }
281
282 #[test]
283 fn test_image() {
284 let wikilink = parse_wikilink("[[Test.jpg]]").unwrap();
285 assert_eq!(wikilink.name, "Test.jpg");
286 assert_eq!(wikilink.link, "Test.jpg");
287 assert_eq!(wikilink.anchor, "");
288 assert_eq!(wikilink.link_type, IMAGE);
289 }
290
291 #[test]
292 fn test_image_name() {
293 let wikilink = parse_wikilink("[[Test.png|Another]]").unwrap();
294 assert_eq!(wikilink.name, "Another");
295 assert_eq!(wikilink.link, "Test.png");
296 assert_eq!(wikilink.anchor, "");
297 assert_eq!(wikilink.link_type, IMAGE);
298 }
299
300 #[test]
301 fn test_extension_jpg() {
302 let filename = "foo.jpg";
303 assert_eq!(filename_type(filename), IMAGE);
304 let filename2 = "FOO.JPG";
305 assert_eq!(filename_type(filename2), IMAGE);
306 }
307
308 #[test]
309 fn test_extension_png() {
310 let filename = "foo.png";
311 assert_eq!(filename_type(filename), IMAGE);
312 let filename2 = "FOO.PNG";
313 assert_eq!(filename_type(filename2), IMAGE);
314 }
315
316 #[test]
317 fn test_extension_gif() {
318 let filename = "foo.gif";
319 assert_eq!(filename_type(filename), IMAGE);
320 let filename2 = "FOO.GIF";
321 assert_eq!(filename_type(filename), IMAGE);
322 }
323}