diff --git a/bin/core/imag-tag/src/lib.rs b/bin/core/imag-tag/src/lib.rs index 83f58722..874ebf5a 100644 --- a/bin/core/imag-tag/src/lib.rs +++ b/bin/core/imag-tag/src/lib.rs @@ -66,10 +66,12 @@ use failure::Error; use failure::err_msg; use resiter::AndThen; use resiter::Map; +use resiter::FilterMap; use libimagrt::runtime::Runtime; use libimagrt::application::ImagApplication; use libimagentrytag::tagable::Tagable; +use libimagentrytag::tag::is_tag_str; use libimagentrytag::tag::Tag; use libimagstore::storeid::StoreId; @@ -86,51 +88,118 @@ pub enum ImagTag {} impl ImagApplication for ImagTag { fn run(rt: Runtime) -> Result<()> { let process = |iter: &mut dyn Iterator>| -> Result<()> { - if let Some(name) = rt.cli().subcommand_name() { - match name { - "list" => iter - .map_ok(|id| list(id, &rt)) + match rt.cli().subcommand() { + ("list", _) => iter + .map_ok(|id| list(id, &rt)) + .collect::>>() + .map(|_| ()), + + ("remove", _) => iter.and_then_ok(|id| { + let add = None; + let rem = get_remove_tags(rt.cli())?; + debug!("id = {:?}, add = {:?}, rem = {:?}", id, add, rem); + alter(&rt, id, add, rem) + }).collect(), + + ("add", _) => iter.and_then_ok(|id| { + let add = get_add_tags(rt.cli())?; + let rem = None; + debug!("id = {:?}, add = {:?}, rem = {:?}", id, add, rem); + alter(&rt, id, add, rem) + }).collect(), + + ("present", Some(scmd)) => { + let must_be_present = scmd + .values_of("present-tag") + .unwrap() + .map(String::from) + .collect::>(); + + must_be_present.iter().map(|t| is_tag_str(t)).collect::>>()?; + + iter.filter_map_ok(|id| { + match rt.store().get(id.clone()) { + Err(e) => Some(Err(e)), + Ok(None) => Some(Err(format_err!("No entry for id {}", id))), + Ok(Some(entry)) => { + let entry_tags = match entry.get_tags() { + Err(e) => return Some(Err(e)), + Ok(e) => e, + }; + + if must_be_present.iter().all(|pres| entry_tags.contains(pres)) { + Some(Ok(entry)) + } else { + None + } + } + } + }) + .flatten() + .and_then_ok(|e| { + if !rt.output_is_pipe() { + writeln!(rt.stdout(), "{}", e.get_location())?; + } + Ok(e) + }) + .and_then_ok(|e| rt.report_touched(e.get_location()).map_err(Error::from)) .collect::>>() - .map(|_| ()), + .map(|_| ()) + }, - "remove" => iter.and_then_ok(|id| { - let add = None; - let rem = get_remove_tags(rt.cli())?; - debug!("id = {:?}, add = {:?}, rem = {:?}", id, add, rem); - alter(&rt, id, add, rem) - }).collect(), + ("missing", Some(scmd)) => { + let must_be_missing = scmd + .values_of("missing-tag") + .unwrap() + .map(String::from) + .collect::>(); - "add" => iter.and_then_ok(|id| { - let add = get_add_tags(rt.cli())?; - let rem = None; - debug!("id = {:?}, add = {:?}, rem = {:?}", id, add, rem); - alter(&rt, id, add, rem) - }).collect(), + must_be_missing.iter().map(|t| is_tag_str(t)).collect::>>()?; - other => { - debug!("Unknown command"); - if rt.handle_unknown_subcommand("imag-tag", other, rt.cli())?.success() { - Ok(()) - } else { - Err(format_err!("Subcommand failed")) - } - }, - } - } else { - Ok(()) + iter.filter_map_ok(|id| { + match rt.store().get(id.clone()) { + Err(e) => Some(Err(e)), + Ok(None) => Some(Err(format_err!("No entry for id {}", id))), + Ok(Some(entry)) => { + let entry_tags = match entry.get_tags() { + Err(e) => return Some(Err(e)), + Ok(e) => e, + }; + + if must_be_missing.iter().all(|miss| !entry_tags.contains(miss)) { + Some(Ok(entry)) + } else { + None + } + } + } + }) + .flatten() + .and_then_ok(|e| { + if !rt.output_is_pipe() { + writeln!(rt.stdout(), "{}", e.get_location())?; + } + Ok(e) + }) + .and_then_ok(|e| rt.report_touched(e.get_location()).map_err(Error::from)) + .collect::>>() + .map(|_| ()) + }, + + (other, _) => { + debug!("Unknown command"); + if rt.handle_unknown_subcommand("imag-tag", other, rt.cli())?.success() { + Ok(()) + } else { + Err(format_err!("Subcommand failed")) + } + }, } }; - if rt.ids_from_stdin() { - debug!("Fetching IDs from stdin..."); - let mut iter = rt.ids::()? - .ok_or_else(|| err_msg("No ids supplied"))? - .into_iter() - .map(Ok); - - process(&mut iter) - } else { - process(&mut rt.store().entries()?) + match rt.ids::()? { + Some(ids) => process(&mut ids.into_iter().map(Ok)), + None => process(&mut rt.store().entries()?), } } diff --git a/bin/core/imag-tag/src/ui.rs b/bin/core/imag-tag/src/ui.rs index ba3a7859..a4a00f21 100644 --- a/bin/core/imag-tag/src/ui.rs +++ b/bin/core/imag-tag/src/ui.rs @@ -101,6 +101,26 @@ pub fn build_ui<'a>(app: App<'a, 'a>) -> App<'a, 'a> { .required(true)) ) + .subcommand(SubCommand::with_name("present") + .about("List entries that have a certain tag") + .version("0.1") + .arg(Arg::with_name("present-tag") + .index(1) + .required(true) + .multiple(true) + .help("Tag to list entries for")) + ) + + .subcommand(SubCommand::with_name("missing") + .about("List entries miss a certain tag") + .version("0.1") + .arg(Arg::with_name("missing-tag") + .index(1) + .required(true) + .multiple(true) + .help("Tag which should be missing in the entries")) + ) + } pub struct PathProvider; diff --git a/tests/ui/src/imag_tag.rs b/tests/ui/src/imag_tag.rs index 352dcf25..77784a1e 100644 --- a/tests/ui/src/imag_tag.rs +++ b/tests/ui/src/imag_tag.rs @@ -34,6 +34,14 @@ pub fn call(tmpdir: &TempDir, args: &[&str]) -> Vec { crate::imag::stdout_of_command(binary) } +pub fn call_give_ids(tmpdir: &TempDir, args: &[&str]) -> Vec { + let mut binary = binary(tmpdir); + binary.stdin(std::process::Stdio::inherit()); + binary.args(args); + debug!("Command = {:?}", binary); + crate::imag::stdout_of_command(binary) +} + #[test] fn test_new_entry_has_no_tags() { crate::setup_logging(); @@ -94,3 +102,43 @@ fn test_adding_twice_does_not_add_twice() { assert_eq!(output[0], "tag"); } +#[test] +fn test_listing_entries_with_certain_tag() { + crate::setup_logging(); + let imag_home = crate::imag::make_temphome(); + crate::imag_init::call(&imag_home); + crate::imag_create::call(&imag_home, &["test1", "test2", "test3"]); + let _ = call(&imag_home, &["test1", "add", "tag1"]); + let _ = call(&imag_home, &["test2", "add", "tag2"]); + let _ = call(&imag_home, &["test3", "add", "tag3"]); + + let output = call_give_ids(&imag_home, &["present", "tag1"]); + debug!("output = {:?}", output); + + assert!(!output.is_empty()); + assert_eq!(output.len(), 1); + assert!(output[0].contains("test1")); + assert!(output.iter().all(|s| !s.contains("test2"))); + assert!(output.iter().all(|s| !s.contains("test3"))); +} + +#[test] +fn test_listing_entries_without_certain_tag() { + crate::setup_logging(); + let imag_home = crate::imag::make_temphome(); + crate::imag_init::call(&imag_home); + crate::imag_create::call(&imag_home, &["test1", "test2", "test3"]); + let _ = call(&imag_home, &["test1", "add", "tag1"]); + let _ = call(&imag_home, &["test2", "add", "tag2"]); + let _ = call(&imag_home, &["test3", "add", "tag3"]); + + let output = call_give_ids(&imag_home, &["missing", "tag1"]); + debug!("output = {:?}", output); + + assert!(!output.is_empty()); + assert_eq!(output.len(), 2); + assert!(output.iter().any(|s| s.contains("test2"))); + assert!(output.iter().any(|s| s.contains("test3"))); + assert!(output.iter().all(|s| !s.contains("test1"))); +} +