imag-wiki 0.10.1

Part of the imag core distribution: imag-wiki command
Documentation
//
// imag - the personal information management suite for the commandline
// Copyright (C) 2015-2020 Matthias Beyer <mail@beyermatthias.de> 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::<Result<Vec<_>>>()
        .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<Vec<String>>);
    impl Filter<String> 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::<Vec<String>>()));

    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::<Result<Vec<_>>>()
        .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)
}