diff options
author | Matthias Beyer <mail@beyermatthias.de> | 2016-11-14 14:09:22 +0100 |
---|---|---|
committer | Matthias Beyer <mail@beyermatthias.de> | 2016-11-14 14:52:50 +0100 |
commit | 5470ffceacac6fd3a4a022df73b30699523387ee (patch) | |
tree | cdd8ac15b51cc08cdbcd16c620ea57b18204d40b /libimagstore | |
parent | b210b0ec3edfc6269baedc2791d780b169975877 (diff) |
Extract TOML extension to own module
Diffstat (limited to 'libimagstore')
-rw-r--r-- | libimagstore/src/lib.rs | 1 | ||||
-rw-r--r-- | libimagstore/src/store.rs | 369 | ||||
-rw-r--r-- | libimagstore/src/toml_ext.rs | 411 |
3 files changed, 430 insertions, 351 deletions
diff --git a/libimagstore/src/lib.rs b/libimagstore/src/lib.rs index 08eb0afa..a696f7cc 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 eca2ea07..c97d8298 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; @@ -970,12 +971,6 @@ pub struct EntryHeader { 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 */ @@ -1014,367 +1009,39 @@ impl EntryHeader { } } - /** - * 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.header.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.header.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.header.read_with_sep(spec, splitchr) } + #[inline] 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) + self.header.delete(spec) } - 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(EntryHeader::extract(value, &token), i) - } else { - Ok(value) - } - }) - } - - walk_iter(Ok(v), &mut tokens.into_iter()) - } - - 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 insert(&mut self, spec: &str, v: Value) -> Result<bool> { + self.insert_with_sep(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 set(&mut self, spec: &str, v: Value) -> Result<Option<Value>> { + self.set_with_sep(spec, '.', v) } - 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 read(&self, spec: &str) -> Result<Option<Value>> { + self.read_with_sep(spec, '.') } } diff --git a/libimagstore/src/toml_ext.rs b/libimagstore/src/toml_ext.rs new file mode 100644 index 00000000..59e2a35a --- /dev/null +++ b/libimagstore/src/toml_ext.rs @@ -0,0 +1,411 @@ +// +// 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::{StoreError as SE, StoreErrorKind as SEK}; + +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 tokens = match 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 walk_header(self, path_to_dest) { + Err(e) => return Err(e), + Ok(v) => v + }; + + // There is already an value at this place + if 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 + */ + fn set_with_sep(&mut self, spec: &str, sep: char, v: Value) -> Result<Option<Value>> { + let tokens = match 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 walk_header(self, 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. + */ + fn read_with_sep(&self, spec: &str, splitchr: char) -> Result<Option<Value>> { + let tokens = match tokenize(spec, splitchr) { + Err(e) => return Err(e), + Ok(t) => t, + }; + + 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 tokens = match tokenize(spec, splitchr) { + 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 walk_header(self, 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() +} + +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(extract(value, &token), i) + } else { + Ok(value) + } + }) + } + + walk_iter(Ok(v), &mut tokens.into_iter()) +} + +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)), + } +} + +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)), + } +} + +fn extract<'a>(v: &'a mut Value, token: &Token) -> Result<&'a mut Value> { + match *token { + Token::Key(ref s) => extract_from_table(v, s), + Token::Index(i) => extract_from_array(v, i), + } +} + |