Merge pull request #1400 from matthiasbeyer/imag-wiki/init

imag-wiki: init
This commit is contained in:
Matthias Beyer 2018-04-18 15:42:27 +02:00 committed by GitHub
commit 51205af668
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 504 additions and 17 deletions

View File

@ -23,6 +23,7 @@ members = [
"bin/domain/imag-notes", "bin/domain/imag-notes",
"bin/domain/imag-timetrack", "bin/domain/imag-timetrack",
"bin/domain/imag-todo", "bin/domain/imag-todo",
"bin/domain/imag-wiki",
"lib/core/libimagerror", "lib/core/libimagerror",
"lib/core/libimagrt", "lib/core/libimagrt",
"lib/core/libimagstore", "lib/core/libimagstore",

View File

@ -0,0 +1,36 @@
[package]
name = "imag-wiki"
version = "0.7.0"
authors = ["Matthias Beyer <mail@beyermatthias.de>"]
description = "Part of the imag core distribution: imag-wiki command"
keywords = ["imag", "PIM", "personal", "information", "management"]
readme = "../../../README.md"
license = "LGPL-2.1"
documentation = "https://matthiasbeyer.github.io/imag/imag_documentation/index.html"
repository = "https://github.com/matthiasbeyer/imag"
homepage = "http://imag-pim.org"
build = "../../../build.rs"
[dependencies]
clap = ">=2.17"
log = "0.3"
toml = "0.4"
toml-query = "0.6"
is-match = "0.1"
version = "2.0.1"
regex = "0.2"
filters = "0.2"
libimagentryedit = { version = "0.7.0", path = "../../../lib/entry/libimagentryedit" }
libimagentrylink = { version = "0.7.0", path = "../../../lib/entry/libimagentrylink" }
libimagentrymarkdown = { version = "0.7.0", path = "../../../lib/entry/libimagentrymarkdown" }
libimagerror = { version = "0.7.0", path = "../../../lib/core/libimagerror" }
libimagrt = { version = "0.7.0", path = "../../../lib/core/libimagrt" }
libimagstore = { version = "0.7.0", path = "../../../lib/core/libimagstore" }
libimagwiki = { version = "0.7.0", path = "../../../lib/domain/libimagwiki" }
libimagutil = { version = "0.7.0", path = "../../../lib/etc/libimagutil" }

View File

@ -0,0 +1 @@
../../../doc/src/04020-module-wiki.md

View File

@ -0,0 +1,274 @@
//
// imag - the personal information management suite for the commandline
// Copyright (C) 2015-2018 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
//
extern crate clap;
extern crate regex;
extern crate filters;
#[macro_use] extern crate log;
#[macro_use] 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 libimagrt::runtime::Runtime;
use libimagrt::setup::generate_runtime_setup;
use libimagerror::trace::MapErrTrace;
use libimagerror::exit::ExitUnwrap;
use libimagerror::io::ToExitCode;
use libimagwiki::store::WikiStore;
use libimagentryedit::edit::{Edit, EditHeader};
mod ui;
use ui::build_ui;
fn main() {
let version = make_imag_version!();
let rt = generate_runtime_setup("imag-wiki",
&version,
"Personal wiki",
build_ui);
let wiki_name = rt.cli().value_of("wikiname").unwrap_or("default");
match rt.cli().subcommand_name() {
Some("ids") => ids(&rt, wiki_name),
Some("idof") => idof(&rt, wiki_name),
Some("create") => create(&rt, wiki_name),
Some("create-wiki") => create_wiki(&rt),
Some("show") => show(&rt, wiki_name),
Some("delete") => delete(&rt, wiki_name),
Some(other) => {
debug!("Unknown command");
let _ = rt.handle_unknown_subcommand("imag-wiki", other, rt.cli())
.map_err_trace_exit_unwrap(1)
.code()
.map(std::process::exit);
}
None => warn!("No command"),
} // end match scmd
} // end main
fn ids(rt: &Runtime, wiki_name: &str) {
let scmd = rt.cli().subcommand_matches("ids").unwrap(); // safed by clap
let prefix = if scmd.is_present("ids-full") {
format!("{}/", rt.store().path().display())
} else {
String::from("")
};
let out = rt.stdout();
let mut outlock = out.lock();
rt.store()
.get_wiki(wiki_name)
.map_err_trace_exit_unwrap(1)
.unwrap_or_else(|| {
error!("No wiki '{}' found", wiki_name);
::std::process::exit(1)
})
.all_ids()
.map_err_trace_exit_unwrap(1)
.for_each(|id| {
let _ = writeln!(outlock, "{}{}", prefix, id)
.to_exit_code()
.unwrap_or_exit();
});
}
fn idof(rt: &Runtime, wiki_name: &str) {
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();
let _ = rt.store()
.get_wiki(wiki_name)
.map_err_trace_exit_unwrap(1)
.unwrap_or_else(|| {
error!("No wiki '{}' found", wiki_name);
::std::process::exit(1)
})
.get_entry(&entryname)
.map_err_trace_exit_unwrap(1)
.map(|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).to_exit_code().unwrap_or_exit()
})
.unwrap_or_else(|| {
error!("Entry '{}' in wiki '{}' not found!", entryname, wiki_name);
::std::process::exit(1)
});
}
fn create(rt: &Runtime, wiki_name: &str) {
use libimagwiki::entry::WikiEntry;
use libimagutil::warn_result::WarnResult;
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)
.map_err_trace_exit_unwrap(1)
.unwrap_or_else(|| {
error!("No wiki '{}' found", wiki_name);
::std::process::exit(1)
});
let mut entry = wiki.create_entry(name).map_err_trace_exit_unwrap(1);
if !scmd.is_present("create-noedit") {
if scmd.is_present("create-editheader") {
let _ = entry.edit_header_and_content(rt).map_err_trace_exit_unwrap(1);
} else {
let _ = entry.edit_content(rt).map_err_trace_exit_unwrap(1);
}
}
let _ = entry.autolink(rt.store())
.map_warn_err_str("Linking has failed. Trying to safe the entry now. Please investigate by hand if this succeeds.")
.map_err(|e| {
let _ = rt.store().update(&mut entry).map_err_trace_exit_unwrap(1);
e
})
.map_warn_err_str("Safed entry")
.map_err_trace_exit_unwrap(1);
if scmd.is_present("create-printid") {
let out = rt.stdout();
let mut lock = out.lock();
let id = entry.get_location();
writeln!(lock, "{}", id).to_exit_code().unwrap_or_exit()
}
}
fn create_wiki(rt: &Runtime) {
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 _ = rt.store().create_wiki(&wiki_name).map_err_trace_exit_unwrap(1);
}
fn show(rt: &Runtime, wiki_name: &str) {
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 names = scmd
.values_of("show-name")
.unwrap() // safe by clap
.map(String::from)
.filter(|e| namefilter.filter(e))
.collect::<Vec<_>>();
let wiki = rt
.store()
.get_wiki(&wiki_name)
.map_err_trace_exit_unwrap(1)
.unwrap_or_else(|| {
error!("No wiki '{}' found", wiki_name);
::std::process::exit(1)
});
let out = rt.stdout();
let mut outlock = out.lock();
for name in names {
let entry = wiki
.get_entry(&name)
.map_err_trace_exit_unwrap(1)
.unwrap_or_else(|| {
error!("No wiki entry '{}' found in wiki '{}'", name, wiki_name);
::std::process::exit(1)
});
writeln!(outlock, "{}", entry.get_location())
.to_exit_code()
.unwrap_or_exit();
writeln!(outlock, "{}", entry.get_content())
.to_exit_code()
.unwrap_or_exit();
}
}
fn delete(rt: &Runtime, wiki_name: &str) {
use libimagentrylink::internal::InternalLinker;
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)
.map_err_trace_exit_unwrap(1)
.unwrap_or_else(|| {
error!("No wiki '{}' found", wiki_name);
::std::process::exit(1)
});
if unlink {
wiki.get_entry(&name)
.map_err_trace_exit_unwrap(1)
.unwrap_or_else(|| {
error!("No wiki entry '{}' in '{}' found", name, wiki_name);
::std::process::exit(1)
})
.unlink(rt.store())
.map_err_trace_exit_unwrap(1);
}
let _ = wiki
.delete_entry(&name)
.map_err_trace_exit_unwrap(1);
}

