diff options
author | Matthias Beyer <mail@beyermatthias.de> | 2017-08-28 14:10:40 +0200 |
---|---|---|
committer | GitHub <noreply@github.com> | 2017-08-28 14:10:40 +0200 |
commit | c987130cc1adca9c8074e7e1afe235c4b3f7157e (patch) | |
tree | e92280d550d2d601783821e832e6c3f8617a64b5 | |
parent | b2d6a881b0902a71055d93072ad0914cccb6003f (diff) | |
parent | c0c62bd1b5c5b9ece1d8289721dc8617987a7885 (diff) |
Merge pull request #1018 from matthiasbeyer/remove-toml-ext
Remove toml ext
38 files changed, 219 insertions, 999 deletions
diff --git a/lib/core/libimagrt/Cargo.toml b/lib/core/libimagrt/Cargo.toml index 4f0f0863..0599e233 100644 --- a/lib/core/libimagrt/Cargo.toml +++ b/lib/core/libimagrt/Cargo.toml @@ -23,6 +23,7 @@ itertools = "0.5" tempfile = "2.1" ansi_term = "0.9" is-match = "0.1" +toml-query = "0.3.0" libimagstore = { version = "0.4.0", path = "../../../lib/core/libimagstore" } libimagerror = { version = "0.4.0", path = "../../../lib/core/libimagerror" } diff --git a/lib/core/libimagrt/src/configuration.rs b/lib/core/libimagrt/src/configuration.rs index dcb3326b..bc3b52b4 100644 --- a/lib/core/libimagrt/src/configuration.rs +++ b/lib/core/libimagrt/src/configuration.rs @@ -133,7 +133,8 @@ impl Configuration { use self::error::ConfigErrorKind as CEK; use self::error::MapErrInto; use libimagerror::into::IntoError; - use libimagstore::toml_ext::TomlValueExt; + + use toml_query::read::TomlValueReadExt; v.into_iter() .map(|s| { debug!("Trying to process '{}'", s); s }) @@ -170,10 +171,10 @@ impl Configuration { /// Returns None if string cannot be converted. /// /// Arrays and Tables are not supported and will yield `None`. -fn into_value(value: Value, s: String) -> Option<Value> { +fn into_value(value: &Value, s: String) -> Option<Value> { use std::str::FromStr; - match value { + match *value { Value::String(_) => Some(Value::String(s)), Value::Integer(_) => FromStr::from_str(&s[..]).ok().map(Value::Integer), Value::Float(_) => FromStr::from_str(&s[..]).ok().map(Value::Float), diff --git a/lib/core/libimagrt/src/lib.rs b/lib/core/libimagrt/src/lib.rs index 55398013..a28b525f 100644 --- a/lib/core/libimagrt/src/lib.rs +++ b/lib/core/libimagrt/src/lib.rs @@ -42,6 +42,7 @@ extern crate ansi_term; extern crate clap; extern crate toml; +extern crate toml_query; #[macro_use] extern crate is_match; extern crate libimagstore; diff --git a/lib/core/libimagstore/src/lib.rs b/lib/core/libimagstore/src/lib.rs index afb40e80..ba9abe83 100644 --- a/lib/core/libimagstore/src/lib.rs +++ b/lib/core/libimagstore/src/lib.rs @@ -60,5 +60,4 @@ pub mod error; pub mod store; mod configuration; pub mod file_abstraction; -pub mod toml_ext; diff --git a/lib/core/libimagstore/src/store.rs b/lib/core/libimagstore/src/store.rs index a0ee33e5..af7e5aaf 100644 --- a/lib/core/libimagstore/src/store.rs +++ b/lib/core/libimagstore/src/store.rs @@ -18,6 +18,7 @@ // use std::collections::HashMap; +use std::collections::BTreeMap; use std::ops::Drop; use std::path::PathBuf; use std::result::Result as RResult; @@ -32,15 +33,16 @@ use std::fmt::Debug; use std::fmt::Error as FMTError; use toml::Value; +use toml::value::Table; use glob::glob; use walkdir::WalkDir; use walkdir::Iter as WalkDirIter; -use error::{StoreError as SE, StoreErrorKind as SEK}; +use error::{StoreError as SE, StoreErrorKind as SEK, ParserError, ParserErrorKind}; use error::MapErrInto; use storeid::{IntoStoreId, StoreId, StoreIdIterator}; use file_abstraction::FileAbstractionInstance; -use toml_ext::*; + // We re-export the following things so tests can use them pub use file_abstraction::FileAbstraction; pub use file_abstraction::FSFileAbstraction; @@ -1115,6 +1117,96 @@ mod glob_store_iter { } +/// Extension trait for top-level toml::Value::Table, will only yield correct results on the +/// top-level Value::Table, but not on intermediate tables. +pub trait Header { + fn verify(&self) -> Result<()>; + fn parse(s: &str) -> RResult<Value, ParserError>; + fn default_header() -> Value; +} + +impl Header for Value { + + fn verify(&self) -> Result<()> { + match *self { + Value::Table(ref t) => verify_header(&t), + _ => Err(SE::new(SEK::HeaderTypeFailure, None)), + } + } + + fn parse(s: &str) -> RResult<Value, ParserError> { + use toml::de::from_str; + + from_str(s) + .map_err(|_| ParserErrorKind::TOMLParserErrors.into()) + .and_then(verify_header_consistency) + .map(Value::Table) + } + + fn default_header() -> Value { + let mut m = BTreeMap::new(); + + m.insert(String::from("imag"), { + let mut imag_map = BTreeMap::<String, Value>::new(); + + imag_map.insert(String::from("version"), Value::String(String::from(version!()))); + imag_map.insert(String::from("links"), Value::Array(vec![])); + + Value::Table(imag_map) + }); + + Value::Table(m) + } + +} + +fn verify_header_consistency(t: Table) -> RResult<Table, ParserError> { + verify_header(&t) + .map_err(Box::new) + .map_err(|e| ParserErrorKind::HeaderInconsistency.into_error_with_cause(e)) + .map(|_| t) +} + +fn verify_header(t: &Table) -> Result<()> { + if !has_main_section(t) { + Err(SE::from(ParserErrorKind::MissingMainSection.into_error())) + } else if !has_imag_version_in_main_section(t) { + Err(SE::from(ParserErrorKind::MissingVersionInfo.into_error())) + } else if !has_only_tables(t) { + debug!("Could not verify that it only has tables in its base table"); + Err(SE::from(ParserErrorKind::NonTableInBaseTable.into_error())) + } else { + Ok(()) + } +} + +fn has_only_tables(t: &Table) -> bool { + debug!("Verifying that table has only tables"); + t.iter().all(|(_, x)| is_match!(*x, Value::Table(_))) +} + +fn has_main_section(t: &Table) -> bool { + t.contains_key("imag") && is_match!(t.get("imag"), Some(&Value::Table(_))) +} + +fn has_imag_version_in_main_section(t: &Table) -> bool { + use semver::Version; + + match *t.get("imag").unwrap() { + Value::Table(ref sec) => { + sec.get("version") + .and_then(|v| { + match *v { + Value::String(ref s) => Some(Version::parse(&s[..]).is_ok()), + _ => Some(false), + } + }) + .unwrap_or(false) + } + _ => false, + } +} + #[cfg(test)] mod test { @@ -1122,13 +1214,14 @@ mod test { use std::collections::BTreeMap; use storeid::StoreId; + use store::has_main_section; + use store::has_imag_version_in_main_section; + use store::verify_header_consistency; use toml::Value; #[test] fn test_imag_section() { - use toml_ext::has_main_section; - let mut map = BTreeMap::new(); map.insert("imag".into(), Value::Table(BTreeMap::new())); @@ -1137,8 +1230,6 @@ mod test { #[test] fn test_imag_invalid_section_type() { - use toml_ext::has_main_section; - let mut map = BTreeMap::new(); map.insert("imag".into(), Value::Boolean(false)); @@ -1147,8 +1238,6 @@ mod test { #[test] fn test_imag_abscent_main_section() { - use toml_ext::has_main_section; - let mut map = BTreeMap::new(); map.insert("not_imag".into(), Value::Boolean(false)); @@ -1157,8 +1246,6 @@ mod test { #[test] fn test_main_section_without_version() { - use toml_ext::has_imag_version_in_main_section; - let mut map = BTreeMap::new(); map.insert("imag".into(), Value::Table(BTreeMap::new())); @@ -1167,8 +1254,6 @@ mod test { #[test] fn test_main_section_with_version() { - use toml_ext::has_imag_version_in_main_section; - let mut map = BTreeMap::new(); let mut sub = BTreeMap::new(); sub.insert("version".into(), Value::String("0.0.0".into())); @@ -1179,8 +1264,6 @@ mod test { #[test] fn test_main_section_with_version_in_wrong_type() { - use toml_ext::has_imag_version_in_main_section; - let mut map = BTreeMap::new(); let mut sub = BTreeMap::new(); sub.insert("version".into(), Value::Boolean(false)); @@ -1191,8 +1274,6 @@ mod test { #[test] fn test_verification_good() { - use toml_ext::verify_header_consistency; - let mut header = BTreeMap::new(); let sub = { let mut sub = BTreeMap::new(); @@ -1208,8 +1289,6 @@ mod test { #[test] fn test_verification_invalid_versionstring() { - use toml_ext::verify_header_consistency; - let mut header = BTreeMap::new(); let sub = { let mut sub = BTreeMap::new(); @@ -1226,8 +1305,6 @@ mod test { #[test] fn test_verification_current_version() { - use toml_ext::verify_header_consistency; - let mut header = BTreeMap::new(); let sub = { let mut sub = BTreeMap::new(); diff --git a/lib/core/libimagstore/src/toml_ext.rs b/lib/core/libimagstore/src/toml_ext.rs deleted file mode 100644 index 9c0ae540..00000000 --- a/lib/core/libimagstore/src/toml_ext.rs +++ /dev/null @@ -1,894 +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 std::result::Result as RResult; -use std::collections::BTreeMap; - -use toml::Value; - -use store::Result; -use error::StoreError as SE; -use error::StoreErrorKind as SEK; -use error::{ParserErrorKind, ParserError}; -use libimagerror::into::IntoError; - -type Table = BTreeMap<String, Value>; - -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()), - } - } - } -} - -pub type EntryResult<T> = RResult<T, ParserError>; - -/// Extension trait for top-level toml::Value::Table, will only yield correct results on the -/// top-level Value::Table, but not on intermediate tables. -pub trait Header { - fn verify(&self) -> Result<()>; - fn parse(s: &str) -> EntryResult<Value>; - fn default_header() -> Value; -} - -impl Header for Value { - - fn verify(&self) -> Result<()> { - match *self { - Value::Table(ref t) => verify_header(&t), - _ => Err(SE::new(SEK::HeaderTypeFailure, None)), - } - } - - fn parse(s: &str) -> EntryResult<Value> { - use toml::de::from_str; - - from_str(s) - .map_err(|_| ParserErrorKind::TOMLParserErrors.into()) - .and_then(verify_header_consistency) - .map(Value::Table) - } - - fn default_header() -> Value { - let mut m = BTreeMap::new(); - - m.insert(String::from("imag"), { - let mut imag_map = BTreeMap::<String, Value>::new(); - - imag_map.insert(String::from("version"), Value::String(String::from(version!()))); - imag_map.insert(String::from("links"), Value::Array(vec![])); - - Value::Table(imag_map) - }); - - Value::Table(m) - } - -} - -pub fn verify_header_consistency(t: Table) -> EntryResult<Table> { - verify_header(&t) - .map_err(Box::new) - .map_err(|e| ParserErrorKind::HeaderInconsistency.into_error_with_cause(e)) - .map(|_| t) -} - -fn verify_header(t: &Table) -> Result<()> { - if !has_main_section(t) { - Err(SE::from(ParserErrorKind::MissingMainSection.into_error())) - } else if !has_imag_version_in_main_section(t) { - Err(SE::from(ParserErrorKind::MissingVersionInfo.into_error())) - } else if !has_only_tables(t) { - debug!("Could not verify that it only has tables in its base table"); - Err(SE::from(ParserErrorKind::NonTableInBaseTable.into_error())) - } else { - Ok(()) - } -} - -fn has_only_tables(t: &Table) -> bool { - debug!("Verifying that table has only tables"); - t.iter().all(|(_, x)| is_match!(*x, Value::Table(_))) -} - -pub fn has_main_section(t: &Table) -> bool { - t.contains_key("imag") && is_match!(t.get("imag"), Some(&Value::Table(_))) -} - -pub fn has_imag_version_in_main_section(t: &Table) -> bool { - use semver::Version; - - match *t.get("imag").unwrap() { - Value::Table(ref sec) => { - sec.get("version") - .and_then(|v| { - match *v { - Value::String(ref s) => Some(Version::parse(&s[..]).is_ok()), - _ => Some(false), - } - }) - .unwrap_or(false) - } - _ => false, - } -} - - -#[cfg(test)] -mod test { - extern crate env_logger; - use super::TomlValueExt; - 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! |