use clap::ArgMatches;
use clap_complete::Shell::*;
use rayon::prelude::*;
use regex::Regex;
use rusqlite::Error;
use crate::config::ConfigOptions;
use crate::Database;
use crate::Zettel;
use crate::cli;
use crate::io::file_exists;
fn print_zettel_info(zettel: &[Zettel])
{
zettel.iter().for_each(|z| {
println!("[{}] {}", z.project, z.title);
})
}
fn print_list_of_strings(elems: &Vec<String>)
{
elems.iter().for_each(|e| {
println!("{}", e);
})
}
pub fn compl(matches: &ArgMatches) -> Result<(), Error>
{
let shell = matches.value_of("SHELL").unwrap_or_default();
let sh = match shell {
"zsh" => Some(Zsh),
"bash" => Some(Bash),
"fish" => Some(Fish),
_ => None,
};
if let Some(sh) = sh {
let app = &mut cli::build();
clap_complete::generate(sh, app, app.get_name().to_string(), &mut std::io::stdout());
} else {
eprintln!("error: '{}' isn't a (supported) shell", shell);
}
Ok(())
}
pub fn new(matches: &ArgMatches, cfg: &ConfigOptions) -> Result<(), Error>
{
let db = Database::new(&cfg.db_file())?;
db.init()?;
let title = matches.value_of("TITLE").unwrap();
let project = matches.value_of("PROJECT").unwrap_or_default();
let zettel = Zettel::new(title, project);
let exists_in_fs = file_exists(&zettel.filename(cfg));
let exists_in_db = db.all().unwrap().into_par_iter().any(|z| z == zettel);
if exists_in_fs && exists_in_db {
eprintln!("error: couldn't create new Zettel: one with the same title already exists");
return Ok(());
} else if exists_in_fs {
println!("file exists in the filesystem but not in the database; added entry");
} else {
zettel.create(cfg);
print_zettel_info(&[zettel.clone()]); }
db.save(&zettel)?;
Ok(())
}
pub fn rename(matches: &ArgMatches, cfg: &ConfigOptions) -> Result<(), Error>
{
let db = Database::new(&cfg.db_file())?;
let old_title = matches.value_of("TITLE").unwrap();
let new_title = matches.value_of("NEW_TITLE").unwrap();
let results = db.find_by_title(old_title)?;
let overwrite_failsafe = db.find_by_title(new_title)?;
if overwrite_failsafe.first().is_some() {
eprintln!("error: a note with the new title already exists: won't overwrite");
return Ok(());
}
let old_zettel = if results.first().is_none() {
eprintln!("error: no Zettel with that title");
return Ok(());
} else {
results.first().unwrap()
};
let new_zettel = Zettel::new(new_title, &old_zettel.project);
let mut dial = dialoguer::Confirm::new();
let prompt = dial.with_prompt(format!("{} --> {}", old_title, new_title));
if prompt.interact().unwrap_or_default() {
crate::io::rename(&old_zettel.filename(cfg), &new_zettel.filename(cfg));
db.change_title(old_zettel, new_title).unwrap();
let backlinks = db.find_by_links_to(old_title)?;
backlinks.iter().for_each(|bl| {
let contents = crate::io::file_to_string(&bl.filename(cfg));
let regex_string =
&format!(r"\[\[{}\]\]", old_title).replace(" ", r"[\n\t ]");
let old_title_reg = Regex::new(regex_string).unwrap();
let new_contents =
old_title_reg.replace_all(&contents, format!(r"[[{}]]", new_title));
crate::io::write_to_file(&bl.filename(cfg), &new_contents);
db.update(cfg, bl).unwrap();
})
}
Ok(())
}
pub fn mv(matches: &ArgMatches, cfg: &ConfigOptions) -> Result<(), Error>
{
let db = Database::new(&cfg.db_file())?;
let pattern = matches.value_of("PATTERN").unwrap();
let project = matches.value_of("PROJECT").unwrap();
let notes = db.find_by_title(pattern)?;
print_zettel_info(¬es);
let mut dial = dialoguer::Confirm::new();
let prompt =
dial.with_prompt(format!(">> These notes will be transferred to the {}. Proceed?",
if project.is_empty() {
"main zettelkasten".to_string()
} else {
format!("'{}' project", project)
}));
if prompt.interact().unwrap_or_default() {
crate::io::mkdir(&format!("{}/{}", cfg.zettelkasten, project));
let new_notes = notes.iter().map(|z| Zettel { title: z.title.clone(),
project: project.to_string(),
links: z.links.clone(),
tags: z.tags.clone() });
let pairs = notes.iter().zip(new_notes);
pairs.for_each(|(old, new)| {
crate::io::rename(&old.filename(cfg), &new.filename(cfg));
db.change_project(old, project).unwrap();
});
}
Ok(())
}
pub fn update(matches: &ArgMatches, cfg: &ConfigOptions) -> Result<(), Error>
{
let db = Database::new(&cfg.db_file())?;
let path = matches.value_of("FILENAME").unwrap();
if file_exists(path) {
let zettel = Zettel::from_file(cfg, path);
db.update(cfg, &zettel)?;
} else {
eprintln!("error: provided path isn't a file");
}
Ok(())
}
pub fn query(matches: &ArgMatches, cfg: &ConfigOptions) -> Result<(), Error>
{
let db = Database::new(&cfg.db_file())?;
let pattern = matches.value_of("PATTERN").unwrap_or_default();
let result = db.find_by_title(pattern)?;
print_zettel_info(&result);
Ok(())
}
pub fn find(matches: &ArgMatches, cfg: &ConfigOptions) -> Result<(), Error>
{
let db = Database::new(&cfg.db_file())?;
let input = matches.value_of("TAG").unwrap_or_default();
let mut zettel = db.find_by_tag(input)?;
let mut zettel_with_subtag = db.find_by_tag(&format!("{}/*", input))?;
zettel.append(&mut zettel_with_subtag);
zettel.par_sort();
zettel.dedup();
print_zettel_info(&zettel);
Ok(())
}
pub fn tags(cfg: &ConfigOptions) -> Result<(), Error>
{
let db = Database::new(&cfg.db_file())?;
let tags = db.list_tags()?;
print_list_of_strings(&tags);
Ok(())
}
pub fn projects(cfg: &ConfigOptions) -> Result<(), Error>
{
let db = Database::new(&cfg.db_file())?;
let projects = db.list_projects()?;
print_list_of_strings(&projects);
Ok(())
}
pub fn links(matches: &ArgMatches, cfg: &ConfigOptions) -> Result<(), Error>
{
let title = matches.value_of("TITLE").unwrap_or_default();
let db = Database::new(&cfg.db_file())?;
let zettel = db.find_by_title(title)?;
for z in zettel {
print_zettel_info(&[z.clone()]);
for link in &z.links {
println!(" | {}", link);
}
}
Ok(())
}
pub fn backlinks(matches: &ArgMatches, cfg: &ConfigOptions) -> Result<(), Error>
{
let title = matches.value_of("TITLE").unwrap_or_default();
let db = Database::new(&cfg.db_file())?;
let zettel = db.find_by_title(title)?;
for z in zettel {
print_zettel_info(&[z.clone()]);
let res = db.find_by_links_to(&z.title)?;
for blink in res {
print!(" | ");
print_zettel_info(&[blink]);
}
}
Ok(())
}
pub fn isolated(cfg: &ConfigOptions) -> Result<(), Error>
{
let db = Database::new(&cfg.db_file())?;
let all = db.find_by_title("*")?;
let isolated_zettel = all.iter()
.filter(|z| {
if z.project != "" || z.links.len() != 0 {
return false;
}
let backlinks = db.find_by_links_to(&z.title).unwrap_or_default();
backlinks.len() == 0
})
.cloned()
.collect::<Vec<Zettel>>();
print_zettel_info(&isolated_zettel);
Ok(())
}
pub fn search(matches: &ArgMatches, cfg: &ConfigOptions) -> Result<(), Error>
{
let text = matches.value_of("TEXT").unwrap();
let db = Database::new(&cfg.db_file())?;
let results = db.search_text(cfg, text)?;
print_zettel_info(&results);
Ok(())
}
pub fn generate(cfg: &ConfigOptions) -> Result<(), Error>
{
let start = std::time::Instant::now();
let mem_db = Database::in_memory(&cfg.db_file())?;
mem_db.init()?;
mem_db.generate(cfg);
mem_db.write_to(&cfg.db_file())?;
println!("database generated successfully, took {}ms",
start.elapsed().as_millis());
Ok(())
}
pub fn ghosts(cfg: &ConfigOptions) -> Result<(), Error>
{
let db = Database::new(&cfg.db_file())?;
let results = db.zettel_not_yet_created()?;
print_list_of_strings(&results);
Ok(())
}
pub fn ls(cfg: &ConfigOptions) -> Result<(), Error>
{
let db = Database::new(&cfg.db_file())?;
let results = db.all()?;
print_zettel_info(&results);
Ok(())
}
pub fn zk(cfg: &ConfigOptions) -> Result<(), Error>
{
println!("{}", cfg.zettelkasten);
Ok(())
}