diff options
-rw-r--r-- | imag-tag/Cargo.toml | 9 | ||||
-rw-r--r-- | imag-tag/src/main.rs | 204 | ||||
-rw-r--r-- | libimagentrytag/src/lib.rs | 1 | ||||
-rw-r--r-- | libimagentrytag/src/tag.rs | 5 | ||||
-rw-r--r-- | libimagentrytag/src/tagable.rs | 12 | ||||
-rw-r--r-- | libimagentrytag/src/ui.rs | 3 | ||||
-rw-r--r-- | libimagentrytag/src/util.rs | 24 | ||||
-rw-r--r-- | libimagutil/src/cli_validators.rs | 9 |
8 files changed, 224 insertions, 43 deletions
diff --git a/imag-tag/Cargo.toml b/imag-tag/Cargo.toml index 2cdc7823..0388270e 100644 --- a/imag-tag/Cargo.toml +++ b/imag-tag/Cargo.toml @@ -35,3 +35,12 @@ path = "../libimagentrytag" [dependencies.libimagutil] path = "../libimagutil" +[dev-dependencies.libimagutil] +path = "../libimagutil" +default-features = false +features = ["testing"] + +[dev-dependencies] +toml-query = "0.3.0" +env_logger = "0.3" + diff --git a/imag-tag/src/main.rs b/imag-tag/src/main.rs index d0a83725..7e710c9b 100644 --- a/imag-tag/src/main.rs +++ b/imag-tag/src/main.rs @@ -27,8 +27,16 @@ extern crate libimagstore; extern crate libimagrt; extern crate libimagentrytag; extern crate libimagerror; + +#[macro_use] extern crate libimagutil; +#[cfg(test)] +extern crate toml_query; + +#[cfg(test)] +extern crate env_logger; + use std::path::PathBuf; use libimagrt::runtime::Runtime; @@ -169,3 +177,199 @@ fn list(id: PathBuf, rt: &Runtime) { } } +#[cfg(test)] +mod tests { + use std::path::PathBuf; + use std::ffi::OsStr; + + use toml::value::Value; + use toml_query::read::TomlValueReadExt; + use toml_query::error::Result as TomlQueryResult; + + use libimagentrytag::ui::{get_add_tags, get_remove_tags}; + use libimagrt::runtime::Runtime; + use libimagstore::storeid::StoreId; + use libimagstore::store::{Result as StoreResult, FileLockEntry}; + + use super::alter; + + make_mock_app! { + app "imag-tag"; + modulename mock; + version "0.3.0"; + with help "imag-tag mocking app"; + } + use self::mock::generate_test_runtime; + use libimagutil::testing::DEFAULT_ENTRY; + + fn create_test_default_entry<'a, S: AsRef<OsStr>>(rt: &'a Runtime, name: S) -> StoreResult<StoreId> { + let mut path = PathBuf::new(); + path.set_file_name(name); + + let id = StoreId::new_baseless(path)?; + let mut entry = rt.store().create(id.clone())?; + entry.get_content_mut().push_str(DEFAULT_ENTRY); + + Ok(id) + } + + fn get_entry_tags<'a>(entry: &'a FileLockEntry<'a>) -> TomlQueryResult<Option<&'a Value>> { + entry.get_header().read(&"imag.tags".to_owned()) + } + + fn tags_toml_value<'a, I: IntoIterator<Item = &'static str>>(tags: I) -> Value { + Value::Array(tags.into_iter().map(|s| Value::String(s.to_owned())).collect()) + } + + fn setup_logging() { + use env_logger; + let _ = env_logger::init().unwrap_or(()); + } + + #[test] + fn test_tag_add_adds_tag() { + setup_logging(); + debug!("Generating runtime"); + let rt = generate_test_runtime(vec!["--id", "test", "--add", "foo"]).unwrap(); + + debug!("Creating default entry"); + create_test_default_entry(&rt, "test").unwrap(); + let id = PathBuf::from(String::from("test")); + + debug!("Getting 'add' tags"); + let add = get_add_tags(rt.cli()); + debug!("Add-tags: {:?}", add); + + debug!("Getting 'remove' tags"); + let rem = get_remove_tags(rt.cli()); + debug!("Rem-tags: {:?}", rem); + + debug!("Altering things"); + alter(&rt, id.clone(), add, rem); + debug!("Altered"); + + let test_entry = rt.store().get(id).unwrap().unwrap(); + let test_tags = get_entry_tags(&test_entry).unwrap().unwrap(); + + assert_ne!(*test_tags, tags_toml_value(vec![])); + assert_eq!(*test_tags, tags_toml_value(vec!["foo"])); + } + + #[test] + fn test_tag_add_more_than_remove_adds_tags() { + setup_logging(); + debug!("Generating runtime"); + let rt = generate_test_runtime(vec!["--id", "test", + "--add", "foo", + "--add", "bar", + "--add", "baz", + "--add", "bub", + "--remove", "foo", + "--remove", "bar", + "--remove", "baz", + ]).unwrap(); + + debug!("Creating default entry"); + create_test_default_entry(&rt, "test").unwrap(); + let id = PathBuf::from(String::from("test")); + + // Manually add tags + let add = get_add_tags(rt.cli()); + + debug!("Getting 'remove' tags"); + let rem = get_remove_tags(rt.cli()); + debug!("Rem-tags: {:?}", rem); + + debug!("Altering things"); + alter(&rt, id.clone(), add, rem); + debug!("Altered"); + + let test_entry = rt.store().get(id).unwrap().unwrap(); + let test_tags = get_entry_tags(&test_entry).unwrap().unwrap(); + + assert_eq!(*test_tags, tags_toml_value(vec!["bub"])); + } + + #[test] + fn test_tag_remove_removes_tag() { + setup_logging(); + debug!("Generating runtime"); + let rt = generate_test_runtime(vec!["--id", "test", "--remove", "foo"]).unwrap(); + + debug!("Creating default entry"); + create_test_default_entry(&rt, "test").unwrap(); + let id = PathBuf::from(String::from("test")); + + // Manually add tags + let add = Some(vec![ "foo".to_owned() ]); + + debug!("Getting 'remove' tags"); + let rem = get_remove_tags(rt.cli()); + debug!("Rem-tags: {:?}", rem); + + debug!("Altering things"); + alter(&rt, id.clone(), add, rem); + debug!("Altered"); + + let test_entry = rt.store().get(id).unwrap().unwrap(); + let test_tags = get_entry_tags(&test_entry).unwrap().unwrap(); + + assert_eq!(*test_tags, tags_toml_value(vec![])); + } + + #[test] + fn test_tag_remove_removes_only_to_remove_tag() { + setup_logging(); + debug!("Generating runtime"); + let rt = generate_test_runtime(vec!["--id", "test", "--remove", "foo"]).unwrap(); + + debug!("Creating default entry"); + create_test_default_entry(&rt, "test").unwrap(); + let id = PathBuf::from(String::from("test")); + + // Manually add tags + let add = Some(vec![ "foo".to_owned(), "bar".to_owned() ]); + + debug!("Getting 'remove' tags"); + let rem = get_remove_tags(rt.cli()); + debug!("Rem-tags: {:?}", rem); + + debug!("Altering things"); + alter(&rt, id.clone(), add, rem); + debug!("Altered"); + + let test_entry = rt.store().get(id).unwrap().unwrap(); + let test_tags = get_entry_tags(&test_entry).unwrap().unwrap(); + + assert_eq!(*test_tags, tags_toml_value(vec!["bar"])); + } + + #[test] + fn test_tag_remove_removes_but_doesnt_crash_on_nonexistent_tag() { + setup_logging(); + debug!("Generating runtime"); + let rt = generate_test_runtime(vec!["--id", "test", "--remove", "foo", "--remove", "bar"]).unwrap(); + + debug!("Creating default entry"); + create_test_default_entry(&rt, "test").unwrap(); + let id = PathBuf::from(String::from("test")); + + // Manually add tags + let add = Some(vec![ "foo".to_owned() ]); + + debug!("Getting 'remove' tags"); + let rem = get_remove_tags(rt.cli()); + debug!("Rem-tags: {:?}", rem); + + debug!("Altering things"); + alter(&rt, id.clone(), add, rem); + debug!("Altered"); + + let test_entry = rt.store().get(id).unwrap().unwrap(); + let test_tags = get_entry_tags(&test_entry).unwrap().unwrap(); + + assert_eq!(*test_tags, tags_toml_value(vec![])); + } + +} + diff --git a/libimagentrytag/src/lib.rs b/libimagentrytag/src/lib.rs index 6cac9690..bfc4d461 100644 --- a/libimagentrytag/src/lib.rs +++ b/libimagentrytag/src/lib.rs @@ -48,6 +48,5 @@ pub mod exec; pub mod result; pub mod tag; pub mod tagable; -pub mod util; pub mod ui; diff --git a/libimagentrytag/src/tag.rs b/libimagentrytag/src/tag.rs index 6cfcf1c8..be968da7 100644 --- a/libimagentrytag/src/tag.rs +++ b/libimagentrytag/src/tag.rs @@ -17,6 +17,8 @@ // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA // +use regex::Regex; + pub type Tag = String; pub type TagSlice<'a> = &'a str; @@ -31,8 +33,9 @@ pub fn is_tag_str(s: &String) -> Result<(), String> { let is_lower = |s: &String| s.chars().all(|c| c.is_lowercase()); let no_whitespace = |s: &String| s.chars().all(|c| !c.is_whitespace()); let is_alphanum = |s: &String| s.chars().all(|c| c.is_alphanumeric()); + let matches_regex = |s: &String| Regex::new("^[a-zA-Z]([a-zA-Z0-9_-]*)$").unwrap().captures(s).is_some(); - if is_lower.and(no_whitespace).and(is_alphanum).filter(s) { + if is_lower.and(no_whitespace).and(is_alphanum).and(matches_regex).filter(s) { Ok(()) } else { Err(format!("The string '{}' is not a valid tag", s)) diff --git a/libimagentrytag/src/tagable.rs b/libimagentrytag/src/tagable.rs index 9e0fa2fd..bd53a122 100644 --- a/libimagentrytag/src/tagable.rs +++ b/libimagentrytag/src/tagable.rs @@ -27,7 +27,7 @@ use error::TagErrorKind; use error::MapErrInto; use result::Result; use tag::{Tag, TagSlice}; -use util::is_tag; +use tag::is_tag_str; use toml::Value; @@ -55,7 +55,7 @@ impl Tagable for Value { return Err(TagErrorKind::TagTypeError.into()); } if tags.iter().any(|t| match *t { - Value::String(ref s) => !is_tag(s), + Value::String(ref s) => !is_tag_str(s).is_ok(), _ => unreachable!()}) { return Err(TagErrorKind::NotATag.into()); @@ -77,8 +77,8 @@ impl Tagable for Value { } fn set_tags(&mut self, ts: &[Tag]) -> Result<()> { - if ts.iter().any(|tag| !is_tag(tag)) { - debug!("Not a tag: '{}'", ts.iter().filter(|t| !is_tag(t)).next().unwrap()); + if ts.iter().any(|tag| !is_tag_str(tag).is_ok()) { + debug!("Not a tag: '{}'", ts.iter().filter(|t| !is_tag_str(t).is_ok()).next().unwrap()); return Err(TagErrorKind::NotATag.into()); } @@ -90,7 +90,7 @@ impl Tagable for Value { } fn add_tag(&mut self, t: Tag) -> Result<()> { - if !is_tag(&t) { + if !try!(is_tag_str(&t).map(|_| true).map_err(|_| TagErrorKind::NotATag.into_error())) { debug!("Not a tag: '{}'", t); return Err(TagErrorKind::NotATag.into()); } @@ -104,7 +104,7 @@ impl Tagable for Value { } fn remove_tag(&mut self, t: Tag) -> Result<()> { - if !is_tag(&t) { + if !try!(is_tag_str(&t).map(|_| true).map_err(|_| TagErrorKind::NotATag.into_error())) { debug!("Not a tag: '{}'", t); return Err(TagErrorKind::NotATag.into()); } diff --git a/libimagentrytag/src/ui.rs b/libimagentrytag/src/ui.rs index dad4c87e..734708b3 100644 --- a/libimagentrytag/src/ui.rs +++ b/libimagentrytag/src/ui.rs @@ -20,8 +20,7 @@ use clap::{Arg, ArgMatches, App, SubCommand}; use tag::Tag; - -use libimagutil::cli_validators::is_tag; +use tag::is_tag; /// Generates a `clap::SubCommand` to be integrated in the commandline-ui builder for building a /// "tags --add foo --remove bar" subcommand to do tagging action. diff --git a/libimagentrytag/src/util.rs b/libimagentrytag/src/util.rs deleted file mode 100644 index d0d31db2..00000000 --- a/libimagentrytag/src/util.rs +++ /dev/null @@ -1,24 +0,0 @@ -// -// imag - the personal information management suite for the commandline -// Copyright (C) 2015, 2016 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 regex::Regex; - -pub fn is_tag(s: &str) -> bool { - Regex::new("^[a-zA-Z]([a-zA-Z0-9_-]*)$").unwrap().captures(s).is_some() -} diff --git a/libimagutil/src/cli_validators.rs b/libimagutil/src/cli_validators.rs index bbd376f2..24340a4d 100644 --- a/libimagutil/src/cli_validators.rs +++ b/libimagutil/src/cli_validators.rs @@ -47,12 +47,3 @@ pub fn is_url(s: String) -> Result<(), String> { Url::parse(&s).map(|_| ()).map_err(|_| format!("Not a URL: {}", s)) } -pub fn is_tag(s: String) -> Result<(), String> { - use regex::Regex; - lazy_static! { static ref TAG_RE : Regex = Regex::new("[:alpha:][:word:]*").unwrap(); } - - TAG_RE - .is_match(&s) - .as_result((), format!("Not a valid Tag: '{}' - Valid is [a-zA-Z][0-9a-zA-Z]*", s)) -} - |