use crate::core::*;
use std::ffi::OsStr;
use std::fs;
use std::path::{Path, PathBuf};
pub type QueryResult = Vec<(String, Vec<String>)>;
#[derive(Debug)]
pub struct Vault {
pub config: Config,
pub root: PathBuf,
pub ideas: PathBuf,
pub journals: PathBuf,
pub notes: PathBuf,
pub tasks: PathBuf,
}
impl Vault {
fn list_paths(&self) -> Vec<PathBuf> {
vec![
self.ideas.to_path_buf(),
self.journals.to_path_buf(),
self.notes.to_path_buf(),
self.tasks.to_path_buf(),
]
}
pub fn new<P: AsRef<Path>>(config: &Config, root: P) -> Self {
let path = root.as_ref().to_path_buf();
Vault {
config: config.clone(),
root: root.as_ref().to_path_buf(),
ideas: path.join("ideas"),
journals: path.join("journals"),
notes: path.join("notes"),
tasks: path.join("tasks"),
}
}
pub fn is_bootstrapped(&self) -> bool {
for path in self.list_paths() {
let dir_present = path.exists() && path.is_dir();
if !dir_present {
return false;
}
}
true
}
pub fn bootstrap(&self) -> Result<()> {
fs::create_dir_all(&self.ideas)?;
fs::File::create(&self.ideas.join(".gitkeep"))?;
fs::create_dir_all(&self.journals)?;
fs::File::create(&self.journals.join(".gitkeep"))?;
fs::create_dir_all(&self.notes)?;
fs::File::create(&self.notes.join(".gitkeep"))?;
fs::create_dir_all(&self.tasks)?;
fs::File::create(&self.tasks.join(".gitkeep"))?;
Ok(())
}
pub fn detect(config: &Config) -> Result<Vault> {
Vault::current(config).or(Vault::global_preped(config))
}
fn load_local_config(&self, config: &Config) -> Option<Config> {
if let Some(local) = ConfigOpt::load(self.root.join(".mjconfig")) {
let (config, _) = Config::merge(config, &local);
Some(config)
} else {
None
}
}
fn current(config: &Config) -> Result<Vault> {
let mut path = std::env::current_dir()?;
let mut counter = 5;
while counter >= 0 {
let vault = Vault::new(config, &path);
if vault.is_bootstrapped() {
if let Some(config) = vault.load_local_config(config) {
return Ok(Vault::new(&config, &path))
}
return Ok(vault)
}
if let Some(p) = path.parent() {
path = p.to_path_buf();
}
counter -= 1;
}
Err(Error::VaultNotFound)
}
fn global_preped(config: &Config) -> Result<Vault> {
let global_vault_root = &config.global_vault_root;
if !global_vault_root.exists() {
fs::create_dir_all(global_vault_root)?;
}
let vault = Vault::new(config, global_vault_root);
if !vault.is_bootstrapped() {
vault.bootstrap()?;
}
if let Some(config) = vault.load_local_config(config) {
return Ok(Vault::new(&config, &global_vault_root))
}
Ok(vault)
}
pub fn list_ideas(&self) -> Result<QueryResult> {
let base = Path::new(&self.ideas);
let reader = std::fs::read_dir(&base)?;
let mut files: Vec<String> =
reader
.filter(|x| x.is_ok()).map(|x| x.unwrap().path())
.filter(|x| x.as_path().extension().unwrap_or(std::ffi::OsStr::new("")) == "md")
.map(|x| x.as_path().file_stem().unwrap_or(OsStr::new("")).to_str().unwrap_or("").to_owned())
.filter(|x| x != "")
.collect();
files.sort();
let mut result: QueryResult = vec![];
for file in files {
result.push((file, vec![]));
}
Ok(result)
}
pub fn list_journals(&self) -> Result<QueryResult> {
let base = Path::new(&self.journals);
let reader = std::fs::read_dir(&base)?;
let mut dates: Vec<_> =
reader
.filter(|x| x.is_ok()).map(|x| x.unwrap().path())
.filter(|x| x.as_path().is_dir())
.map(|x| x.as_path().file_name().unwrap_or(OsStr::new("")).to_str().unwrap_or("").to_owned())
.filter(|x| x != "")
.collect();
dates.sort_by(|a, b| b.cmp(a)); let mut result: QueryResult = vec![];
for date in dates {
result.push((date, vec![]));
}
Ok(result)
}
pub fn list_notes(&self, show_notes: bool, category: &Option<String>) -> Result<QueryResult> {
let base = Path::new(&self.notes);
let reader = std::fs::read_dir(&base)?;
let mut categories: Vec<_> =
reader
.filter(|x| x.is_ok()).map(|x| x.unwrap().path())
.filter(|x| x.as_path().is_dir())
.map(|x| x.as_path().file_name().unwrap_or(OsStr::new("")).to_str().unwrap_or("").to_owned())
.filter(|x| x != "")
.collect();
categories.sort();
let category = category.as_ref().unwrap_or(&String::from("")).to_owned();
let mut result: QueryResult = vec![];
for cat in categories {
if category == "" || cat == category {
if show_notes {
let path = base.join(&cat);
let notes_reader = std::fs::read_dir(&path)?;
let mut notes: Vec<_> =
notes_reader
.filter(|x| x.is_ok()).map(|x| x.unwrap().path())
.filter(|x| x.as_path().extension().unwrap_or(std::ffi::OsStr::new("")) == "md")
.map(|x| x.as_path().file_stem().unwrap_or(OsStr::new("")).to_str().unwrap_or("").to_owned())
.filter(|x| x != "")
.collect();
notes.sort();
result.push((cat, notes));
} else {
result.push((cat, vec![]));
}
}
}
Ok(result)
}
pub fn list_tasks(&self, show_tasks: bool, project: &Option<String>) -> Result<QueryResult> {
let base = Path::new(&self.tasks);
let reader = std::fs::read_dir(&base)?;
let mut projects: Vec<_> =
reader
.filter(|x| x.is_ok()).map(|x| x.unwrap().path())
.filter(|x| x.as_path().is_dir())
.map(|x| x.as_path().file_name().unwrap_or(OsStr::new("")).to_str().unwrap_or("").to_owned())
.filter(|x| x != "")
.collect();
projects.sort();
let project = project.as_ref().unwrap_or(&String::from("")).to_owned();
let mut result: QueryResult = vec![];
for proj in projects {
if project == "" || proj == project {
if show_tasks {
let path = base.join(&proj).join("tasks").with_extension("md");
result.push((proj, self.parse_tasks(path)?));
} else {
result.push((proj, vec![]));
}
}
}
Ok(result)
}
fn parse_tasks(&self, path: PathBuf) -> Result<Vec<String>> {
let mut result: Vec<String> = vec![];
let content = std::fs::read_to_string(path)?;
let mut lines = content.as_str().lines().into_iter()
.map(|x| x.to_owned())
.collect::<Vec<String>>();
lines.dedup();
for line in lines {
result.push(line.to_owned());
}
Ok(result)
}
}