summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMatthias Beyer <mail@beyermatthias.de>2019-06-29 22:54:26 +0200
committerMatthias Beyer <mail@beyermatthias.de>2019-06-29 22:54:26 +0200
commit57039654ca388d2d7a24dfdc05ec3de9ea113629 (patch)
tree9fd35acebb048199e52b31144b98e9a713655274
parent1047a06a64be862acc0de8aabab4c11996099824 (diff)
parent0d5027051dcf2f381e3ff1c3cdc4979fa2566e16 (diff)
downloadimag-57039654ca388d2d7a24dfdc05ec3de9ea113629.zip
imag-57039654ca388d2d7a24dfdc05ec3de9ea113629.tar.gz
Merge branch 'libimagentrytag-refactor' into master
-rw-r--r--bin/core/imag-tag/Cargo.toml2
-rw-r--r--lib/entry/libimagentrytag/Cargo.toml12
-rw-r--r--lib/entry/libimagentrytag/src/lib.rs5
-rw-r--r--lib/entry/libimagentrytag/src/tag.rs7
-rw-r--r--lib/entry/libimagentrytag/src/tagable.rs247
5 files changed, 159 insertions, 114 deletions
diff --git a/bin/core/imag-tag/Cargo.toml b/bin/core/imag-tag/Cargo.toml
index 6f3f613..31faafb 100644
--- a/bin/core/imag-tag/Cargo.toml
+++ b/bin/core/imag-tag/Cargo.toml
@@ -36,7 +36,7 @@ default-features = false
features = ["color", "suggestions", "wrap_help"]
[dev-dependencies]
-toml-query = "0.9"
+toml-query = "0.9.2"
env_logger = "0.5"
[dev-dependencies.libimagutil]
diff --git a/lib/entry/libimagentrytag/Cargo.toml b/lib/entry/libimagentrytag/Cargo.toml
index 2b4fca2..da5a6a7 100644
--- a/lib/entry/libimagentrytag/Cargo.toml
+++ b/lib/entry/libimagentrytag/Cargo.toml
@@ -24,16 +24,24 @@ log = "0.4.0"
regex = "1"
toml = "0.5"
itertools = "0.7"
-is-match = "0.1"
filters = "0.3"
-toml-query = "0.9"
failure = "0.1"
+serde = "1"
+serde_derive = "1"
libimagstore = { version = "0.10.0", path = "../../../lib/core/libimagstore" }
libimagerror = { version = "0.10.0", path = "../../../lib/core/libimagerror" }
+[dependencies.toml-query]
+version = "0.9.2"
+default-features = false
+features = [ "typed" ]
+
[dependencies.clap]
version = "^2.29"
default-features = false
features = ["color", "suggestions", "wrap_help"]
+[dev-dependencies]
+env_logger = "0.5"
+
diff --git a/lib/entry/libimagentrytag/src/lib.rs b/lib/entry/libimagentrytag/src/lib.rs
index ba94440..c036fca 100644
--- a/lib/entry/libimagentrytag/src/lib.rs
+++ b/lib/entry/libimagentrytag/src/lib.rs
@@ -43,10 +43,13 @@ extern crate itertools;
extern crate regex;
extern crate toml;
extern crate toml_query;
-#[macro_use] extern crate is_match;
+extern crate serde;
+#[macro_use] extern crate serde_derive;
extern crate filters;
#[macro_use] extern crate failure;
+#[cfg(test)] extern crate env_logger;
+
extern crate libimagstore;
extern crate libimagerror;
diff --git a/lib/entry/libimagentrytag/src/tag.rs b/lib/entry/libimagentrytag/src/tag.rs
index 227bc54..c9607bb 100644
--- a/lib/entry/libimagentrytag/src/tag.rs
+++ b/lib/entry/libimagentrytag/src/tag.rs
@@ -20,16 +20,17 @@
use std::result::Result;
use regex::Regex;
+use failure::Error;
pub type Tag = String;
pub type TagSlice<'a> = &'a str;
/// validator which can be used by clap to validate that a string is a valid tag
pub fn is_tag(s: String) -> Result<(), String> {
- is_tag_str(&s)
+ is_tag_str(&s).map_err(|_| format!("The string '{}' is not a valid tag", s))
}
-pub fn is_tag_str(s: &String) -> Result<(), String> {
+pub fn is_tag_str(s: &String) -> Result<(), Error> {
use filters::filter::Filter;
trace!("Checking whether '{}' is a valid tag", s);
@@ -41,7 +42,7 @@ pub fn is_tag_str(s: &String) -> Result<(), String> {
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))
+ Err(format_err!("The string '{}' is not a valid tag", s))
}
}
diff --git a/lib/entry/libimagentrytag/src/tagable.rs b/lib/entry/libimagentrytag/src/tagable.rs
index 196b3f0..5525331 100644
--- a/lib/entry/libimagentrytag/src/tagable.rs
+++ b/lib/entry/libimagentrytag/src/tagable.rs
@@ -23,17 +23,14 @@ use libimagstore::store::Entry;
use libimagerror::errors::ErrorMsg as EM;
use toml_query::read::TomlValueReadExt;
+use toml_query::read::Partial;
use toml_query::insert::TomlValueInsertExt;
use failure::Error;
-use failure::ResultExt;
use failure::Fallible as Result;
-use failure::err_msg;
use crate::tag::{Tag, TagSlice};
use crate::tag::is_tag_str;
-use toml::Value;
-
pub trait Tagable {
fn get_tags(&self) -> Result<Vec<Tag>>;
@@ -47,144 +44,180 @@ pub trait Tagable {
}
-impl Tagable for Value {
+#[derive(Serialize, Deserialize, Debug)]
+struct TagHeader {
+ values: Vec<String>,
+}
+
+impl<'a> Partial<'a> for TagHeader {
+ const LOCATION: &'static str = "tag";
+ type Output = Self;
+}
+
+impl Tagable for Entry {
fn get_tags(&self) -> Result<Vec<Tag>> {
- self.read("tag.values")
- .context(format_err!("Failed to read header at 'tag.values'"))
- .map_err(Error::from)
- .context(EM::EntryHeaderReadError)?
- .map(|val| {
- debug!("Got Value of tags...");
- val.as_array()
- .map(|tags| {
- debug!("Got Array<T> of tags...");
- if !tags.iter().all(|t| is_match!(*t, Value::String(_))) {
- return Err(format_err!("Tag type error: Got Array<T> where T is not a String: {:?}", tags));
- }
- debug!("Got Array<String> of tags...");
- if tags.iter().any(|t| match *t {
- Value::String(ref s) => !is_tag_str(s).is_ok(),
- _ => unreachable!()})
- {
- return Err(format_err!("At least one tag is not a valid tag string"));
- }
-
- Ok(tags.iter()
- .cloned()
- .map(|t| {
- match t {
- Value::String(s) => s,
- _ => unreachable!(),
- }
- })
- .collect())
- })
- .unwrap_or(Ok(vec![]))
+ self.get_header()
+ .read_partial::<TagHeader>()?
+ .map(|header| {
+ let _ = header.values
+ .iter()
+ .map(is_tag_str)
+ .collect::<Result<_>>()?;
+
+ Ok(header.values)
})
- .unwrap_or(Ok(vec![]))
+ .unwrap_or_else(|| Ok(vec![]))
}
fn set_tags(&mut self, ts: &[Tag]) -> Result<()> {
- if ts.iter().any(|tag| !is_tag_str(tag).is_ok()) {
- let not_tag = ts.iter().filter(|t| !is_tag_str(t).is_ok()).next().unwrap();
- return Err(format_err!("Not a tag: '{}'", not_tag));
- }
-
- let a = ts.iter().unique().map(|t| Value::String(t.clone())).collect();
- debug!("Setting tags = {:?}", a);
- self.insert("tag.values", Value::Array(a))
+ let _ = ts
+ .iter()
+ .map(is_tag_str)
+ .collect::<Result<Vec<_>>>()?;
+
+ let header = TagHeader {
+ values: ts.iter().unique().cloned().collect(),
+ };
+
+ debug!("Setting tags = {:?}", header);
+ self.get_header_mut()
+ .insert_serialized("tag", header)
.map(|_| ())
.map_err(|_| Error::from(EM::EntryHeaderWriteError))
}
fn add_tag(&mut self, t: Tag) -> Result<()> {
- if !is_tag_str(&t).map(|_| true)
- .map_err(|s| format_err!("{}", s))
- .context(err_msg("Not a tag"))?
- {
- return Err(format_err!("Not a tag: '{}'", t));
- }
-
- self.get_tags()
- .map(|mut tags| {
- debug!("Pushing tag = {:?} to list = {:?}", t, tags);
- tags.push(t);
- self.set_tags(&tags.into_iter().unique().collect::<Vec<_>>()[..])
- })
- .map(|_| ())
+ let _ = is_tag_str(&t)?;
+
+ let mut tags = self.get_tags()?;
+ debug!("Pushing tag = {:?} to list = {:?}", t, tags);
+ tags.push(t);
+ self.set_tags(&tags)
}
fn remove_tag(&mut self, t: Tag) -> Result<()> {
- if !is_tag_str(&t).map(|_| true)
- .map_err(|s| format_err!("{}", s))
- .context(err_msg("Not a tag"))?
- {
- debug!("Not a tag: '{}'", t);
- return Err(format_err!("Not a tag: '{}'", t));
- }
-
- self.get_tags()
- .map(|mut tags| {
- tags.retain(|tag| tag.clone() != t);
- self.set_tags(&tags[..])
- })
- .map(|_| ())
+ let _ = is_tag_str(&t)?;
+
+ let mut tags = self.get_tags()?;
+ tags.retain(|tag| *tag != t);
+ self.set_tags(&tags)
}
fn has_tag(&self, t: TagSlice) -> Result<bool> {
- let tags = self.read("tag.values").context(EM::EntryHeaderReadError)?;
-
- if !tags.iter().all(|t| is_match!(*t, &Value::String(_))) {
- return Err(err_msg("Tag type error"))
- }
-
- Ok(tags
- .iter()
- .any(|tag| {
- match *tag {
- &Value::String(ref s) => { s == t },
- _ => unreachable!()
- }
- }))
+ // use any() because Vec::contains() wants &String, but we do not want to allocate.
+ self.get_tags().map(|v| v.iter().any(|s| s == t))
}
fn has_tags(&self, tags: &[Tag]) -> Result<bool> {
- let mut result = true;
- for tag in tags {
- result = result && self.has_tag(tag)?;
- }
-
- Ok(result)
+ tags.iter().map(|t| self.has_tag(t)).fold(Ok(true), |a, e| a.and_then(|b| Ok(b && e?)))
}
+
}
-impl Tagable for Entry {
+#[cfg(test)]
+mod tests {
+ use std::path::PathBuf;
- fn get_tags(&self) -> Result<Vec<Tag>> {
- self.get_header().get_tags()
+ use toml_query::read::TomlValueReadTypeExt;
+
+ use libimagstore::store::Store;
+
+ use super::*;
+
+ fn setup_logging() {
+ let _ = ::env_logger::try_init();
}
- fn set_tags(&mut self, ts: &[Tag]) -> Result<()> {
- self.get_header_mut().set_tags(ts)
+ fn get_store() -> Store {
+ Store::new_inmemory(PathBuf::from("/"), &None).unwrap()
}
- fn add_tag(&mut self, t: Tag) -> Result<()> {
- self.get_header_mut().add_tag(t)
+ #[test]
+ fn test_tag_get_tag() {
+ setup_logging();
+ let store = get_store();
+ let name = "test-tag-get-tags";
+
+ debug!("Creating default entry");
+ let id = PathBuf::from(String::from(name));
+ let mut entry = store.create(id).unwrap();
+
+ let tags = vec![String::from("a")];
+ entry.set_tags(&tags).unwrap();
+
+ let v = entry.get_tags();
+
+ assert!(v.is_ok());
+ let v = v.unwrap();
+
+ assert_eq!(v, vec!["a"]);
}
- fn remove_tag(&mut self, t: Tag) -> Result<()> {
- self.get_header_mut().remove_tag(t)
+ #[test]
+ fn test_tag_add_adds_tag() {
+ setup_logging();
+ let store = get_store();
+ let name = "test-tag-set-sets-tags";
+
+ debug!("Creating default entry");
+ let id = PathBuf::from(String::from(name));
+ let mut entry = store.create(id).unwrap();
+
+ entry.add_tag(String::from("test")).unwrap();
+
+ let v = entry.get_header().read_string("tag.values.[0]").unwrap();
+
+ assert!(v.is_some());
+ let v = v.unwrap();
+
+ assert_eq!(v, "test");
}
- fn has_tag(&self, t: TagSlice) -> Result<bool> {
- self.get_header().has_tag(t)
+ #[test]
+ fn test_tag_remove_removes_tag() {
+ setup_logging();
+ let store = get_store();
+ let name = "test-tag-set-sets-tags";
+
+ debug!("Creating default entry");
+ let id = PathBuf::from(String::from(name));
+ let mut entry = store.create(id).unwrap();
+
+ entry.add_tag(String::from("test")).unwrap();
+
+ let v = entry.get_header().read_string("tag.values.[0]").unwrap();
+ assert!(v.is_some());
+
+ entry.remove_tag(String::from("test")).unwrap();
+
+ assert!(entry.get_header().read_string("tag.values.[0]").is_err());
+ let tags = entry.get_tags();
+ assert!(tags.is_ok());
+ let tags = tags.unwrap();
+ assert!(tags.is_empty());
}
- fn has_tags(&self, ts: &[Tag]) -> Result<bool> {
- self.get_header().has_tags(ts)
+ #[test]
+ fn test_tag_set_sets_tag() {
+ setup_logging();
+ let store = get_store();
+ let name = "test-tag-set-sets-tags";
+
+ debug!("Creating default entry");
+ let id = PathBuf::from(String::from(name));
+ let mut entry = store.create(id).unwrap();
+
+ let tags = vec![String::from("testtag")];
+ entry.set_tags(&tags).unwrap();
+
+ let v = entry.get_header().read_string("tag.values.[0]").unwrap();
+
+ assert!(v.is_some());
+ let v = v.unwrap();
+
+ assert_eq!(v, "testtag");
}
}
-