use chrono::prelude::*;
use rayon::prelude::*;
use regex::Regex;
use crate::config::ConfigOptions;
use crate::io::*;
fn find_links(contents: &str) -> Vec<String>
{
let re = Regex::new(r#"\[\[((?s).*?)\]\]"#).unwrap();
let mut links: Vec<String> = re.captures_iter(contents)
.par_bridge()
.map(|cap| {
cap.get(1).map_or("".to_string(), |m| {
strip_multiple_whitespace(m.as_str())
})
})
.collect();
links.par_sort();
links.dedup();
links
}
fn find_tags(contents: &str) -> Vec<String>
{
let re = Regex::new(r"\s#([\w/_-]+?)\s").unwrap();
re.captures_iter(contents)
.par_bridge()
.map(|cap| {
let tag = cap.get(1).map_or("", |m| m.as_str()).to_string();
tag
})
.collect()
}
pub fn strip_multiple_whitespace(s: &str) -> String
{
let re = Regex::new(r#"[\n\t ]+"#).unwrap();
re.replace_all(s, " ").to_string()
}
#[derive(PartialEq, Eq, PartialOrd, Ord, Clone)]
pub struct Zettel
{
pub title: String,
pub project: String,
pub tags: Vec<String>,
pub links: Vec<String>,
pub backlinks: Vec<String>,
}
impl Zettel
{
pub fn new(title: &str, project: &str) -> Self
{
Zettel { title: title.to_string(),
project: project.to_string(),
tags: vec![],
links: vec![],
backlinks: vec![] }
}
pub fn from_file(cfg: &ConfigOptions, path: &str) -> Self
{
let mut title = basename(&replace_extension(path, ""));
let contents = file_to_string(path);
let fixed_ws = strip_multiple_whitespace(&title);
if fixed_ws != title {
eprintln!("warning: multiple consecutive whitespaces in titles are not allowed; '{}' was renamed",
title);
let newpath = format!("{}/{}.md", dirname(path), fixed_ws);
rename(path, &newpath);
title = fixed_ws;
}
let project = if dirname(path) == cfg.zettelkasten {
"".to_string()
} else {
let segments: Vec<&str> = path.split('/').collect();
segments[segments.len() - 2].to_string()
};
let mut zettel = Zettel::new(&title, &project);
zettel.links = find_links(&contents);
zettel.tags = find_tags(&contents);
zettel
}
pub fn create(&self, cfg: &ConfigOptions)
{
mkdir(&format!("{}/{}", &cfg.zettelkasten, &self.project));
let new_contents = if file_exists(&cfg.template) {
let template_contents = file_to_string(&cfg.template);
self.replace_template_placeholders(&template_contents)
} else {
String::from("")
};
write_to_file(&self.filename(cfg), &new_contents);
}
pub fn filename(&self, cfg: &ConfigOptions) -> String
{
let dir = format!("{}/{}",
cfg.zettelkasten,
if self.project.is_empty() {
self.project.clone()
} else {
format!("{}/", &self.project)
});
format!("{}{}.md", dir, &self.title)
}
pub fn find_pattern(&self, cfg: &ConfigOptions, pattern: &str) -> String
{
let contents = file_to_string(&self.filename(cfg));
let re = Regex::new(&format!(r"(?i){}", pattern)).unwrap();
if let Some(first) = re.captures(&contents) {
if let Some(second) = first.get(0) {
second.as_str()
} else {
""
}
} else {
""
}.to_string()
}
fn replace_template_placeholders(&self, contents: &str) -> String
{
let re_title = Regex::new(r"\$\{TITLE\}").unwrap();
let c1 = re_title.replace_all(contents, &self.title).to_string();
let re_date = Regex::new(r"\$\{DATE\}").unwrap();
re_date.replace_all(&c1, Utc::now().format("%Y-%m-%d").to_string())
.to_string()
}
}