summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMatthias Beyer <mail@beyermatthias.de>2016-11-15 19:35:33 +0100
committerGitHub <noreply@github.com>2016-11-15 19:35:33 +0100
commitfe1c5779634d22913db78b44717559b5a4e7c53f (patch)
tree659abc49acfdd3ab916646de61734a2b1adc7a30
parentb210b0ec3edfc6269baedc2791d780b169975877 (diff)
parentecfe78d87b65f4719396d176f52506e5dfd04955 (diff)
downloadimag-fe1c5779634d22913db78b44717559b5a4e7c53f.zip
imag-fe1c5779634d22913db78b44717559b5a4e7c53f.tar.gz
Merge pull request #835 from matthiasbeyer/libimagstore/extract-toml-functionality
libimagstore: extract toml functionality
-rw-r--r--libimagstore/src/lib.rs1
-rw-r--r--libimagstore/src/store.rs846
-rw-r--r--libimagstore/src/toml_ext.rs817
3 files changed, 848 insertions, 816 deletions
diff --git a/libimagstore/src/lib.rs b/libimagstore/src/lib.rs
index 08eb0af..a696f7c 100644
--- a/libimagstore/src/lib.rs
+++ b/libimagstore/src/lib.rs
@@ -52,4 +52,5 @@ pub mod hook;
pub mod store;
mod configuration;
mod file_abstraction;
+mod toml_ext;
diff --git a/libimagstore/src/store.rs b/libimagstore/src/store.rs
index eca2ea0..18759a6 100644
--- a/libimagstore/src/store.rs
+++ b/libimagstore/src/store.rs
@@ -45,6 +45,7 @@ use error::{StoreError as SE, StoreErrorKind as SEK};
use error::MapErrInto;
use storeid::{IntoStoreId, StoreId, StoreIdIterator};
use file_abstraction::FileAbstraction;
+use toml_ext::*;
use hook::aspect::Aspect;
use hook::error::HookErrorKind;
@@ -964,37 +965,25 @@ pub type EntryContent = String;
/// This is basically a wrapper around `toml::Table` which provides convenience to the user of the
/// library.
#[derive(Debug, Clone, PartialEq)]
-pub struct EntryHeader {
- header: Value,
-}
+pub struct EntryHeader(Value);
pub type EntryResult<V> = RResult<V, ParserError>;
-#[derive(Debug, Clone, PartialEq, Eq)]
-enum Token {
- Key(String),
- Index(usize),
-}
-
/**
* Wrapper type around file header (TOML) object
*/
impl EntryHeader {
pub fn new() -> EntryHeader {
- EntryHeader {
- header: build_default_header()
- }
+ EntryHeader(build_default_header())
}
pub fn header(&self) -> &Value {
- &self.header
+ &self.0
}
fn from_table(t: Table) -> EntryHeader {
- EntryHeader {
- header: Value::Table(t)
- }
+ EntryHeader(Value::Table(t))
}
pub fn parse(s: &str) -> EntryResult<EntryHeader> {
@@ -1008,373 +997,50 @@ impl EntryHeader {
}
pub fn verify(&self) -> Result<()> {
- match self.header {
+ match self.0 {
Value::Table(ref t) => verify_header(&t),
_ => Err(SE::new(SEK::HeaderTypeFailure, None)),
}
}
- /**
- * Insert a header field by a string-spec
- *
- * ```ignore
- * insert("something.in.a.field", Boolean(true));
- * ```
- *
- * If an array field was accessed which is _out of bounds_ of the array available, the element
- * is appended to the array.
- *
- * Inserts a Boolean in the section "something" -> "in" -> "a" -> "field"
- * A JSON equivalent would be
- *
- * {
- * something: {
- * in: {
- * a: {
- * field: true
- * }
- * }
- * }
- * }
- *
- * Returns true if header field was set, false if there is already a value
- */
- pub fn insert(&mut self, spec: &str, v: Value) -> Result<bool> {
- self.insert_with_sep(spec, '.', v)
- }
-
+ #[inline]
pub fn insert_with_sep(&mut self, spec: &str, sep: char, v: Value) -> Result<bool> {
- let tokens = match EntryHeader::tokenize(spec, sep) {
- Err(e) => return Err(e),
- Ok(t) => t
- };
-
- let destination = match tokens.iter().last() {
- None => return Err(SE::new(SEK::HeaderPathSyntaxError, None)),
- Some(d) => d,
- };
-
- let path_to_dest = tokens[..(tokens.len() - 1)].into(); // N - 1 tokens
-
- // walk N-1 tokens
- let value = match EntryHeader::walk_header(&mut self.header, path_to_dest) {
- Err(e) => return Err(e),
- Ok(v) => v
- };
-
- // There is already an value at this place
- if EntryHeader::extract(value, destination).is_ok() {
- return Ok(false);
- }
-
- match *destination {
- Token::Key(ref s) => { // if the destination shall be an map key
- match *value {
- /*
- * Put it in there if we have a map
- */
- Value::Table(ref mut t) => {
- t.insert(s.clone(), v);
- }
-
- /*
- * Fail if there is no map here
- */
- _ => return Err(SE::new(SEK::HeaderPathTypeFailure, None)),
- }
- },
-
- Token::Index(i) => { // if the destination shall be an array
- match *value {
-
- /*
- * Put it in there if we have an array
- */
- Value::Array(ref mut a) => {
- a.push(v); // push to the end of the array
-
- // if the index is inside the array, we swap-remove the element at this
- // index
- if a.len() < i {
- a.swap_remove(i);
- }
- },
-
- /*
- * Fail if there is no array here
- */
- _ => return Err(SE::new(SEK::HeaderPathTypeFailure, None)),
- }
- },
- }
-
- Ok(true)
- }
-
- /**
- * Set a header field by a string-spec
- *
- * ```ignore
- * set("something.in.a.field", Boolean(true));
- * ```
- *
- * Sets a Boolean in the section "something" -> "in" -> "a" -> "field"
- * A JSON equivalent would be
- *
- * {
- * something: {
- * in: {
- * a: {
- * field: true
- * }
- * }
- * }
- * }
- *
- * If there is already a value at this place, this value will be overridden and the old value
- * will be returned
- */
- pub fn set(&mut self, spec: &str, v: Value) -> Result<Option<Value>> {
- self.set_with_sep(spec, '.', v)
+ self.0.insert_with_sep(spec, sep, v)
}
+ #[inline]
pub fn set_with_sep(&mut self, spec: &str, sep: char, v: Value) -> Result<Option<Value>> {
- let tokens = match EntryHeader::tokenize(spec, sep) {
- Err(e) => return Err(e),
- Ok(t) => t,
- };
- debug!("tokens = {:?}", tokens);
-
- let destination = match tokens.iter().last() {
- None => return Err(SE::new(SEK::HeaderPathSyntaxError, None)),
- Some(d) => d
- };
- debug!("destination = {:?}", destination);
-
- let path_to_dest = tokens[..(tokens.len() - 1)].into(); // N - 1 tokens
- // walk N-1 tokens
- let value = match EntryHeader::walk_header(&mut self.header, path_to_dest) {
- Err(e) => return Err(e),
- Ok(v) => v
- };
- debug!("walked value = {:?}", value);
-
- match *destination {
- Token::Key(ref s) => { // if the destination shall be an map key->value
- match *value {
- /*
- * Put it in there if we have a map
- */
- Value::Table(ref mut t) => {
- debug!("Matched Key->Table");
- return Ok(t.insert(s.clone(), v));
- }
-
- /*
- * Fail if there is no map here
- */
- _ => {
- debug!("Matched Key->NON-Table");
- return Err(SE::new(SEK::HeaderPathTypeFailure, None));
- }
- }
- },
-
- Token::Index(i) => { // if the destination shall be an array
- match *value {
-
- /*
- * Put it in there if we have an array
- */
- Value::Array(ref mut a) => {
- debug!("Matched Index->Array");
- a.push(v); // push to the end of the array
-
- // if the index is inside the array, we swap-remove the element at this
- // index
- if a.len() > i {
- debug!("Swap-Removing in Array {:?}[{:?}] <- {:?}", a, i, a[a.len()-1]);
- return Ok(Some(a.swap_remove(i)));
- }
-
- debug!("Appended");
- return Ok(None);
- },
-
- /*
- * Fail if there is no array here
- */
- _ => {
- debug!("Matched Index->NON-Array");
- return Err(SE::new(SEK::HeaderPathTypeFailure, None));
- },
- }
- },
- }
-
- Ok(None)
- }
-
- /**
- * Read a header field by a string-spec
- *
- * ```ignore
- * let value = read("something.in.a.field");
- * ```
- *
- * Reads a Value in the section "something" -> "in" -> "a" -> "field"
- * A JSON equivalent would be
- *
- * {
- * something: {
- * in: {
- * a: {
- * field: true
- * }
- * }
- * }
- * }
- *
- * If there is no a value at this place, None will be returned. This also holds true for Arrays
- * which are accessed at an index which is not yet there, even if the accessed index is much
- * larger than the array length.
- */
- pub fn read(&self, spec: &str) -> Result<Option<Value>> {
- self.read_with_sep(spec, '.')
+ self.0.set_with_sep(spec, sep, v)
}
+ #[inline]
pub fn read_with_sep(&self, spec: &str, splitchr: char) -> Result<Option<Value>> {
- let tokens = match EntryHeader::tokenize(spec, splitchr) {
- Err(e) => return Err(e),
- Ok(t) => t,
- };
-
- let mut header_clone = self.header.clone(); // we clone as READing is simpler this way
- // walk N-1 tokens
- match EntryHeader::walk_header(&mut header_clone, tokens) {
- Err(e) => match e.err_type() {
- // We cannot find the header key, as there is no path to it
- SEK::HeaderKeyNotFound => Ok(None),
- _ => Err(e),
- },
- Ok(v) => Ok(Some(v.clone())),
- }
+ self.0.read_with_sep(spec, splitchr)
}
- pub fn delete(&mut self, spec: &str) -> Result<Option<Value>> {
- let tokens = match EntryHeader::tokenize(spec, '.') {
- Err(e) => return Err(e),
- Ok(t) => t
- };
-
- let destination = match tokens.iter().last() {
- None => return Err(SE::new(SEK::HeaderPathSyntaxError, None)),
- Some(d) => d
- };
- debug!("destination = {:?}", destination);
-
- let path_to_dest = tokens[..(tokens.len() - 1)].into(); // N - 1 tokens
- // walk N-1 tokens
- let mut value = match EntryHeader::walk_header(&mut self.header, path_to_dest) {
- Err(e) => return Err(e),
- Ok(v) => v
- };
- debug!("walked value = {:?}", value);
-
- match *destination {
- Token::Key(ref s) => { // if the destination shall be an map key->value
- match *value {
- Value::Table(ref mut t) => {
- debug!("Matched Key->Table, removing {:?}", s);
- return Ok(t.remove(s));
- },
- _ => {
- debug!("Matched Key->NON-Table");
- return Err(SE::new(SEK::HeaderPathTypeFailure, None));
- }
- }
- },
-
- Token::Index(i) => { // if the destination shall be an array
- match *value {
- Value::Array(ref mut a) => {
- // if the index is inside the array, we swap-remove the element at this
- // index
- if a.len() > i {
- debug!("Removing in Array {:?}[{:?}]", a, i);
- return Ok(Some(a.remove(i)));
- } else {
- return Ok(None);
- }
- },
- _ => {
- debug!("Matched Index->NON-Array");
- return Err(SE::new(SEK::HeaderPathTypeFailure, None));
- },
- }
- },
- }
-
- Ok(None)
- }
-
- fn tokenize(spec: &str, splitchr: char) -> Result<Vec<Token>> {
- use std::str::FromStr;
-
- spec.split(splitchr)
- .map(|s| {
- usize::from_str(s)
- .map(Token::Index)
- .or_else(|_| Ok(Token::Key(String::from(s))))
- })
- .collect()
+ #[inline]
+ pub fn delete_with_sep(&mut self, spec: &str, splitchr: char) -> Result<Option<Value>> {
+ self.0.delete_with_sep(spec, splitchr)
}
- fn walk_header(v: &mut Value, tokens: Vec<Token>) -> Result<&mut Value> {
- use std::vec::IntoIter;
-
- fn walk_iter<'a>(v: Result<&'a mut Value>, i: &mut IntoIter<Token>) -> Result<&'a mut Value> {
- let next = i.next();
- v.and_then(move |value| {
- if let Some(token) = next {
- walk_iter(EntryHeader::extract(value, &token), i)
- } else {
- Ok(value)
- }
- })
- }
-
- walk_iter(Ok(v), &mut tokens.into_iter())
+ #[inline]
+ pub fn insert(&mut self, spec: &str, v: Value) -> Result<bool> {
+ self.0.insert(spec, v)
}
- fn extract_from_table<'a>(v: &'a mut Value, s: &str) -> Result<&'a mut Value> {
- match *v {
- Value::Table(ref mut t) => {
- t.get_mut(&s[..])
- .ok_or(SE::new(SEK::HeaderKeyNotFound, None))
- },
- _ => Err(SE::new(SEK::HeaderPathTypeFailure, None)),
- }
+ #[inline]
+ pub fn set(&mut self, spec: &str, v: Value) -> Result<Option<Value>> {
+ self.0.set(spec, v)
}
- fn extract_from_array(v: &mut Value, i: usize) -> Result<&mut Value> {
- match *v {
- Value::Array(ref mut a) => {
- if a.len() < i {
- Err(SE::new(SEK::HeaderKeyNotFound, None))
- } else {
- Ok(&mut a[i])
- }
- },
- _ => Err(SE::new(SEK::HeaderPathTypeFailure, None)),
- }
+ #[inline]
+ pub fn read(&self, spec: &str) -> Result<Option<Value>> {
+ self.0.read(spec)
}
- fn extract<'a>(v: &'a mut Value, token: &Token) -> Result<&'a mut Value> {
- match *token {
- Token::Key(ref s) => EntryHeader::extract_from_table(v, s),
- Token::Index(i) => EntryHeader::extract_from_array(v, i),
- }
+ #[inline]
+ pub fn delete(&mut self, spec: &str) -> Result<Option<Value>> {
+ self.0.delete(spec)
}
}
@@ -1382,7 +1048,7 @@ impl EntryHeader {
impl Into<Table> for EntryHeader {
fn into(self) -> Table {
- match self.header {
+ match self.0 {
Value::Table(t) => t,
_ => panic!("EntryHeader is not a table!"),
}
@@ -1393,7 +1059,7 @@ impl Into<Table> for EntryHeader {
impl From<Table> for EntryHeader {
fn from(t: Table) -> EntryHeader {
- EntryHeader { header: Value::Table(t) }
+ EntryHeader(Value::Table(t))
}
}
@@ -1528,7 +1194,7 @@ impl Entry {
pub fn to_str(&self) -> String {
format!("---\n{header}---\n{content}",
- header = ::toml::encode_str(&self.header.header),
+ header = ::toml::encode_str(&self.header.0),
content = self.content)
}
@@ -1642,8 +1308,6 @@ mod test {
extern crate env_logger;
use std::collections::BTreeMap;
- use super::EntryHeader;
- use super::Token;
use storeid::StoreId;
use toml::Value;
@@ -1793,456 +1457,6 @@ Hai";
assert_eq!(TEST_ENTRY, string);
}
- #[test]
- fn test_walk_header_simple() {
- let tokens = EntryHeader::tokenize("a", '.').unwrap();
- assert!(tokens.len() == 1, "1 token was expected, {} were parsed", tokens.len());
- assert!(tokens.iter().next().unwrap() == &Token::Key(String::from("a")),
- "'a' token was expected, {:?} was parsed", tokens.iter().next());
-
- let mut header = BTreeMap::new();
- header.insert(String::from("a"), Value::Integer(1));
-
- let mut v_header = Value::Table(header);
- let res = EntryHeader::walk_header(&mut v_header, tokens);
- assert_eq!(&mut Value::Integer(1), res.unwrap());
- }
-
- #[test]
- fn test_walk_header_with_array() {
- let tokens = EntryHeader::tokenize("a.0", '.').unwrap();
- assert!(tokens.len() == 2, "2 token was expected, {} were parsed", tokens.len());
- assert!(tokens.iter().next().unwrap() == &Token::Key(String::from("a")),
- "'a' token was expected, {:?} was parsed", tokens.iter().next());
-
- let mut header = BTreeMap::new();
- let ary = Value::Array(vec![Value::Integer(1)]);
- header.insert(String::from("a"), ary);
-
-
- let mut v_header = Value::Table(header);
- let res = EntryHeader::walk_header(&mut v_header, tokens);
- assert_eq!(&mut Value::Integer(1), res.unwrap());
- }
-
- #[test]
- fn test_walk_header_extract_array() {
- let tokens = EntryHeader::tokenize("a", '.').unwrap();
- assert!(tokens.len() == 1, "1 token was expected, {} were parsed", tokens.len());
- assert!(tokens.iter().next().unwrap() == &Token::Key(String::from("a")),
- "'a' token was expected, {:?} was parsed", tokens.iter().next());
-
- let mut header = BTreeMap::new();
- let ary = Value::Array(vec![Value::Integer(1)]);
- header.insert(String::from("a"), ary);
-
- let mut v_header = Value::Table(header);
- let res = EntryHeader::walk_header(&mut v_header, tokens);
- assert_eq!(&mut Value::Array(vec![Value::Integer(1)]), res.unwrap());
- }
-
- /**
- * Creates a big testing header.
- *
- * JSON equivalent:
- *
- * ```json
- * {
- * "a": {
- * "array": [ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 ]
- * },
- * "b": {
- * "array": [ "string1", "string2", "string3", "string4" ]
- * },
- * "c": {
- * "array": [ 1, "string2", 3, "string4" ]
- * },
- * "d": {
- * "array": [
- * {
- * "d1": 1
- * },
- * {
- * "d2": 2
- * },
- * {
- * "d3": 3
- * },
- * ],
- *
- * "something": "else",
- *
- * "and": {
- * "something": {
- * "totally": "different"
- * }
- * }
- * }
- * }
- * ```
- *
- * The sections "a", "b", "c", "d" are created in the respective helper functions
- * create_header_section_a, create_header_section_b, create_header_section_c and
- * create_header_section_d.
- *
- * These functions can also be used for testing.
- *
- */
- fn create_header() -> Value {
- let a = create_header_section_a();
- let b = create_header_section_b();
- let c = create_header_section_c();
- let d = create_header_section_d();
-
- let mut header = BTreeMap::new();
- header.insert(String::from("a"), a);
- header.insert(String::from("b"), b);
- header.insert(String::from("c"), c);
- header.insert(String::from("d"), d);
-
- Value::Table(header)
- }
-
- fn create_header_section_a() -> Value {
- // 0..10 is exclusive 10
- let a_ary = Value::Array((0..10).map(|x| Value::Integer(x)).collect());
-
- let mut a_obj = BTreeMap::new();
- a_obj.insert(String::from("array"), a_ary);
- Value::Table(a_obj)
- }
-
- fn create_header_section_b() -> Value {
- let b_ary = Value::Array((0..9)
- .map(|x| Value::String(format!("string{}", x)))
- .collect());
-
- let mut b_obj = BTreeMap::new();
- b_obj.insert(String::from("array"), b_ary);
- Value::Table(b_obj)
- }
-
- fn create_header_section_c() -> Value {
- let c_ary = Value::Array(
- vec![
- Value::Integer(1),
- Value::String(String::from("string2")),
- Value::Integer(3),
- Value::String(String::from("string4"))
- ]);
-
- let mut c_obj = BTreeMap::new();
- c_obj.insert(String::from("array"), c_ary);
- Value::Table(c_obj)
- }
-
- fn create_header_section_d() -> Value {
- let d_ary = Value::Array(
- vec![
- {
- let mut tab = BTreeMap::new();
- tab.insert(String::from("d1"), Value::Integer(1));
- tab
- },
- {
- let mut tab = BTreeMap::new();
- tab.insert(String::from("d2"), Value::Integer(2));
- tab
- },
- {
- let mut tab = BTreeMap::new();
- tab.insert(String::from("d3"), Value::Integer(3));
- tab
- },
- ].into_iter().map(Value::Table).collect());
-
- let and_obj = Value::Table({
- let mut tab = BTreeMap::new();
- let something_tab = Value::Table({
- let mut tab = BTreeMap::new();
- tab.insert(String::from("totally"), Value::String(String::from("different")));
- tab
- });
- tab.insert(String::from("something"), something_tab);
- tab
- });
-
- let mut d_obj = BTreeMap::new();
- d_obj.insert(String::from("array"), d_ary);
- d_obj.insert(String::from("something"), Value::String(String::from("else")));
- d_obj.insert(String::from("and"), and_obj);
- Value::Table(d_obj)
- }
-
- #[test]
- fn test_walk_header_big_a() {
- test_walk_header_extract_section("a", &create_header_section_a());
- }
-
- #[test]
- fn test_walk_header_big_b() {
- test_walk_header_extract_section("b", &create_header_section_b());
- }
-
- #[test]
- fn test_walk_header_big_c() {
- test_walk_header_extract_section("c", &create_header_section_c());
- }
-
- #[test]
- fn test_walk_header_big_d() {
- test_walk_header_extract_section("d", &create_header_section_d());
- }
-
- fn test_walk_header_extract_section(secname: &str, expected: &Value) {
- let tokens = EntryHeader::tokenize(secname, '.').unwrap();
- assert!(tokens.len() == 1, "1 token was expected, {} were parsed", tokens.len());
- assert!(tokens.iter().next().unwrap() == &Token::Key(String::from(secname)),
- "'{}' token was expected, {:?} was parsed", secname, tokens.iter().next());
-
- let mut header = create_header();
- let res = EntryHeader::walk_header(&mut header, tokens);
- assert_eq!(expected, res.unwrap());
- }
-
- #[test]
- fn test_walk_header_extract_numbers() {
- test_extract_number("a", 0, 0);
- test_extract_number("a", 1, 1);
- test_extract_number("a", 2, 2);
- test_extract_number("a", 3, 3);
- test_extract_number("a", 4, 4);
- test_extract_number("a", 5, 5);
- test_extract_number("a", 6, 6);
- test_extract_number("a", 7, 7);
- test_extract_number("a", 8, 8);
- test_extract_number("a", 9, 9);
-
- test_extract_number("c", 0, 1);
- test_extract_number("c", 2, 3);
- }
-
- fn test_extract_number(sec: &str, idx: usize, exp: i64) {
- let tokens = EntryHeader::tokenize(&format!("{}.array.{}", sec, idx)[..], '.').unwrap();
- assert!(tokens.len() == 3, "3 token was expected, {} were parsed", tokens.len());
- {
- let mut iter = tokens.iter();
-
- let tok = iter.next().unwrap();
- let exp = Token::Key(String::from(sec));
- assert!(tok == &exp, "'{}' token was expected, {:?} was parsed", sec, tok);
-
- let tok = iter.next().unwrap();
- let exp = Token::Key(String::from("array"));
- assert!(tok == &exp, "'array' token was expected, {:?} was parsed", tok);
-
- let tok = iter.next().unwrap();
- let exp = Token::Index(idx);
- assert!(tok == &exp, "'{}' token was expected, {:?} was parsed", idx, tok);
- }
-
- let mut header = create_header();
- let res = EntryHeader::walk_header(&mut header, tokens);
- assert_eq!(&mut Value::Integer(exp), res.unwrap());
- }
-
- #[test]
- fn test_header_read() {
- let v = create_header();
- let h = match v {
- Value::Table(t) => EntryHeader::from_table(t),
- _ => panic!("create_header() doesn't return a table!"),
- };
-
- assert!(if let Ok(Some(Value::Table(_))) = h.read("a") { true } else { false });
- assert!(if let Ok(Some(Value::Array(_))) = h.read("a.array") { true } else { false });
- assert!(if let Ok(Some(Value::Integer(_))) = h.read("a.array.1") { true } else { false });
- assert!(if let Ok(Some(Value::Integer(_))) = h.read("a.array.9") { true } else { false });
-
- assert!(if let Ok(Some(Value::Table(_))) = h.read("c") { true } else { false });
- assert!(if let Ok(Some(Value::Array(_))) = h.read("c.array") { true } else { false });
- assert!(if let Ok(Some(Value::String(_))) = h.read("c.array.1") { true } else { false });
- assert!(if let Ok(None) = h.read("c.array.9") { true } else { false });
-
- assert!(if let Ok(Some(Value::Integer(_))) = h.read("d.array.0.d1") { true } else { false });
- assert!(if let Ok(None) = h.read("d.array.0.d2") { true } else { false });
- assert!(if let Ok(None) = h.read("d.array.0.d3") { true } else { false });
-
- assert!(if let Ok(None) = h.read("d.array.1.d1") { true } else { false });
- assert!(if let Ok(Some(Value::Integer(_))) = h.read("d.array.1.d2") { true } else { false });
- assert!(if let Ok(None) = h.read("d.array.1.d3") { true } else { false });
-
- assert!(if let Ok(None) = h.read("d.array.2.d1") { true } else { false });
- assert!(if let Ok(None) = h.read("d.array.2.d2") { true } else { false });
- assert!(if let Ok(Some(Value::Integer(_))) = h.read("d.array.2.d3") { true } else { false });
-
- assert!(if let Ok(Some(Value::String(_))) = h.read("d.something") { true } else { false });
- assert!(if let Ok(Some(Value::Table(_))) = h.read("d.and") { true } else { false });
- assert!(if let Ok(Some(Value::Table(_))) = h.read("d.and.something") { true } else { false });
- assert!(if let Ok(Some(Value::String(_))) = h.read("d.and.something.totally") { true } else { false });
- }
-
- #[test]
- fn test_header_set_override() {
- let _ = env_logger::init();
- let v = create_header();
- let mut h = match v {
- Value::Table(t) => EntryHeader::from_table(t),
- _ => panic!("create_header() doesn't return a table!"),
- };
-
- println!("Testing index 0");
- assert_eq!(h.read("a.array.0").unwrap().unwrap(), Value::Integer(0));
-
- println!("Altering index 0");
- assert_eq!(h.set("a.array.0", Value::Integer(42)).unwrap().unwrap(), Value::Integer(0));
-
- println!("Values now: {:?}", h);
-
- println!("Testing all indexes");
- assert_eq!(h.read("a.array.0").unwrap().unwrap(), Value::Integer(42));
- assert_eq!(h.read("a.array.1").unwrap().unwrap(), Value::Integer(1));
- assert_eq!(h.read("a.array.2").unwrap().unwrap(), Value::Integer(2));
- assert_eq!(h.read("a.array.3").unwrap().unwrap(), Value::Integer(3));
- assert_eq!(h.read("a.array.4").unwrap().unwrap(), Value::Integer(4));
- assert_eq!(h.read("a.array.5").unwrap().unwrap(), Value::Integer(5));
- assert_eq!(h.read("a.array.6").unwrap().unwrap(), Value::Integer(6));
- assert_eq!(h.read("a.array.7").unwrap().unwrap(), Value::Integer(7));
- assert_eq!(h.read("a.array.8").unwrap().unwrap(), Value::Integer(8));
- assert_eq!(h.read("a.array.9").unwrap().unwrap(), Value::Integer(9));
- }
-
- #[test]
- fn test_header_set_new() {
- let _ = env_logger::init();
- let v = create_header();
- let mut h = match v {
- Value::Table(t) => EntryHeader::from_table(t),
- _ => panic!("create_header() doesn't return a table!"),
- };
-
- assert!(h.read("a.foo").is_ok());
- assert!(h.read("a.foo").unwrap().is_none());
-
- {
- let v = h.set("a.foo", Value::Integer(42));
- assert!(v.is_ok());
- assert!(v.unwrap().is_none());
-
- assert!(if let Ok(Some(Value::Table(_))) = h.read("a") { true } else { false });
- assert!(if let Ok(Some(Value::Integer(_))) = h.read("a.foo") { true } else { false });
- }
-
- {
- let v = h.set("new", Value::Table(BTreeMap::new()));
- assert!(v.is_ok());
- assert!(v.unwrap().is_none());
-
- let v = h.set("new.subset", Value::Table(BTreeMap::new()));
- assert!(v.is_ok());
- assert!(v.unwrap().is_none());
-
- let v = h.set("new.subset.dest", Value::Integer(1337));
- assert!(v.is_ok());
- assert!(v.unwrap().is_none());
-
- assert!(if let Ok(Some(Value::Table(_))) = h.read("new") { true } else { false });
- assert!(if let Ok(Some(Value::Table(_))) = h.read("new.subset") { true } else { false });
- assert!(if let Ok(Some(Value::Integer(_))) = h.read("new.subset.dest") { true } else { false });
- }
- }
-
-
- #[test]
- fn test_header_insert_override() {
- let _ = env_logger::init();
- let v = create_header();
- let mut h = match v {
- Value::Table(t) => EntryHeader::from_table(t),
- _ => panic!("create_header() doesn't return a table!"),
- };
-
- println!("Testing index 0");
- assert_eq!(h.read("a.array.0").unwrap().unwrap(), Value::Integer(0));
-
- println!("Altering index 0");
- assert_eq!(h.insert("a.array.0", Value::Integer(42)).unwrap(), false);
- println!("...should have failed");
-
- println!("Testing all indexes");
- assert_eq!(h.read("a.array.0").unwrap().unwrap(), Value::Integer(0));
- assert_eq!(h.read("a.array.1").unwrap().unwrap(), Value::Integer(1));
- assert_eq!(h.read("a.array.2").unwrap().unwrap(), Value::Integer(2));
- assert_eq!(h.read("a.array.3").unwrap().unwrap(), Value::Integer(3));
- assert_eq!(h.read("a.array.4").unwrap().unwrap(), Value::Integer(4));
- assert_eq!(h.read("a.array.5").unwrap().unwrap(), Value::Integer(5));
- assert_eq!(h.read("a.array.6").unwrap().unwrap(), Value::Integer(6));
- assert_eq!(h.read("a.array.7").unwrap().unwrap(), Value::Integer(7));
- assert_eq!(h.read("a.array.8").unwrap().unwrap(), Value::Integer(8));
- assert_eq!(h.read("a.array.9").unwrap().unwrap(), Value::Integer(9));
- }
-
- #[test]
- fn test_header_insert_new() {
- let _ = env_logger::init();
- let v = create_header();
- let mut h = match v {
- Value::Table(t) => EntryHeader::from_table(t),
- _ => panic!("create_header() doesn't return a table!"),
- };
-
- assert!(h.read("a.foo").is_ok());
- assert!(h.read("a.foo").unwrap().is_none());
-
- {
- let v = h.insert("a.foo", Value::Integer(42));
- assert!(v.is_ok());
- assert_eq!(v.unwrap(), true);
-
- assert!(if let Ok(Some(Value::Table(_))) = h.read("a") { true } else { false });
- assert!(if let Ok(Some(Value::Integer(_))) = h.read("a.foo") { true } else { false });
- }
-
- {
- let v = h.insert("new", Value::Table(BTreeMap::new()));
- assert!(v.is_ok());
- assert_eq!(v.unwrap(), true);
-
- let v = h.insert("new.subset", Value::Table(BTreeMap::new()));
- assert!(v.is_ok());
- assert_eq!(v.unwrap(), true);
-
- let v = h.insert("new.subset.dest", Value::Integer(1337));
- assert!(v.is_ok());
- assert_eq!(v.unwrap(), true);
-
- assert!(if let Ok(Some(Value::Table(_))) = h.read("new") { true } else { false });
- assert!(if let Ok(Some(Value::Table(_))) = h.read("new.subset") { true } else { false });
- assert!(if let Ok(Some(Value::Integer(_))) = h.read("new.subset.dest") { true } else { false });
- }
- }
-
- #[test]
- fn test_header_delete() {
- let _ = env_logger::init();
- let v = create_header();
- let mut h = match v {
- Value::Table(t) => EntryHeader::from_table(t),
- _ => panic!("create_header() doesn't return a table!"),
- };
-
- assert!(if let Ok(Some(Value::Table(_))) = h.read("a") { true } else { false });
- assert!(if let Ok(Some(Value::Array(_))) = h.read("a.array") { true } else { false });
- assert!(if let Ok(Some(Value::Integer(_))) = h.read("a.array.1") { true } else { false });
- assert!(if let Ok(Some(Value::Integer(_))) = h.read("a.array.9") { true } else { false });
-
- assert!(if let Ok(Some(Value::Integer(1))) = h.delete("a.array.1") { true } else { false });
- assert!(if let Ok(Some(Value::Integer(9))) = h.delete("a.array.8") { true } else { false });
- assert!(if let Ok(Some(Value::Array(_))) = h.delete("a.array") { true } else { false });
- assert!(if let Ok(Some(Value::Table(_))) = h.delete("a") { true } else { false });
-
- }
-
}
#[cfg(test)]
diff --git a/libimagstore/src/toml_ext.rs b/libimagstore/src/toml_ext.rs
new file mode 100644
index 0000000..c0bd79b
--- /dev/null
+++ b/libimagstore/src/toml_ext.rs
@@ -0,0 +1,817 @@
+//
+// 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 toml::Value;
+
+use store::Result;
+use error::StoreErrorKind as SEK;
+use libimagerror::into::IntoError;
+
+pub trait TomlValueExt {
+ fn insert_with_sep(&mut self, spec: &str, sep: char, v: Value) -> Result<bool>;
+ fn set_with_sep(&mut self, spec: &str, sep: char, v: Value) -> Result<Option<Value>>;
+ fn read_with_sep(&self, spec: &str, splitchr: char) -> Result<Option<Value>>;
+ fn delete_with_sep(&mut self, spec: &str, splitchr: char) -> Result<Option<Value>>;
+
+ #[inline]
+ fn insert(&mut self, spec: &str, v: Value) -> Result<bool> {
+ self.insert_with_sep(spec, '.', v)
+ }
+
+ #[inline]
+ fn set(&mut self, spec: &str, v: Value) -> Result<Option<Value>> {
+ self.set_with_sep(spec, '.', v)
+ }
+
+ #[inline]
+ fn read(&self, spec: &str) -> Result<Option<Value>> {
+ self.read_with_sep(spec, '.')
+ }
+
+ #[inline]
+ fn delete(&mut self, spec: &str) -> Result<Option<Value>> {
+ self.delete_with_sep(spec, '.')
+ }
+}
+
+#[derive(Debug, Clone, PartialEq, Eq)]
+enum Token {
+ Key(String),
+ Index(usize),
+}
+
+impl TomlValueExt for Value {
+ /**
+ * Insert a header field by a string-spec
+ *
+ * ```ignore
+ * insert("something.in.a.field", Boolean(true));
+ * ```
+ *
+ * If an array field was accessed which is _out of bounds_ of the array available, the element
+ * is appended to the array.
+ *
+ * Inserts a Boolean in the section "something" -> "in" -> "a" -> "field"
+ * A JSON equivalent would be
+ *
+ * {
+ * something: {
+ * in: {
+ * a: {
+ * field: true
+ * }
+ * }
+ * }
+ * }
+ *
+ * Returns true if header field was set, false if there is already a value
+ */
+ fn insert_with_sep(&mut self, spec: &str, sep: char, v: Value) -> Result<bool> {
+ let (destination, value) = try!(setup(self, spec, sep));
+
+ // There is already an value at this place
+ if value.extract(&destination).is_ok() {
+ return Ok(false);
+ }
+
+ match destination {
+ // if the destination shall be an map key
+ Token::Key(ref s) => match *value {
+ /*
+ * Put it in there if we have a map
+ */
+ Value::Table(ref mut t) => { t.insert(s.clone(), v); },
+
+ /*
+ * Fail if there is no map here
+ */
+ _ => return Err(SEK::HeaderPathTypeFailure.into_error()),
+ },
+
+ // if the destination shall be an array
+ Token::Index(i) => match *value {
+
+ /*
+ * Put it in there if we have an array
+ */
+ Value::Array(ref mut a) => {
+ a.push(v); // push to the end of the array
+
+ // if the index is inside the array, we swap-remove the element at this
+ // index
+ if a.len() < i {
+ a.swap_remove(i);
+ }
+ },
+
+ /*
+ * Fail if there is no array here
+ */
+ _ => return Err(SEK::HeaderPathTypeFailure.into_error()),
+ },
+ }
+
+ Ok(true)
+ }
+
+ /**
+ * Set a header field by a string-spec
+ *
+ * ```ignore
+ * set("something.in.a.field", Boolean(true));
+ * ```
+ *
+ * Sets a Boolean in the section "something" -> "in" -> "a" -> "field"
+ * A JSON equivalent would be
+ *
+ * {
+ * something: {
+ * in: {
+ * a: {
+ * field: true
+ * }
+ * }
+ * }
+ * }
+ *
+ * If there is already a value at this place, this value will be overridden and the old value
+ * will be returned
+ */
+ fn set_with_sep(&mut self, spec: &str, sep: char, v: Value) -> Result<Option<Value>> {
+ let (destination, value) = try!(setup(self, spec, sep));
+
+ match destination {
+ // if the destination shall be an map key->value
+ Token::Key(ref s) => match *value {
+ /*
+ * Put it in there if we have a map
+ */
+ Value::Table(ref mut t) => {
+ debug!("Matched Key->Table");
+ return Ok(t.insert(s.clone(), v));
+ }
+
+ /*
+ * Fail if there is no map here
+ */
+ _ => {
+ debug!("Matched Key->NON-Table");
+ return Err(SEK::HeaderPathTypeFailure.into_error());
+ }
+ },
+
+ // if the destination shall be an array
+ Token::Index(i) => match *value {
+
+ /*
+ * Put it in there if we have an array
+ */
+ Value::Array(ref mut a) => {
+ debug!("Matched Index->Array");
+ a.push(v); // push to the end of the array
+
+ // if the index is inside the array, we swap-remove the element at this
+ // index
+ if a.len() > i {
+ debug!("Swap-Removing in Array {:?}[{:?}] <- {:?}", a, i, a[a.len()-1]);
+ return Ok(Some(a.swap_remove(i)));
+ }
+
+ debug!("Appended");
+ return Ok(None);
+ },
+
+ /*
+ * Fail if there is no array here
+ */
+ _ => {
+ debug!("Matched Index->NON-Array");
+ return Err(SEK::HeaderPathTypeFailure.into_error());
+ },
+ },
+ }
+ }
+
+ /**
+ * Read a header field by a string-spec
+ *
+ * ```ignore
+ * let value = read("something.in.a.field");
+ * ```
+ *
+ * Reads a Value in the section "something" -> "in" -> "a" -> "field"
+ * A JSON equivalent would be
+ *
+ * {
+ * something: {
+ * in: {
+ * a: {
+ * field: true
+ * }
+ * }
+ * }
+ * }
+ *
+ * If there is no a value at this place, None will be returned. This also holds true for Arrays
+ * which are accessed at an index which is not yet there, even if the accessed index is much
+ * larger than the array length.
+ */
+ fn read_with_sep(&self, spec: &str, splitchr: char) -> Result<Option<Value>> {
+ let tokens = try!(tokenize(spec, splitchr));
+
+ let mut header_clone = self.clone(); // we clone as READing is simpler this way
+ // walk N-1 tokens
+ match walk_header(&mut header_clone, tokens) {
+ Err(e) => match e.err_type() {
+ // We cannot find the header key, as there is no path to it
+ SEK::HeaderKeyNotFound => Ok(None),
+ _ => Err(e),
+ },
+ Ok(v) => Ok(Some(v.clone())),
+ }
+ }
+
+ fn delete_with_sep(&mut self, spec: &str, splitchr: char) -> Result<Option<Value>> {
+ let (destination, value) = try!(setup(self, spec, splitchr));
+
+ match destination {
+ // if the destination shall be an map key->value
+ Token::Key(ref s) => match *value {
+ Value::Table(ref mut t) => {
+ debug!("Matched Key->Table, removing {:?}", s);
+ return Ok(t.remove(s));
+ },
+ _ => {
+ debug!("Matched Key->NON-Table");
+ return Err(SEK::HeaderPathTypeFailure.into_error());
+ }
+ },
+
+ // if the destination shall be an array
+ Token::Index(i) => match *value {
+
+ // if the index is inside the array, we swap-remove the element at this
+ // index
+ Value::Array(ref mut a) => if a.len() > i {
+ debug!("Removing in Array {:?}[{:?}]", a, i);
+ return Ok(Some(a.remove(i)));
+ } else {
+ return Ok(None);
+ },
+ _ => {
+ debug!("Matched Index->NON-Array");
+ return Err(SEK::HeaderPathTypeFailure.into_error());
+ },
+ },
+ }
+ }
+
+}
+
+fn setup<'a>(v: &'a mut Value, spec: &str, sep: char)
+ -> Result<(Token, &'a mut Value)>
+{
+ let tokens = try!(tokenize(spec, sep));
+ debug!("tokens = {:?}", tokens);
+
+ let destination = try!(tokens.iter().last().cloned().ok_or(SEK::HeaderPathSyntaxError.into_error()));
+ debug!("destination = {:?}", destination);
+
+ let path_to_dest : Vec<Token> = tokens[..(tokens.len() - 1)].into(); // N - 1 tokens
+ let value = try!(walk_header(v, path_to_dest)); // walk N-1 tokens
+
+ debug!("walked value = {:?}", value);
+
+ Ok((destination, value))
+}
+
+fn tokenize(spec: &str, splitchr: char) -> Result<Vec<Token>> {
+ use std::str::FromStr;
+
+ spec.split(splitchr)
+ .map(|s| usize::from_str(s).map(Token::Index).or_else(|_| Ok(Token::Key(String::from(s)))))
+ .collect()
+}
+
+fn walk_header(v: &mut Value, tokens: Vec<Token>) -> Result<&mut Value> {
+ use std::vec::IntoIter;
+
+ fn walk_iter<'a>(v: Result<&'a mut Value>, i: &mut IntoIter<Token>) -> Result<&'a mut Value> {
+ let next = i.next();
+ v.and_then(move |value| if let Some(token) = next {
+ walk_iter(value.extract(&token), i)
+ } else {
+ Ok(value)
+ })
+ }
+
+ walk_iter(Ok(v), &mut tokens.into_iter())
+}
+
+trait Extract {
+ fn extract<'a>(&'a mut self, &Token) -> Result<&'a mut Self>;
+}
+
+impl Extract for Value {
+ fn extract<'a>(&'a mut self, token: &Token) -> Result<&'a mut Value> {
+ match *token {
+ // on Token::Key extract from Value::Table
+ Token::Key(ref s) => match *self {
+ Value::Table(ref mut t) =>
+ t.get_mut(&s[..]).ok_or(SEK::HeaderKeyNotFound.into_error()),
+
+ _ => Err(SEK::HeaderPathTypeFailure.into_error()),
+ },
+
+ // on Token::Index extract from Value::Array
+ Token::Index(i) => match *self {
+ Value::Array(ref mut a) => if a.len() < i {
+ Err(SEK::HeaderKeyNotFound.into_error())
+ } else {
+ Ok(&mut a[i])
+ },
+
+ _ => Err(SEK::HeaderPathTypeFailure.into_error()),
+ }
+ }
+ }
+}
+
+#[cfg(test)]
+mod test {
+ extern crate env_logger;
+ use store::EntryHeader;
+ use super::{tokenize, walk_header};
+ use super::Token;
+
+ use std::collections::BTreeMap;
+
+ use toml::Value;
+
+ #[test]
+ fn test_walk_header_simple() {
+ let tokens = tokenize("a", '.').unwrap();
+ assert!(tokens.len() == 1, "1 token was expected, {} were parsed", tokens.len());
+ assert!(tokens.iter().next().unwrap() == &Token::Key(String::from("a")),
+ "'a' token was expected, {:?} was parsed", tokens.iter().next());
+
+ let mut header = BTreeMap::new();
+ header.insert(String::from("a"), Value::Integer(1));
+
+ let mut v_header = Value::Table(header);
+ let res = walk_header(&mut v_header, tokens);
+ assert_eq!(&mut Value::Integer(1), res.unwrap());
+ }
+
+ #[test]
+ fn test_walk_header_with_array() {
+ let tokens = tokenize("a.0", '.').unwrap();
+ assert!(tokens.len() == 2, "2 token was expected, {} were parsed", tokens.len());
+ assert!(tokens.iter().next().unwrap() == &Token::Key(String::from("a")),
+ "'a' token was expected, {:?} was parsed", tokens.iter().next());
+
+ let mut header = BTreeMap::new();
+ let ary = Value::Array(vec![Value::Integer(1)]);
+ header.insert(String::from("a"), ary);
+
+
+ let mut v_header = Value::Table(header);
+ let res = walk_header(&mut v_header, tokens);
+ assert_eq!(&mut Value::Integer(1), res.unwrap());
+ }
+
+ #[test]
+ fn test_walk_header_extract_array() {
+ let tokens = tokenize("a", '.').unwrap();
+ assert!(tokens.len() == 1, "1 token was expected, {} were parsed", tokens.len());
+ assert!(tokens.iter().next().unwrap() == &Token::Key(String::from("a")),
+ "'a' token was expected, {:?} was parsed", tokens.iter().next());
+
+ let mut header = BTreeMap::new();
+ let ary = Value::Array(vec![Value::Integer(1)]);
+ header.insert(String::from("a"), ary);
+
+ let mut v_header = Value::Table(header);
+ let res = walk_header(&mut v_header, tokens);
+ assert_eq!(&mut Value::Array(vec![Value::Integer(1)]), res.unwrap());
+ }
+
+ /**
+ * Creates a big testing header.
+ *
+ * JSON equivalent:
+ *
+ * ```json
+ * {
+ * "a": {
+ * "array": [ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 ]
+ * },
+ * "b": {
+ * "array": [ "string1", "string2", "string3", "string4" ]
+ * },
+ * "c": {
+ * "array": [ 1, "string2", 3, "string4" ]
+ * },
+ * "d": {
+ * "array": [
+ * {
+ * "d1": 1
+ * },
+ * {
+ * "d2": 2
+ * },
+ * {
+ * "d3": 3
+ * },
+ * ],
+ *
+ * "something": "else",
+ *
+ * "and": {
+ * "something": {
+ * "totally": "different"
+ * }
+ * }
+ * }
+ * }
+ * ```
+ *
+ * The sections "a", "b", "c", "d" are created in the respective helper functions
+ * create_header_section_a, create_header_section_b, create_header_section_c and
+ * create_header_section_d.
+ *
+ * These functions can also be used for testing.
+ *
+ */
+ fn create_header() -> Value {
+ let a = create_header_section_a();
+ let b = create_header_section_b();
+ let c = create_header_section_c();
+ let d = create_header_section_d();
+
+ let mut header = BTreeMap::new();
+ header.insert(String::from("a"), a);
+ header.insert(String::from("b"), b);
+ header.insert(String::from("c"), c);
+ header.insert(String::from("d"), d);
+
+ Value::Table(header)
+ }
+
+ fn create_header_section_a() -> Value {
+ // 0..10 is exclusive 10
+ let a_ary = Value::Array((0..10).map(|x| Value::Integer(x)).collect());
+
+ let mut a_obj = BTreeMap::new();
+ a_obj.insert(String::from("array"), a_ary);
+ Value::Table(a_obj)
+ }
+
+ fn create_header_section_b() -> Value {
+ let b_ary = Value::Array((0..9)
+ .map(|x| Value::String(format!("string{}", x)))
+ .collect());
+
+ let mut b_obj = BTreeMap::new();
+ b_obj.insert(String::from("array"), b_ary);
+ Value::Table(b_obj)
+ }
+
+ fn create_header_section_c() -> Value {
+ let c_ary = Value::Array(
+ vec![
+ Value::Integer(1),
+ Value::String(String::from("string2")),
+ Value::Integer(3),
+ Value::String(String::from("string4"))
+ ]);
+
+ let mut c_obj = BTreeMap::new();
+ c_obj.insert(String::from("array"), c_ary);
+ Value::Table(c_obj)
+ }
+
+ fn create_header_section_d() -> Value {
+ let d_ary = Value::Array(
+ vec![
+ {
+ let mut tab = BTreeMap::new();
+ tab.insert(String::from("d1"), Value::Integer(1));
+ tab
+ },
+ {
+ let mut tab = BTreeMap::new();
+ tab.insert(String::from("d2"), Value::Integer(2));
+ tab
+ },
+ {
+ let mut tab = BTreeMap::new();
+ tab.insert(String::from("d3"), Value::Integer(3));
+ tab
+ },
+ ].into_iter().map(Value::Table).collect());
+
+ let and_obj = Value::Table({
+ let mut tab = BTreeMap::new();
+ let something_tab = Value::Table({
+ let mut tab = BTreeMap::new();
+ tab.insert(String::from("totally"), Value::String(String::from("different")));
+ tab
+ });
+ tab.insert(String::from("something"), something_tab);
+ tab
+ });
+
+ let mut d_obj = BTreeMap::new();
+ d_obj.insert(String::from("array"), d_ary);
+ d_obj.insert(String::from("something"), Value::String(String::from("else")));
+ d_obj.insert(String::from("and"), and_obj);
+ Value::Table(d_obj)
+ }
+
+ #[test]
+ fn test_walk_header_big_a() {
+ test_walk_header_extract_section("a", &create_header_section_a());
+ }
+
+ #[test]
+ fn test_walk_header_big_b() {
+ test_walk_header_extract_section("b", &create_header_section_b());
+ }
+
+ #[test]
+ fn test_walk_header_big_c() {
+ test_walk_header_extract_section("c", &create_header_section_c());
+ }
+
+ #[test]
+ fn test_walk_header_big_d() {
+ test_walk_header_extract_section("d", &create_header_section_d());
+ }
+
+ fn test_walk_header_extract_section(secname: &str, expected: &Value) {
+ let tokens = tokenize(secname, '.').unwrap();
+ assert!(tokens.len() == 1, "1 token was expected, {} were parsed", tokens.len());
+ assert!(tokens.iter().next().unwrap() == &Token::Key(String::from(secname)),
+ "'{}' token was expected, {:?} was parsed", secname, tokens.iter().next());
+
+ let mut header = create_header();
+ let res = walk_header(&mut header, tokens);
+ assert_eq!(expected, res.unwrap());
+ }
+
+ #[test]
+ fn test_walk_header_extract_numbers() {
+ test_extract_number("a", 0, 0);
+ test_extract_number("a", 1, 1);
+ test_extract_number("a", 2, 2);
+ test_extract_number("a", 3, 3);
+ test_extract_number("a", 4, 4);
+ test_extract_number("a", 5, 5);
+ test_extract_number("a", 6, 6);
+ test_extract_number("a", 7, 7);
+ test_extract_number("a", 8, 8);
+ test_extract_number("a", 9, 9);
+
+ test_extract_number("c", 0, 1);
+ test_extract_number("c", 2, 3);
+ }
+
+ fn test_extract_number(sec: &str, idx: usize, exp: i64) {
+ let tokens = tokenize(&format!("{}.array.{}", sec, idx)[..], '.').unwrap();
+ assert!(tokens.len() == 3, "3 token was expected, {} were parsed", tokens.len());
+ {
+ let mut iter = tokens.iter();
+
+ let tok = iter.next().unwrap();
+ let exp = Token::Key(String::from(sec));
+ assert!(tok == &exp, "'{}' token was expected, {:?} was parsed", sec, tok);
+
+ let tok = iter.next().unwrap();
+ let exp = Token::Key(String::from("array"));
+ assert!(tok == &exp, "'array' token was expected, {:?} was parsed", tok);
+
+ let tok = iter.next().unwrap();
+ let exp = Token::Index(idx);
+ assert!(tok == &exp, "'{}' token was expected, {:?} was parsed", idx, tok);
+ }
+
+ let mut header = create_header();
+ let res = walk_header(&mut header, tokens);
+ assert_eq!(&mut Value::Integer(exp), res.unwrap());
+ }
+
+ #[test]
+ fn test_header_read() {
+ let v = create_header();
+ let h = match v {
+ Value::Table(t) => EntryHeader::from(t),
+ _ => panic!("create_header() doesn't return a table!"),
+ };
+
+ assert!(if let Ok(Some(Value::Table(_))) = h.read("a") { true } else { false });
+ assert!(if let Ok(Some(Value::Array(_))) = h.read("a.array") { true } else { false });
+ assert!(if let Ok(Some(Value::Integer(_))) = h.read("a.array.1") { true } else { false });
+ assert!(if let Ok(Some(Value::Integer(_))) = h.read("a.array.9") { true } else { false });
+
+ assert!(if let Ok(Some(Value::Table(_))) = h.read("c") { true } else { false });
+ assert!(if let Ok(Some(Value::Array(_))) = h.read("c.array") { true } else { false });
+ assert!(if let Ok(Some(Value::String(_))) = h.read("c.array.1") { true } else { false });
+ assert!(if let Ok(None) = h.read("c.array.9") { true } else { false });
+
+ assert!(if let Ok(Some(Value::Integer(_))) = h.read("d.array.0.d1") { true } else { false });
+ assert!(if let Ok(None) = h.read("d.array.0.d2") { true } else { false });
+ assert!(if let Ok(None) = h.read("d.array.0.d3") { true } else { false });
+
+ assert!(if let Ok(None) = h.read("d.array.1.d1") { true } else { false });
+ assert!(if let Ok(Some(Value::Integer(_))) = h.read("d.array.1.d2") { true } else { false });
+ assert!(if let Ok(None) = h.read("d.array.1.d3") { true } else { false });
+
+ assert!(if let Ok(None) = h.read("d.array.2.d1") { true } else { false });
+ assert!(if let Ok(None) = h.read("d.array.2.d2") { true } else { false });
+ assert!(if let Ok(Some(Value::Integer(_))) = h.read("d.array.2.d3") { true } else { false });
+
+ assert!(if let Ok(Some(Value::String(_))) = h.read("d.something") { true } else { false });
+ assert!(if let Ok(Some(Value::Table(_))) = h.read("d.and") { true } else { false });
+ assert!(if let Ok(Some(Value::Table(_))) = h.read("d.and.something") { true } else { false });
+ assert!(if let Ok(Some(Value::String(_))) = h.read("d.and.something.totally") { true } else { false });
+ }
+
+ #[test]
+ fn test_header_set_override() {
+ let _ = env_logger::init();
+ let v = create_header();
+ let mut h = match v {
+ Value::Table(t) => EntryHeader::from(t),
+ _ => panic!("create_header() doesn't return a table!"),
+ };
+
+ println!("Testing index 0");
+ assert_eq!(h.read("a.array.0").unwrap().unwrap(), Value::Integer(0));
+
+ println!("Altering index 0");
+ assert_eq!(h.set("a.array.0", Value::Integer(42)).unwrap().unwrap(), Value::Integer(0));
+
+ println!("Values now: {:?}", h);
+
+ println!("Testing all indexes");
+ assert_eq!(h.read("a.array.0").unwrap().unwrap(), Value::Integer(42));
+ assert_eq!(h.read("a.array.1").unwrap().unwrap(), Value::Integer(1));
+ assert_eq!(h.read("a.array.2").unwrap().unwrap(), Value::Integer(2));
+ assert_eq!(h.read("a.array.3").unwrap().unwrap(), Value::Integer(3));
+ assert_eq!(h.read("a.array.4").unwrap().unwrap(), Value::Integer(4));
+ assert_eq!(h.read("a.array.5").unwrap().unwrap(), Value::Integer(5));
+ assert_eq!(h.read("a.array.6").unwrap().unwrap(), Value::Integer(6));
+ assert_eq!(h.read("a.array.7").unwrap().unwrap(), Value::Integer(7));
+ assert_eq!(h.read("a.array.8").unwrap().unwrap(), Value::Integer(8));
+ assert_eq!(h.read("a.array.9").unwrap().unwrap(), Value::Integer(9));
+ }
+
+ #[test]
+ fn test_header_set_new() {
+ let _ = env_logger::init();
+ let v = create_header();
+ let mut h = match v {
+ Value::Table(t) => EntryHeader::from(t),
+ _ => panic!("create_header() doesn't return a table!"),
+ };
+
+ assert!(h.read("a.foo").is_ok());
+ assert!(h.read("a.foo").unwrap().is_none());
+
+ {
+ let v = h.set("a.foo", Value::Integer(42));
+ assert!(v.is_ok());
+ assert!(v.unwrap().is_none());
+
+ assert!(if let Ok(Some(Value::Table(_))) = h.read("a") { true } else { false });
+ assert!(if let Ok(Some(Value::Integer(_))) = h.read("a.foo") { true } else { false });
+ }
+
+ {
+ let v = h.set("new", Value::Table(BTreeMap::new()));
+ assert!(v.is_ok());
+ assert!(v.unwrap().is_none());
+
+ let v = h.set("new.subset", Value::Table(BTreeMap::new()));
+ assert!(v.is_ok());
+ assert!(v.unwrap().is_none());
+
+ let v = h.set("new.subset.dest", Value::Integer(1337));
+ assert!(v.is_ok());
+ assert!(v.unwrap().is_none());
+
+ assert!(if let Ok(Some(Value::Table(_))) = h.read("new") { true } else { false });
+ assert!(if let Ok(Some(Value::Table(_))) = h.read("new.subset") { true } else { false });
+ assert!(if let Ok(Some(Value::Integer(_))) = h.read("new.subset.dest") { true } else { false });
+ }
+ }
+
+
+ #[test]
+ fn test_header_insert_override() {
+ let _ = env_logger::init();
+ let v = create_header();
+ let mut h = match v {
+ Value::Table(t) => EntryHeader::from(t),
+ _ => panic!("create_header() doesn't return a table!"),
+ };
+
+ println!("Testing index 0");
+ assert_eq!(h.read("a.array.0").unwrap().unwrap(), Value::Integer(0));
+
+ println!("Altering index 0");
+ assert_eq!(h.insert("a.array.0", Value::Integer(42)).unwrap(), false);
+ println!("...should have failed");
+
+ println!("Testing all indexes");
+ assert_eq!(h.read("a.array.0").unwrap().unwrap(), Value::Integer(0));
+ assert_eq!(h.read("a.array.1").unwrap().unwrap(), Value::Integer(1));
+ assert_eq!(h.read("a.array.2").unwrap().unwrap(), Value::Integer(2));
+ assert_eq!(h.read("a.array.3").unwrap().unwrap(), Value::Integer(3));
+ assert_eq!(h.read("a.array.4").unwrap().unwrap(), Value::Integer(4));
+ assert_eq!(h.read("a.array.5").unwrap().unwrap(), Value::Integer(5));
+ assert_eq!(h.read("a.array.6").unwrap().unwrap(), Value::Integer(6));
+ assert_eq!(h.read("a.array.7").unwrap().unwrap(), Value::Integer(7));
+ assert_eq!(h.read("a.array.8").unwrap().unwrap(), Value::Integer(8));
+ assert_eq!(h.read("a.array.9").unwrap().unwrap(), Value::Integer(9));
+ }
+
+ #[test]
+ fn test_header_insert_new() {
+ let _ = env_logger::init();
+ let v = create_header();
+ let mut h = match v {
+ Value::Table(t) => EntryHeader::from(t),
+ _ => panic!("create_header() doesn't return a table!"),
+ };
+
+ assert!(h.read("a.foo").is_ok());
+ assert!(h.read("a.foo").unwrap().is_none());
+
+ {
+ let v = h.insert("a.foo", Value::Integer(42));
+ assert!(v.is_ok());
+ assert_eq!(v.unwrap(), true);
+
+ assert!(if let Ok(Some(Value::Table(_))) = h.read("a") { true } else { false });
+ assert!(if let Ok(Some(Value::Integer(_))) = h.read("a.foo") { true } else { false });
+ }
+
+ {
+ let v = h.insert("new", Value::Table(BTreeMap::new()));
+ assert!(v.is_ok());
+ assert_eq!(v.unwrap(), true);
+
+ let v = h.insert("new.subset", Value::Table(BTreeMap::new()));
+ assert!(v.is_ok());
+ assert_eq!(v.unwrap(), true);
+
+ let v = h.insert("new.subset.dest", Value::Integer(1337));
+ assert!(v.is_ok());
+ assert_eq!(v.unwrap(), true);
+
+ assert!(if let Ok(Some(Value::Table(_))) = h.read("new") { true } else { false });
+ assert!(if let Ok(Some(Value::Table(_))) = h.read("new.subset") { true } else { false });
+ assert!(if let Ok(Some(Value::Integer(_))) = h.read("new.subset.dest") { true } else { false });
+ }
+ }
+
+ #[test]
+ fn test_header_delete() {
+ let _ = env_logger::init();
+ let v = create_header();
+ let mut h = match v {
+ Value::Table(t) => EntryHeader::from(t),
+ _ => panic!("create_header() doesn't return a table!"),
+ };
+
+ assert!(if let Ok(Some(Value::Table(_))) = h.read("a") { true } else { false });
+ assert!(if let Ok(Some(Value::Array(_))) = h.read("a.array") { true } else { false });
+ assert!(if let Ok(Some(Value::Integer(_))) = h.read("a.array.1") { true } else { false });
+ assert!(if let Ok(Some(Value::Integer(_))) = h.read("a.array.9") { true } else { false });
+
+ assert!(if let Ok(Some(Value::Integer(1))) = h.delete("a.array.1") { true } else { false });
+ assert!(if let Ok(Some(Value::Integer(9))) = h.delete("a.array.8") { true } else { false });
+ assert!(if let Ok(Some(Value::Array(_))) = h.delete("a.array") { true } else { false });
+ assert!(if let Ok(Some(Value::Table(_))) = h.delete("a") { true } else { false });
+
+ }
+
+}