View File

@ -0,0 +1,170 @@
//
// imag - the personal information management suite for the commandline
// Copyright (C) 2015-2018 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
//
use clap::{Arg, App, SubCommand};
pub fn build_ui<'a>(app: App<'a, 'a>) -> App<'a, 'a> {
app
.arg(Arg::with_name("wikiname")
.long("wiki")
.short("w")
.takes_value(true)
.required(false)
.multiple(false)
.value_name("WIKI")
.help("Name of the wiki to use. Defaults to 'default'"))
.subcommand(SubCommand::with_name("ids")
.about("List all ids in this wiki")
.version("0.1")
.arg(Arg::with_name("ids-full")
.long("full")
.takes_value(false)
.required(false)
.multiple(false)
.help("Print full filepath")))
.subcommand(SubCommand::with_name("idof")
.about("List id of an entry in this wiki, if it exists")
.version("0.1")
.arg(Arg::with_name("idof-full")
.long("full")
.takes_value(false)
.required(false)
.multiple(false)
.help("Print full filepath"))
.arg(Arg::with_name("idof-name")
.index(1)
.takes_value(true)
.required(true)
.multiple(false)
.value_name("NAME")
.help("Add the entry under this name. The name must be unique, namespaces ('foo/bar') are allowed."))
)
.subcommand(SubCommand::with_name("create-wiki")
.about("Create wiki")
.version("0.1")
.arg(Arg::with_name("create-wiki-name")
.index(1)
.takes_value(true)
.required(true)
.multiple(false)
.value_name("NAME")
.help("Name of the wiki"))
.arg(Arg::with_name("create-wiki-noedit")
.long("no-edit")
.short("E")
.takes_value(false)
.required(false)
.multiple(false)
.help("Do not call the editor on the newly created entry.")
.conflicts_with("create-wiki-editheader"))
.arg(Arg::with_name("create-wiki-editheader")
.long("header")
.takes_value(false)
.required(false)
.multiple(false)
.help("Do edit header when editing main page entry.")
.conflicts_with("create-wiki-noedit"))
.arg(Arg::with_name("create-wiki-printid")
.long("print-id")
.short("I")
.takes_value(false)
.required(false)
.multiple(false)
.help("Print the store id after creating"))
)
.subcommand(SubCommand::with_name("create")
.about("Add wiki entry")
.version("0.1")
.arg(Arg::with_name("create-name")
.index(1)
.takes_value(true)
.required(true)
.multiple(false)
.help("Name of the page."))
.arg(Arg::with_name("create-noedit")
.long("no-edit")
.short("E")
.takes_value(false)
.required(false)
.multiple(false)
.help("Do not call the editor on the newly created entry.")
.conflicts_with("create-editheader"))
.arg(Arg::with_name("create-editheader")
.long("header")
.takes_value(false)
.required(false)
.multiple(false)
.help("Do edit header when editing entry.")
.conflicts_with("create-noedit"))
.arg(Arg::with_name("create-printid")
.long("print-id")
.short("I")
.takes_value(false)
.required(false)
.multiple(false)
.help("Print the store id after creating"))
)
.subcommand(SubCommand::with_name("show")
.about("Show wiki entry/entries")
.version("0.1")
.arg(Arg::with_name("show-name")
.index(1)
.takes_value(true)
.required(true)
.multiple(true)
.help("Name of the entry/entries to show (if not passed, all are shown)."))
)
.subcommand(SubCommand::with_name("delete")
.about("Delete wiki entry")
.version("0.1")
.arg(Arg::with_name("delete-name")
.index(1)
.takes_value(true)
.required(true)
.multiple(false)
.value_name("NAME")
.help("Delete the entry under this name. The name must be unique, namespaces ('foo/bar') are allowed."))
.arg(Arg::with_name("delete-no-remove-linkings")
.long("no-remove-links")
.takes_value(false)
.required(false)
.multiple(false)
.help("Do not remote links. WARNING: This leaves the store in an inconsistent state."))
)
}

