Merge pull request #1179 from matthiasbeyer/imag-link/intelligent-positional-args

Rewrite imag-link commandline to be intelligent
This commit is contained in:
Matthias Beyer 2017-12-23 14:20:24 +01:00 committed by GitHub
commit 4819061d68
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 227 additions and 366 deletions

View file

@ -51,21 +51,20 @@ extern crate libimagutil;
#[cfg(not(test))] #[cfg(not(test))]
extern crate libimagutil; extern crate libimagutil;
use std::ops::Deref; use std::path::PathBuf;
use libimagentrylink::external::ExternalLinker;
use libimagentrylink::internal::InternalLinker;
use libimagentrylink::internal::store_check::StoreLinkConsistentExt;
use libimagentrylink::error::LinkError as LE;
use libimagerror::trace::{MapErrTrace, trace_error, trace_error_exit};
use libimagrt::runtime::Runtime; use libimagrt::runtime::Runtime;
use libimagrt::setup::generate_runtime_setup; use libimagrt::setup::generate_runtime_setup;
use libimagstore::error::StoreError; use libimagstore::error::StoreError;
use libimagstore::store::Entry;
use libimagstore::store::FileLockEntry; use libimagstore::store::FileLockEntry;
use libimagstore::store::Store;
use libimagerror::trace::{MapErrTrace, trace_error, trace_error_exit};
use libimagentrylink::external::ExternalLinker;
use libimagentrylink::internal::InternalLinker;
use libimagutil::warn_result::*;
use libimagutil::warn_exit::warn_exit; use libimagutil::warn_exit::warn_exit;
use libimagutil::info_result::*; use libimagutil::warn_result::*;
use clap::ArgMatches;
use url::Url; use url::Url;
mod ui; mod ui;
@ -77,271 +76,183 @@ fn main() {
&version!()[..], &version!()[..],
"Link entries", "Link entries",
build_ui); build_ui);
if rt.cli().is_present("check-consistency") {
let exit_code = match rt.store().check_link_consistency() {
Ok(_) => {
info!("Store is consistent");
0
}
Err(e) => {
trace_error(&e);
1
}
};
::std::process::exit(exit_code);
}
rt.cli() let _ = rt.cli()
.subcommand_name() .subcommand_name()
.map(|name| { .map(|name| {
match name { match name {
"internal" => handle_internal_linking(&rt), "remove" => remove_linking(&rt),
"external" => handle_external_linking(&rt), "list" => list_linkings(&rt),
_ => warn_exit("No commandline call", 1) _ => panic!("BUG"),
} }
});
}
fn handle_internal_linking(rt: &Runtime) {
use libimagentrylink::internal::store_check::StoreLinkConsistentExt;
debug!("Handle internal linking call");
let cmd = rt.cli().subcommand_matches("internal").unwrap();
if cmd.is_present("check-consistency") {
match rt.store().check_link_consistency() {
Ok(_) => {
info!("Store is consistent");
return;
}
Err(e) => {
trace_error(&e);
::std::process::exit(1);
}
}
}
match cmd.subcommand_name() {
Some("list") => {
cmd.subcommand_matches("list")
.map(|matches| handle_internal_linking_list_call(rt, cmd, matches));
},
Some("add") => {
let (mut from, to) = get_from_to_entry(&rt, "add");
for mut to_entry in to {
if let Err(e) = to_entry.add_internal_link(&mut from) {
trace_error_exit(&e, 1);
}
};
},
Some("remove") => {
let (mut from, to) = get_from_to_entry(&rt, "remove");
for mut to_entry in to {
if let Err(e) = to_entry.remove_internal_link(&mut from) {
trace_error_exit(&e, 1);
}
};
},
_ => unreachable!(),
}
}
#[inline]
fn handle_internal_linking_list_call(rt: &Runtime, cmd: &ArgMatches, list: &ArgMatches) {
use libimagentrylink::external::is_external_link_storeid;
debug!("List...");
for entry in list.values_of("entries").unwrap() { // clap has our back
debug!("Listing for '{}'", entry);
match get_entry_by_name(rt, entry) {
Ok(Some(e)) => {
e.get_internal_links()
.map(|iter| {
iter.filter(move |id| {
cmd.is_present("list-externals-too") || !is_external_link_storeid(&id)
})
})
.map(|links| {
let i = links
.filter_map(|l| {
l.to_str()
.map_warn_err(|e| format!("Failed to convert StoreId to string: {:?}", e))
.ok()
})
.enumerate();
for (i, link) in i {
println!("{: <3}: {}", i, link);
}
})
.map_err_trace()
.ok();
},
Ok(None) => {
warn!("Entry not found: {:?}", entry);
break;
}
Err(e) => {
trace_error(&e);
break;
},
}
}
debug!("Listing ready!");
}
fn get_from_to_entry<'a>(rt: &'a Runtime, subcommand: &str) -> (FileLockEntry<'a>, Vec<FileLockEntry<'a>>) {
let from = match get_from_entry(&rt, subcommand) {
None => warn_exit("No 'from' entry", 1),
Some(s) => s,
};
debug!("Link from = {:?}", from.deref());
let to = match get_to_entries(&rt, subcommand) {
None => warn_exit("No 'to' entry", 1),
Some(to) => to,
};
debug!("Link to = {:?}", to.iter().map(|f| f.deref()).collect::<Vec<&Entry>>());
(from, to)
}
fn get_from_entry<'a>(rt: &'a Runtime, subcommand: &str) -> Option<FileLockEntry<'a>> {
rt.cli()
.subcommand_matches("internal")
.unwrap() // safe, we know there is an "internal" subcommand"
.subcommand_matches(subcommand)
.unwrap() // safe, we know there is an "add" subcommand
.value_of("from")
.and_then(|from_name| {
match get_entry_by_name(rt, from_name) {
Err(e) => {
debug!("We couldn't get the entry from name: '{:?}'", from_name);
trace_error(&e); None
},
Ok(Some(e)) => Some(e),
Ok(None) => None,
}
}) })
} .or_else(|| {
if let (Some(from), Some(to)) = (rt.cli().value_of("from"), rt.cli().values_of("to")) {
fn get_to_entries<'a>(rt: &'a Runtime, subcommand: &str) -> Option<Vec<FileLockEntry<'a>>> { Some(link_from_to(&rt, from, to))
rt.cli() } else {
.subcommand_matches("internal") warn_exit("No commandline call", 1)
.unwrap() // safe, we know there is an "internal" subcommand"
.subcommand_matches(subcommand)
.unwrap() // safe, we know there is an "add" subcommand
.values_of("to")
.map(|values| {
let mut v = vec![];
for entry in values.map(|v| get_entry_by_name(rt, v)) {
match entry {
Err(e) => trace_error(&e),
Ok(Some(e)) => v.push(e),
Ok(None) => warn!("Entry not found: {:?}", v),
}
} }
v
}) })
.ok_or(LE::from("No commandline call".to_owned()))
.map_err_trace_exit(1);
} }
fn get_entry_by_name<'a>(rt: &'a Runtime, name: &str) -> Result<Option<FileLockEntry<'a>>, StoreError> { fn get_entry_by_name<'a>(rt: &'a Runtime, name: &str) -> Result<Option<FileLockEntry<'a>>, StoreError> {
use std::path::PathBuf;
use libimagstore::storeid::StoreId; use libimagstore::storeid::StoreId;
StoreId::new(Some(rt.store().path().clone()), PathBuf::from(name)) StoreId::new(Some(rt.store().path().clone()), PathBuf::from(name))
.and_then(|id| rt.store().get(id)) .and_then(|id| rt.store().get(id))
} }
fn handle_external_linking(rt: &Runtime) { fn link_from_to<'a, I>(rt: &'a Runtime, from: &'a str, to: I)
let scmd = rt.cli().subcommand_matches("external").unwrap(); where I: Iterator<Item = &'a str>
let entry_name = scmd.value_of("id").unwrap(); // enforced by clap {
let mut entry = match get_entry_by_name(rt, entry_name) { let mut from_entry = match get_entry_by_name(rt, from) {
Err(e) => trace_error_exit(&e, 1), Ok(Some(e)) => e,
Ok(None) => { Ok(None) => warn_exit("No 'from' entry", 1),
warn!("Entry not found: {:?}", entry_name); Err(e) => trace_error_exit(&e, 1),
return;
},
Ok(Some(entry)) => entry
}; };
if scmd.is_present("add") { for entry in to {
debug!("Adding link to entry!"); if PathBuf::from(entry).exists() {
add_link_to_entry(rt.store(), scmd, &mut entry); debug!("Linking externally: {:?} -> {:?}", from, entry);
return; let url = Url::parse(entry).map_err_trace_exit_unwrap(1);
} let _ = from_entry
.add_external_link(rt.store(), url)
if scmd.is_present("remove") { .map_err_trace_exit_unwrap(1);
debug!("Removing link from entry!"); } else {
remove_link_from_entry(rt.store(), scmd, &mut entry); debug!("Linking internally: {:?} -> {:?}", from, entry);
return; let mut to_entry = match get_entry_by_name(rt, entry) {
} Ok(Some(e)) => e,
Ok(None) => {
if scmd.is_present("set") { warn!("No 'to' entry: {}", entry);
debug!("Setting links in entry!"); ::std::process::exit(1)
set_links_for_entry(rt.store(), scmd, &mut entry);
return;
}
if scmd.is_present("list") {
debug!("Listing links in entry!");
list_links_for_entry(rt.store(), &mut entry);
return;
}
panic!("Clap failed to enforce one of 'add', 'remove', 'set' or 'list'");
}
fn add_link_to_entry(store: &Store, matches: &ArgMatches, entry: &mut FileLockEntry) {
Url::parse(matches.value_of("add").unwrap())
.map_err_trace_exit(1)
.map(|link| entry.add_external_link(store, link).map_err_trace().map_info_str("Ok"))
.ok();
}
fn remove_link_from_entry(store: &Store, matches: &ArgMatches, entry: &mut FileLockEntry) {
Url::parse(matches.value_of("remove").unwrap())
.map_err_trace_exit(1)
.map(|link| entry.remove_external_link(store, link).map_err_trace().map_info_str("Ok"))
.ok();
}
fn set_links_for_entry(store: &Store, matches: &ArgMatches, entry: &mut FileLockEntry) {
let links = matches
.value_of("links")
.map(String::from)
.unwrap()
.split(',')
.map(|uri| {
match Url::parse(uri) {
Err(e) => {
warn!("Could not parse '{}' as URL, ignoring", uri);
trace_error(&e);
None
}, },
Ok(u) => Some(u), Err(e) => trace_error_exit(&e, 1),
} };
}) let _ = from_entry
.filter_map(|x| x) .add_internal_link(&mut to_entry)
.collect(); .map_err_trace_exit_unwrap(1);
}
entry.set_external_links(store, links) info!("Ok: {} -> {}", from, entry);
.map_err_trace() }
.map_info_str("Ok")
.ok(); info!("Ok");
} }
fn list_links_for_entry(store: &Store, entry: &mut FileLockEntry) { fn remove_linking(rt: &Runtime) {
entry.get_external_links(store)
.and_then(|links| { fn get_from_entry<'a>(rt: &'a Runtime) -> Option<FileLockEntry<'a>> {
for (i, link) in links.enumerate() { rt.cli()
match link { .subcommand_matches("remove")
Ok(link) => println!("{: <3}: {}", i, link), .unwrap() // safe, we know there is an "remove" subcommand
Err(e) => trace_error(&e), .value_of("from")
.and_then(|from_name| {
match get_entry_by_name(rt, from_name) {
Err(e) => {
debug!("We couldn't get the entry from name: '{:?}'", from_name);
trace_error(&e); None
},
Ok(Some(e)) => Some(e),
Ok(None) => None,
}
})
}
let mut from = match get_from_entry(&rt) {
None => warn_exit("No 'from' entry", 1),
Some(s) => s,
};
rt.cli()
.subcommand_matches("remove")
.unwrap()
.values_of("to")
.map(|values| {
for (entry, value) in values.map(|v| (get_entry_by_name(rt, v), v)) {
match entry {
Err(e) => trace_error(&e),
Ok(Some(mut to_entry)) => {
if let Err(e) = to_entry.remove_internal_link(&mut from) {
trace_error_exit(&e, 1);
}
},
Ok(None) => {
// looks like this is not an entry, but a filesystem URI and therefor an
// external link...?
if PathBuf::from(value).is_file() {
let url = Url::parse(value).map_err_trace_exit_unwrap(1);
from.remove_external_link(rt.store(), url).map_err_trace_exit_unwrap(1);
info!("Ok: {}", value);
} else {
warn!("Entry not found: {:?}", value);
}
}
} }
} }
Ok(()) });
}) }
.map_err_trace()
.map_info_str("Ok") fn list_linkings(rt: &Runtime) {
.ok(); let cmd = rt.cli()
.subcommand_matches("list")
.unwrap(); // safed by clap
let list_externals = cmd.is_present("list-externals-too");
for entry in cmd.values_of("entries").unwrap() { // safed by clap
match rt.store().get(PathBuf::from(entry)) {
Ok(Some(entry)) => {
let mut i = 0;
for link in entry.get_internal_links().map_err_trace_exit_unwrap(1) {
let link = link
.to_str()
.map_warn_err(|e| format!("Failed to convert StoreId to string: {:?}", e))
.ok();
if let Some(link) = link {
println!("{: <3}: {}", i, link);
i += 1;
}
}
if list_externals {
for link in entry.get_external_links(rt.store()).map_err_trace_exit_unwrap(1) {
let link = link
.map_err_trace_exit_unwrap(1)
.into_string();
println!("{: <3}: {}", i, link);
i += 1;
}
}
},
Ok(None) => warn!("Not found: {}", entry),
Err(e) => trace_error(&e),
}
}
} }
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use handle_internal_linking; use super::link_from_to;
use super::remove_linking;
use std::path::PathBuf; use std::path::PathBuf;
use std::ffi::OsStr; use std::ffi::OsStr;
@ -399,7 +310,7 @@ mod tests {
let test_id1 = create_test_default_entry(&rt, "test1").unwrap(); let test_id1 = create_test_default_entry(&rt, "test1").unwrap();
let test_id2 = create_test_default_entry(&rt, "test2").unwrap(); let test_id2 = create_test_default_entry(&rt, "test2").unwrap();
handle_internal_linking(&rt); link_from_to(&rt, "test1", vec!["test2"].into_iter());
let test_entry1 = rt.store().get(test_id1).unwrap().unwrap(); let test_entry1 = rt.store().get(test_id1).unwrap().unwrap();
let test_links1 = get_entry_links(&test_entry1).unwrap(); let test_links1 = get_entry_links(&test_entry1).unwrap();
@ -419,7 +330,7 @@ mod tests {
let test_id1 = create_test_default_entry(&rt, "test1").unwrap(); let test_id1 = create_test_default_entry(&rt, "test1").unwrap();
let test_id2 = create_test_default_entry(&rt, "test2").unwrap(); let test_id2 = create_test_default_entry(&rt, "test2").unwrap();
handle_internal_linking(&rt); link_from_to(&rt, "test1", vec!["test2"].into_iter());
let test_entry1 = rt.store().get(test_id1).unwrap().unwrap(); let test_entry1 = rt.store().get(test_id1).unwrap().unwrap();
let test_links1 = get_entry_links(&test_entry1).unwrap(); let test_links1 = get_entry_links(&test_entry1).unwrap();
@ -439,8 +350,8 @@ mod tests {
let test_id1 = create_test_default_entry(&rt, "test1").unwrap(); let test_id1 = create_test_default_entry(&rt, "test1").unwrap();
let test_id2 = create_test_default_entry(&rt, "test2").unwrap(); let test_id2 = create_test_default_entry(&rt, "test2").unwrap();
handle_internal_linking(&rt); link_from_to(&rt, "test1", vec!["test2"].into_iter());
handle_internal_linking(&rt); link_from_to(&rt, "test1", vec!["test2"].into_iter());
let test_entry1 = rt.store().get(test_id1).unwrap().unwrap(); let test_entry1 = rt.store().get(test_id1).unwrap().unwrap();
let test_links1 = get_entry_links(&test_entry1).unwrap(); let test_links1 = get_entry_links(&test_entry1).unwrap();
@ -461,8 +372,8 @@ mod tests {
let test_id2 = create_test_default_entry(&rt, "test2").unwrap(); let test_id2 = create_test_default_entry(&rt, "test2").unwrap();
let test_id3 = create_test_default_entry(&rt, "test3").unwrap(); let test_id3 = create_test_default_entry(&rt, "test3").unwrap();
handle_internal_linking(&rt); link_from_to(&rt, "test1", vec!["test2", "test3"].into_iter());
handle_internal_linking(&rt); link_from_to(&rt, "test1", vec!["test2", "test3"].into_iter());
let test_entry1 = rt.store().get(test_id1).unwrap().unwrap(); let test_entry1 = rt.store().get(test_id1).unwrap().unwrap();
let test_links1 = get_entry_links(&test_entry1).unwrap(); let test_links1 = get_entry_links(&test_entry1).unwrap();
@ -488,12 +399,12 @@ mod tests {
let test_id1 = create_test_default_entry(&rt, "test1").unwrap(); let test_id1 = create_test_default_entry(&rt, "test1").unwrap();
let test_id2 = create_test_default_entry(&rt, "test2").unwrap(); let test_id2 = create_test_default_entry(&rt, "test2").unwrap();
handle_internal_linking(&rt); link_from_to(&rt, "test1", vec!["test2"].into_iter());
let rt = reset_test_runtime(vec!["internal", "remove", "test1", "test2"], rt) let rt = reset_test_runtime(vec!["remove", "test1", "test2"], rt)
.unwrap(); .unwrap();
handle_internal_linking(&rt); remove_linking(&rt);
let test_entry1 = rt.store().get(test_id1).unwrap().unwrap(); let test_entry1 = rt.store().get(test_id1).unwrap().unwrap();
let test_links1 = get_entry_links(&test_entry1).unwrap(); let test_links1 = get_entry_links(&test_entry1).unwrap();
@ -514,12 +425,12 @@ mod tests {
let test_id2 = create_test_default_entry(&rt, "test2").unwrap(); let test_id2 = create_test_default_entry(&rt, "test2").unwrap();
let test_id3 = create_test_default_entry(&rt, "test3").unwrap(); let test_id3 = create_test_default_entry(&rt, "test3").unwrap();
handle_internal_linking(&rt); link_from_to(&rt, "test1", vec!["test2", "test3"].into_iter());
let rt = reset_test_runtime(vec!["internal", "remove", "test1", "test2", "test3"], rt) let rt = reset_test_runtime(vec!["remove", "test1", "test2", "test3"], rt)
.unwrap(); .unwrap();
handle_internal_linking(&rt); remove_linking(&rt);
let test_entry1 = rt.store().get(test_id1).unwrap().unwrap(); let test_entry1 = rt.store().get(test_id1).unwrap().unwrap();
let test_links1 = get_entry_links(&test_entry1).unwrap(); let test_links1 = get_entry_links(&test_entry1).unwrap();

View file

@ -17,121 +17,69 @@
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
// //
use clap::{Arg, ArgGroup, App, SubCommand}; use clap::{Arg, App, SubCommand};
pub fn build_ui<'a>(app: App<'a, 'a>) -> App<'a, 'a> { pub fn build_ui<'a>(app: App<'a, 'a>) -> App<'a, 'a> {
app app
.subcommand(SubCommand::with_name("internal") .subcommand(SubCommand::with_name("remove")
.about("Add, remove and list internal links") .about("Remove a link between two or more entries")
.version("0.1") .version("0.1")
.subcommand(SubCommand::with_name("add") .arg(Arg::with_name("from")
.about("Add link from one entry to another (and vice-versa)") .index(1)
.version("0.1") .takes_value(true)
.arg(Arg::with_name("from") .required(true)
.index(1) .multiple(false)
.takes_value(true) .help("Remove Link from this entry")
.required(true) .value_name("ENTRY"))
.multiple(false) .arg(Arg::with_name("to")
.help("Link from this entry") .index(2)
.value_name("ENTRY")) .takes_value(true)
.arg(Arg::with_name("to") .required(true)
.index(2) .multiple(true)
.takes_value(true) .help("Remove links to these entries")
.required(true) .value_name("ENTRIES"))
.multiple(true) )
.help("Link to this entries")
.value_name("ENTRIES"))
)
.subcommand(SubCommand::with_name("remove") .subcommand(SubCommand::with_name("list")
.about("Remove a link between two or more entries") .about("List links to this entry")
.version("0.1") .version("0.1")
.arg(Arg::with_name("from") .arg(Arg::with_name("entries")
.index(1) .index(1)
.takes_value(true) .takes_value(true)
.required(true) .multiple(true)
.multiple(false) .required(true)
.help("Remove Link from this entry") .help("List these entries, seperate by comma")
.value_name("ENTRY")) .value_name("ENTRIES"))
.arg(Arg::with_name("to")
.index(2)
.takes_value(true)
.required(true)
.multiple(true)
.help("Remove links to these entries")
.value_name("ENTRIES"))
)
.subcommand(SubCommand::with_name("list") .arg(Arg::with_name("list-externals-too")
.about("List links to this entry") .long("list-external")
.version("0.1") .takes_value(false)
.arg(Arg::with_name("entries") .required(false)
.index(1) .help("Also list external links (debugging helper that might be removed at some point"))
.takes_value(true) )
.multiple(true)
.required(true)
.help("List these entries, seperate by comma")
.value_name("ENTRIES"))
.arg(Arg::with_name("list-externals-too") .arg(Arg::with_name("check-consistency")
.long("list-external") .long("check-consistency")
.takes_value(false) .short("C")
.required(false) .takes_value(false)
.help("If --list is provided, also list external links (debugging helper that might be removed at some point")) .required(false)
) .help("Check the link-consistency in the store (might be time-consuming)"))
.arg(Arg::with_name("check-consistency") .arg(Arg::with_name("from")
.long("check-consistency") .index(1)
.short("C") .takes_value(true)
.takes_value(false) .required(false)
.required(false) .multiple(false)
.help("Check the link-consistency in the store (might be time-consuming)")) .help("Link from this entry")
) .requires("to")
.subcommand(SubCommand::with_name("external") .value_name("ENTRY"))
.about("Add and remove external links")
.version("0.1")
.arg(Arg::with_name("id") .arg(Arg::with_name("to")
.long("id") .index(2)
.short("i") .takes_value(true)
.takes_value(true) .required(false)
.required(true) .multiple(true)
.help("Modify external link of this entry") .help("Link to this entries")
.value_name("ENTRY")) .requires("from")
.value_name("ENTRIES"))
.arg(Arg::with_name("add")
.long("add")
.short("a")
.takes_value(true)
.required(false)
.help("Add this URI as external link")
.value_name("URI"))
.arg(Arg::with_name("remove")
.long("remove")
.short("r")
.takes_value(true)
.required(true)
.help("Remove one external link"))
.arg(Arg::with_name("set")
.long("set")
.short("s")
.takes_value(true)
.required(false)
.help("Set these URIs as external link (seperate by comma)")
.value_name("URIs"))
.arg(Arg::with_name("list")
.long("list")
.short("l")
.takes_value(false)
.required(false)
.help("List external links"))
.group(ArgGroup::with_name("external-link-group")
.args(&["add", "remove", "set", "list"])
.required(true))
)
} }

View file

@ -35,6 +35,8 @@ This section contains the changelog from the last release to the next release.
variable still possible. variable still possible.
* `imag-contact` was added (with basic contact support so far). * `imag-contact` was added (with basic contact support so far).
* `imag-habit` was introduced * `imag-habit` was introduced
* `imag-link` commandline was redesigned to be easier but with the same
features.
* Minor changes * Minor changes
* `libimagentryannotation` got a rewrite, is not based on `libimagnotes` * `libimagentryannotation` got a rewrite, is not based on `libimagnotes`