// // imag - the personal information management suite for the commandline // Copyright (C) 2015-2019 Matthias Beyer and contributors // // This library is free software; you can redistribute it and/or // modify it under the terms of the GNU Lesser General Public // License as published by the Free Software Foundation; version // 2.1 of the License. // // This library is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU // Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with this library; if not, write to the Free Software // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA // #![forbid(unsafe_code)] extern crate clap; extern crate regex; extern crate filters; #[macro_use] extern crate log; #[macro_use] extern crate failure; extern crate resiter; extern crate libimagrt; extern crate libimagerror; extern crate libimagstore; extern crate libimagwiki; extern crate libimagentryedit; extern crate libimagentrylink; extern crate libimagutil; use std::io::Write; use failure::Fallible as Result; use failure::ResultExt; use failure::Error; use failure::err_msg; use clap::App; use resiter::AndThen; use libimagrt::runtime::Runtime; use libimagrt::application::ImagApplication; use libimagentryedit::edit::{Edit, EditHeader}; use libimagwiki::store::WikiStore; use libimagwiki::entry::WikiEntry; mod ui; /// Marker enum for implementing ImagApplication on /// /// This is used by binaries crates to execute business logic /// or to build a CLI completion. pub enum ImagWiki {} impl ImagApplication for ImagWiki { fn run(rt: Runtime) -> Result<()> { let wiki_name = rt.cli().value_of("wikiname").unwrap_or("default"); trace!("wiki_name = {}", wiki_name); trace!("calling = {:?}", rt.cli().subcommand_name()); match rt.cli().subcommand_name().ok_or_else(|| err_msg("No subcommand called"))? { "list" => list(&rt, wiki_name), "idof" => idof(&rt, wiki_name), "create" => create(&rt, wiki_name), "create-wiki" => create_wiki(&rt), "show" => show(&rt, wiki_name), "delete" => delete(&rt, wiki_name), other => { debug!("Unknown command"); if rt.handle_unknown_subcommand("imag-wiki", other, rt.cli())?.success() { Ok(()) } else { Err(err_msg("Failed to handle unknown subcommand")) } } } // end match scmd } fn build_cli<'a>(app: App<'a, 'a>) -> App<'a, 'a> { ui::build_ui(app) } fn name() -> &'static str { env!("CARGO_PKG_NAME") } fn description() -> &'static str { "Personal wiki" } fn version() -> &'static str { env!("CARGO_PKG_VERSION") } } fn list(rt: &Runtime, wiki_name: &str) -> Result<()> { let scmd = rt.cli().subcommand_matches("list").unwrap(); // safed by clap let prefix = if scmd.is_present("list-full") { format!("{}/", rt.store().path().display()) } else { String::from("") }; let out = rt.stdout(); let mut outlock = out.lock(); rt.store() .get_wiki(wiki_name)? .ok_or_else(|| format_err!("No wiki '{}' found", wiki_name))? .all_ids()? .and_then_ok(|id| writeln!(outlock, "{}{}", prefix, id).map_err(Error::from)) .collect::>>() .map(|_| ()) } fn idof(rt: &Runtime, wiki_name: &str) -> Result<()> { let scmd = rt.cli().subcommand_matches("idof").unwrap(); // safed by clap let entryname = scmd .value_of("idof-name") .map(String::from) .unwrap(); // safed by clap let out = rt.stdout(); let mut lock = out.lock(); rt.store() .get_wiki(wiki_name)? .ok_or_else(|| format_err!("No wiki '{}' found", wiki_name))? .get_entry(&entryname)? .ok_or_else(|| format_err!("Entry '{}' in wiki '{}' not found!", entryname, wiki_name)) .and_then(|entry| { let id = entry.get_location().clone(); let prefix = if scmd.is_present("idof-full") { format!("{}/", rt.store().path().display()) } else { String::from("") }; writeln!(lock, "{}{}", prefix, id).map_err(Error::from) }) } fn create(rt: &Runtime, wiki_name: &str) -> Result<()> { let scmd = rt.cli().subcommand_matches("create").unwrap(); // safed by clap let name = String::from(scmd.value_of("create-name").unwrap()); // safe by clap let wiki = rt .store() .get_wiki(&wiki_name)? .ok_or_else(|| format_err!("No wiki '{}' found", wiki_name))?; let mut entry = wiki.create_entry(name)?; if !scmd.is_present("create-noedit") { if scmd.is_present("create-editheader") { entry.edit_header_and_content(rt)?; } else { entry.edit_content(rt)?; } } if let Err(e) = entry .autolink(rt.store()) .context("Linking has failed. Trying to safe the entry now. Please investigate by hand if this succeeds.") { rt.store().update(&mut entry).context("Safed entry")?; return Err(e).map_err(Error::from) } let id = entry.get_location(); if scmd.is_present("create-printid") { let out = rt.stdout(); let mut lock = out.lock(); writeln!(lock, "{}", id)?; } rt.report_touched(&id).map_err(Error::from) } fn create_wiki(rt: &Runtime) -> Result<()> { let scmd = rt.cli().subcommand_matches("create-wiki").unwrap(); // safed by clap let wiki_name = String::from(scmd.value_of("create-wiki-name").unwrap()); // safe by clap let (_, index) = rt.store().create_wiki(&wiki_name)?; rt.report_touched(index.get_location()).map_err(Error::from) } fn show(rt: &Runtime, wiki_name: &str) -> Result<()> { use filters::filter::Filter; let scmd = rt.cli().subcommand_matches("show").unwrap(); // safed by clap struct NameFilter(Option>); impl Filter for NameFilter { fn filter(&self, e: &String) -> bool { match self.0 { Some(ref v) => v.contains(e), None => false, } } } let namefilter = NameFilter(scmd .values_of("show-name") .map(|v| v.map(String::from).collect::>())); let wiki = rt .store() .get_wiki(&wiki_name)? .ok_or_else(|| format_err!("No wiki '{}' found", wiki_name))?; let out = rt.stdout(); let mut outlock = out.lock(); scmd.values_of("show-name") .unwrap() // safe by clap .map(String::from) .filter(|e| namefilter.filter(e)) .map(|name| { let entry = wiki .get_entry(&name)? .ok_or_else(|| format_err!("No wiki entry '{}' found in wiki '{}'", name, wiki_name))?; writeln!(outlock, "{}", entry.get_location())?; writeln!(outlock, "{}", entry.get_content())?; rt.report_touched(entry.get_location()).map_err(Error::from) }) .collect::>>() .map(|_| ()) } fn delete(rt: &Runtime, wiki_name: &str) -> Result<()> { use libimagentrylink::linkable::Linkable; let scmd = rt.cli().subcommand_matches("delete").unwrap(); // safed by clap let name = String::from(scmd.value_of("delete-name").unwrap()); // safe by clap let unlink = !scmd.is_present("delete-no-remove-linkings"); let wiki = rt .store() .get_wiki(&wiki_name)? .ok_or_else(|| format_err!("No wiki '{}' found", wiki_name))?; if unlink { wiki.get_entry(&name)? .ok_or_else(|| format_err!("No wiki entry '{}' in '{}' found", name, wiki_name))? .unlink(rt.store())?; } wiki.delete_entry(&name) }