View File

@ -0,0 +1,12 @@
## Wiki {#sec:modules:wiki}
The Wiki module provides a personal wiki implementation.
The wiki entries are markdown-formatted files in the imag store. All entries are
automatically searched for links and those links are automatically added to the
header (or as external link, depending on the format).
Wiki entries can have no or one category and a arbitrary number of tags.
Entries can be listed (as a "tree" shape) and filtered by content, category and
tag.

View File

@ -29,10 +29,10 @@ pub trait WikiStore {
fn get_wiki<'a, 'b>(&'a self, name: &'b str) -> Result<Option<Wiki<'a, 'b>>>; fn get_wiki<'a, 'b>(&'a self, name: &'b str) -> Result<Option<Wiki<'a, 'b>>>;
fn create_wiki<'a, 'b>(&'a self, name: &'b str, mainpagename: Option<&str>) fn create_wiki<'a, 'b>(&'a self, name: &'b str)
-> Result<Wiki<'a, 'b>>; -> Result<Wiki<'a, 'b>>;
fn retrieve_wiki<'a, 'b>(&'a self, name: &'b str, mainpagename: Option<&str>) fn retrieve_wiki<'a, 'b>(&'a self, name: &'b str)
-> Result<Wiki<'a, 'b>>; -> Result<Wiki<'a, 'b>>;
} }
@ -56,31 +56,23 @@ impl WikiStore for Store {
/// ///
/// Returns the Wiki object. /// Returns the Wiki object.
/// ///
/// Ob success, an empty Wiki entry with the name `mainpagename` (or "main" if none is passed) /// Ob success, an empty Wiki entry with the name `index` is created inside the wiki. Later, new
/// is created inside the wiki. /// entries are automatically linked to this entry.
/// ///
fn create_wiki<'a, 'b>(&'a self, name: &'b str, mainpagename: Option<&str>) fn create_wiki<'a, 'b>(&'a self, name: &'b str) -> Result<Wiki<'a, 'b>> {
-> Result<Wiki<'a, 'b>>
{
debug!("Trying to get wiki '{}'", name); debug!("Trying to get wiki '{}'", name);
debug!("Trying to create wiki '{}' with mainpage: '{:?}'", name, mainpagename);
let wiki = Wiki::new(self, name); let wiki = Wiki::new(self, name);
let _ = wiki.create_index_page()?; let _ = wiki.create_index_page()?;
Ok(wiki)
wiki.create_entry(mainpagename.unwrap_or("main"))
.map(|_| wiki)
} }
fn retrieve_wiki<'a, 'b>(&'a self, name: &'b str, mainpagename: Option<&str>) fn retrieve_wiki<'a, 'b>(&'a self, name: &'b str)
-> Result<Wiki<'a, 'b>> -> Result<Wiki<'a, 'b>>
{ {
match self.get_wiki(name)? { match self.get_wiki(name)? {
None => self.create_wiki(name, mainpagename), None => self.create_wiki(name),
Some(wiki) => { Some(wiki) => Ok(wiki),
let _ = wiki.retrieve_entry(mainpagename.unwrap_or("main"))?; // to make sure the page exists
Ok(wiki)
}
} }
} }

View File

@ -53,6 +53,7 @@ CRATES=(
./bin/core/imag-init ./bin/core/imag-init
./bin/core/imag-edit ./bin/core/imag-edit
./bin/core/imag-ids ./bin/core/imag-ids
./bin/core/imag-wiki
./bin/core/imag ./bin/core/imag
) )