summaryrefslogtreecommitdiff
path: root/lib
diff options
context:
space:
mode:
Diffstat (limited to 'lib')
-rw-r--r--lib/core/libimagerror/Cargo.toml18
l---------lib/core/libimagerror/README.md1
-rw-r--r--lib/core/libimagerror/src/error_gen.rs440
-rw-r--r--lib/core/libimagerror/src/into.rs33
-rw-r--r--lib/core/libimagerror/src/iter.rs232
-rw-r--r--lib/core/libimagerror/src/lib.rs40
-rw-r--r--lib/core/libimagerror/src/trace.rs154
-rw-r--r--lib/core/libimagrt/.gitignore1
-rw-r--r--lib/core/libimagrt/Cargo.toml29
l---------lib/core/libimagrt/README.md1
-rw-r--r--lib/core/libimagrt/src/configuration.rs303
-rw-r--r--lib/core/libimagrt/src/error.rs36
-rw-r--r--lib/core/libimagrt/src/lib.rs57
-rw-r--r--lib/core/libimagrt/src/logger.rs133
-rw-r--r--lib/core/libimagrt/src/runtime.rs463
-rw-r--r--lib/core/libimagrt/src/setup.rs46
-rw-r--r--lib/core/libimagrt/src/spec.rs30
-rw-r--r--lib/core/libimagstore/.gitignore1
-rw-r--r--lib/core/libimagstore/Cargo.toml73
l---------lib/core/libimagstore/README.md1
-rw-r--r--lib/core/libimagstore/src/configuration.rs104
-rw-r--r--lib/core/libimagstore/src/error.rs116
-rw-r--r--lib/core/libimagstore/src/file_abstraction/fs.rs167
-rw-r--r--lib/core/libimagstore/src/file_abstraction/inmemory.rs177
-rw-r--r--lib/core/libimagstore/src/file_abstraction/mod.rs120
-rw-r--r--lib/core/libimagstore/src/file_abstraction/stdio/mapper/json.rs250
-rw-r--r--lib/core/libimagstore/src/file_abstraction/stdio/mapper/mod.rs32
-rw-r--r--lib/core/libimagstore/src/file_abstraction/stdio/mod.rs127
-rw-r--r--lib/core/libimagstore/src/file_abstraction/stdio/out.rs157
-rw-r--r--lib/core/libimagstore/src/lib.rs62
-rw-r--r--lib/core/libimagstore/src/store.rs1747
-rw-r--r--lib/core/libimagstore/src/storeid.rs391
-rw-r--r--lib/core/libimagstore/src/toml_ext.rs894
-rw-r--r--lib/core/libimagstore/src/util.rs70
-rw-r--r--lib/domain/libimagbookmark/Cargo.toml24
l---------lib/domain/libimagbookmark/README.md1
-rw-r--r--lib/domain/libimagbookmark/src/collection.rs222
-rw-r--r--lib/domain/libimagbookmark/src/error.rs33
-rw-r--r--lib/domain/libimagbookmark/src/lib.rs47
-rw-r--r--lib/domain/libimagbookmark/src/link.rs76
-rw-r--r--lib/domain/libimagbookmark/src/result.rs25
-rw-r--r--lib/domain/libimagcounter/Cargo.toml23
l---------lib/domain/libimagcounter/README.md1
-rw-r--r--lib/domain/libimagcounter/src/counter.rs266
-rw-r--r--lib/domain/libimagcounter/src/error.rs32
-rw-r--r--lib/domain/libimagcounter/src/lib.rs47
-rw-r--r--lib/domain/libimagcounter/src/result.rs25
-rw-r--r--lib/domain/libimagdiary/Cargo.toml30
l---------lib/domain/libimagdiary/README.md1
-rw-r--r--lib/domain/libimagdiary/src/config.rs43
-rw-r--r--lib/domain/libimagdiary/src/diary.rs128
-rw-r--r--lib/domain/libimagdiary/src/diaryid.rs257
-rw-r--r--lib/domain/libimagdiary/src/entry.rs90
-rw-r--r--lib/domain/libimagdiary/src/error.rs38
-rw-r--r--lib/domain/libimagdiary/src/is_in_diary.rs44
-rw-r--r--lib/domain/libimagdiary/src/iter.rs132
-rw-r--r--lib/domain/libimagdiary/src/lib.rs62
-rw-r--r--lib/domain/libimagdiary/src/result.rs24
-rw-r--r--lib/domain/libimagdiary/src/viewer.rs64
-rw-r--r--lib/domain/libimagmail/Cargo.toml25
l---------lib/domain/libimagmail/README.md1
-rw-r--r--lib/domain/libimagmail/src/error.rs35
-rw-r--r--lib/domain/libimagmail/src/hasher.rs87
-rw-r--r--lib/domain/libimagmail/src/iter.rs56
-rw-r--r--lib/domain/libimagmail/src/lib.rs35
-rw-r--r--lib/domain/libimagmail/src/mail.rs137
-rw-r--r--lib/domain/libimagmail/src/result.rs25
-rw-r--r--lib/domain/libimagnotes/Cargo.toml25
-rw-r--r--lib/domain/libimagnotes/src/error.rs32
-rw-r--r--lib/domain/libimagnotes/src/lib.rs51
-rw-r--r--lib/domain/libimagnotes/src/note.rs204
-rw-r--r--lib/domain/libimagnotes/src/result.rs25
-rw-r--r--lib/domain/libimagtodo/Cargo.toml28
-rw-r--r--lib/domain/libimagtodo/src/error.rs32
-rw-r--r--lib/domain/libimagtodo/src/lib.rs52
-rw-r--r--lib/domain/libimagtodo/src/result.rs24
-rw-r--r--lib/domain/libimagtodo/src/task.rs294
-rw-r--r--lib/entry/libimagentryannotation/Cargo.toml21
l---------lib/entry/libimagentryannotation/README.md1
-rw-r--r--lib/entry/libimagentryannotation/src/annotateable.rs84
-rw-r--r--lib/entry/libimagentryannotation/src/annotation_fetcher.rs115
-rw-r--r--lib/entry/libimagentryannotation/src/error.rs35
-rw-r--r--lib/entry/libimagentryannotation/src/lib.rs33
-rw-r--r--lib/entry/libimagentryannotation/src/result.rs26
-rw-r--r--lib/entry/libimagentrycategory/Cargo.toml27
-rw-r--r--lib/entry/libimagentrycategory/src/category.rs103
-rw-r--r--lib/entry/libimagentrycategory/src/error.rs36
-rw-r--r--lib/entry/libimagentrycategory/src/lib.rs38
-rw-r--r--lib/entry/libimagentrycategory/src/register.rs290
-rw-r--r--lib/entry/libimagentrycategory/src/result.rs26
-rw-r--r--lib/entry/libimagentrydatetime/Cargo.toml28
-rw-r--r--lib/entry/libimagentrydatetime/src/datepath/accuracy.rs112
-rw-r--r--lib/entry/libimagentrydatetime/src/datepath/compiler.rs196
-rw-r--r--lib/entry/libimagentrydatetime/src/datepath/error.rs30
-rw-r--r--lib/entry/libimagentrydatetime/src/datepath/format.rs92
-rw-r--r--lib/entry/libimagentrydatetime/src/datepath/mod.rs26
-rw-r--r--lib/entry/libimagentrydatetime/src/datepath/result.rs25
-rw-r--r--lib/entry/libimagentrydatetime/src/datepath/to_store_id.rs39
-rw-r--r--lib/entry/libimagentrydatetime/src/datetime.rs330
-rw-r--r--lib/entry/libimagentrydatetime/src/error.rs39
-rw-r--r--lib/entry/libimagentrydatetime/src/lib.rs37
-rw-r--r--lib/entry/libimagentrydatetime/src/range.rs111
-rw-r--r--lib/entry/libimagentrydatetime/src/result.rs25
-rw-r--r--lib/entry/libimagentryedit/Cargo.toml21
-rw-r--r--lib/entry/libimagentryedit/src/edit.rs66
-rw-r--r--lib/entry/libimagentryedit/src/error.rs32
-rw-r--r--lib/entry/libimagentryedit/src/lib.rs27
-rw-r--r--lib/entry/libimagentryedit/src/result.rs25
-rw-r--r--lib/entry/libimagentryfilter/Cargo.toml26
l---------lib/entry/libimagentryfilter/README.md1
-rw-r--r--lib/entry/libimagentryfilter/src/builtin/bool_filter.rs41
-rw-r--r--lib/entry/libimagentryfilter/src/builtin/content/grep.rs72
-rw-r--r--lib/entry/libimagentryfilter/src/builtin/content/length/is_over.rs45
-rw-r--r--lib/entry/libimagentryfilter/src/builtin/content/length/is_under.rs45
-rw-r--r--lib/entry/libimagentryfilter/src/builtin/content/length/mod.rs21
-rw-r--r--lib/entry/libimagentryfilter/src/builtin/content/mod.rs21
-rw-r--r--lib/entry/libimagentryfilter/src/builtin/header/field_eq.rs63
-rw-r--r--lib/entry/libimagentryfilter/src/builtin/header/field_exists.rs48
-rw-r--r--lib/entry/libimagentryfilter/src/builtin/header/field_grep.rs68
-rw-r--r--lib/entry/libimagentryfilter/src/builtin/header/field_gt.rs79
-rw-r--r--lib/entry/libimagentryfilter/src/builtin/header/field_isempty.rs64
-rw-r--r--lib/entry/libimagentryfilter/src/builtin/header/field_istype.rs89
-rw-r--r--lib/entry/libimagentryfilter/src/builtin/header/field_lt.rs79
-rw-r--r--lib/entry/libimagentryfilter/src/builtin/header/field_path.rs20
-rw-r--r--lib/entry/libimagentryfilter/src/builtin/header/field_predicate.rs65
-rw-r--r--lib/entry/libimagentryfilter/src/builtin/header/mod.rs29
-rw-r--r--lib/entry/libimagentryfilter/src/builtin/header/version/eq.rs62
-rw-r--r--lib/entry/libimagentryfilter/src/builtin/header/version/gt.rs64
-rw-r--r--lib/entry/libimagentryfilter/src/builtin/header/version/lt.rs63
-rw-r--r--lib/entry/libimagentryfilter/src/builtin/header/version/mod.rs23
-rw-r--r--lib/entry/libimagentryfilter/src/builtin/header/version/range.rs69
-rw-r--r--lib/entry/libimagentryfilter/src/builtin/mod.rs24
-rw-r--r--lib/entry/libimagentryfilter/src/cli.rs19
-rw-r--r--lib/entry/libimagentryfilter/src/lib.rs54
-rw-r--r--lib/entry/libimagentryfilter/src/tags/mod.rs96
-rw-r--r--lib/entry/libimagentrylink/Cargo.toml28
l---------lib/entry/libimagentrylink/README.md1
-rw-r--r--lib/entry/libimagentrylink/src/error.rs40
-rw-r--r--lib/entry/libimagentrylink/src/external.rs416
-rw-r--r--lib/entry/libimagentrylink/src/internal.rs1060
-rw-r--r--lib/entry/libimagentrylink/src/lib.rs55
-rw-r--r--lib/entry/libimagentrylink/src/result.rs25
-rw-r--r--lib/entry/libimagentrylist/Cargo.toml24
l---------lib/entry/libimagentrylist/README.md1
-rw-r--r--lib/entry/libimagentrylist/src/cli.rs101
-rw-r--r--lib/entry/libimagentrylist/src/error.rs33
-rw-r--r--lib/entry/libimagentrylist/src/lib.rs50
-rw-r--r--lib/entry/libimagentrylist/src/lister.rs29
-rw-r--r--lib/entry/libimagentrylist/src/listers/core.rs65
-rw-r--r--lib/entry/libimagentrylist/src/listers/line.rs55
-rw-r--r--lib/entry/libimagentrylist/src/listers/mod.rs23
-rw-r--r--lib/entry/libimagentrylist/src/listers/path.rs75
-rw-r--r--lib/entry/libimagentrylist/src/listers/table.rs110
-rw-r--r--lib/entry/libimagentrylist/src/result.rs25
-rw-r--r--lib/entry/libimagentrymarkdown/Cargo.toml24
l---------lib/entry/libimagentrymarkdown/README.md1
-rw-r--r--lib/entry/libimagentrymarkdown/src/error.rs30
-rw-r--r--lib/entry/libimagentrymarkdown/src/html.rs101
-rw-r--r--lib/entry/libimagentrymarkdown/src/lib.rs44
-rw-r--r--lib/entry/libimagentrymarkdown/src/link.rs162
-rw-r--r--lib/entry/libimagentrymarkdown/src/result.rs25
-rw-r--r--lib/entry/libimagentryref/Cargo.toml28
l---------lib/entry/libimagentryref/README.md1
-rw-r--r--lib/entry/libimagentryref/src/error.rs55
-rw-r--r--lib/entry/libimagentryref/src/flags.rs107
-rw-r--r--lib/entry/libimagentryref/src/hasher.rs67
-rw-r--r--lib/entry/libimagentryref/src/hashers/mod.rs20
-rw-r--r--lib/entry/libimagentryref/src/hashers/nbytes.rs71
-rw-r--r--lib/entry/libimagentryref/src/lib.rs55
-rw-r--r--lib/entry/libimagentryref/src/lister.rs194
-rw-r--r--lib/entry/libimagentryref/src/reference.rs577
-rw-r--r--lib/entry/libimagentryref/src/result.rs25
-rw-r--r--lib/entry/libimagentrytag/Cargo.toml27
l---------lib/entry/libimagentrytag/README.md1
-rw-r--r--lib/entry/libimagentrytag/src/error.rs32
-rw-r--r--lib/entry/libimagentrytag/src/exec.rs46
-rw-r--r--lib/entry/libimagentrytag/src/lib.rs52
-rw-r--r--lib/entry/libimagentrytag/src/result.rs25
-rw-r--r--lib/entry/libimagentrytag/src/tag.rs44
-rw-r--r--lib/entry/libimagentrytag/src/tagable.rs175
-rw-r--r--lib/entry/libimagentrytag/src/ui.rs125
-rw-r--r--lib/entry/libimagentrytimetrack/Cargo.toml27
l---------lib/entry/libimagentrytimetrack/README.md1
-rw-r--r--lib/entry/libimagentrytimetrack/src/constants.rs25
-rw-r--r--lib/entry/libimagentrytimetrack/src/error.rs39
-rw-r--r--lib/entry/libimagentrytimetrack/src/iter/create.rs77
-rw-r--r--lib/entry/libimagentrytimetrack/src/iter/filter.rs124
-rw-r--r--lib/entry/libimagentrytimetrack/src/iter/get.rs65
-rw-r--r--lib/entry/libimagentrytimetrack/src/iter/mod.rs63
-rw-r--r--lib/entry/libimagentrytimetrack/src/iter/setendtime.rs65
-rw-r--r--lib/entry/libimagentrytimetrack/src/iter/storeid.rs73
-rw-r--r--lib/entry/libimagentrytimetrack/src/iter/tag.rs55
-rw-r--r--lib/entry/libimagentrytimetrack/src/lib.rs45
-rw-r--r--lib/entry/libimagentrytimetrack/src/result.rs26
-rw-r--r--lib/entry/libimagentrytimetrack/src/tag.rs69
-rw-r--r--lib/entry/libimagentrytimetrack/src/timetracking.rs152
-rw-r--r--lib/entry/libimagentrytimetrack/src/timetrackingstore.rs114
-rw-r--r--lib/entry/libimagentryview/Cargo.toml24
-rw-r--r--lib/entry/libimagentryview/src/builtin/editor.rs44
-rw-r--r--lib/entry/libimagentryview/src/builtin/mod.rs22
-rw-r--r--lib/entry/libimagentryview/src/builtin/plain.rs49
-rw-r--r--lib/entry/libimagentryview/src/builtin/stdout.rs57
-rw-r--r--lib/entry/libimagentryview/src/error.rs32
-rw-r--r--lib/entry/libimagentryview/src/lib.rs48
-rw-r--r--lib/entry/libimagentryview/src/result.rs24
-rw-r--r--lib/entry/libimagentryview/src/viewer.rs36
-rw-r--r--lib/etc/libimaginteraction/Cargo.toml30
l---------lib/etc/libimaginteraction/README.md1
-rw-r--r--lib/etc/libimaginteraction/src/ask.rs350
-rw-r--r--lib/etc/libimaginteraction/src/error.rs39
-rw-r--r--lib/etc/libimaginteraction/src/filter.rs20
-rw-r--r--lib/etc/libimaginteraction/src/lib.rs53
-rw-r--r--lib/etc/libimaginteraction/src/readline.rs129
-rw-r--r--lib/etc/libimaginteraction/src/result.rs24
-rw-r--r--lib/etc/libimaginteraction/src/ui.rs83
-rw-r--r--lib/etc/libimagtimeui/Cargo.toml22
-rw-r--r--lib/etc/libimagtimeui/src/cli.rs42
-rw-r--r--lib/etc/libimagtimeui/src/date.rs141
-rw-r--r--lib/etc/libimagtimeui/src/datetime.rs67
-rw-r--r--lib/etc/libimagtimeui/src/lib.rs45
-rw-r--r--lib/etc/libimagtimeui/src/parse.rs29
-rw-r--r--lib/etc/libimagtimeui/src/time.rs159
-rw-r--r--lib/etc/libimagtimeui/src/ui.rs31
-rw-r--r--lib/etc/libimagutil/.gitignore1
-rw-r--r--lib/etc/libimagutil/Cargo.toml29
l---------lib/etc/libimagutil/README.md1
-rw-r--r--lib/etc/libimagutil/src/cli_validators.rs49
-rw-r--r--lib/etc/libimagutil/src/debug_result.rs29
-rw-r--r--lib/etc/libimagutil/src/edit.rs54
-rw-r--r--lib/etc/libimagutil/src/info_result.rs29
-rw-r--r--lib/etc/libimagutil/src/iter.rs74
-rw-r--r--lib/etc/libimagutil/src/key_value_split.rs124
-rw-r--r--lib/etc/libimagutil/src/lib.rs55
-rw-r--r--lib/etc/libimagutil/src/log_result.rs108
-rw-r--r--lib/etc/libimagutil/src/testing.rs115
-rw-r--r--lib/etc/libimagutil/src/variants.rs78
-rw-r--r--lib/etc/libimagutil/src/warn_exit.rs41
-rw-r--r--lib/etc/libimagutil/src/warn_result.rs30
238 files changed, 20623 insertions, 0 deletions
diff --git a/lib/core/libimagerror/Cargo.toml b/lib/core/libimagerror/Cargo.toml
new file mode 100644
index 0000000..e108497
--- /dev/null
+++ b/lib/core/libimagerror/Cargo.toml
@@ -0,0 +1,18 @@
+[package]
+name = "libimagerror"
+version = "0.4.0"
+authors = ["Matthias Beyer <mail@beyermatthias.de>"]
+
+description = "Library for the imag core distribution"
+
+keywords = ["imag", "PIM", "personal", "information", "management"]
+readme = "../README.md"
+license = "LGPL-2.1"
+
+documentation = "https://matthiasbeyer.github.io/imag/imag_documentation/index.html"
+repository = "https://github.com/matthiasbeyer/imag"
+homepage = "http://imag-pim.org"
+
+[dependencies]
+log = "0.3"
+ansi_term = "0.9"
diff --git a/lib/core/libimagerror/README.md b/lib/core/libimagerror/README.md
new file mode 120000
index 0000000..359a1ec
--- /dev/null
+++ b/lib/core/libimagerror/README.md
@@ -0,0 +1 @@
+../doc/src/05100-lib-error.md \ No newline at end of file
diff --git a/lib/core/libimagerror/src/error_gen.rs b/lib/core/libimagerror/src/error_gen.rs
new file mode 100644
index 0000000..ccfd157
--- /dev/null
+++ b/lib/core/libimagerror/src/error_gen.rs
@@ -0,0 +1,440 @@
+//
+// 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
+//
+
+#[macro_export]
+macro_rules! generate_error_imports {
+ () => {
+ use std::error::Error;
+ use std::fmt::Error as FmtError;
+ use std::fmt::{Display, Formatter};
+
+ use $crate::into::IntoError;
+ }
+}
+
+#[macro_export]
+macro_rules! generate_error_module {
+ ( $exprs:item ) => {
+ pub mod error {
+ generate_error_imports!();
+ $exprs
+ }
+ }
+}
+
+#[macro_export]
+macro_rules! generate_custom_error_types {
+ {
+ $name: ident,
+ $kindname: ident,
+ $customMemberTypeName: ident,
+ $($kind:ident => $string:expr),*
+ } => {
+ #[derive(Clone, Copy, Debug, PartialEq)]
+ pub enum $kindname {
+ $( $kind ),*
+ }
+
+ impl Display for $kindname {
+
+ fn fmt(&self, fmt: &mut Formatter) -> Result<(), FmtError> {
+ let s = match *self {
+ $( $kindname::$kind => $string ),*
+ };
+ try!(write!(fmt, "{}", s));
+ Ok(())
+ }
+
+ }
+
+ impl IntoError for $kindname {
+ type Target = $name;
+
+ fn into_error(self) -> Self::Target {
+ $name::new(self, None)
+ }
+
+ fn into_error_with_cause(self, cause: Box<Error>) -> Self::Target {
+ $name::new(self, Some(cause))
+ }
+
+ }
+
+ #[derive(Debug)]
+ pub struct $name {
+ err_type: $kindname,
+ cause: Option<Box<Error>>,
+ custom_data: Option<$customMemberTypeName>,
+ }
+
+ impl $name {
+
+ pub fn new(errtype: $kindname, cause: Option<Box<Error>>) -> $name {
+ $name {
+ err_type: errtype,
+ cause: cause,
+ custom_data: None,
+ }
+ }
+
+ #[allow(dead_code)]
+ pub fn err_type(&self) -> $kindname {
+ self.err_type
+ }
+
+ #[allow(dead_code)]
+ pub fn with_custom_data(mut self, custom: $customMemberTypeName) -> $name {
+ self.custom_data = Some(custom);
+ self
+ }
+
+ }
+
+ impl Into<$name> for $kindname {
+
+ fn into(self) -> $name {
+ $name::new(self, None)
+ }
+
+ }
+
+ impl Display for $name {
+
+ fn fmt(&self, fmt: &mut Formatter) -> Result<(), FmtError> {
+ try!(write!(fmt, "[{}]", self.err_type));
+ match self.custom_data {
+ Some(ref c) => write!(fmt, "{}", c),
+ None => Ok(()),
+ }
+ }
+
+ }
+
+ impl Error for $name {
+
+ fn description(&self) -> &str {
+ match self.err_type {
+ $( $kindname::$kind => $string ),*
+ }
+ }
+
+ fn cause(&self) -> Option<&Error> {
+ self.cause.as_ref().map(|e| &**e)
+ }
+
+ }
+
+ }
+}
+
+#[macro_export]
+macro_rules! generate_result_helper {
+ (
+ $name: ident,
+ $kindname: ident
+ ) => {
+ /// Trait to replace
+ ///
+ /// ```ignore
+ /// foo.map_err(Box::new).map_err(|e| SomeType::SomeErrorKind.into_error_with_cause(e))
+ /// // or:
+ /// foo.map_err(|e| SomeType::SomeErrorKind.into_error_with_cause(Box::new(e)))
+ /// ```
+ ///
+ /// with much nicer
+ ///
+ /// ```ignore
+ /// foo.map_err_into(SomeType::SomeErrorKind)
+ /// ```
+ ///
+ pub trait MapErrInto<T> {
+ fn map_err_into(self, error_kind: $kindname) -> Result<T, $name>;
+ }
+
+ impl<T, E: Error + 'static> MapErrInto<T> for Result<T, E> {
+
+ fn map_err_into(self, error_kind: $kindname) -> Result<T, $name> {
+ self.map_err(Box::new)
+ .map_err(|e| error_kind.into_error_with_cause(e))
+ }
+
+ }
+ }
+}
+
+#[macro_export]
+macro_rules! generate_option_helper {
+ (
+ $name: ident,
+ $kindname: ident
+ ) => {
+ /// Trait to replace
+ ///
+ /// ```ignore
+ /// foo.ok_or(SomeType::SomeErrorKind.into_error())
+ /// ```
+ ///
+ /// with
+ ///
+ /// ```ignore
+ /// foo.ok_or_errkind(SomeType::SomeErrorKind)
+ /// ```
+ pub trait OkOrErr<T> {
+ fn ok_or_errkind(self, kind: $kindname) -> Result<T, $name>;
+ }
+
+ impl<T> OkOrErr<T> for Option<T> {
+
+ fn ok_or_errkind(self, kind: $kindname) -> Result<T, $name> {
+ self.ok_or(kind.into_error())
+ }
+
+ }
+ }
+}
+
+#[macro_export]
+macro_rules! generate_error_types {
+ (
+ $name: ident,
+ $kindname: ident,
+ $($kind:ident => $string:expr),*
+ ) => {
+ #[derive(Debug, Clone, Eq, PartialEq, Ord, PartialOrd, Copy)]
+ pub struct SomeNotExistingTypeWithATypeNameNoOneWillEverChoose {}
+
+ impl Display for SomeNotExistingTypeWithATypeNameNoOneWillEverChoose {
+ fn fmt(&self, _: &mut Formatter) -> Result<(), FmtError> {
+ Ok(())
+ }
+ }
+
+ generate_custom_error_types!($name, $kindname,
+ SomeNotExistingTypeWithATypeNameNoOneWillEverChoose,
+ $($kind => $string),*);
+
+ generate_result_helper!($name, $kindname);
+ generate_option_helper!($name, $kindname);
+ }
+}
+
+
+#[cfg(test)]
+#[allow(dead_code)]
+mod test {
+
+ generate_error_module!(
+ generate_error_types!(TestError, TestErrorKind,
+ TestErrorKindA => "testerrorkind a",
+ TestErrorKindB => "testerrorkind B");
+ );
+
+ #[derive(Debug, Clone, Eq, PartialEq, Ord, PartialOrd, Copy)]
+ pub struct CustomData {
+ pub test: i32,
+ pub othr: i64,
+ }
+
+ impl Display for CustomData {
+ fn fmt(&self, fmt: &mut Formatter) -> Result<(), FmtError> {
+ Ok(())
+ }
+ }
+
+ generate_error_imports!();
+
+ #[allow(dead_code)]
+ generate_custom_error_types!(CustomTestError, CustomTestErrorKind,
+ CustomData,
+ CustomErrorKindA => "customerrorkind a",
+ CustomErrorKindB => "customerrorkind B");
+
+ // Allow dead code here.
+ // We wrote this to show that custom test types can be implemented.
+ #[allow(dead_code)]
+ impl CustomTestError {
+ pub fn test(&self) -> i32 {
+ match self.custom_data {
+ Some(t) => t.test,
+ None => 0,
+ }
+ }
+
+ pub fn bar(&self) -> i64 {
+ match self.custom_data {
+ Some(t) => t.othr,
+ None => 0,
+ }
+ }
+ }
+
+
+ #[test]
+ fn test_a() {
+ use self::error::{TestError, TestErrorKind};
+
+ let kind = TestErrorKind::TestErrorKindA;
+ assert_eq!(String::from("testerrorkind a"), format!("{}", kind));
+
+ let e = TestError::new(kind, None);
+ assert_eq!(String::from("[testerrorkind a]"), format!("{}", e));
+ }
+
+ #[test]
+ fn test_b() {
+ use self::error::{TestError, TestErrorKind};
+
+ let kind = TestErrorKind::TestErrorKindB;
+ assert_eq!(String::from("testerrorkind B"), format!("{}", kind));
+
+ let e = TestError::new(kind, None);
+ assert_eq!(String::from("[testerrorkind B]"), format!("{}", e));
+
+ }
+
+ #[test]
+ fn test_ab() {
+ use std::error::Error;
+ use self::error::{TestError, TestErrorKind};
+
+ let kinda = TestErrorKind::TestErrorKindA;
+ let kindb = TestErrorKind::TestErrorKindB;
+ assert_eq!(String::from("testerrorkind a"), format!("{}", kinda));
+ assert_eq!(String::from("testerrorkind B"), format!("{}", kindb));
+
+ let e = TestError::new(kinda, Some(Box::new(TestError::new(kindb, None))));
+ assert_eq!(String::from("[testerrorkind a]"), format!("{}", e));
+ assert_eq!(TestErrorKind::TestErrorKindA, e.err_type());
+ assert_eq!(String::from("[testerrorkind B]"), format!("{}", e.cause().unwrap()));
+ }
+
+ pub mod anothererrormod {
+ generate_error_imports!();
+ generate_error_types!(TestError, TestErrorKind,
+ TestErrorKindA => "testerrorkind a",
+ TestErrorKindB => "testerrorkind B");
+ }
+
+ #[test]
+ fn test_other_a() {
+ use self::anothererrormod::{TestError, TestErrorKind};
+
+ let kind = TestErrorKind::TestErrorKindA;
+ assert_eq!(String::from("testerrorkind a"), format!("{}", kind));
+
+ let e = TestError::new(kind, None);
+ assert_eq!(String::from("[testerrorkind a]"), format!("{}", e));
+ }
+
+ #[test]
+ fn test_other_b() {
+ use self::anothererrormod::{TestError, TestErrorKind};
+
+ let kind = TestErrorKind::TestErrorKindB;
+ assert_eq!(String::from("testerrorkind B"), format!("{}", kind));
+
+ let e = TestError::new(kind, None);
+ assert_eq!(String::from("[testerrorkind B]"), format!("{}", e));
+
+ }
+
+ #[test]
+ fn test_other_ab() {
+ use std::error::Error;
+ use self::anothererrormod::{TestError, TestErrorKind};
+
+ let kinda = TestErrorKind::TestErrorKindA;
+ let kindb = TestErrorKind::TestErrorKindB;
+ assert_eq!(String::from("testerrorkind a"), format!("{}", kinda));
+ assert_eq!(String::from("testerrorkind B"), format!("{}", kindb));
+
+ let e = TestError::new(kinda, Some(Box::new(TestError::new(kindb, None))));
+ assert_eq!(String::from("[testerrorkind a]"), format!("{}", e));
+ assert_eq!(TestErrorKind::TestErrorKindA, e.err_type());
+ assert_eq!(String::from("[testerrorkind B]"), format!("{}", e.cause().unwrap()));
+ }
+
+ #[test]
+ fn test_error_kind_mapping() {
+ use std::io::{Error, ErrorKind};
+ use self::error::MapErrInto;
+ use self::error::TestErrorKind;
+
+ let err : Result<(), _> = Err(Error::new(ErrorKind::Other, ""));
+ let err : Result<(), _> = err.map_err_into(TestErrorKind::TestErrorKindA);
+
+ assert!(err.is_err());
+ let err = err.unwrap_err();
+
+ match err.err_type() {
+ TestErrorKind::TestErrorKindA => assert!(true),
+ _ => assert!(false),
+ }
+ }
+
+ #[test]
+ fn test_error_kind_double_mapping() {
+ use std::io::{Error, ErrorKind};
+ use self::error::MapErrInto;
+ use self::error::TestErrorKind;
+
+ let err : Result<(), _> = Err(Error::new(ErrorKind::Other, ""));
+ let err : Result<(), _> = err.map_err_into(TestErrorKind::TestErrorKindA)
+ .map_err_into(TestErrorKind::TestErrorKindB);
+
+ assert!(err.is_err());
+ let err = err.unwrap_err();
+ match err.err_type() {
+ TestErrorKind::TestErrorKindB => assert!(true),
+ _ => assert!(false),
+ }
+
+ // not sure how to test that the inner error is of TestErrorKindA, actually...
+ match err.cause() {
+ Some(_) => assert!(true),
+ None => assert!(false),
+ }
+
+ }
+
+ #[test]
+ fn test_error_option_good() {
+ use self::error::OkOrErr;
+ use self::error::TestErrorKind;
+
+ let something = Some(1);
+ match something.ok_or_errkind(TestErrorKind::TestErrorKindA) {
+ Ok(1) => assert!(true),
+ _ => assert!(false),
+ }
+ }
+
+ #[test]
+ fn test_error_option_bad() {
+ use self::error::OkOrErr;
+ use self::error::TestErrorKind;
+
+ let something : Option<i32> = None;
+ match something.ok_or_errkind(TestErrorKind::TestErrorKindA) {
+ Ok(_) => assert!(false),
+ Err(_) => assert!(true),
+ }
+ }
+
+}
diff --git a/lib/core/libimagerror/src/into.rs b/lib/core/libimagerror/src/into.rs
new file mode 100644
index 0000000..bc3cfb4
--- /dev/null
+++ b/lib/core/libimagerror/src/into.rs
@@ -0,0 +1,33 @@
+//
+// 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::error::Error;
+
+/// Trait to help converting Error kinds into Error instances
+pub trait IntoError {
+ type Target: Error;
+
+ /// Convert the type into an error with no cause
+ fn into_error(self) -> Self::Target;
+
+ /// Convert the type into an error with cause
+ fn into_error_with_cause(self, cause: Box<Error>) -> Self::Target;
+
+}
+
diff --git a/lib/core/libimagerror/src/iter.rs b/lib/core/libimagerror/src/iter.rs
new file mode 100644
index 0000000..0297bc6
--- /dev/null
+++ b/lib/core/libimagerror/src/iter.rs
@@ -0,0 +1,232 @@
+//
+// imag - the personal information management suite for the commandline
+// Copyright (C) 2016 the imag 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::error::Error;
+
+/// An iterator that maps `f` over the `Error` elements of `iter`, similar to `std::iter::Map`.
+///
+/// This `struct` is created by the `on_err()` method on `TraceIterator`. See its
+/// documentation for more information.
+#[must_use = "iterator adaptors are lazy and do nothing unless consumed"]
+#[derive(Clone)]
+pub struct OnErr<I, F>{
+ iter: I,
+ f: F
+}
+
+impl<I, F, T, E> Iterator for OnErr<I, F> where
+ I: Iterator<Item = Result<T, E>>,
+ F: FnMut(&E)
+{
+ type Item = Result<T, E>;
+
+ #[inline]
+ fn next(&mut self) -> Option<Self::Item> {
+ self.iter.next().map(|r| r.map_err(|e| { (self.f)(&e); e }))
+ }
+
+ #[inline]
+ fn size_hint(&self) -> (usize, Option<usize>) {
+ self.iter.size_hint()
+ }
+}
+
+impl<I, F> ExactSizeIterator for OnErr<I, F> where
+ I: ExactSizeIterator,
+ OnErr<I, F>: Iterator
+{
+}
+
+impl<I, F, T, E> DoubleEndedIterator for OnErr<I, F> where
+ I: DoubleEndedIterator<Item = Result<T, E>>,
+ F: FnMut(&E)
+{
+ #[inline]
+ fn next_back(&mut self) -> Option<Self::Item> {
+ self.iter.next_back().map(|r| r.map_err(|e| { (self.f)(&e); e }))
+ }
+}
+
+/// An iterator that unwraps the `Ok` items of `iter`, while passing the `Err` items to its
+/// closure `f`.
+///
+/// This `struct` is created by the `unwrap_with()` method on `TraceIterator`. See its
+/// documentation for more information.
+#[must_use = "iterator adaptors are lazy and do nothing unless consumed"]
+#[derive(Clone)]
+pub struct UnwrapWith<I, F>{
+ iter: I,
+ f: F
+}
+
+impl<I, F, T, E> Iterator for UnwrapWith<I, F> where
+ I: Iterator<Item = Result<T, E>>,
+ F: FnMut(E)
+{
+ type Item = T;
+
+ #[inline]
+ fn next(&mut self) -> Option<Self::Item> {
+ loop {
+ match self.iter.next() {
+ Some(Err(e)) => {
+ (self.f)(e);
+ },
+ Some(Ok(item)) => return Some(item),
+ None => return None,
+ }
+ }
+ }
+ #[inline]
+ fn size_hint(&self) -> (usize, Option<usize>) {
+ let (_, upper) = self.iter.size_hint();
+ (0, upper)
+ }
+}
+
+impl<I, F, T, E> DoubleEndedIterator for UnwrapWith<I, F> where
+ I: DoubleEndedIterator<Item = Result<T, E>>,
+ F: FnMut(E)
+{
+ #[inline]
+ fn next_back(&mut self) -> Option<Self::Item> {
+ loop {
+ match self.iter.next_back() {
+ Some(Err(e)) => {
+ (self.f)(e);
+ },
+ Some(Ok(item)) => return Some(item),
+ None => return None,
+ }
+ }
+ }
+}
+
+/// This trait provides methods that make it easier to work with iterators that yield a `Result`.
+pub trait TraceIterator<T, E> : Iterator<Item = Result<T, E>> + Sized {
+ /// Creates an iterator that yields the item in each `Ok` item, while filtering out the `Err`
+ /// items. Each filtered `Err` will be trace-logged with [`::trace::trace_error`].
+ ///
+ /// As with all iterators, the processing is lazy. If you do not use the result of this method,
+ /// nothing will be passed to `::trace::trace_error`, no matter how many `Err` items might
+ /// be present.
+ #[inline]
+ fn trace_unwrap(self) -> UnwrapWith<Self, fn(E)> where E: Error {
+ #[inline]
+ fn trace_error<E: Error>(err: E) {
+ ::trace::trace_error(&err);
+ }
+
+ self.unwrap_with(trace_error)
+ }
+
+ /// Takes a closure and creates an iterator that will call that closure for each `Err` element.
+ /// The resulting iterator will yield the exact same items as the original iterator. A close
+ /// analogue from the standard library would be `Iterator::inspect`.
+ ///
+ /// As with all iterators, the processing is lazy. The result of this method must be evaluated
+ /// for the closure to be called.
+ #[inline]
+ fn on_err<F>(self, f: F) -> OnErr<Self, F> where F: FnMut(&E) {
+ OnErr { iter: self, f: f }
+ }
+
+ /// Takes a closure and creates an iterator that will yield the items inside all `Ok` items
+ /// yielded by the original iterator. All `Err` items will be filtered out, and the contents
+ /// of each `Err` will be passed to the closure.
+ ///
+ /// As with all iterators, the processing is lazy. The result of this method must be evaluated
+ /// for the closure to be called.
+ #[inline]
+ fn unwrap_with<F>(self, f: F) -> UnwrapWith<Self, F>
+ where F: FnMut(E)
+ {
+ UnwrapWith { iter: self, f: f }
+ }
+}
+
+impl<I, T, E> TraceIterator<T, E> for I where
+ I: Iterator<Item = Result<T, E>>
+{}
+
+#[cfg(test)]
+mod test {
+ use super::TraceIterator;
+
+ #[derive(Copy, Clone, Eq, PartialEq, Debug)]
+ struct TestError(i32);
+
+ #[test]
+ fn test_unwrap_with() {
+ let original = vec![Ok(1), Err(TestError(2)), Ok(3), Err(TestError(4))];
+ let mut errs = vec![];
+
+ let oks = original
+ .into_iter()
+ .unwrap_with(|e|errs.push(e))
+ .collect::<Vec<_>>();
+
+ assert_eq!(&oks, &[1, 3]);
+ assert_eq!(&errs, &[TestError(2), TestError(4)]);
+ }
+
+ #[test]
+ fn test_unwrap_with_backward() {
+ let original = vec![Ok(1), Err(TestError(2)), Ok(3), Err(TestError(4))];
+ let mut errs = vec![];
+
+ let oks = original
+ .into_iter()
+ .rev()
+ .unwrap_with(|e|errs.push(e))
+ .collect::<Vec<_>>();
+
+ assert_eq!(&oks, &[3, 1]);
+ assert_eq!(&errs, &[TestError(4), TestError(2)]);
+ }
+
+ #[test]
+ fn test_on_err() {
+ let original = vec![Ok(1), Err(TestError(2)), Ok(3), Err(TestError(4))];
+ let mut errs = vec![];
+
+ let result = original
+ .into_iter()
+ .on_err(|e|errs.push(e.clone()))
+ .collect::<Vec<_>>();
+
+ assert_eq!(&result, &[Ok(1), Err(TestError(2)), Ok(3), Err(TestError(4))]);
+ assert_eq!(&errs, &[TestError(2), TestError(4)]);
+ }
+
+ #[test]
+ fn test_on_err_backward() {
+ let original = vec![Ok(1), Err(TestError(2)), Ok(3), Err(TestError(4))];
+ let mut errs = vec![];
+
+ let result = original
+ .into_iter()
+ .rev()
+ .on_err(|e|errs.push(e.clone()))
+ .collect::<Vec<_>>();
+
+ assert_eq!(&result, &[Err(TestError(4)), Ok(3), Err(TestError(2)), Ok(1)]);
+ assert_eq!(&errs, &[TestError(4), TestError(2)]);
+ }
+}
diff --git a/lib/core/libimagerror/src/lib.rs b/lib/core/libimagerror/src/lib.rs
new file mode 100644
index 0000000..bc4f865
--- /dev/null
+++ b/lib/core/libimagerror/src/lib.rs
@@ -0,0 +1,40 @@
+//
+// 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
+//
+
+#![deny(
+ non_camel_case_types,
+ non_snake_case,
+ path_statements,
+ trivial_numeric_casts,
+ unstable_features,
+ unused_allocation,
+ unused_import_braces,
+ unused_imports,
+ unused_mut,
+ unused_qualifications,
+ while_true,
+)]
+
+#[macro_use] extern crate log;
+extern crate ansi_term;
+
+pub mod into;
+pub mod error_gen;
+pub mod trace;
+pub mod iter;
diff --git a/lib/core/libimagerror/src/trace.rs b/lib/core/libimagerror/src/trace.rs
new file mode 100644
index 0000000..3fbc4fa
--- /dev/null
+++ b/lib/core/libimagerror/src/trace.rs
@@ -0,0 +1,154 @@
+//
+// 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::error::Error;
+use std::io::Write;
+use std::io::stderr;
+
+use ansi_term::Colour::Red;
+
+/// Print an Error type and its cause recursively
+///
+/// The error is printed with "Error NNNN :" as prefix, where "NNNN" is a number which increases
+/// which each recursion into the errors cause. The error description is used to visualize what
+/// failed and if there is a cause "-- caused by:" is appended, and the cause is printed on the next
+/// line.
+///
+/// Example output:
+///
+/// ```ignore
+/// Error 1 : Some error -- caused by:
+/// Error 2 : Some other error -- caused by:
+/// Error 3 : Yet another Error -- caused by:
+/// ...
+///
+/// Error <NNNN> : <Error description>
+/// ```
+pub fn trace_error(e: &Error) {
+ print_trace_maxdepth(count_error_causes(e), e, ::std::u64::MAX);
+ write!(stderr(), "\n").ok();
+}
+
+/// Convenience function: calls `trace_error()` with `e` and afterwards `std::process::exit()`
+/// with `code`
+pub fn trace_error_exit(e: &Error, code: i32) -> ! {
+ use std::process::exit;
+
+ debug!("Tracing error...");
+ trace_error(e);
+ debug!("Calling exit()");
+ exit(code);
+}
+
+/// Print an Error type and its cause recursively, but only `max` levels
+///
+/// Output is the same as for `trace_error()`, though there are only `max` levels printed.
+pub fn trace_error_maxdepth(e: &Error, max: u64) {
+ let n = count_error_causes(e);
+ let msg = Red.blink().paint(format!("{}/{} Levels of errors will be printed\n",
+ (if max > n { n } else { max }), n));
+ write!(stderr(), "{}", msg).ok();
+ print_trace_maxdepth(n, e, max);
+ write!(stderr(), "").ok();
+}
+
+/// Print an Error type and its cause recursively with the debug!() macro
+///
+/// Output is the same as for `trace_error()`.
+pub fn trace_error_dbg(e: &Error) {
+ print_trace_dbg(0, e);
+}
+
+/// Helper function for `trace_error()` and `trace_error_maxdepth()`.
+///
+/// Returns the cause of the last processed error in the recursion, so `None` if all errors where
+/// processed.
+fn print_trace_maxdepth(idx: u64, e: &Error, max: u64) -> Option<&Error> {
+ if e.cause().is_some() && idx > 0 {
+ e.cause().map(|cause| {
+ match print_trace_maxdepth(idx - 1, cause, max) {
+ None => write!(stderr(), "\n").ok(),
+ Some(_) => write!(stderr(), " -- caused:\n").ok(),
+ };
+ });
+ } else {
+ write!(stderr(), "\n").ok();
+ }
+ write!(stderr(), "{}: {}", Red.paint(format!("ERROR[{:>4}]", idx)), e.description()).ok();
+ e.cause()
+}
+
+/// Count errors in `Error::cause()` recursively
+fn count_error_causes(e: &Error) -> u64 {
+ 1 + e.cause().map(|c| count_error_causes(c)).unwrap_or(0)
+}
+
+fn print_trace_dbg(idx: u64, e: &Error) {
+ debug!("{}: {}", Red.blink().paint(format!("ERROR[{:>4}]", idx)), e.description());
+ if e.cause().is_some() {
+ e.cause().map(|c| print_trace_dbg(idx + 1, c));
+ }
+}
+
+/// Helper functions for `Result<T, E>` types to reduce overhead in the following situations:
+///
+/// ```ignore
+/// function().map_err(|e| { trace_error(&e); e })
+/// ```
+///
+/// and variants
+pub trait MapErrTrace {
+ fn map_err_trace(self) -> Self;
+ fn map_err_dbg_trace(self) -> Self;
+ fn map_err_trace_exit(self, code: i32) -> Self;
+ fn map_err_trace_maxdepth(self, max: u64) -> Self;
+}
+
+impl<U, E: Error> MapErrTrace for Result<U, E> {
+
+ /// Simply call `trace_error()` on the Err (if there is one) and return the error.
+ ///
+ /// This does nothing besides the side effect of printing the error trace
+ fn map_err_trace(self) -> Self {
+ self.map_err(|e| { trace_error(&e); e })
+ }
+
+ /// Simply call `trace_error_dbg()` on the Err (if there is one) and return the error.
+ ///
+ /// This does nothing besides the side effect of printing the error trace
+ fn map_err_dbg_trace(self) -> Self {
+ self.map_err(|e| { trace_error_dbg(&e); e })
+ }
+
+ /// Simply call `trace_error_exit(code)` on the Err (if there is one).
+ ///
+ /// This does not return if there is an Err(e).
+ fn map_err_trace_exit(self, code: i32) -> Self {
+ self.map_err(|e| { trace_error_exit(&e, code) })
+ }
+
+ /// Simply call `trace_error_maxdepth(max)` on the Err (if there is one) and return the error.
+ ///
+ /// This does nothing besides the side effect of printing the error trace to a certain depth
+ fn map_err_trace_maxdepth(self, max: u64) -> Self {
+ self.map_err(|e| { trace_error_maxdepth(&e, max); e })
+ }
+
+}
+
diff --git a/lib/core/libimagrt/.gitignore b/lib/core/libimagrt/.gitignore
new file mode 100644
index 0000000..eb5a316
--- /dev/null
+++ b/lib/core/libimagrt/.gitignore
@@ -0,0 +1 @@
+target
diff --git a/lib/core/libimagrt/Cargo.toml b/lib/core/libimagrt/Cargo.toml
new file mode 100644
index 0000000..4f0f086
--- /dev/null
+++ b/lib/core/libimagrt/Cargo.toml
@@ -0,0 +1,29 @@
+[package]
+name = "libimagrt"
+version = "0.4.0"
+authors = ["Matthias Beyer <mail@beyermatthias.de>"]
+
+description = "Library for the imag core distribution"
+
+keywords = ["imag", "PIM", "personal", "information", "management"]
+readme = "../README.md"
+license = "LGPL-2.1"
+
+documentation = "https://matthiasbeyer.github.io/imag/imag_documentation/index.html"
+repository = "https://github.com/matthiasbeyer/imag"
+homepage = "http://imag-pim.org"
+
+[dependencies]
+clap = ">=2.17"
+env_logger = "0.3"
+toml = "^0.4"
+log = "0.3"
+xdg-basedir = "1.0"
+itertools = "0.5"
+tempfile = "2.1"
+ansi_term = "0.9"
+is-match = "0.1"
+
+libimagstore = { version = "0.4.0", path = "../../../lib/core/libimagstore" }
+libimagerror = { version = "0.4.0", path = "../../../lib/core/libimagerror" }
+libimagutil = { version = "0.4.0", path = "../../../lib/etc/libimagutil" }
diff --git a/lib/core/libimagrt/README.md b/lib/core/libimagrt/README.md
new file mode 120000
index 0000000..0fbc1f2
--- /dev/null
+++ b/lib/core/libimagrt/README.md
@@ -0,0 +1 @@
+../doc/src/05100-lib-rt.md \ No newline at end of file
diff --git a/lib/core/libimagrt/src/configuration.rs b/lib/core/libimagrt/src/configuration.rs
new file mode 100644
index 0000000..dcb3326
--- /dev/null
+++ b/lib/core/libimagrt/src/configuration.rs
@@ -0,0 +1,303 @@
+//
+// 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::path::PathBuf;
+use std::result::Result as RResult;
+use std::ops::Deref;
+
+use toml::Value;
+use clap::App;
+
+generate_error_module!(
+ generate_error_types!(ConfigError, ConfigErrorKind,
+ TOMLParserError => "TOML Parsing error",
+ NoConfigFileFound => "No config file found",
+
+ ConfigOverrideError => "Config override error",
+ ConfigOverrideKeyNotAvailable => "Key not available",
+ ConfigOverrideTypeNotMatching => "Configuration Type not matching"
+
+ );
+);
+
+pub use self::error::{ConfigError, ConfigErrorKind, MapErrInto};
+use libimagerror::into::IntoError;
+
+/// Result type of this module. Either `T` or `ConfigError`
+pub type Result<T> = RResult<T, ConfigError>;
+
+/// `Configuration` object
+///
+/// Holds all config variables which are globally available plus the configuration object from the
+/// config parser, which can be accessed.
+#[derive(Debug)]
+pub struct Configuration {
+
+ /// The plain configuration object for direct access if necessary
+ config: Value,
+
+ /// The verbosity the program should run with
+ verbosity: bool,
+
+ /// The editor which should be used
+ editor: Option<String>,
+
+ ///The options the editor should get when opening some file
+ editor_opts: String,
+}
+
+impl Configuration {
+
+ /// Get a new configuration object.
+ ///
+ /// The passed runtimepath is used for searching the configuration file, whereas several file
+ /// names are tested. If that does not work, the home directory and the XDG basedir are tested
+ /// with all variants.
+ ///
+ /// If that doesn't work either, an error is returned.
+ pub fn new(config_searchpath: &PathBuf) -> Result<Configuration> {
+ fetch_config(&config_searchpath).map(|cfg| {
+ let verbosity = get_verbosity(&cfg);
+ let editor = get_editor(&cfg);
+ let editor_opts = get_editor_opts(&cfg);
+
+ debug!("Building configuration");
+ debug!(" - verbosity : {:?}", verbosity);
+ debug!(" - editor : {:?}", editor);
+ debug!(" - editor-opts: {}", editor_opts);
+
+ Configuration {
+ config: cfg,
+ verbosity: verbosity,
+ editor: editor,
+ editor_opts: editor_opts,
+ }
+ })
+ }
+
+ /// Get a new configuration object built from the given toml value.
+ pub fn with_value(value: Value) -> Configuration {
+ Configuration{
+ verbosity: get_verbosity(&value),
+ editor: get_editor(&value),
+ editor_opts: get_editor_opts(&value),
+ config: value,
+ }
+ }
+
+ /// Get the Editor setting from the configuration
+ pub fn editor(&self) -> Option<&String> {
+ self.editor.as_ref()
+ }
+
+ /// Get the underlying configuration TOML object
+ pub fn config(&self) -> &Value {
+ &self.config
+ }
+
+ /// Get the configuration of the store, if any.
+ pub fn store_config(&self) -> Option<&Value> {
+ match self.config {
+ Value::Table(ref tabl) => tabl.get("store"),
+ _ => None,
+ }
+ }
+
+ /// Override the configuration.
+ /// The `v` parameter is expected to contain 'key=value' pairs where the key is a path in the
+ /// TOML tree, the value to be an appropriate value.
+ ///
+ /// The override fails if the configuration which is about to be overridden does not exist or
+ /// the `value` part cannot be converted to the type of the configuration value.
+ ///
+ /// If `v` is empty, this is considered to be a successful `override_config()` call.
+ pub fn override_config(&mut self, v: Vec<String>) -> Result<()> {
+ use libimagutil::key_value_split::*;
+ use libimagutil::iter::*;
+ use self::error::ConfigErrorKind as CEK;
+ use self::error::MapErrInto;
+ use libimagerror::into::IntoError;
+ use libimagstore::toml_ext::TomlValueExt;
+
+ v.into_iter()
+ .map(|s| { debug!("Trying to process '{}'", s); s })
+ .filter_map(|s| match s.into_kv() {
+ Some(kv) => Some(kv.into()),
+ None => {
+ warn!("Could split at '=' - will be ignore override");
+ None
+ }
+ })
+ .map(|(k, v)| self
+ .config
+ .read(&k[..])
+ .map_err_into(CEK::TOMLParserError)
+ .map(|toml| match toml {
+ Some(value) => match into_value(value, v) {
+ Some(v) => {
+ info!("Successfully overridden: {} = {}", k, v);
+ Ok(v)
+ },
+ None => Err(CEK::ConfigOverrideTypeNotMatching.into_error()),
+ },
+ None => Err(CEK::ConfigOverrideKeyNotAvailable.into_error()),
+ })
+ )
+ .fold_result(|i| i)
+ .map_err(Box::new)
+ .map_err(|e| CEK::ConfigOverrideError.into_error_with_cause(e))
+ }
+}
+
+/// Tries to convert the String `s` into the same type as `value`.
+///
+/// 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> {
+ use std::str::FromStr;
+
+ 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),
+ Value::Boolean(_) => {
+ if s == "true" { Some(Value::Boolean(true)) }
+ else if s == "false" { Some(Value::Boolean(false)) }
+ else { None }
+ }
+ Value::Datetime(_) => Value::try_from(s).ok(),
+ _ => None,
+ }
+}
+
+impl Deref for Configuration {
+ type Target = Value;
+
+ fn deref(&self) -> &Value {
+ &self.config
+ }
+
+}
+
+fn get_verbosity(v: &Value) -> bool {
+ match *v {
+ Value::Table(ref t) => t.get("verbose")
+ .map_or(false, |v| is_match!(v, &Value::Boolean(true))),
+ _ => false,
+ }
+}
+
+fn get_editor(v: &Value) -> Option<String> {
+ match *v {
+ Value::Table(ref t) => t.get("editor")
+ .and_then(|v| match *v { Value::String(ref s) => Some(s.clone()), _ => None, }),
+ _ => None,
+ }
+}
+
+fn get_editor_opts(v: &Value) -> String {
+ match *v {
+ Value::Table(ref t) => t.get("editor-opts")
+ .and_then(|v| match *v { Value::String(ref s) => Some(s.clone()), _ => None, })
+ .unwrap_or_default(),
+ _ => String::new(),
+ }
+}
+
+/// Helper to fetch the config file
+///
+/// Tests several variants for the config file path and uses the first one which works.
+fn fetch_config(searchpath: &PathBuf) -> Result<Value> {
+ use std::env;
+ use std::fs::File;
+ use std::io::Read;
+ use std::io::Write;
+ use std::io::stderr;
+
+ use xdg_basedir;
+ use itertools::Itertools;
+
+ use libimagutil::variants::generate_variants as gen_vars;
+ use libimagerror::trace::trace_error;
+
+ let variants = vec!["config", "config.toml", "imagrc", "imagrc.toml"];
+ let modifier = |base: &PathBuf, v: &'static str| {
+ let mut base = base.clone();
+ base.push(String::from(v));
+ base
+ };
+
+ vec![
+ vec![searchpath.clone()],
+ gen_vars(searchpath.clone(), variants.clone(), &modifier),
+
+ env::var("HOME").map(|home| gen_vars(PathBuf::from(home), variants.clone(), &modifier))
+ .unwrap_or(vec![]),
+
+ xdg_basedir::get_data_home().map(|data_dir| gen_vars(data_dir, variants.clone(), &modifier))
+ .unwrap_or(vec![]),
+ ].iter()
+ .flatten()
+ .filter(|path| path.exists() && path.is_file())
+ .filter_map(|path| {
+ let content = {
+ let f = File::open(path);
+ if f.is_err() {
+ let _ = write!(stderr(), "Error opening file: {:?}", f);
+ return None
+ }
+ let mut f = f.unwrap();
+
+ let mut s = String::new();
+ f.read_to_string(&mut s).ok();
+ s
+ };
+
+ match ::toml::de::from_str::<::toml::Value>(&content[..]) {
+ Ok(res) => Some(res),
+ Err(e) => {
+ let line_col = e
+ .line_col()
+ .map(|(line, col)| {
+ format!("Line {}, Column {}", line, col)
+ })
+ .unwrap_or_else(|| String::from("Line unknown, Column unknown"));
+
+ let _ = write!(stderr(), "Config file parser error at {}", line_col);
+ trace_error(&ConfigErrorKind::TOMLParserError.into_error_with_cause(Box::new(e)));
+ None
+ }
+ }
+ })
+ .nth(0)
+ .ok_or(ConfigErrorKind::NoConfigFileFound.into())
+}
+
+pub trait InternalConfiguration {
+ fn enable_logging(&self) -> bool {
+ true
+ }
+
+ fn use_inmemory_fs(&self) -> bool {
+ false
+ }
+}
+
+impl<'a> InternalConfiguration for App<'a, 'a> {}
diff --git a/lib/core/libimagrt/src/error.rs b/lib/core/libimagrt/src/error.rs
new file mode 100644
index 0000000..388335a
--- /dev/null
+++ b/lib/core/libimagrt/src/error.rs
@@ -0,0 +1,36 @@
+//
+// 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
+//
+
+generate_error_imports!();
+use std::io::Error as IOError;
+
+generate_error_types!(RuntimeError, RuntimeErrorKind,
+ Instantiate => "Could not instantiate",
+ IOError => "IO Error",
+ ProcessExitFailure => "Process exited with failure"
+);
+
+impl From<IOError> for RuntimeError {
+
+ fn from(ioe: IOError) -> RuntimeError {
+ RuntimeErrorKind::IOError.into_error_with_cause(Box::new(ioe))
+ }
+
+}
+
diff --git a/lib/core/libimagrt/src/lib.rs b/lib/core/libimagrt/src/lib.rs
new file mode 100644
index 0000000..5539801
--- /dev/null
+++ b/lib/core/libimagrt/src/lib.rs
@@ -0,0 +1,57 @@
+//
+// 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
+//
+
+#![deny(
+ dead_code,
+ non_camel_case_types,
+ non_snake_case,
+ path_statements,
+ trivial_numeric_casts,
+ unstable_features,
+ unused_allocation,
+ unused_import_braces,
+ unused_imports,
+ unused_must_use,
+ unused_mut,
+ unused_qualifications,
+ while_true,
+)]
+
+#[macro_use] extern crate log;
+extern crate itertools;
+#[cfg(unix)] extern crate xdg_basedir;
+extern crate env_logger;
+extern crate tempfile;
+extern crate ansi_term;
+
+extern crate clap;
+extern crate toml;
+#[macro_use] extern crate is_match;
+
+extern crate libimagstore;
+extern crate libimagutil;
+#[macro_use] extern crate libimagerror;
+
+pub mod error;
+pub mod configuration;
+pub mod logger;
+pub mod runtime;
+pub mod setup;
+pub mod spec;
+
diff --git a/lib/core/libimagrt/src/logger.rs b/lib/core/libimagrt/src/logger.rs
new file mode 100644
index 0000000..992a9d9
--- /dev/null
+++ b/lib/core/libimagrt/src/logger.rs
@@ -0,0 +1,133 @@
+//
+// 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::io::Write;
+use std::io::stderr;
+
+use log::{Log, LogLevel, LogRecord, LogMetadata};
+
+use ansi_term::Style;
+use ansi_term::Colour;
+use ansi_term::ANSIString;
+
+/// Logger implementation for `log` crate.
+pub struct ImagLogger {
+ prefix: String,
+ dbg_fileline: bool,
+ lvl: LogLevel,
+ color_enabled: bool,
+}
+
+impl ImagLogger {
+
+ /// Create a new ImagLogger object with a certain level
+ pub fn new(lvl: LogLevel) -> ImagLogger {
+ ImagLogger {
+ prefix: "[imag]".to_owned(),
+ dbg_fileline: true,
+ lvl: lvl,
+ color_enabled: true
+ }
+ }
+
+ /// Set debugging to include file and line
+ pub fn with_dbg_file_and_line(mut self, b: bool) -> ImagLogger {
+ self.dbg_fileline = b;
+ self
+ }
+
+ /// Set debugging to include prefix
+ pub fn with_prefix(mut self, pref: String) -> ImagLogger {
+ self.prefix = pref;
+ self
+ }
+
+ /// Set debugging to have color
+ pub fn with_color(mut self, b: bool) -> ImagLogger {
+ self.color_enabled = b;
+ self
+ }
+
+ /// Helper function to colorize a string with a certain Style
+ fn style_or_not(&self, c: Style, s: String) -> ANSIString {
+ if self.color_enabled {
+ c.paint(s)
+ } else {
+ ANSIString::from(s)
+ }
+ }
+
+ /// Helper function to colorize a string with a certain Color
+ fn color_or_not(&self, c: Colour, s: String) -> ANSIString {
+ if self.color_enabled {
+ c.paint(s)
+ } else {
+ ANSIString::from(s)
+ }
+ }
+
+}
+
+impl Log for ImagLogger {
+
+ fn enabled(&self, metadata: &LogMetadata) -> bool {
+ metadata.level() <= self.lvl
+ }
+
+ fn log(&self, record: &LogRecord) {
+ use ansi_term::Colour::Red;
+ use ansi_term::Colour::Yellow;
+ use ansi_term::Colour::Cyan;
+
+ if self.enabled(record.metadata()) {
+ // TODO: This is just simple logging. Maybe we can enhance this lateron
+ let loc = record.location();
+ match record.metadata().level() {
+ LogLevel::Debug => {
+ let lvl = self.color_or_not(Cyan, format!("{}", record.level()));
+ let args = self.color_or_not(Cyan, format!("{}", record.args()));
+ if self.dbg_fileline {
+ let file = self.color_or_not(Cyan, format!("{}", loc.file()));
+ let ln = self.color_or_not(Cyan, format!("{}", loc.line()));
+
+ writeln!(stderr(), "{}[{: <5}][{}][{: >5}]: {}", self.prefix, lvl, file, ln, args).ok();
+ } else {
+ writeln!(stderr(), "{}[{: <5}]: {}", self.prefix, lvl, args).ok();
+ }
+ },
+ LogLevel::Warn | LogLevel::Error => {
+ let lvl = self.style_or_not(Red.blink(), format!("{}", record.level()));
+ let args = self.color_or_not(Red, format!("{}", record.args()));
+
+ writeln!(stderr(), "{}[{: <5}]: {}", self.prefix, lvl, args).ok();
+ },
+ LogLevel::Info => {
+ let lvl = self.color_or_not(Yellow, format!("{}", record.level()));
+ let args = self.color_or_not(Yellow, format!("{}", record.args()));
+
+ writeln!(stderr(), "{}[{: <5}]: {}", self.prefix, lvl, args).ok();
+ },
+ _ => {
+ writeln!(stderr(), "{}[{: <5}]: {}", self.prefix, record.level(), record.args()).ok();
+ },
+ }
+ }
+ }
+}
+
diff --git a/lib/core/libimagrt/src/runtime.rs b/lib/core/libimagrt/src/runtime.rs
new file mode 100644
index 0000000..1b5f3f7
--- /dev/null
+++ b/lib/core/libimagrt/src/runtime.rs
@@ -0,0 +1,463 @@
+//
+// 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::path::PathBuf;
+use std::process::Command;
+use std::env;
+use std::io::stderr;
+use std::io::Write;
+
+pub use clap::App;
+
+use clap::{Arg, ArgMatches};
+use log;
+use log::LogLevelFilter;
+
+use configuration::{Configuration, InternalConfiguration};
+use error::RuntimeError;
+use error::RuntimeErrorKind;
+use error::MapErrInto;
+use logger::ImagLogger;
+
+use libimagstore::store::Store;
+use libimagstore::file_abstraction::InMemoryFileAbstraction;
+use spec::CliSpec;
+
+/// The Runtime object
+///
+/// This object contains the complete runtime environment of the imag application running.
+#[derive(Debug)]
+pub struct Runtime<'a> {
+ rtp: PathBuf,
+ configuration: Option<Configuration>,
+ cli_matches: ArgMatches<'a>,
+ store: Store,
+}
+
+impl<'a> Runtime<'a> {
+
+ /// Gets the CLI spec for the program and retreives the config file path (or uses the default on
+ /// in $HOME/.imag/config, $XDG_CONFIG_DIR/imag/config or from env("$IMAG_CONFIG")
+ /// and builds the Runtime object with it.
+ ///
+ /// The cli_app object should be initially build with the ::get_default_cli_builder() function.
+ pub fn new<C>(cli_app: C) -> Result<Runtime<'a>, RuntimeError>
+ where C: Clone + CliSpec<'a> + InternalConfiguration
+ {
+ use libimagerror::trace::trace_error;
+ use libimagerror::into::IntoError;
+
+ use configuration::error::ConfigErrorKind;
+
+ let matches = cli_app.clone().matches();
+
+ let rtp = get_rtp_match(&matches);
+
+ let configpath = matches.value_of(Runtime::arg_config_name())
+ .map_or_else(|| rtp.clone(), PathBuf::from);
+
+ debug!("Config path = {:?}", configpath);
+
+ let config = match Configuration::new(&configpath) {
+ Err(e) => if e.err_type() != ConfigErrorKind::NoConfigFileFound {
+ return Err(RuntimeErrorKind::Instantiate.into_error_with_cause(Box::new(e)));
+ } else {
+ warn!("No config file found.");
+ warn!("Continuing without configuration file");
+ None
+ },
+
+ Ok(mut config) => {
+ if let Err(e) = config.override_config(get_override_specs(&matches)) {
+ error!("Could not apply config overrides");
+ trace_error(&e);
+
+ // TODO: continue question (interactive)
+ }
+
+ Some(config)
+ }
+ };
+
+ Runtime::_new(cli_app, matches, config)
+ }
+
+ /// Builds the Runtime object using the given `config`.
+ pub fn with_configuration<C>(cli_app: C, config: Option<Configuration>)
+ -> Result<Runtime<'a>, RuntimeError>
+ where C: Clone + CliSpec<'a> + InternalConfiguration
+ {
+ let matches = cli_app.clone().matches();
+ Runtime::_new(cli_app, matches, config)
+ }
+
+ fn _new<C>(mut cli_app: C, matches: ArgMatches<'a>, config: Option<Configuration>)
+ -> Result<Runtime<'a>, RuntimeError>
+ where C: Clone + CliSpec<'a> + InternalConfiguration
+ {
+ use std::io::stdout;
+
+ use clap::Shell;
+
+ let is_debugging = matches.is_present(Runtime::arg_debugging_name());
+
+ if cli_app.enable_logging() {
+ let is_verbose = matches.is_present(Runtime::arg_verbosity_name());
+ let colored = !matches.is_present(Runtime::arg_no_color_output_name());
+
+ Runtime::init_logger(is_debugging, is_verbose, colored);
+ }
+
+ match matches.value_of(Runtime::arg_generate_compl()) {
+ Some(shell) => {
+ debug!("Generating shell completion script, writing to stdout");
+ let shell = shell.parse::<Shell>().unwrap(); // clap has our back here.
+ let appname = String::from(cli_app.name());
+ cli_app.completions(appname, shell, &mut stdout());
+ },
+ _ => debug!("Not generating shell completion script"),
+ }
+
+ let rtp = get_rtp_match(&matches);
+
+ let storepath = matches.value_of(Runtime::arg_storepath_name())
+ .map_or_else(|| {
+ let mut spath = rtp.clone();
+ spath.push("store");
+ spath
+ }, PathBuf::from);
+
+ debug!("RTP path = {:?}", rtp);
+ debug!("Store path = {:?}", storepath);
+
+ let store_config = match config {
+ Some(ref c) => c.store_config().cloned(),
+ None => None,
+ };
+
+ if is_debugging {
+ write!(stderr(), "Config: {:?}\n", config).ok();
+ write!(stderr(), "Store-config: {:?}\n", store_config).ok();
+ }
+
+ let store_result = if cli_app.use_inmemory_fs() {
+ Store::new_with_backend(storepath,
+ store_config,
+ Box::new(InMemoryFileAbstraction::new()))
+ } else {
+ Store::new(storepath, store_config)
+ };
+
+ store_result.map(|store| {
+ Runtime {
+ cli_matches: matches,
+ configuration: config,
+ rtp: rtp,
+ store: store,
+ }
+ })
+ .map_err_into(RuntimeErrorKind::Instantiate)
+ }
+
+ ///
+ /// Get a commandline-interface builder object from `clap`
+ ///
+ /// This commandline interface builder object already contains some predefined interface flags:
+ /// * -v | --verbose for verbosity
+ /// * --debug for debugging
+ /// * -c <file> | --config <file> for alternative configuration file
+ /// * -r <path> | --rtp <path> for alternative runtimepath
+ /// * --store <path> for alternative store path
+ /// Each has the appropriate help text included.
+ ///
+ /// The `appname` shall be "imag-<command>".
+ ///
+ pub fn get_default_cli_builder(appname: &'a str,
+ version: &'a str,
+ about: &'a str)
+ -> App<'a, 'a>
+ {
+ App::new(appname)
+ .version(version)
+ .author("Matthias Beyer <mail@beyermatthias.de>")
+ .about(about)
+ .arg(Arg::with_name(Runtime::arg_verbosity_name())
+ .short("v")
+ .long("verbose")
+ .help("Enables verbosity")
+ .required(false)
+ .takes_value(false))
+
+ .arg(Arg::with_name(Runtime::arg_debugging_name())
+ .long("debug")
+ .help("Enables debugging output")
+ .required(false)
+ .takes_value(false))
+
+ .arg(Arg::with_name(Runtime::arg_no_color_output_name())
+ .long("no-color")
+ .help("Disable color output")
+ .required(false)
+ .takes_value(false))
+
+ .arg(Arg::with_name(Runtime::arg_config_name())
+ .long("config")
+ .help("Path to alternative config file")
+ .required(false)
+ .takes_value(true))
+
+ .arg(Arg::with_name(Runtime::arg_config_override_name())
+ .long("override-config")
+ .help("Override a configuration settings. Use 'key=value' pairs, where the key is a path in the TOML configuration. The value must be present in the configuration and be convertible to the type of the configuration setting. If the argument does not contain a '=', it gets ignored. Setting Arrays and Tables is not yet supported.")
+ .required(false)
+ .takes_value(true))
+
+ .arg(Arg::with_name(Runtime::arg_runtimepath_name())
+ .long("rtp")
+ .help("Alternative runtimepath")
+ .required(false)
+ .takes_value(true))
+
+ .arg(Arg::with_name(Runtime::arg_storepath_name())
+ .long("store")
+ .help("Alternative storepath. Must be specified as full path, can be outside of the RTP")
+ .required(false)
+ .takes_value(true))
+
+ .arg(Arg::with_name(Runtime::arg_editor_name())
+ .long("editor")
+ .help("Set editor")
+ .required(false)
+ .takes_value(true))
+
+ .arg(Arg::with_name(Runtime::arg_generate_compl())
+ .long("generate-commandline-completion")
+ .help("Generate the commandline completion for bash or zsh or fish")
+ .required(false)
+ .takes_value(true)
+ .value_name("SHELL")
+ .possible_values(&["bash", "fish", "zsh"]))
+
+ }
+
+ /// Get the argument names of the Runtime which are available
+ pub fn arg_names() -> Vec<&'static str> {
+ vec![
+ Runtime::arg_verbosity_name(),
+ Runtime::arg_debugging_name(),
+ Runtime::arg_no_color_output_name(),
+ Runtime::arg_config_name(),
+ Runtime::arg_config_override_name(),
+ Runtime::arg_runtimepath_name(),
+ Runtime::arg_storepath_name(),
+ Runtime::arg_editor_name(),
+ ]
+ }
+
+ /// Get the verbosity argument name for the Runtime
+ pub fn arg_verbosity_name() -> &'static str {
+ "verbosity"
+ }
+
+ /// Get the debugging argument name for the Runtime
+ pub fn arg_debugging_name() -> &'static str {
+ "debugging"
+ }
+
+ /// Get the argument name for no color output of the Runtime
+ pub fn arg_no_color_output_name() -> &'static str {
+ "no-color-output"
+ }
+
+ /// Get the config argument name for the Runtime
+ pub fn arg_config_name() -> &'static str {
+ "config"
+ }
+
+ /// Get the config-override argument name for the Runtime
+ pub fn arg_config_override_name() -> &'static str {
+ "config-override"
+ }
+
+ /// Get the runtime argument name for the Runtime
+ pub fn arg_runtimepath_name() -> &'static str {
+ "runtimepath"
+ }
+
+ /// Get the storepath argument name for the Runtime
+ pub fn arg_storepath_name() -> &'static str {
+ "storepath"
+ }
+
+ /// Get the editor argument name for the Runtime
+ pub fn arg_editor_name() -> &'static str {
+ "editor"
+ }
+
+ /// Get the argument name for generating the completion
+ pub fn arg_generate_compl() -> &'static str {
+ "generate-completion"
+ }
+
+ /// Initialize the internal logger
+ fn init_logger(is_debugging: bool, is_verbose: bool, colored: bool) {
+ use std::env::var as env_var;
+ use env_logger;
+
+ if env_var("IMAG_LOG_ENV").is_ok() {
+ env_logger::init().unwrap();
+ } else {
+ let lvl = if is_debugging {
+ LogLevelFilter::Debug
+ } else if is_verbose {
+ LogLevelFilter::Info
+ } else {
+ LogLevelFilter::Warn
+ };
+
+ log::set_logger(|max_log_lvl| {
+ max_log_lvl.set(lvl);
+ debug!("Init logger with {}", lvl);
+ Box::new(ImagLogger::new(lvl.to_log_level().unwrap()).with_color(colored))
+ })
+ .map_err(|e| panic!("Could not setup logger: {:?}", e))
+ .ok();
+ }
+ }
+
+ /// Get the verbosity flag value
+ pub fn is_verbose(&self) -> bool {
+ self.cli_matches.is_present("verbosity")
+ }
+
+ /// Get the debugging flag value
+ pub fn is_debugging(&self) -> bool {
+ self.cli_matches.is_present("debugging")
+ }
+
+ /// Get the runtimepath
+ pub fn rtp(&self) -> &PathBuf {
+ &self.rtp
+ }
+
+ /// Get the commandline interface matches
+ pub fn cli(&self) -> &ArgMatches {
+ &self.cli_matches
+ }
+
+ /// Get the configuration object
+ pub fn config(&self) -> Option<&Configuration> {
+ self.configuration.as_ref()
+ }
+
+ /// Get the store object
+ pub fn store(&self) -> &Store {
+ &self.store
+ }
+
+ /// Change the store backend to stdout
+ ///
+ /// For the documentation on purpose and cavecats, have a look at the documentation of the
+ /// `Store::reset_backend()` function.
+ ///
+ pub fn store_backend_to_stdio(&mut self) -> Result<(), RuntimeError> {
+ use libimagstore::file_abstraction::stdio::*;
+ use libimagstore::file_abstraction::stdio::mapper::json::JsonMapper;
+ use std::rc::Rc;
+ use std::cell::RefCell;
+
+ let mut input = ::std::io::stdin();
+ let output = ::std::io::stdout();
+ let output = Rc::new(RefCell::new(output));
+ let mapper = JsonMapper::new();
+
+ StdIoFileAbstraction::new(&mut input, output, mapper)
+ .map_err_into(RuntimeErrorKind::Instantiate)
+ .and_then(|backend| {
+ self.store
+ .reset_backend(Box::new(backend))
+ .map_err_into(RuntimeErrorKind::Instantiate)
+ })
+ }
+
+ pub fn store_backend_to_stdout(&mut self) -> Result<(), RuntimeError> {
+ use libimagstore::file_abstraction::stdio::mapper::json::JsonMapper;
+ use libimagstore::file_abstraction::stdio::out::StdoutFileAbstraction;
+ use std::rc::Rc;
+ use std::cell::RefCell;
+
+ let output = ::std::io::stdout();
+ let output = Rc::new(RefCell::new(output));
+ let mapper = JsonMapper::new();
+
+ StdoutFileAbstraction::new(output, mapper)
+ .map_err_into(RuntimeErrorKind::Instantiate)
+ .and_then(|backend| {
+ self.store
+ .reset_backend(Box::new(backend))
+ .map_err_into(RuntimeErrorKind::Instantiate)
+ })
+ }
+
+ /// Get a editor command object which can be called to open the $EDITOR
+ pub fn editor(&self) -> Option<Command> {
+ self.cli()
+ .value_of("editor")
+ .map(String::from)
+ .or(match self.configuration {
+ Some(ref c) => c.editor().cloned(),
+ _ => None,
+ })
+ .or(env::var("EDITOR").ok())
+ .map(Command::new)
+ }
+}
+
+fn get_rtp_match<'a>(matches: &ArgMatches<'a>) -> PathBuf {
+ use std::env;
+
+ matches.value_of(Runtime::arg_runtimepath_name())
+ .map_or_else(|| {
+ env::var("HOME")
+ .map(PathBuf::from)
+ .map(|mut p| { p.push(".imag"); p })
+ .unwrap_or_else(|_| {
+ panic!("You seem to be $HOME-less. Please get a $HOME before using this \
+ software. We are sorry for you and hope you have some \
+ accommodation anyways.");
+ })
+ }, PathBuf::from)
+}
+
+fn get_override_specs(matches: &ArgMatches) -> Vec<String> {
+ matches
+ .values_of("config-override")
+ .map(|values| {
+ values
+ .filter(|s| {
+ let b = s.contains("=");
+ if !b { warn!("override '{}' does not contain '=' - will be ignored!", s); }
+ b
+ })
+ .map(String::from)
+ .collect()
+ })
+ .unwrap_or(vec![])
+}
+
diff --git a/lib/core/libimagrt/src/setup.rs b/lib/core/libimagrt/src/setup.rs
new file mode 100644
index 0000000..735a888
--- /dev/null
+++ b/lib/core/libimagrt/src/setup.rs
@@ -0,0 +1,46 @@
+//
+// 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 clap::App;
+
+use runtime::Runtime;
+
+pub type Name = &'static str;
+pub type Version<'a> = &'a str;
+pub type About = &'static str;
+
+/// Helper to generate the Runtime object
+///
+/// exit()s the program if the runtime couldn't be build, prints error with println!() before
+/// exiting
+pub fn generate_runtime_setup<'a, B>(name: Name, version: Version<'a>, about: About, builder: B)
+ -> Runtime<'a>
+ where B: FnOnce(App<'a, 'a>) -> App<'a, 'a>
+{
+ use std::process::exit;
+ use libimagerror::trace::trace_error_dbg;
+
+ Runtime::new(builder(Runtime::get_default_cli_builder(name, version, about)))
+ .unwrap_or_else(|e| {
+ println!("Could not set up Runtime");
+ println!("{:?}", e);
+ trace_error_dbg(&e);
+ exit(1);
+ })
+}
diff --git a/lib/core/libimagrt/src/spec.rs b/lib/core/libimagrt/src/spec.rs
new file mode 100644
index 0000000..0293f24
--- /dev/null
+++ b/lib/core/libimagrt/src/spec.rs
@@ -0,0 +1,30 @@
+use std::io::Write;
+
+use clap::{App, ArgMatches, Shell};
+
+/// An abstraction over `clap::App` functionality needed for initializing `Runtime`. Different
+/// implementations can be used for testing `imag` binaries without running them as separate
+/// processes.
+pub trait CliSpec<'a> {
+ fn name(&self) -> &str;
+ fn matches(self) -> ArgMatches<'a>;
+ fn completions<W: Write, S: Into<String>>(&mut self, _: S, _: Shell, _: &mut W) {}
+}
+
+impl<'a> CliSpec<'a> for App<'a, 'a> {
+ fn name(&self) -> &str {
+ self.get_name()
+ }
+
+ fn matches(self) -> ArgMatches<'a> {
+ self.get_matches()
+ }
+
+ fn completions<W: Write, S: Into<String>>(&mut self,
+ bin_name: S,
+ for_shell: Shell,
+ buf: &mut W) {
+
+ self.gen_completions_to(bin_name, for_shell, buf);
+ }
+}
diff --git a/lib/core/libimagstore/.gitignore b/lib/core/libimagstore/.gitignore
new file mode 100644
index 0000000..eb5a316
--- /dev/null
+++ b/lib/core/libimagstore/.gitignore
@@ -0,0 +1 @@
+target
diff --git a/lib/core/libimagstore/Cargo.toml b/lib/core/libimagstore/Cargo.toml
new file mode 100644
index 0000000..c5fb527
--- /dev/null
+++ b/lib/core/libimagstore/Cargo.toml
@@ -0,0 +1,73 @@
+[package]
+name = "libimagstore"
+version = "0.4.0"
+authors = ["Matthias Beyer <mail@beyermatthias.de>"]
+
+description = "Library for the imag core distribution"
+
+keywords = ["imag", "PIM", "personal", "information", "management"]
+readme = "../README.md"
+license = "LGPL-2.1"
+
+documentation = "https://matthiasbeyer.github.io/imag/imag_documentation/index.html"
+repository = "https://github.com/matthiasbeyer/imag"
+homepage = "http://imag-pim.org"
+
+[dependencies]
+fs2 = "0.4"
+glob = "0.2.11"
+lazy_static = "0.2.*"
+log = "0.3"
+regex = "0.2"
+semver = "0.5"
+toml = "^0.4"
+version = "2.0.1"
+crossbeam = "0.2.*"
+walkdir = "1.0.*"
+itertools = "0.6.*"
+is-match = "0.1"
+serde = "1.0"
+serde_json = "1.0"
+serde_derive = "1.0"
+
+libimagerror = { version = "0.4.0", path = "../../../lib/core/libimagerror" }
+libimagutil = { version = "0.4.0", path = "../../../lib/etc/libimagutil" }
+
+[dev-dependencies]
+tempdir = "0.3.4"
+env_logger = "0.3"
+
+[features]
+default = []
+verify = []
+
+# Enable panic!()s if critical errors occur.
+#
+# # Howto
+#
+# To enable this, put
+#
+# ```toml
+# [features]
+# early-panic = [ "libimagstore/early-panic" ]
+# ```
+#
+# In the crate depending on this library and compile your crate with
+# `cargo build --features early-panic`. This way, the `libimagstore`
+# implementation fails via `panic!()` instead of propagating errors which have
+# to be printed somewhere to be visible.
+#
+# # WARNING
+#
+# The behaviour of the store implementation might be broken with this, resulting
+# in partially written store entries and/or worse, so this is
+#
+# _NOT INTENDED FOR PRODUCTION USE_!
+#
+early-panic=[]
+
+# File system locking
+#
+# Enable this feature to enable file-system locking in the store.
+fs-locking = []
+
diff --git a/lib/core/libimagstore/README.md b/lib/core/libimagstore/README.md
new file mode 120000
index 0000000..ccb6253
--- /dev/null
+++ b/lib/core/libimagstore/README.md
@@ -0,0 +1 @@
+../doc/src/05100-lib-store.md \ No newline at end of file
diff --git a/lib/core/libimagstore/src/configuration.rs b/lib/core/libimagstore/src/configuration.rs
new file mode 100644
index 0000000..0dfc049
--- /dev/null
+++ b/lib/core/libimagstore/src/configuration.rs
@@ -0,0 +1,104 @@
+//
+// 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 libimagerror::into::IntoError;
+
+use store::Result;
+
+/// Check whether the configuration is valid for the store
+pub fn config_is_valid(config: &Option<Value>) -> Result<()> {
+ use error::StoreErrorKind as SEK;
+
+ if config.is_none() {
+ return Ok(());
+ }
+
+ match *config {
+ Some(Value::Table(_)) => Ok(()),
+ _ => {
+ warn!("Store config is no table");
+ Err(SEK::ConfigTypeError.into_error())
+ },
+ }
+}
+
+/// Checks whether the store configuration has a key "implicit-create" which maps to a boolean
+/// value. If that key is present, the boolean is returned, otherwise false is returned.
+pub fn config_implicit_store_create_allowed(config: Option<&Value>) -> bool {
+ config.map(|t| {
+ match *t {
+ Value::Table(ref t) => {
+ match t.get("implicit-create") {
+ Some(&Value::Boolean(b)) => b,
+ Some(_) => {
+ warn!("Key 'implicit-create' does not contain a Boolean value");
+ false
+ }
+ None => {
+ warn!("Key 'implicit-create' in store configuration missing");
+ false
+ },
+ }
+ }
+ _ => {
+ warn!("Store configuration seems to be no Table");
+ false
+ },
+ }
+ }).unwrap_or(false)
+}
+
+#[cfg(test)]
+mod tests {
+ use toml::de::from_str as toml_from_str;
+ use configuration::*;
+
+ #[test]
+ fn test_implicit_store_create_allowed_no_toml() {
+ assert!(!config_implicit_store_create_allowed(None));
+ }
+
+ #[test]
+ fn test_implicit_store_create_allowed_toml_empty() {
+ let config = toml_from_str("").unwrap();
+ assert!(!config_implicit_store_create_allowed(Some(config).as_ref()));
+ }
+
+ #[test]
+ fn test_implicit_store_create_allowed_toml_false() {
+ let config = toml_from_str(r#"
+ implicit-create = false
+ "#).unwrap();
+
+ assert!(!config_implicit_store_create_allowed(Some(config).as_ref()));
+ }
+
+ #[test]
+ fn test_implicit_store_create_allowed_toml_true() {
+ let config = toml_from_str(r#"
+ implicit-create = true
+ "#).unwrap();
+
+ assert!(config_implicit_store_create_allowed(Some(config).as_ref()));
+ }
+
+}
+
diff --git a/lib/core/libimagstore/src/error.rs b/lib/core/libimagstore/src/error.rs
new file mode 100644
index 0000000..00e4768
--- /dev/null
+++ b/lib/core/libimagstore/src/error.rs
@@ -0,0 +1,116 @@
+//
+// 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
+//
+
+generate_error_imports!();
+use std::convert::From;
+
+#[derive(Debug, Clone, Eq, PartialEq, Ord, PartialOrd, Copy)]
+pub struct CustomErrorData {}
+
+impl Display for CustomErrorData {
+ fn fmt(&self, _: &mut Formatter) -> Result<(), FmtError> {
+ Ok(()) // Do nothing here, we don't need to print smth
+ }
+}
+
+generate_custom_error_types!(StoreError, StoreErrorKind, CustomErrorData,
+ ConfigurationError => "Store Configuration Error",
+ ConfigTypeError => "Store configuration type error",
+ ConfigKeyMissingError => "Configuration Key missing",
+
+ VersionError => "Incompatible store versions detected",
+
+ CreateStoreDirDenied => "Creating store directory implicitely denied",
+ FileError => "File Error",
+ IoError => "IO Error",
+ IdLocked => "ID locked",
+ IdNotFound => "ID not found",
+ OutOfMemory => "Out of Memory",
+ FileNotFound => "File corresponding to ID not found",
+ FileNotCreated => "File corresponding to ID could not be created",
+ FileNotWritten => "File corresponding to ID could not be written to",
+ FileNotSeeked => "File corresponding to ID could not be seeked",
+ FileNotRemoved => "File corresponding to ID could not be removed",
+ FileNotRenamed => "File corresponding to ID could not be renamed",
+ FileNotCopied => "File could not be copied",
+ DirNotCreated => "Directory/Directories could not be created",
+ StorePathExists => "Store path exists",
+ StorePathCreate => "Store path create",
+ LockError => "Error locking datastructure",
+ LockPoisoned => "The internal Store Lock has been poisoned",
+ EntryAlreadyBorrowed => "Entry is already borrowed",
+ EntryAlreadyExists => "Entry already exists",
+ MalformedEntry => "Entry has invalid formatting, missing header",
+ HeaderPathSyntaxError => "Syntax error in accessor string",
+ HeaderPathTypeFailure => "Header has wrong type for path",
+ HeaderKeyNotFound => "Header Key not found",
+ HeaderTypeFailure => "Header type is wrong",
+ StorePathLacksVersion => "The supplied store path has no version part",
+ GlobError => "glob() error",
+ EncodingError => "Encoding error",
+ StorePathError => "Store Path error",
+ EntryRenameError => "Entry rename error",
+ StoreIdHandlingError => "StoreId handling error",
+ StoreIdLocalPartAbsoluteError => "StoreId 'id' part is absolute (starts with '/') which is not allowed",
+ StoreIdBuildFromFullPathError => "Building StoreId from full file path failed",
+ StoreIdHasNoBaseError => "StoreId has no 'base' part",
+
+ CreateCallError => "Error when calling create()",
+ RetrieveCallError => "Error when calling retrieve()",
+ GetCallError => "Error when calling get()",
+ GetAllVersionsCallError => "Error when calling get_all_versions()",
+ RetrieveForModuleCallError => "Error when calling retrieve_for_module()",
+ UpdateCallError => "Error when calling update()",
+ RetrieveCopyCallError => "Error when calling retrieve_copy()",
+ DeleteCallError => "Error when calling delete()",
+ MoveCallError => "Error when calling move()",
+ MoveByIdCallError => "Error when calling move_by_id()"
+);
+
+generate_result_helper!(StoreError, StoreErrorKind);
+generate_option_helper!(StoreError, StoreErrorKind);
+
+generate_custom_error_types!(ParserError, ParserErrorKind, CustomErrorData,
+ TOMLParserErrors => "Several TOML-Parser-Errors",
+ MissingMainSection => "Missing main section",
+ MissingVersionInfo => "Missing version information in main section",
+ NonTableInBaseTable => "A non-table was found in the base table",
+ HeaderInconsistency => "The header is inconsistent"
+);
+
+impl From<ParserError> for StoreError {
+ fn from(ps: ParserError) -> StoreError {
+ StoreError {
+ err_type: StoreErrorKind::MalformedEntry,
+ cause: Some(Box::new(ps)),
+ custom_data: None,
+ }
+ }
+}
+
+impl From<::std::io::Error> for StoreError {
+ fn from(ps: ::std::io::Error) -> StoreError {
+ StoreError {
+ err_type: StoreErrorKind::IoError,
+ cause: Some(Box::new(ps)),
+ custom_data: None,
+ }
+ }
+}
+
diff --git a/lib/core/libimagstore/src/file_abstraction/fs.rs b/lib/core/libimagstore/src/file_abstraction/fs.rs
new file mode 100644
index 0000000..9fd1445
--- /dev/null
+++ b/lib/core/libimagstore/src/file_abstraction/fs.rs
@@ -0,0 +1,167 @@
+//
+// 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::fs::{File, OpenOptions, create_dir_all, remove_file, copy, rename};
+use std::io::{Seek, SeekFrom, Read};
+use std::path::{Path, PathBuf};
+
+use error::{MapErrInto, StoreError as SE, StoreErrorKind as SEK};
+
+use super::FileAbstraction;
+use super::FileAbstractionInstance;
+use super::Drain;
+use store::Entry;
+use storeid::StoreId;
+
+#[derive(Debug)]
+pub enum FSFileAbstractionInstance {
+ Absent(PathBuf),
+ File(File, PathBuf)
+}
+
+impl FileAbstractionInstance for FSFileAbstractionInstance {
+
+ /**
+ * Get the content behind this file
+ */
+ fn get_file_content(&mut self, id: StoreId) -> Result<Entry, SE> {
+ debug!("Getting lazy file: {:?}", self);
+ let (file, path) = match *self {
+ FSFileAbstractionInstance::File(ref mut f, _) => return {
+ // We seek to the beginning of the file since we expect each
+ // access to the file to be in a different context
+ try!(f.seek(SeekFrom::Start(0))
+ .map_err_into(SEK::FileNotSeeked));
+
+ let mut s = String::new();
+ f.read_to_string(&mut s)
+ .map_err_into(SEK::IoError)
+ .map(|_| s)
+ .and_then(|s| Entry::from_str(id, &s))
+ },
+ FSFileAbstractionInstance::Absent(ref p) =>
+ (try!(open_file(p).map_err_into(SEK::FileNotFound)), p.clone()),
+ };
+ *self = FSFileAbstractionInstance::File(file, path);
+ if let FSFileAbstractionInstance::File(ref mut f, _) = *self {
+ let mut s = String::new();
+ f.read_to_string(&mut s)
+ .map_err_into(SEK::IoError)
+ .map(|_| s)
+ .and_then(|s| Entry::from_str(id, &s))
+ } else {
+ unreachable!()
+ }
+ }
+
+ /**
+ * Write the content of this file
+ */
+ fn write_file_content(&mut self, buf: &Entry) -> Result<(), SE> {
+ use std::io::Write;
+
+ let buf = buf.to_str().into_bytes();
+
+ let (file, path) = match *self {
+ FSFileAbstractionInstance::File(ref mut f, _) => return {
+ // We seek to the beginning of the file since we expect each
+ // access to the file to be in a different context
+ try!(f.seek(SeekFrom::Start(0))
+ .map_err_into(SEK::FileNotCreated));
+
+ try!(f.set_len(buf.len() as u64).map_err_into(SEK::FileNotWritten));
+
+ f.write_all(&buf).map_err_into(SEK::FileNotWritten)
+ },
+ FSFileAbstractionInstance::Absent(ref p) =>
+ (try!(create_file(p).map_err_into(SEK::FileNotCreated)), p.clone()),
+ };
+ *self = FSFileAbstractionInstance::File(file, path);
+ if let FSFileAbstractionInstance::File(ref mut f, _) = *self {
+ return f.write_all(&buf).map_err_into(SEK::FileNotWritten);
+ }
+ unreachable!();
+ }
+
+}
+
+/// `FSFileAbstraction` state type
+///
+/// A lazy file is either absent, but a path to it is available, or it is present.
+#[derive(Debug)]
+pub struct FSFileAbstraction {
+}
+
+impl FSFileAbstraction {
+ pub fn new() -> FSFileAbstraction {
+ FSFileAbstraction { }
+ }
+}
+
+impl FileAbstraction for FSFileAbstraction {
+
+ fn remove_file(&self, path: &PathBuf) -> Result<(), SE> {
+ remove_file(path).map_err_into(SEK::FileNotRemoved)
+ }
+
+ fn copy(&self, from: &PathBuf, to: &PathBuf) -> Result<(), SE> {
+ copy(from, to).map_err_into(SEK::FileNotCopied).map(|_| ())
+ }
+
+ fn rename(&self, from: &PathBuf, to: &PathBuf) -> Result<(), SE> {
+ rename(from, to).map_err_into(SEK::FileNotRenamed)
+ }
+
+ fn create_dir_all(&self, path: &PathBuf) -> Result<(), SE> {
+ create_dir_all(path).map_err_into(SEK::DirNotCreated)
+ }
+
+ fn new_instance(&self, p: PathBuf) -> Box<FileAbstractionInstance> {
+ Box::new(FSFileAbstractionInstance::Absent(p))
+ }
+
+ /// We return nothing from the FS here.
+ fn drain(&self) -> Result<Drain, SE> {
+ Ok(Drain::empty())
+ }
+
+ /// FileAbstraction::fill implementation that consumes the Drain and writes everything to the
+ /// filesystem
+ fn fill(&mut self, mut d: Drain) -> Result<(), SE> {
+ d.iter()
+ .fold(Ok(()), |acc, (path, element)| {
+ acc.and_then(|_| self.new_instance(path).write_file_content(&element))
+ })
+ }
+}
+
+fn open_file<A: AsRef<Path>>(p: A) -> ::std::io::Result<File> {
+ OpenOptions::new().write(true).read(true).open(p)
+}
+
+fn create_file<A: AsRef<Path>>(p: A) -> ::std::io::Result<File> {
+ if let Some(parent) = p.as_ref().parent() {
+ debug!("Implicitely creating directory: {:?}", parent);
+ if let Err(e) = create_dir_all(parent) {
+ return Err(e);
+ }
+ }
+ OpenOptions::new().write(true).read(true).create(true).open(p)
+}
+
diff --git a/lib/core/libimagstore/src/file_abstraction/inmemory.rs b/lib/core/libimagstore/src/file_abstraction/inmemory.rs
new file mode 100644
index 0000000..57ca241
--- /dev/null
+++ b/lib/core/libimagstore/src/file_abstraction/inmemory.rs
@@ -0,0 +1,177 @@
+//
+// 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 error::StoreError as SE;
+use error::StoreErrorKind as SEK;
+use std::path::PathBuf;
+use std::collections::HashMap;
+use std::sync::Mutex;
+use std::cell::RefCell;
+use std::sync::Arc;
+use std::ops::Deref;
+
+use libimagerror::into::IntoError;
+
+use super::FileAbstraction;
+use super::FileAbstractionInstance;
+use super::Drain;
+use store::Entry;
+use storeid::StoreId;
+
+type Backend = Arc<Mutex<RefCell<HashMap<PathBuf, Entry>>>>;
+
+/// `FileAbstraction` type, this is the Test version!
+///
+/// A lazy file is either absent, but a path to it is available, or it is present.
+#[derive(Debug)]
+pub struct InMemoryFileAbstractionInstance {
+ fs_abstraction: Backend,
+ absent_path: PathBuf,
+}
+
+impl InMemoryFileAbstractionInstance {
+
+ pub fn new(fs: Backend, pb: PathBuf) -> InMemoryFileAbstractionInstance {
+ InMemoryFileAbstractionInstance {
+ fs_abstraction: fs,
+ absent_path: pb
+ }
+ }
+
+}
+
+impl FileAbstractionInstance for InMemoryFileAbstractionInstance {
+
+ /**
+ * Get the mutable file behind a InMemoryFileAbstraction object
+ */
+ fn get_file_content(&mut self, _: StoreId) -> Result<Entry, SE> {
+ debug!("Getting lazy file: {:?}", self);
+
+ match self.fs_abstraction.lock() {
+ Ok(mut mtx) => {
+ mtx.get_mut()
+ .get(&self.absent_path)
+ .cloned()
+ .ok_or(SEK::FileNotFound.into_error())
+ }
+
+ Err(_) => Err(SEK::LockError.into_error())
+ }
+ }
+
+ fn write_file_content(&mut self, buf: &Entry) -> Result<(), SE> {
+ match *self {
+ InMemoryFileAbstractionInstance { ref absent_path, .. } => {
+ let mut mtx = self.fs_abstraction.lock().expect("Locking Mutex failed");
+ let mut backend = mtx.get_mut();
+ let _ = backend.insert(absent_path.clone(), buf.clone());
+ return Ok(());
+ },
+ };
+ }
+}
+
+#[derive(Debug)]
+pub struct InMemoryFileAbstraction {
+ virtual_filesystem: Backend,
+}
+
+impl InMemoryFileAbstraction {
+
+ pub fn new() -> InMemoryFileAbstraction {
+ InMemoryFileAbstraction {
+ virtual_filesystem: Arc::new(Mutex::new(RefCell::new(HashMap::new()))),
+ }
+ }
+
+ pub fn backend(&self) -> &Backend {
+ &self.virtual_filesystem
+ }
+
+ fn backend_cloned<'a>(&'a self) -> Result<HashMap<PathBuf, Entry>, SE> {
+ self.virtual_filesystem
+ .lock()
+ .map_err(|_| SEK::LockError.into_error())
+ .map(|mtx| mtx.deref().borrow().clone())
+ }
+
+}
+
+impl FileAbstraction for InMemoryFileAbstraction {
+
+ fn remove_file(&self, path: &PathBuf) -> Result<(), SE> {
+ debug!("Removing: {:?}", path);
+ self.backend()
+ .lock()
+ .expect("Locking Mutex failed")
+ .get_mut()
+ .remove(path)
+ .map(|_| ())
+ .ok_or(SEK::FileNotFound.into_error())
+ }
+
+ fn copy(&self, from: &PathBuf, to: &PathBuf) -> Result<(), SE> {
+ debug!("Copying : {:?} -> {:?}", from, to);
+ let mut mtx = self.backend().lock().expect("Locking Mutex failed");
+ let mut backend = mtx.get_mut();
+
+ let a = try!(backend.get(from).cloned().ok_or(SEK::FileNotFound.into_error()));
+ backend.insert(to.clone(), a);
+ debug!("Copying: {:?} -> {:?} worked", from, to);
+ Ok(())
+ }
+
+ fn rename(&self, from: &PathBuf, to: &PathBuf) -> Result<(), SE> {
+ debug!("Renaming: {:?} -> {:?}", from, to);
+ let mut mtx = self.backend().lock().expect("Locking Mutex failed");
+ let mut backend = mtx.get_mut();
+
+ let a = try!(backend.get(from).cloned().ok_or(SEK::FileNotFound.into_error()));
+ backend.insert(to.clone(), a);
+ debug!("Renaming: {:?} -> {:?} worked", from, to);
+ Ok(())
+ }
+
+ fn create_dir_all(&self, _: &PathBuf) -> Result<(), SE> {
+ Ok(())
+ }
+
+ fn new_instance(&self, p: PathBuf) -> Box<FileAbstractionInstance> {
+ Box::new(InMemoryFileAbstractionInstance::new(self.backend().clone(), p))
+ }
+
+ fn drain(&self) -> Result<Drain, SE> {
+ self.backend_cloned().map(Drain::new)
+ }
+
+ fn fill<'a>(&'a mut self, mut d: Drain) -> Result<(), SE> {
+ debug!("Draining into : {:?}", self);
+ let mut mtx = try!(self.backend().lock().map_err(|_| SEK::LockError.into_error()));
+ let mut backend = mtx.get_mut();
+
+ for (path, element) in d.iter() {
+ debug!("Drain into {:?}: {:?}", self, path);
+ backend.insert(path, element);
+ }
+
+ Ok(())
+ }
+}
+
diff --git a/lib/core/libimagstore/src/file_abstraction/mod.rs b/lib/core/libimagstore/src/file_abstraction/mod.rs
new file mode 100644
index 0000000..07362c3
--- /dev/null
+++ b/lib/core/libimagstore/src/file_abstraction/mod.rs
@@ -0,0 +1,120 @@
+//
+// 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::path::PathBuf;
+use std::fmt::Debug;
+use std::collections::HashMap;
+
+use error::StoreError as SE;
+use store::Entry;
+use storeid::StoreId;
+
+mod fs;
+mod inmemory;
+pub mod stdio;
+
+pub use self::fs::FSFileAbstraction;
+pub use self::fs::FSFileAbstractionInstance;
+pub use self::inmemory::InMemoryFileAbstraction;
+pub use self::inmemory::InMemoryFileAbstractionInstance;
+
+/// An abstraction trait over filesystem actions
+pub trait FileAbstraction : Debug {
+ fn remove_file(&self, path: &PathBuf) -> Result<(), SE>;
+ fn copy(&self, from: &PathBuf, to: &PathBuf) -> Result<(), SE>;
+ fn rename(&self, from: &PathBuf, to: &PathBuf) -> Result<(), SE>;
+ fn create_dir_all(&self, _: &PathBuf) -> Result<(), SE>;
+
+ fn new_instance(&self, p: PathBuf) -> Box<FileAbstractionInstance>;
+
+ fn drain(&self) -> Result<Drain, SE>;
+ fn fill<'a>(&'a mut self, d: Drain) -> Result<(), SE>;
+}
+
+/// An abstraction trait over actions on files
+pub trait FileAbstractionInstance : Debug {
+
+ /// Get the contents of the FileAbstractionInstance, as Entry object.
+ ///
+ /// The `StoreId` is passed because the backend does not know where the Entry lives, but the
+ /// Entry type itself must be constructed with the id.
+ fn get_file_content(&mut self, id: StoreId) -> Result<Entry, SE>;
+ fn write_file_content(&mut self, buf: &Entry) -> Result<(), SE>;
+}
+
+pub struct Drain(HashMap<PathBuf, Entry>);
+
+impl Drain {
+
+ pub fn new(hm: HashMap<PathBuf, Entry>) -> Drain {
+ Drain(hm)
+ }
+
+ pub fn empty() -> Drain {
+ Drain::new(HashMap::new())
+ }
+
+ pub fn iter<'a>(&'a mut self) -> DrainIter<'a> {
+ DrainIter(self.0.drain())
+ }
+
+}
+
+pub struct DrainIter<'a>(::std::collections::hash_map::Drain<'a, PathBuf, Entry>);
+
+impl<'a> Iterator for DrainIter<'a> {
+ type Item = (PathBuf, Entry);
+
+ fn next(&mut self) -> Option<Self::Item> {
+ self.0.next()
+ }
+}
+
+#[cfg(test)]
+mod test {
+ use std::path::PathBuf;
+
+ use super::FileAbstractionInstance;
+ use super::inmemory::InMemoryFileAbstraction;
+ use super::inmemory::InMemoryFileAbstractionInstance;
+ use storeid::StoreId;
+ use store::Entry;
+
+ #[test]
+ fn lazy_file() {
+ let fs = InMemoryFileAbstraction::new();
+
+ let mut path = PathBuf::from("tests");
+ path.set_file_name("test1");
+ let mut lf = InMemoryFileAbstractionInstance::new(fs.backend().clone(), path.clone());
+
+ let loca = StoreId::new_baseless(path).unwrap();
+ let file = Entry::from_str(loca.clone(), r#"---
+[imag]
+version = "0.4.0"
+---
+Hello World"#).unwrap();
+
+ lf.write_file_content(&file).unwrap();
+ let bah = lf.get_file_content(loca).unwrap();
+ assert_eq!(bah.get_content(), "Hello World");
+ }
+
+}
+
diff --git a/lib/core/libimagstore/src/file_abstraction/stdio/mapper/json.rs b/lib/core/libimagstore/src/file_abstraction/stdio/mapper/json.rs
new file mode 100644
index 0000000..0b66f91
--- /dev/null
+++ b/lib/core/libimagstore/src/file_abstraction/stdio/mapper/json.rs
@@ -0,0 +1,250 @@
+//
+// 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::collections::HashMap;
+use std::io::{Read, Write};
+use std::path::PathBuf;
+
+use serde_json;
+use toml;
+
+use error::StoreErrorKind as SEK;
+use error::MapErrInto;
+use super::Mapper;
+use store::Result;
+use store::Entry;
+use storeid::StoreId;
+
+use libimagerror::into::IntoError;
+
+#[derive(Debug, Deserialize, Serialize)]
+struct BackendEntry {
+ header: serde_json::Value,
+ content: String,
+}
+
+impl BackendEntry {
+
+ fn to_string(self) -> Result<String> {
+ toml::to_string(&self.header)
+ .map_err_into(SEK::IoError)
+ .map(|hdr| {
+ format!("---\n{header}---\n{content}",
+ header = hdr,
+ content = self.content)
+ })
+ }
+
+}
+
+#[derive(Debug, Deserialize, Serialize)]
+struct Document {
+ version: String,
+ store: HashMap<PathBuf, BackendEntry>,
+}
+
+pub struct JsonMapper;
+
+impl JsonMapper {
+
+ pub fn new() -> JsonMapper {
+ JsonMapper
+ }
+
+}
+
+impl Mapper for JsonMapper {
+ fn read_to_fs<R: Read>(&self, r: &mut R, hm: &mut HashMap<PathBuf, Entry>) -> Result<()> {
+ let mut document = {
+ debug!("Reading Document");
+ let mut s = String::new();
+ try!(r.read_to_string(&mut s).map_err_into(SEK::IoError));
+ debug!("Document = {:?}", s);
+ debug!("Parsing Document");
+ let doc : Document = try!(serde_json::from_str(&s).map_err_into(SEK::IoError));
+ debug!("Document = {:?}", doc);
+ doc
+ };
+
+ let _ = try!(::semver::Version::parse(&document.version)
+ .map_err_into(SEK::VersionError)
+ .and_then(|doc_vers| {
+ // safe because cargo does not compile if crate version is not valid
+ let crate_version = ::semver::Version::parse(version!()).unwrap();
+
+ debug!("Document version vs. own version: {doc_vers} > {crate_vers}",
+ doc_vers = doc_vers,
+ crate_vers = crate_version);
+
+ if doc_vers > crate_version {
+ Err(SEK::VersionError.into_error())
+ } else {
+ Ok(())
+ }
+ }));
+
+ for (key, val) in document.store.drain() {
+ debug!("(key, value) ({:?}, {:?})", key, val);
+ let res = val
+ .to_string()
+ .and_then(|vals| {
+ debug!("value string = {:?}", vals);
+ StoreId::new_baseless(key.clone())
+ .and_then(|id| Entry::from_str(id, &vals))
+ .map(|entry| hm.insert(key, entry))
+ })
+ .map(|_| ());
+
+ let _ = try!(res);
+ }
+
+ Ok(())
+ }
+
+ fn fs_to_write<W: Write>(&self, hm: &mut HashMap<PathBuf, Entry>, out: &mut W) -> Result<()> {
+ #[derive(Serialize)]
+ struct BackendEntry {
+ header: ::toml::Value,
+ content: String,
+ }
+
+ impl BackendEntry {
+ fn construct_from(e: Entry) -> BackendEntry {
+ BackendEntry {
+ header: e.get_header().clone(),
+ content: e.get_content().clone(),
+ }
+ }
+ }
+
+ #[derive(Serialize)]
+ struct OutDocument {
+ version: String,
+ store: HashMap<PathBuf, BackendEntry>,
+ }
+
+ let mut store = HashMap::new();
+ for (key, value) in hm.drain() {
+ store.insert(key, BackendEntry::construct_from(value));
+ }
+
+ let doc = OutDocument {
+ version: String::from(version!()),
+ store: store,
+ };
+
+ serde_json::to_string(&doc)
+ .map_err_into(SEK::IoError)
+ .and_then(|json| out.write(&json.into_bytes()).map_err_into(SEK::IoError))
+ .and_then(|_| out.flush().map_err_into(SEK::IoError))
+ .map(|_| ())
+ }
+}
+
+#[cfg(test)]
+mod test {
+ use std::io::Cursor;
+
+ use super::*;
+
+ #[test]
+ fn test_empty_json_to_fs() {
+ let json = r#"{"version":"0.4.0","store":{}}"#;
+ let mut json = Cursor::new(String::from(json).into_bytes());
+ let mapper = JsonMapper::new();
+ let mut hm = HashMap::new();
+
+ let io_res = mapper.read_to_fs(&mut json, &mut hm);
+ assert!(io_res.is_ok());
+ assert!(hm.is_empty());
+ }
+
+ #[test]
+ fn test_json_to_fs() {
+ let json = r#"
+ { "version": "0.4.0",
+ "store": {
+ "example": {
+ "header": {
+ "imag": {
+ "version": "0.4.0"
+ }
+ },
+ "content": "test"
+ }
+ }
+ }
+ "#;
+ let mut json = Cursor::new(String::from(json).into_bytes());
+ let mapper = JsonMapper::new();
+ let mut hm = HashMap::new();
+
+ let io_res = mapper.read_to_fs(&mut json, &mut hm);
+ assert!(io_res.is_ok());
+
+ assert_eq!(1, hm.len()); // we should have exactly one entry
+ }
+
+ #[test]
+ fn test_fs_to_json() {
+ let mapper = JsonMapper::new();
+ let mut out : Cursor<Vec<u8>> = Cursor::new(vec![]);
+
+ let mut hm = {
+ let mut hm = HashMap::new();
+ let content = r#"---
+[imag]
+version = "0.4.0"
+---
+hi there!"#;
+
+ let id = PathBuf::from("example");
+ let entry = Entry::from_str(id.clone(), content).unwrap();
+ hm.insert(id, entry);
+ hm
+ };
+
+ let io_res = mapper.fs_to_write(&mut hm, &mut out);
+ assert!(io_res.is_ok());
+
+ let example = r#"
+ {
+ "version": "0.4.0",
+ "store": {
+ "example": {
+ "header": {
+ "imag": {
+ "version": "0.4.0"
+ }
+ },
+ "content": "hi there!"
+ }
+ }
+ }
+ "#;
+
+ let example_json : ::serde_json::Value = ::serde_json::from_str(example).unwrap();
+
+ let output_json = String::from_utf8(out.into_inner()).unwrap();
+ let output_json : ::serde_json::Value = ::serde_json::from_str(&output_json).unwrap();
+
+ assert_eq!(example_json, output_json);
+ }
+}
+
diff --git a/lib/core/libimagstore/src/file_abstraction/stdio/mapper/mod.rs b/lib/core/libimagstore/src/file_abstraction/stdio/mapper/mod.rs
new file mode 100644
index 0000000..f7192b3
--- /dev/null
+++ b/lib/core/libimagstore/src/file_abstraction/stdio/mapper/mod.rs
@@ -0,0 +1,32 @@
+//
+// 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::collections::HashMap;
+use std::io::{Read, Write};
+use std::path::PathBuf;
+use store::Result;
+use store::Entry;
+
+pub trait Mapper {
+ fn read_to_fs<R: Read>(&self, &mut R, &mut HashMap<PathBuf, Entry>) -> Result<()>;
+ fn fs_to_write<W: Write>(&self, &mut HashMap<PathBuf, Entry>, &mut W) -> Result<()>;
+}
+
+pub mod json;
+
diff --git a/lib/core/libimagstore/src/file_abstraction/stdio/mod.rs b/lib/core/libimagstore/src/file_abstraction/stdio/mod.rs
new file mode 100644
index 0000000..e2ac5ec
--- /dev/null
+++ b/lib/core/libimagstore/src/file_abstraction/stdio/mod.rs
@@ -0,0 +1,127 @@
+//
+// 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::rc::Rc;
+use std::cell::RefCell;
+use std::collections::HashMap;
+use std::io::{Read, Write};
+use std::path::PathBuf;
+use std::sync::Arc;
+use std::sync::Mutex;
+use std::ops::Deref;
+use std::fmt::Debug;
+use std::fmt::Error as FmtError;
+use std::fmt::Formatter;
+
+use libimagerror::into::IntoError;
+
+use error::StoreErrorKind as SEK;
+use error::StoreError as SE;
+use super::FileAbstraction;
+use super::FileAbstractionInstance;
+use super::Drain;
+use super::InMemoryFileAbstraction;
+use store::Entry;
+
+pub mod mapper;
+pub mod out;
+use self::mapper::Mapper;
+use self::out::StdoutFileAbstraction;
+
+// Because this is not exported in super::inmemory;
+type Backend = Arc<Mutex<RefCell<HashMap<PathBuf, Entry>>>>;
+
+pub struct StdIoFileAbstraction<W: Write, M: Mapper>(StdoutFileAbstraction<W, M>);
+
+impl<W, M> StdIoFileAbstraction<W, M>
+ where M: Mapper,
+ W: Write
+{
+
+ pub fn new<R: Read>(in_stream: &mut R, out_stream: Rc<RefCell<W>>, mapper: M) -> Result<StdIoFileAbstraction<W, M>, SE> {
+ StdoutFileAbstraction::new(out_stream, mapper)
+ .and_then(|out| {
+ let fill_res = match out.backend().lock() {
+ Err(_) => Err(SEK::LockError.into_error()),
+ Ok(mut mtx) => out.mapper().read_to_fs(in_stream, mtx.get_mut())
+ };
+ let _ = try!(fill_res);
+
+ Ok(StdIoFileAbstraction(out))
+ })
+ }
+
+ pub fn backend(&self) -> &Backend {
+ self.0.backend()
+ }
+
+}
+
+impl<W, M> Debug for StdIoFileAbstraction<W, M>
+ where M: Mapper,
+ W: Write
+{
+ fn fmt(&self, f: &mut Formatter) -> Result<(), FmtError> {
+ write!(f, "StdIoFileAbstraction({:?}", self.0)
+ }
+}
+
+impl<W, M> Deref for StdIoFileAbstraction<W, M>
+ where M: Mapper,
+ W: Write
+{
+ type Target = StdoutFileAbstraction<W, M>;
+
+ fn deref(&self) -> &Self::Target {
+ &self.0
+ }
+}
+
+// basically #[derive(FileAbstraction)]
+impl<W: Write, M: Mapper> FileAbstraction for StdIoFileAbstraction<W, M> {
+
+ fn remove_file(&self, path: &PathBuf) -> Result<(), SE> {
+ self.0.remove_file(path)
+ }
+
+ fn copy(&self, from: &PathBuf, to: &PathBuf) -> Result<(), SE> {
+ self.0.copy(from, to)
+ }
+
+ fn rename(&self, from: &PathBuf, to: &PathBuf) -> Result<(), SE> {
+ self.0.rename(from, to)
+ }
+
+ fn create_dir_all(&self, pb: &PathBuf) -> Result<(), SE> {
+ self.0.create_dir_all(pb)
+ }
+
+ fn new_instance(&self, p: PathBuf) -> Box<FileAbstractionInstance> {
+ self.0.new_instance(p)
+ }
+
+ fn drain(&self) -> Result<Drain, SE> {
+ self.0.drain()
+ }
+
+ fn fill(&mut self, d: Drain) -> Result<(), SE> {
+ self.0.fill(d)
+ }
+}
+
diff --git a/lib/core/libimagstore/src/file_abstraction/stdio/out.rs b/lib/core/libimagstore/src/file_abstraction/stdio/out.rs
new file mode 100644
index 0000000..eca9911
--- /dev/null
+++ b/lib/core/libimagstore/src/file_abstraction/stdio/out.rs
@@ -0,0 +1,157 @@
+//
+// 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
+//
+
+//! A StdIoFileAbstraction which does not read from stdin.
+
+use std::rc::Rc;
+use std::cell::RefCell;
+use std::collections::HashMap;
+use std::fmt::Debug;
+use std::fmt::Error as FmtError;
+use std::fmt::Formatter;
+use std::io::Write;
+use std::path::PathBuf;
+use std::sync::Arc;
+use std::sync::Mutex;
+use std::ops::Deref;
+
+use libimagerror::into::IntoError;
+use libimagerror::trace::*;
+
+use error::StoreErrorKind as SEK;
+use error::StoreError as SE;
+use super::FileAbstraction;
+use super::FileAbstractionInstance;
+use super::Drain;
+use super::InMemoryFileAbstraction;
+use store::Entry;
+
+use super::mapper::Mapper;
+
+// Because this is not exported in super::inmemory;
+type Backend = Arc<Mutex<RefCell<HashMap<PathBuf, Entry>>>>;
+
+pub struct StdoutFileAbstraction<W: Write, M: Mapper> {
+ mapper: M,
+ mem: InMemoryFileAbstraction,
+ out: Rc<RefCell<W>>,
+}
+
+impl<W, M> StdoutFileAbstraction<W, M>
+ where M: Mapper,
+ W: Write
+{
+
+ pub fn new(out_stream: Rc<RefCell<W>>, mapper: M) -> Result<StdoutFileAbstraction<W, M>, SE> {
+ Ok(StdoutFileAbstraction {
+ mapper: mapper,
+ mem: InMemoryFileAbstraction::new(),
+ out: out_stream,
+ })
+ }
+
+ pub fn backend(&self) -> &Backend {
+ self.mem.backend()
+ }
+
+ fn backend_cloned(&self) -> Result<HashMap<PathBuf, Entry>, SE> {
+ self.mem
+ .backend()
+ .lock()
+ .map_err(|_| SEK::LockError.into_error())
+ .map(|mtx| mtx.deref().borrow().clone())
+ }
+
+ pub fn mapper(&self) -> &M {
+ &self.mapper
+ }
+
+}
+
+impl<W, M> Debug for StdoutFileAbstraction<W, M>
+ where M: Mapper,
+ W: Write
+{
+ fn fmt(&self, f: &mut Formatter) -> Result<(), FmtError> {
+ write!(f, "StdoutFileAbstraction({:?}", self.mem)
+ }
+}
+
+impl<W, M> Drop for StdoutFileAbstraction<W, M>
+ where M: Mapper,
+ W: Write
+{
+ fn drop(&mut self) {
+ use std::ops::DerefMut;
+
+ let fill_res = match self.mem.backend().lock() {
+ Err(_) => Err(SEK::LockError.into_error()),
+ Ok(mut mtx) => {
+ self.mapper.fs_to_write(mtx.get_mut(), self.out.borrow_mut().deref_mut())
+ },
+ };
+
+ // We can do nothing but end this here with a trace.
+ // As this drop gets called when imag almost exits, there is no point in exit()ing here
+ // again.
+ let _ = fill_res.map_err_trace();
+ }
+}
+
+impl<W: Write, M: Mapper> FileAbstraction for StdoutFileAbstraction<W, M> {
+
+ fn remove_file(&self, path: &PathBuf) -> Result<(), SE> {
+ self.mem.remove_file(path)
+ }
+
+ fn copy(&self, from: &PathBuf, to: &PathBuf) -> Result<(), SE> {
+ self.mem.copy(from, to)
+ }
+
+ fn rename(&self, from: &PathBuf, to: &PathBuf) -> Result<(), SE> {
+ self.mem.rename(from, to)
+ }
+
+ fn create_dir_all(&self, pb: &PathBuf) -> Result<(), SE> {
+ self.mem.create_dir_all(pb)
+ }
+
+ fn new_instance(&self, p: PathBuf) -> Box<FileAbstractionInstance> {
+ self.mem.new_instance(p)
+ }
+
+ fn drain(&self) -> Result<Drain, SE> {
+ self.backend_cloned().map(Drain::new)
+ }
+
+ fn fill(&mut self, mut d: Drain) -> Result<(), SE> {
+ debug!("Draining into : {:?}", self);
+ let mut mtx = try!(self.backend().lock().map_err(|_| SEK::IoError.into_error()));
+ let mut backend = mtx.get_mut();
+
+ for (path, element) in d.iter() {
+ debug!("Drain into {:?}: {:?}", self, path);
+ backend.insert(path, element);
+ }
+ Ok(())
+ }
+
+}
+
+
diff --git a/lib/core/libimagstore/src/lib.rs b/lib/core/libimagstore/src/lib.rs
new file mode 100644
index 0000000..ae205b8
--- /dev/null
+++ b/lib/core/libimagstore/src/lib.rs
@@ -0,0 +1,62 @@
+//
+// 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
+//
+
+#![deny(
+ non_camel_case_types,
+ non_snake_case,
+ path_statements,
+ trivial_numeric_casts,
+ unstable_features,
+ unused_allocation,
+ unused_import_braces,
+ unused_imports,
+ unused_mut,
+ unused_qualifications,
+ while_true,
+)]
+
+#[macro_use] extern crate log;
+#[macro_use] extern crate version;
+extern crate fs2;
+extern crate glob;
+#[macro_use] extern crate lazy_static;
+extern crate regex;
+extern crate toml;
+#[cfg(test)] extern crate tempdir;
+extern crate semver;
+extern crate crossbeam;
+extern crate walkdir;
+extern crate itertools;
+#[macro_use] extern crate is_match;
+extern crate serde;
+extern crate serde_json;
+#[macro_use] extern crate serde_derive;
+
+#[macro_use] extern crate libimagerror;
+extern crate libimagutil;
+
+#[macro_use] mod util;
+
+pub mod storeid;
+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
new file mode 100644
index 0000000..a0ee33e
--- /dev/null
+++ b/lib/core/libimagstore/src/store.rs
@@ -0,0 +1,1747 @@
+//
+// 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::collections::HashMap;
+use std::ops::Drop;
+use std::path::PathBuf;
+use std::result::Result as RResult;
+use std::sync::Arc;
+use std::sync::RwLock;
+use std::io::Read;
+use std::convert::Into;
+use std::ops::Deref;
+use std::ops::DerefMut;
+use std::fmt::Formatter;
+use std::fmt::Debug;
+use std::fmt::Error as FMTError;
+
+use toml::Value;
+use glob::glob;
+use walkdir::WalkDir;
+use walkdir::Iter as WalkDirIter;
+
+use error::{StoreError as SE, StoreErrorKind as SEK};
+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;
+pub use file_abstraction::InMemoryFileAbstraction;
+
+use libimagerror::into::IntoError;
+use libimagerror::trace::trace_error;
+use libimagutil::debug_result::*;
+
+use self::glob_store_iter::*;
+
+/// The Result Type returned by any interaction with the store that could fail
+pub type Result<T> = RResult<T, SE>;
+
+
+#[derive(Debug, PartialEq)]
+enum StoreEntryStatus {
+ Present,
+ Borrowed
+}
+
+/// A store entry, depending on the option type it is either borrowed currently
+/// or not.
+#[derive(Debug)]
+struct StoreEntry {
+ id: StoreId,
+ file: Box<FileAbstractionInstance>,
+ status: StoreEntryStatus,
+}
+
+pub enum StoreObject {
+ Id(StoreId),
+ Collection(PathBuf),
+}
+
+pub struct Walk {
+ store_path: PathBuf,
+ dirwalker: WalkDirIter,
+}
+
+impl Walk {
+
+ fn new(mut store_path: PathBuf, mod_name: &str) -> Walk {
+ let pb = store_path.clone();
+ store_path.push(mod_name);
+ Walk {
+ store_path: pb,
+ dirwalker: WalkDir::new(store_path).into_iter(),
+ }
+ }
+}
+
+impl ::std::ops::Deref for Walk {
+ type Target = WalkDirIter;
+
+ fn deref(&self) -> &Self::Target {
+ &self.dirwalker
+ }
+}
+
+impl Iterator for Walk {
+ type Item = StoreObject;
+
+ fn next(&mut self) -> Option<Self::Item> {
+
+ while let Some(something) = self.dirwalker.next() {
+ debug!("[Walk] Processing next item: {:?}", something);
+ match something {
+ Ok(next) => if next.file_type().is_dir() {
+ debug!("Found directory...");
+ return Some(StoreObject::Collection(next.path().to_path_buf()))
+ } else /* if next.file_type().is_file() */ {
+ debug!("Found file...");
+ let n = next.path().to_path_buf();
+ let sid = match StoreId::from_full_path(&self.store_path, n) {
+ Err(e) => {
+ debug!("Could not construct StoreId object from it");
+ trace_error(&e);
+ continue;
+ },
+ Ok(o) => o,
+ };
+ return Some(StoreObject::Id(sid))
+ },
+ Err(e) => {
+ warn!("Error in Walker");
+ debug!("{:?}", e);
+ return None;
+ }
+ }
+ }
+
+ return None;
+ }
+}
+
+impl StoreEntry {
+
+ fn new(id: StoreId, backend: &Box<FileAbstraction>) -> Result<StoreEntry> {
+ let pb = try!(id.clone().into_pathbuf());
+
+ #[cfg(feature = "fs-lock")]
+ {
+ try!(open_file(pb.clone())
+ .and_then(|f| f.lock_exclusive().map_err_into(SEK::FileError))
+ .map_err_into(SEK::IoError));
+ }
+
+ Ok(StoreEntry {
+ id: id,
+ file: backend.new_instance(pb),
+ status: StoreEntryStatus::Present,
+ })
+ }
+
+ /// The entry is currently borrowed, meaning that some thread is currently
+ /// mutating it
+ fn is_borrowed(&self) -> bool {
+ self.status == StoreEntryStatus::Borrowed
+ }
+
+ fn get_entry(&mut self) -> Result<Entry> {
+ if !self.is_borrowed() {
+ self.file
+ .get_file_content(self.id.clone())
+ .or_else(|err| if err.err_type() == SEK::FileNotFound {
+ Ok(Entry::new(self.id.clone()))
+ } else {
+ Err(err)
+ })
+ } else {
+ Err(SE::new(SEK::EntryAlreadyBorrowed, None))
+ }
+ }
+
+ fn write_entry(&mut self, entry: &Entry) -> Result<()> {
+ if self.is_borrowed() {
+ assert_eq!(self.id, entry.location);
+ self.file.write_file_content(entry)
+ .map_err_into(SEK::FileError)
+ .map(|_| ())
+ } else {
+ Ok(())
+ }
+ }
+}
+
+#[cfg(feature = "fs-lock")]
+impl Drop for StoreEntry {
+
+ fn drop(self) {
+ self.get_entry()
+ .and_then(|entry| open_file(entry.get_location().clone()).map_err_into(SEK::IoError))
+ .and_then(|f| f.unlock().map_err_into(SEK::FileError))
+ .map_err_into(SEK::IoError)
+ }
+
+}
+
+
+/// The Store itself, through this object one can interact with IMAG's entries
+pub struct Store {
+ location: PathBuf,
+
+ ///
+ /// Configuration object of the store
+ ///
+ configuration: Option<Value>,
+
+ ///
+ /// Internal Path->File cache map
+ ///
+ /// Caches the files, so they remain flock()ed
+ ///
+ /// Could be optimized for a threadsafe HashMap
+ ///
+ entries: Arc<RwLock<HashMap<StoreId, StoreEntry>>>,
+
+ /// The backend to use
+ ///
+ /// This provides the filesystem-operation functions (or pretends to)
+ backend: Box<FileAbstraction>,
+}
+
+impl Store {
+
+ /// Create a new Store object
+ ///
+ /// This opens a Store in `location` using the configuration from `store_config` (if absent, it
+ /// uses defaults).
+ ///
+ /// If the configuration is not valid, this fails.
+ ///
+ /// If the location does not exist, creating directories is by default denied and the operation
+ /// fails, if not configured otherwise.
+ /// An error is returned in this case.
+ ///
+ /// If the path exists and is a file, the operation is aborted as well, an error is returned.
+ ///
+ /// # Return values
+ ///
+ /// - On success: Store object
+ /// - On Failure:
+ /// - ConfigurationError if config is faulty
+ /// - IoError(FileError(CreateStoreDirDenied())) if store location does not exist and creating
+ /// is denied
+ /// - StorePathCreate(_) if creating the store directory failed
+ /// - StorePathExists() if location exists but is a file
+ pub fn new(location: PathBuf, store_config: Option<Value>) -> Result<Store> {
+ let backend = Box::new(FSFileAbstraction::new());
+ Store::new_with_backend(location, store_config, backend)
+ }
+
+ /// Create a Store object as descripbed in `Store::new()` documentation, but with an alternative
+ /// backend implementation.
+ ///
+ /// Do not use directly, only for testing purposes.
+ pub fn new_with_backend(location: PathBuf,
+ store_config: Option<Value>,
+ backend: Box<FileAbstraction>) -> Result<Store> {
+ use configuration::*;
+
+ debug!("Validating Store configuration");
+ let _ = try!(config_is_valid(&store_config).map_err_into(SEK::ConfigurationError));
+
+ debug!("Building new Store object");
+ if !location.exists() {
+ if !config_implicit_store_create_allowed(store_config.as_ref()) {
+ warn!("Implicitely creating store directory is denied");
+ warn!(" -> Either because configuration does not allow it");
+ warn!(" -> or because there is no configuration");
+ return Err(SEK::CreateStoreDirDenied.into_error())
+ .map_err_into(SEK::FileError)
+ .map_err_into(SEK::IoError);
+ }
+
+ try!(backend.create_dir_all(&location)
+ .map_err_into(SEK::StorePathCreate)
+ .map_dbg_err_str("Failed"));
+ } else if location.is_file() {
+ debug!("Store path exists as file");
+ return Err(SEK::StorePathExists.into_error());
+ }
+
+ let store = Store {
+ location: location.clone(),
+ configuration: store_config,
+ entries: Arc::new(RwLock::new(HashMap::new())),
+ backend: backend,
+ };
+
+ debug!("Store building succeeded");
+ debug!("------------------------");
+ debug!("{:?}", store);
+ debug!("------------------------");
+
+ Ok(store)
+ }
+
+ /// Reset the backend of the store during runtime
+ ///
+ /// # Warning
+ ///
+ /// This is dangerous!
+ /// You should not be able to do that in application code, only the libimagrt should be used to
+ /// do this via safe and careful wrapper functions!
+ ///
+ /// If you are able to do this without using `libimagrt`, please file an issue report.
+ ///
+ /// # Purpose
+ ///
+ /// With the I/O backend of the store, the store is able to pipe itself out via (for example)
+ /// JSON. But because we need a functionality where we load contents from the filesystem and
+ /// then pipe it to stdout, we need to be able to replace the backend during runtime.
+ ///
+ /// This also applies the other way round: If we get the store from stdin and have to persist it
+ /// to stdout, we need to be able to replace the in-memory backend with the real filesystem
+ /// backend.
+ ///
+ pub fn reset_backend(&mut self, mut backend: Box<FileAbstraction>) -> Result<()> {
+ self.backend
+ .drain()
+ .and_then(|drain| backend.fill(drain))
+ .map(|_| self.backend = backend)
+ }
+
+ /// Get the store configuration
+ pub fn config(&self) -> Option<&Value> {
+ self.configuration.as_ref()
+ }
+
+ /// Verify the store.
+ ///
+ /// This function is not intended to be called by normal programs but only by `imag-store`.
+ #[cfg(feature = "verify")]
+ pub fn verify(&self) -> bool {
+ use libimagerror::trace::trace_error_dbg;
+
+ info!("Header | Content length | Path");
+ info!("-------+----------------+-----");
+
+ WalkDir::new(self.location.clone())
+ .into_iter()
+ .all(|res| match res {
+ Ok(dent) => {
+ if dent.file_type().is_file() {
+ match self.get(PathBuf::from(dent.path())) {
+ Ok(Some(fle)) => {
+ let p = fle.get_location();
+ let content_len = fle.get_content().len();
+ let header = if fle.get_header().verify().is_ok() {
+ "ok"
+ } else {
+ "broken"
+ };
+
+ info!("{: >6} | {: >14} | {:?}", header, content_len, p.deref());
+ true
+ },
+
+ Ok(None) => {
+ info!("{: >6} | {: >14} | {:?}", "?", "couldn't load", dent.path());
+ true
+ },
+
+ Err(e) => {
+ trace_error_dbg(&e);
+ if_cfg_panic!("Error verifying: {:?}", e);
+ debug!("{:?}", e);
+ false
+ },
+ }
+ } else {
+ info!("{: >6} | {: >14} | {:?}", "?", "<no file>", dent.path());
+ true
+ }
+ },
+
+ Err(e) => {
+ trace_error_dbg(&e);
+ if_cfg_panic!("Error verifying: {:?}", e);
+ debug!("{:?}", e);
+ false
+ },
+ })
+ }
+
+ /// Creates the Entry at the given location (inside the entry)
+ ///
+ /// # Return value
+ ///
+ /// On success: FileLockEntry
+ ///
+ /// On error:
+ /// - Errors StoreId::into_storeid() might return
+ /// - CreateCallError(LockPoisoned()) if the internal lock is poisened.
+ /// - CreateCallError(EntryAlreadyExists()) if the entry exists already.
+ ///
+ pub fn create<'a, S: IntoStoreId>(&'a self, id: S) -> Result<FileLockEntry<'a>> {
+ let id = try!(id.into_storeid()).with_base(self.path().clone());
+
+ debug!("Creating id: '{}'", id);
+
+ {
+ let mut hsmap = match self.entries.write() {
+ Err(_) => return Err(SEK::LockPoisoned.into_error()).map_err_into(SEK::CreateCallError),
+ Ok(s) => s,
+ };
+
+ if hsmap.contains_key(&id) {
+ debug!("Cannot create, internal cache already contains: '{}'", id);
+ return Err(SEK::EntryAlreadyExists.into_error()).map_err_into(SEK::CreateCallError);
+ }
+ hsmap.insert(id.clone(), {
+ debug!("Creating: '{}'", id);
+ let mut se = try!(StoreEntry::new(id.clone(), &self.backend));
+ se.status = StoreEntryStatus::Borrowed;
+ se
+ });
+ }
+
+ debug!("Constructing FileLockEntry: '{}'", id);
+
+ Ok(FileLockEntry::new(self, Entry::new(id)))
+ }
+
+ /// Borrow a given Entry. When the `FileLockEntry` is either `update`d or
+ /// dropped, the new Entry is written to disk
+ ///
+ /// Implicitely creates a entry in the store if there is no entry with the id `id`. For a
+ /// non-implicitely-create look at `Store::get`.
+ ///
+ /// # Return value
+ ///
+ /// On success: FileLockEntry
+ ///
+ /// On error:
+ /// - Errors StoreId::into_storeid() might return
+ /// - RetrieveCallError(LockPoisoned()) if the internal lock is poisened.
+ ///
+ pub fn retrieve<'a, S: IntoStoreId>(&'a self, id: S) -> Result<FileLockEntry<'a>> {
+ let id = try!(id.into_storeid()).with_base(self.path().clone());
+ debug!("Retrieving id: '{}'", id);
+ let entry = try!({
+ self.entries
+ .write()
+ .map_err(|_| SE::new(SEK::LockPoisoned, None))
+ .and_then(|mut es| {
+ let new_se = try!(StoreEntry::new(id.clone(), &self.backend));
+ let mut se = es.entry(id.clone()).or_insert(new_se);
+ let entry = se.get_entry();
+ se.status = StoreEntryStatus::Borrowed;
+ entry
+ })
+ .map_err_into(SEK::RetrieveCallError)
+ });
+
+ debug!("Constructing FileLockEntry: '{}'", id);
+ Ok(FileLockEntry::new(self, entry))
+ }
+
+ /// Get an entry from the store if it exists.
+ ///
+ /// # Return value
+ ///
+ /// On success: Some(FileLockEntry) or None
+ ///
+ /// On error:
+ /// - Errors StoreId::into_storeid() might return
+ /// - Errors Store::retrieve() might return
+ ///
+ pub fn get<'a, S: IntoStoreId + Clone>(&'a self, id: S) -> Result<Option<FileLockEntry<'a>>> {
+ let id = try!(id.into_storeid()).with_base(self.path().clone());
+
+ debug!("Getting id: '{}'", id);
+
+ let exists = try!(id.exists()) || try!(self.entries
+ .read()
+ .map(|map| map.contains_key(&id))
+ .map_err(|_| SE::new(SEK::LockPoisoned, None))
+ .map_err_into(SEK::GetCallError)
+ );
+
+ if !exists {
+ debug!("Does not exist in internal cache or filesystem: {:?}", id);
+ return Ok(None);
+ }
+
+ self.retrieve(id).map(Some).map_err_into(SEK::GetCallError)
+ }
+
+ /// Iterate over all StoreIds for one module name
+ ///
+ /// # Returns
+ ///
+ /// On success: An iterator over all entries in the module
+ ///
+ /// On failure:
+ /// - RetrieveForModuleCallError(GlobError(EncodingError())) if the path string cannot be
+ /// encoded
+ /// - GRetrieveForModuleCallError(GlobError(lobError())) if the glob() failed.
+ ///
+ pub fn retrieve_for_module(&self, mod_name: &str) -> Result<StoreIdIterator> {
+ let mut path = self.path().clone();
+ path.push(mod_name);
+
+ debug!("Retrieving for module: '{}'", mod_name);
+
+ path.to_str()
+ .ok_or(SE::new(SEK::EncodingError, None))
+ .and_then(|path| {
+ let path = [ path, "/**/*" ].join("");
+ debug!("glob()ing with '{}'", path);
+ glob(&path[..]).map_err_into(SEK::GlobError)
+ })
+ .map(|paths| GlobStoreIdIterator::new(paths, self.path().clone()).into())
+ .map_err_into(SEK::GlobError)
+ .map_err_into(SEK::RetrieveForModuleCallError)
+ }
+
+ /// Walk the store tree for the module
+ ///
+ /// The difference between a `Walk` and a `StoreIdIterator` is that with a `Walk`, one can find
+ /// "collections" (folders).
+ pub fn walk<'a>(&'a self, mod_name: &str) -> Walk {
+ debug!("Creating Walk object for {}", mod_name);
+ Walk::new(self.path().clone(), mod_name)
+ }
+
+ /// Return the `FileLockEntry` and write to disk
+ ///
+ /// See `Store::_update()`.
+ ///
+ pub fn update<'a>(&'a self, entry: &mut FileLockEntry<'a>) -> Result<()> {
+ debug!("Updating FileLockEntry at '{}'", entry.get_location());
+ self._update(entry, false).map_err_into(SEK::UpdateCallError)
+ }
+
+ /// Internal method to write to the filesystem store.
+ ///
+ /// # Assumptions
+ ///
+ /// This method assumes that entry is dropped _right after_ the call, hence
+ /// it is not public.
+ ///
+ /// # Return value
+ ///
+ /// On success: Entry
+ ///
+ /// On error:
+ /// - UpdateCallError(LockPoisoned()) if the internal write lock cannot be aquierd.
+ /// - IdNotFound() if the entry was not found in the stor
+ /// - Errors Entry::verify() might return
+ /// - Errors StoreEntry::write_entry() might return
+ ///
+ fn _update<'a>(&'a self, entry: &mut FileLockEntry<'a>, modify_presence: bool) -> Result<()> {
+ let mut hsmap = match self.entries.write() {
+ Err(_) => return Err(SE::new(SEK::LockPoisoned, None)),
+ Ok(e) => e,
+ };
+
+ let mut se = try!(hsmap.get_mut(&entry.location).ok_or(SE::new(SEK::IdNotFound, None)));
+
+ assert!(se.is_borrowed(), "Tried to update a non borrowed entry.");
+
+ debug!("Verifying Entry");
+ try!(entry.entry.verify());
+
+ debug!("Writing Entry");
+ try!(se.write_entry(&entry.entry));
+ if modify_presence {
+ se.status = StoreEntryStatus::Present;
+ }
+
+ Ok(())
+ }
+
+ /// Retrieve a copy of a given entry, this cannot be used to mutate
+ /// the one on disk
+ ///
+ /// # Return value
+ ///
+ /// On success: Entry
+ ///
+ /// On error:
+ /// - RetrieveCopyCallError(LockPoisoned()) if the internal write lock cannot be aquierd.
+ /// - RetrieveCopyCallError(IdLocked()) if the Entry is borrowed currently
+ /// - Errors StoreEntry::new() might return
+ ///
+ pub fn retrieve_copy<S: IntoStoreId>(&self, id: S) -> Result<Entry> {
+ let id = try!(id.into_storeid()).with_base(self.path().clone());
+ debug!("Retrieving copy of '{}'", id);
+ let entries = match self.entries.write() {
+ Err(_) => {
+ return Err(SE::new(SEK::LockPoisoned, None))
+ .map_err_into(SEK::RetrieveCopyCallError);
+ },
+ Ok(e) => e,
+ };
+
+ // if the entry is currently modified by the user, we cannot drop it
+ if entries.get(&id).map(|e| e.is_borrowed()).unwrap_or(false) {
+ return Err(SE::new(SEK::IdLocked, None)).map_err_into(SEK::RetrieveCopyCallError);
+ }
+
+ try!(StoreEntry::new(id, &self.backend)).get_entry()
+ }
+
+ /// Delete an entry
+ ///
+ /// # Return value
+ ///
+ /// On success: ()
+ ///
+ /// On error:
+ /// - DeleteCallError(LockPoisoned()) if the internal write lock cannot be aquierd.
+ /// - DeleteCallError(FileNotFound()) if the StoreId refers to a non-existing entry.
+ /// - DeleteCallError(FileError()) if the internals failed to remove the file.
+ ///
+ pub fn delete<S: IntoStoreId>(&self, id: S) -> Result<()> {
+ let id = try!(id.into_storeid()).with_base(self.path().clone());
+
+ debug!("Deleting id: '{}'", id);
+
+ {
+ let mut entries = match self.entries.write() {
+ Err(_) => return Err(SE::new(SEK::LockPoisoned, None))
+ .map_err_into(SEK::DeleteCallError),
+ Ok(e) => e,
+ };
+
+ // if the entry is currently modified by the user, we cannot drop it
+ match entries.get(&id) {
+ None => {
+ return Err(SEK::FileNotFound.into_error()).map_err_into(SEK::DeleteCallError)
+ },
+ Some(e) => if e.is_borrowed() {
+ return Err(SE::new(SEK::IdLocked, None)).map_err_into(SEK::DeleteCallError)
+ }
+ }
+
+ // remove the entry first, then the file
+ entries.remove(&id);
+ let pb = try!(id.clone().with_base(self.path().clone()).into_pathbuf());
+ if let Err(e) = self.backend.remove_file(&pb) {
+ return Err(SEK::FileError.into_error_with_cause(Box::new(e)))
+ .map_err_into(SEK::DeleteCallError);
+ }
+ }
+
+ debug!("Deleted");
+ Ok(())
+ }
+
+ /// Save a copy of the Entry in another place
+ pub fn save_to(&self, entry: &FileLockEntry, new_id: StoreId) -> Result<()> {
+ debug!("Saving '{}' to '{}'", entry.get_location(), new_id);
+ self.save_to_other_location(entry, new_id, false)
+ }
+
+ /// Save an Entry in another place
+ /// Removes the original entry
+ pub fn save_as(&self, entry: FileLockEntry, new_id: StoreId) -> Result<()> {
+ debug!("Saving '{}' as '{}'", entry.get_location(), new_id);
+ self.save_to_other_location(&entry, new_id, true)
+ }
+
+ fn save_to_other_location(&self, entry: &FileLockEntry, new_id: StoreId, remove_old: bool)
+ -> Result<()>
+ {
+ let new_id = new_id.with_base(self.path().clone());
+ let hsmap = try!(
+ self.entries
+ .write()
+ .map_err(|_| SEK::LockPoisoned.into_error())
+ .map_err_into(SEK::MoveCallError)
+ );
+
+ if hsmap.contains_key(&new_id) {
+ return Err(SEK::EntryAlreadyExists.into_error()).map_err_into(SEK::MoveCallError)
+ }
+
+ let old_id = entry.get_location().clone();
+
+ let old_id_as_path = try!(old_id.clone().with_base(self.path().clone()).into_pathbuf());
+ let new_id_as_path = try!(new_id.clone().with_base(self.path().clone()).into_pathbuf());
+ self.backend.copy(&old_id_as_path, &new_id_as_path)
+ .and_then(|_| {
+ if remove_old {
+ debug!("Removing old '{:?}'", old_id_as_path);
+ self.backend.remove_file(&old_id_as_path)
+ } else {
+ Ok(())
+ }
+ })
+ .map_err_into(SEK::FileError)
+ .map_err_into(SEK::MoveCallError)
+ }
+
+ /// Move an entry without loading
+ ///
+ /// This function moves an entry from one path to another.
+ ///
+ /// Generally, this function shouldn't be used by library authors, if they "just" want to move
+ /// something around. A library for moving entries while caring about meta-data and links.
+ ///
+ /// # Errors
+ ///
+ /// This function returns an error in certain cases:
+ ///
+ /// * If the about-to-be-moved entry is borrowed
+ /// * If the lock on the internal data structure cannot be aquired
+ /// * If the new path already exists
+ /// * If the about-to-be-moved entry does not exist
+ /// * If the FS-operation failed
+ ///
+ /// # Warnings
+ ///
+ /// This should be used with _great_ care, as moving an entry from `a` to `b` might result in
+ /// dangling links (see below).
+ ///
+ /// ## Moving linked entries
+ ///
+ /// If the entry which is moved is linked to another entry, these links get invalid (but we do
+ /// not detect this here). As links are always two-way-links, so `a` is not only linked to `b`,
+ /// but also the other way round, moving `b` to `c` results in the following scenario:
+ ///
+ /// * `a` links to `b`, which does not exist anymore.
+ /// * `c` links to `a`, which does exist.
+ ///
+ /// So the link is _partly dangling_, so to say.
+ ///
+ pub fn move_by_id(&self, old_id: StoreId, new_id: StoreId) -> Result<()> {
+ let new_id = new_id.with_base(self.path().clone());
+ let old_id = old_id.with_base(self.path().clone());
+
+ debug!("Moving '{}' to '{}'", old_id, new_id);
+
+ {
+ let mut hsmap = match self.entries.write() {
+ Err(_) => return Err(SE::new(SEK::LockPoisoned, None)),
+ Ok(m) => m,
+ };
+
+ if hsmap.contains_key(&new_id) {
+ return Err(SEK::EntryAlreadyExists.into_error());
+ }
+
+ // if we do not have an entry here, we fail in `FileAbstraction::rename()` below.
+ // if we have one, but it is borrowed, we really should not rename it, as this might
+ // lead to strange errors
+ if hsmap.get(&old_id).map(|e| e.is_borrowed()).unwrap_or(false) {
+ return Err(SEK::EntryAlreadyBorrowed.into_error());
+ }
+
+ let old_id_pb = try!(old_id.clone().with_base(self.path().clone()).into_pathbuf());
+ let new_id_pb = try!(new_id.clone().with_base(self.path().clone()).into_pathbuf());
+
+ match self.backend.rename(&old_id_pb, &new_id_pb) {
+ Err(e) => return Err(SEK::EntryRenameError.into_error_with_cause(Box::new(e))),
+ Ok(_) => {
+ debug!("Rename worked on filesystem");
+
+ // assert enforced through check hsmap.contains_key(&new_id) above.
+ // Should therefor never fail
+ assert!(hsmap
+ .remove(&old_id)
+ .and_then(|mut entry| {
+ entry.id = new_id.clone();
+ hsmap.insert(new_id.clone(), entry)
+ }).is_none())
+ }
+ }
+
+ }
+
+ debug!("Moved");
+ Ok(())
+ }
+
+ /// Get _all_ entries in the store (by id as iterator)
+ pub fn entries(&self) -> Result<StoreIdIterator> {
+ let iter = Walk::new(self.path().clone(), "")
+ .filter_map(|id| match id {
+ StoreObject::Id(sid) => Some(sid),
+ _ => None
+ });
+
+ Ok(StoreIdIterator::new(Box::new(iter)))
+
+ }
+
+ /// Gets the path where this store is on the disk
+ pub fn path(&self) -> &PathBuf {
+ &self.location
+ }
+
+}
+
+impl Debug for Store {
+
+ /// TODO: Make pretty.
+ fn fmt(&self, fmt: &mut Formatter) -> RResult<(), FMTError> {
+ try!(write!(fmt, " --- Store ---\n"));
+ try!(write!(fmt, "\n"));
+ try!(write!(fmt, " - location : {:?}\n", self.location));
+ try!(write!(fmt, " - configuration : {:?}\n", self.configuration));
+ try!(write!(fmt, "\n"));
+ try!(write!(fmt, "Entries:\n"));
+ try!(write!(fmt, "{:?}", self.entries));
+ try!(write!(fmt, "\n"));
+ Ok(())
+ }
+
+}
+
+impl Drop for Store {
+
+ ///
+ /// Unlock all files on drop
+ //
+ /// TODO: Unlock them
+ ///
+ fn drop(&mut self) {
+ debug!("Dropping store");
+ }
+
+}
+
+/// A struct that allows you to borrow an Entry
+pub struct FileLockEntry<'a> {
+ store: &'a Store,
+ entry: Entry,
+}
+
+impl<'a> FileLockEntry<'a, > {
+
+ /// Create a new FileLockEntry based on a `Entry` object.
+ ///
+ /// Only for internal use.
+ fn new(store: &'a Store, entry: Entry) -> FileLockEntry<'a> {
+ FileLockEntry {
+ store: store,
+ entry: entry,
+ }
+ }
+}
+
+impl<'a> Debug for FileLockEntry<'a> {
+ fn fmt(&self, fmt: &mut Formatter) -> RResult<(), FMTError> {
+ write!(fmt, "FileLockEntry(Store = {})", self.store.location.to_str()
+ .unwrap_or("Unknown Path"))
+ }
+}
+
+impl<'a> Deref for FileLockEntry<'a> {
+ type Target = Entry;
+
+ fn deref(&self) -> &Self::Target {
+ &self.entry
+ }
+}
+
+impl<'a> DerefMut for FileLockEntry<'a> {
+ fn deref_mut(&mut self) -> &mut Self::Target {
+ &mut self.entry
+ }
+}
+
+#[cfg(not(test))]
+impl<'a> Drop for FileLockEntry<'a> {
+
+ /// This will silently ignore errors, use `Store::update` if you want to catch the errors
+ ///
+ /// This might panic if the store was compiled with the early-panic feature (which is not
+ /// intended for production use, though).
+ fn drop(&mut self) {
+ use libimagerror::trace::trace_error_dbg;
+ match self.store._update(self, true) {
+ Err(e) => {
+ trace_error_dbg(&e);
+ if_cfg_panic!("ERROR WHILE DROPPING: {:?}", e);
+ },
+ Ok(_) => { },
+ }
+ }
+}
+
+#[cfg(test)]
+impl<'a> Drop for FileLockEntry<'a> {
+
+ /// This will not silently ignore errors but prints the result of the _update() call for testing
+ fn drop(&mut self) {
+ let _ = self.store._update(self, true).map_err(|e| trace_error(&e));
+ }
+
+}
+
+
+/// `EntryContent` type
+pub type EntryContent = String;
+
+/// An Entry of the store
+//
+/// Contains location, header and content part.
+#[derive(Debug, Clone)]
+pub struct Entry {
+ location: StoreId,
+ header: Value,
+ content: EntryContent,
+}
+
+impl Entry {
+
+ /// Create a new store entry with its location at `loc`.
+ ///
+ /// This creates the entry with the default header from `Entry::default_header()` and an empty
+ /// content.
+ pub fn new(loc: StoreId) -> Entry {
+ Entry {
+ location: loc,
+ header: Entry::default_header(),
+ content: EntryContent::new()
+ }
+ }
+
+ /// Get the default Header for an Entry.
+ ///
+ /// This function should be used to get a new Header, as the default header may change. Via
+ /// this function, compatibility is ensured.
+ pub fn default_header() -> Value { // BTreeMap<String, Value>
+ Value::default_header()
+ }
+
+ /// See `Entry::from_str()`, as this function is used internally. This is just a wrapper for
+ /// convenience.
+ pub fn from_reader<S: IntoStoreId>(loc: S, file: &mut Read) -> Result<Entry> {
+ let text = {
+ let mut s = String::new();
+ try!(file.read_to_string(&mut s));
+ s
+ };
+ Self::from_str(loc, &text[..])
+ }
+
+ /// Create a new Entry, with contents from the string passed.
+ ///
+ /// The passed string _must_ be a complete valid store entry, including header. So this is
+ /// probably not what end-users want to call.
+ ///
+ /// # Return value
+ ///
+ /// This errors if
+ ///
+ /// - String cannot be matched on regex to find header and content
+ /// - Header cannot be parsed into a TOML object
+ ///
+ pub fn from_str<S: IntoStoreId>(loc: S, s: &str) -> Result<Entry> {
+ use util::entry_buffer_to_header_content;
+
+ let (header, content) = try!(entry_buffer_to_header_content(s));
+
+ Ok(Entry {
+ location: try!(loc.into_storeid()),
+ header: header,
+ content: content,
+ })
+ }
+
+ /// Return the string representation of this entry
+ ///
+ /// This means not only the content of the entry, but the complete entry (from memory, not from
+ /// disk).
+ pub fn to_str(&self) -> String {
+ format!("---\n{header}---\n{content}",
+ header = ::toml::ser::to_string(&self.header).unwrap(),
+ content = self.content)
+ }
+
+ /// Get the location of the Entry
+ pub fn get_location(&self) -> &StoreId {
+ &self.location
+ }
+
+ /// Get the header of the Entry
+ pub fn get_header(&self) -> &Value {
+ &self.header
+ }
+
+ /// Get the header mutably of the Entry
+ pub fn get_header_mut(&mut self) -> &mut Value {
+ &mut self.header
+ }
+
+ /// Get the content of the Entry
+ pub fn get_content(&self) -> &EntryContent {
+ &self.content
+ }
+
+ /// Get the content mutably of the Entry
+ pub fn get_content_mut(&mut self) -> &mut EntryContent {
+ &mut self.content
+ }
+
+ /// Verify the entry.
+ ///
+ /// Currently, this only verifies the header. This might change in the future.
+ pub fn verify(&self) -> Result<()> {
+ self.header.verify()
+ }
+
+}
+
+impl PartialEq for Entry {
+
+ fn eq(&self, other: &Entry) -> bool {
+ self.location == other.location && // As the location only compares from the store root
+ self.header == other.header && // and the other Entry could be from another store (not
+ self.content == other.content // implemented by now, but we think ahead here)
+ }
+
+}
+
+mod glob_store_iter {
+ use std::fmt::{Debug, Formatter};
+ use std::fmt::Error as FmtError;
+ use std::path::PathBuf;
+ use glob::Paths;
+ use storeid::StoreId;
+ use storeid::StoreIdIterator;
+
+ use error::StoreErrorKind as SEK;
+ use error::MapErrInto;
+
+ use libimagerror::trace::trace_error;
+
+ /// An iterator which is constructed from a `glob()` and returns valid `StoreId` objects
+ ///
+ /// # Warning
+ ///
+ /// On error, this iterator currently traces the error and return None (thus ending the
+ /// iteration). This is a known issue and will be resolved at some point.
+ ///
+ /// TODO: See above.
+ ///
+ pub struct GlobStoreIdIterator {
+ store_path: PathBuf,
+ paths: Paths,
+ }
+
+ impl Debug for GlobStoreIdIterator {
+
+ fn fmt(&self, fmt: &mut Formatter) -> Result<(), FmtError> {
+ write!(fmt, "GlobStoreIdIterator")
+ }
+
+ }
+
+ impl Into<StoreIdIterator> for GlobStoreIdIterator {
+
+ fn into(self) -> StoreIdIterator {
+ StoreIdIterator::new(Box::new(self))
+ }
+
+ }
+
+ impl GlobStoreIdIterator {
+
+ pub fn new(paths: Paths, store_path: PathBuf) -> GlobStoreIdIterator {
+ debug!("Create a GlobStoreIdIterator(store_path = {:?}, /* ... */)", store_path);
+
+ GlobStoreIdIterator {
+ store_path: store_path,
+ paths: paths,
+ }
+ }
+
+ }
+
+ impl Iterator for GlobStoreIdIterator {
+ type Item = StoreId;
+
+ fn next(&mut self) -> Option<StoreId> {
+ while let Some(o) = self.paths.next() {
+ debug!("GlobStoreIdIterator::next() => {:?}", o);
+ match o.map_err_into(SEK::StoreIdHandlingError) {
+ Ok(path) => {
+ if path.exists() && path.is_file() {
+ return match StoreId::from_full_path(&self.store_path, path) {
+ Ok(id) => Some(id),
+ Err(e) => {
+ trace_error(&e);
+ None
+ }
+ }
+ /* } else { */
+ /* continue */
+ }
+ }
+ Err(e) => {
+ debug!("GlobStoreIdIterator error: {:?}", e);
+ trace_error(&e);
+ return None
+ }
+ }
+ }
+
+ None
+ }
+
+ }
+
+}
+
+
+#[cfg(test)]
+mod test {
+ extern crate env_logger;
+
+ use std::collections::BTreeMap;
+ use storeid::StoreId;
+
+ 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()));
+
+ assert!(has_main_section(&map));
+ }
+
+ #[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));
+
+ assert!(!has_main_section(&map));
+ }
+
+ #[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));
+
+ assert!(!has_main_section(&map));
+ }
+
+ #[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()));
+
+ assert!(!has_imag_version_in_main_section(&map));
+ }
+
+ #[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()));
+ map.insert("imag".into(), Value::Table(sub));
+
+ assert!(has_imag_version_in_main_section(&map));
+ }
+
+ #[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));
+ map.insert("imag".into(), Value::Table(sub));
+
+ assert!(!has_imag_version_in_main_section(&map));
+ }
+
+ #[test]
+ fn test_verification_good() {
+ use toml_ext::verify_header_consistency;
+
+ let mut header = BTreeMap::new();
+ let sub = {
+ let mut sub = BTreeMap::new();
+ sub.insert("version".into(), Value::String(String::from("0.0.0")));
+
+ Value::Table(sub)
+ };
+
+ header.insert("imag".into(), sub);
+
+ assert!(verify_header_consistency(header).is_ok());
+ }
+
+ #[test]
+ fn test_verification_invalid_versionstring() {
+ use toml_ext::verify_header_consistency;
+
+ let mut header = BTreeMap::new();
+ let sub = {
+ let mut sub = BTreeMap::new();
+ sub.insert("version".into(), Value::String(String::from("000")));
+
+ Value::Table(sub)
+ };
+
+ header.insert("imag".into(), sub);
+
+ assert!(!verify_header_consistency(header).is_ok());
+ }
+
+
+ #[test]
+ fn test_verification_current_version() {
+ use toml_ext::verify_header_consistency;
+
+ let mut header = BTreeMap::new();
+ let sub = {
+ let mut sub = BTreeMap::new();
+ sub.insert("version".into(), Value::String(String::from(version!())));
+
+ Value::Table(sub)
+ };
+
+ header.insert("imag".into(), sub);
+
+ assert!(verify_header_consistency(header).is_ok());
+ }
+
+ static TEST_ENTRY : &'static str = "---
+[imag]
+version = \"0.0.3\"
+---
+Hai";
+
+ #[test]
+ fn test_entry_from_str() {
+ use super::Entry;
+ use std::path::PathBuf;
+ println!("{}", TEST_ENTRY);
+ let entry = Entry::from_str(StoreId::new_baseless(PathBuf::from("test/foo~1.3")).unwrap(),
+ TEST_ENTRY).unwrap();
+
+ assert_eq!(entry.content, "Hai");
+ }
+
+ #[test]
+ fn test_entry_to_str() {
+ use super::Entry;
+ use std::path::PathBuf;
+ println!("{}", TEST_ENTRY);
+ let entry = Entry::from_str(StoreId::new_baseless(PathBuf::from("test/foo~1.3")).unwrap(),
+ TEST_ENTRY).unwrap();
+ let string = entry.to_str();
+
+ assert_eq!(TEST_ENTRY, string);
+ }
+
+}
+
+#[cfg(test)]
+mod store_tests {
+ use std::path::PathBuf;
+
+ use super::Store;
+ use file_abstraction::InMemoryFileAbstraction;
+
+ pub fn get_store() -> Store {
+ let backend = Box::new(InMemoryFileAbstraction::new());
+ Store::new_with_backend(PathBuf::from("/"), None, backend).unwrap()
+ }
+
+ #[test]
+ fn test_store_instantiation() {
+ let store = get_store();
+
+ assert_eq!(store.location, PathBuf::from("/"));
+ assert!(store.entries.read().unwrap().is_empty());
+ }
+
+ #[test]
+ fn test_store_create() {
+ let store = get_store();
+
+ for n in 1..100 {
+ let s = format!("test-{}", n);
+ let entry = store.create(PathBuf::from(s.clone())).unwrap();
+ assert!(entry.verify().is_ok());
+ let loc = entry.get_location().clone().into_pathbuf().unwrap();
+ assert!(loc.starts_with("/"));
+ assert!(loc.ends_with(s));
+ }
+ }
+
+ #[test]
+ fn test_store_create_with_io_backend() {
+ use std::io::Cursor;
+ use std::rc::Rc;
+ use std::cell::RefCell;
+ use serde_json::Value;
+
+ //let sink = vec![];
+ //let output : Cursor<&mut [u8]> = Cursor::new(&mut sink);
+ //let output = Rc::new(RefCell::new(output));
+ let output = Rc::new(RefCell::new(vec![]));
+
+ {
+ let store = {
+ use file_abstraction::stdio::StdIoFileAbstraction;
+ use file_abstraction::stdio::mapper::json::JsonMapper;
+
+ // Lets have an empty store as input
+ let mut input = Cursor::new(r#"
+ { "version": "0.4.0",
+ "store": { }
+ }
+ "#);
+
+ let mapper = JsonMapper::new();
+ let backend = StdIoFileAbstraction::new(&mut input, output.clone(), mapper).unwrap();
+ let backend = Box::new(backend);
+
+ Store::new_with_backend(PathBuf::from("/"), None, backend).unwrap()
+ };
+
+ for n in 1..100 {
+ let s = format!("test-{}", n);
+ let entry = store.create(PathBuf::from(s.clone())).unwrap();
+ assert!(entry.verify().is_ok());
+ let loc = entry.get_location().clone().into_pathbuf().unwrap();
+ assert!(loc.starts_with("/"));
+ assert!(loc.ends_with(s));
+ }
+ }
+
+ let vec = Rc::try_unwrap(output).unwrap().into_inner();
+
+ let errstr = format!("Not UTF8: '{:?}'", vec);
+ let string = String::from_utf8(vec);
+ assert!(string.is_ok(), errstr);
+ let string = string.unwrap();
+
+ assert!(!string.is_empty(), format!("Expected not to be empty: '{}'", string));
+
+ let json : ::serde_json::Value = ::serde_json::from_str(&string).unwrap();
+
+ match json {
+ Value::Object(ref map) => {
+ assert!(map.get("version").is_some(), format!("No 'version' in JSON"));
+ match map.get("version").unwrap() {
+ &Value::String(ref s) => assert_eq!("0.4.0", s),
+ _ => panic!("Wrong type in JSON at 'version'"),
+ }
+
+ assert!(map.get("store").is_some(), format!("No 'store' in JSON"));
+ match map.get("store").unwrap() {
+ &Value::Object(ref objs) => {
+ for n in 1..100 {
+ let s = format!("/test-{}", n);
+ assert!(objs.get(&s).is_some(), format!("No entry: '{}'", s));
+ match objs.get(&s).unwrap() {
+ &Value::Object(ref entry) => {
+ match entry.get("header").unwrap() {
+ &Value::Object(_) => assert!(true),
+ _ => panic!("Wrong type in JSON at 'store.'{}'.header'", s),
+ }
+
+ match entry.get("content").unwrap() {
+ &Value::String(_) => assert!(true),
+ _ => panic!("Wrong type in JSON at 'store.'{}'.content'", s),
+ }
+ },
+ _ => panic!("Wrong type in JSON at 'store.'{}''", s),
+ }
+ }
+ },
+ _ => panic!("Wrong type in JSON at 'store'"),
+ }
+ },
+ _ => panic!("Wrong type in JSON at top level"),
+ }
+
+ }
+
+ #[test]
+ fn test_store_get_create_get_delete_get() {
+ let store = get_store();
+
+ for n in 1..100 {
+ let res = store.get(PathBuf::from(format!("test-{}", n)));
+ assert!(match res { Ok(None) => true, _ => false, })
+ }
+
+ for n in 1..100 {
+ let s = format!("test-{}", n);
+ let entry = store.create(PathBuf::from(s.clone())).unwrap();
+
+ assert!(entry.verify().is_ok());
+
+ let loc = entry.get_location().clone().into_pathbuf().unwrap();
+
+ assert!(loc.starts_with("/"));
+ assert!(loc.ends_with(s));
+ }
+
+ for n in 1..100 {
+ let res = store.get(PathBuf::from(format!("test-{}", n)));
+ assert!(match res { Ok(Some(_)) => true, _ => false, })
+ }
+
+ for n in 1..100 {
+ assert!(store.delete(PathBuf::from(format!("test-{}", n))).is_ok())
+ }
+
+ for n in 1..100 {
+ let res = store.get(PathBuf::from(format!("test-{}", n)));
+ assert!(match res { Ok(None) => true, _ => false, })
+ }
+ }
+
+ #[test]
+ fn test_store_create_twice() {
+ use error::StoreErrorKind as SEK;
+
+ let store = get_store();
+
+ for n in 1..100 {
+ let s = format!("test-{}", n % 50);
+ store.create(PathBuf::from(s.clone()))
+ .map_err(|e| assert!(is_match!(e.err_type(), SEK::CreateCallError) && n >= 50))
+ .ok()
+ .map(|entry| {
+ assert!(entry.verify().is_ok());
+ let loc = entry.get_location().clone().into_pathbuf().unwrap();
+ assert!(loc.starts_with("/"));
+ assert!(loc.ends_with(s));
+ });
+ }
+ }
+
+ #[test]
+ fn test_store_create_in_hm() {
+ use storeid::StoreId;
+
+ let store = get_store();
+
+ for n in 1..100 {
+ let pb = StoreId::new_baseless(PathBuf::from(format!("test-{}", n))).unwrap();
+
+ assert!(store.entries.read().unwrap().get(&pb).is_none());
+ assert!(store.create(pb.clone()).is_ok());
+
+ let pb = pb.with_base(store.path().clone());
+ assert!(store.entries.read().unwrap().get(&pb).is_some());
+ }
+ }
+
+ #[test]
+ fn test_store_retrieve_in_hm() {
+ use storeid::StoreId;
+
+ let store = get_store();
+
+ for n in 1..100 {
+ let pb = StoreId::new_baseless(PathBuf::from(format!("test-{}", n))).unwrap();
+
+ assert!(store.entries.read().unwrap().get(&pb).is_none());
+ assert!(store.retrieve(pb.clone()).is_ok());
+
+ let pb = pb.with_base(store.path().clone());
+ assert!(store.entries.read().unwrap().get(&pb).is_some());
+ }
+ }
+
+ #[test]
+ fn test_get_none() {
+ let store = get_store();
+
+ for n in 1..100 {
+ match store.get(PathBuf::from(format!("test-{}", n))) {
+ Ok(None) => assert!(true),
+ _ => assert!(false),
+ }
+ }
+ }
+
+ #[test]
+ fn test_delete_none() {
+ let store = get_store();
+
+ for n in 1..100 {
+ match store.delete(PathBuf::from(format!("test-{}", n))) {
+ Err(_) => assert!(true),
+ _ => assert!(false),
+ }
+ }
+ }
+
+ // Disabled because we cannot test this by now, as we rely on glob() in
+ // Store::retieve_for_module(), which accesses the filesystem and tests run in-memory, so there
+ // are no files on the filesystem in this test after Store::create().
+ //
+ // #[test]
+ // fn test_retrieve_for_module() {
+ // let pathes = vec![
+ // "foo/1", "foo/2", "foo/3", "foo/4", "foo/5",
+ // "bar/1", "bar/2", "bar/3", "bar/4", "bar/5",
+ // "bla/1", "bla/2", "bla/3", "bla/4", "bla/5",
+ // "boo/1", "boo/2", "boo/3", "boo/4", "boo/5",
+ // "glu/1", "glu/2", "glu/3", "glu/4", "glu/5",
+ // ];
+
+ // fn test(store: &Store, modulename: &str) {
+ // use std::path::Component;
+ // use storeid::StoreId;
+
+ // let retrieved = store.retrieve_for_module(modulename);
+ // assert!(retrieved.is_ok());
+ // let v : Vec<StoreId> = retrieved.unwrap().collect();
+ // println!("v = {:?}", v);
+ // assert!(v.len() == 5);
+
+ // let retrieved = store.retrieve_for_module(modulename);
+ // assert!(retrieved.is_ok());
+
+ // assert!(retrieved.unwrap().all(|e| {
+ // let first = e.components().next();
+ // assert!(first.is_some());
+ // match first.unwrap() {
+ // Component::Normal(s) => s == modulename,
+ // _ => false,
+ // }
+ // }))
+ // }
+
+ // let store = get_store();
+ // for path in pathes {
+ // assert!(store.create(PathBuf::from(path)).is_ok());
+ // }
+
+ // test(&store, "foo");
+ // test(&store, "bar");
+ // test(&store, "bla");
+ // test(&store, "boo");
+ // test(&store, "glu");
+ // }
+
+ #[test]
+ fn test_store_move_moves_in_hm() {
+ use storeid::StoreId;
+
+ let store = get_store();
+
+ for n in 1..100 {
+ if n % 2 == 0 { // every second
+ let id = StoreId::new_baseless(PathBuf::from(format!("t-{}", n))).unwrap();
+ let id_mv = StoreId::new_baseless(PathBuf::from(format!("t-{}", n - 1))).unwrap();
+
+ {
+ assert!(store.entries.read().unwrap().get(&id).is_none());
+ }
+
+ {
+ assert!(store.create(id.clone()).is_ok());
+ }
+
+ {
+ let id_with_base = id.clone().with_base(store.path().clone());
+ assert!(store.entries.read().unwrap().get(&id_with_base).is_some());
+ }
+
+ let r = store.move_by_id(id.clone(), id_mv.clone());
+ assert!(r.map_err(|e| println!("ERROR: {:?}", e)).is_ok());
+
+ {
+ let id_mv_with_base = id_mv.clone().with_base(store.path().clone());
+ assert!(store.entries.read().unwrap().get(&id_mv_with_base).is_some());
+ }
+
+ assert!(match store.get(id.clone()) { Ok(None) => true, _ => false },
+ "Moved id ({:?}) is still there", id);
+ assert!(match store.get(id_mv.clone()) { Ok(Some(_)) => true, _ => false },
+ "New id ({:?}) is not in store...", id_mv);
+ }
+ }
+ }
+
+ #[test]
+ fn test_swap_backend_during_runtime() {
+ use file_abstraction::InMemoryFileAbstraction;
+
+ let mut store = {
+ let backend = InMemoryFileAbstraction::new();
+ let backend = Box::new(backend);
+
+ Store::new_with_backend(PathBuf::from("/"), None, backend).unwrap()
+ };
+
+ for n in 1..100 {
+ let s = format!("test-{}", n);
+ let entry = store.create(PathBuf::from(s.clone())).unwrap();
+ assert!(entry.verify().is_ok());
+ let loc = entry.get_location().clone().into_pathbuf().unwrap();
+ assert!(loc.starts_with("/"));
+ assert!(loc.ends_with(s));
+ }
+
+ {
+ let other_backend = InMemoryFileAbstraction::new();
+ let other_backend = Box::new(other_backend);
+
+ assert!(store.reset_backend(other_backend).is_ok())
+ }
+
+ for n in 1..100 {
+ let s = format!("test-{}", n);
+ let entry = store.get(PathBuf::from(s.clone()));
+
+ assert!(entry.is_ok());
+ let entry = entry.unwrap();
+
+ assert!(entry.is_some());
+ let entry = entry.unwrap();
+
+ assert!(entry.verify().is_ok());
+
+ let loc = entry.get_location().clone().into_pathbuf().unwrap();
+ assert!(loc.starts_with("/"));
+ assert!(loc.ends_with(s));
+ }
+ }
+
+ #[test]
+ fn test_swap_backend_during_runtime_with_io() {
+ use std::io::Cursor;
+ use std::rc::Rc;
+ use std::cell::RefCell;
+ use serde_json::Value;
+ use file_abstraction::stdio::out::StdoutFileAbstraction;
+ use file_abstraction::stdio::mapper::json::JsonMapper;
+
+ // The output we later read from and check whether there is an entry
+ let output = Rc::new(RefCell::new(vec![]));
+
+ {
+ let mut store = {
+ use file_abstraction::stdio::StdIoFileAbstraction;
+ use file_abstraction::stdio::mapper::json::JsonMapper;
+
+ // Lets have an empty store as input
+ let mut input = Cursor::new(r#"
+ { "version": "0.4.0",
+ "store": {
+ "example": {
+ "header": {
+ "imag": {
+ "version": "0.4.0"
+ }
+ },
+ "content": "foobar"
+ }
+ }
+ }
+ "#);
+
+ let output = Rc::new(RefCell::new(::std::io::sink()));
+ let mapper = JsonMapper::new();
+ let backend = StdIoFileAbstraction::new(&mut input, output, mapper).unwrap();
+ let backend = Box::new(backend);
+
+ Store::new_with_backend(PathBuf::from("/"), None, backend).unwrap()
+ };
+
+ // Replacing the backend
+
+ {
+ let mapper = JsonMapper::new();
+ let backend = StdoutFileAbstraction::new(output.clone(), mapper);
+ let _ = assert!(backend.is_ok(), format!("Should be ok: {:?}", backend));
+ let backend = backend.unwrap();
+ let backend = Box::new(backend);
+
+ assert!(store.reset_backend(backend).is_ok());
+ }
+ }
+
+ let vec = Rc::try_unwrap(output).unwrap().into_inner();
+ let errstr = format!("Not UTF8: '{:?}'", vec);
+ let string = String::from_utf8(vec);
+ assert!(string.is_ok(), errstr);
+ let string = string.unwrap();
+
+ assert!(!string.is_empty(), format!("Expected not to be empty: '{}'", string));
+
+ let json : ::serde_json::Value = ::serde_json::from_str(&string).unwrap();
+
+ match json {
+ Value::Object(ref map) => {
+ assert!(map.get("version").is_some(), format!("No 'version' in JSON"));
+ match map.get("version").unwrap() {
+ &Value::String(ref s) => assert_eq!("0.4.0", s),
+ _ => panic!("Wrong type in JSON at 'version'"),
+ }
+
+ assert!(map.get("store").is_some(), format!("No 'store' in JSON"));
+ match map.get("store").unwrap() {
+ &Value::Object(ref objs) => {
+ let s = String::from("example");
+ assert!(objs.get(&s).is_some(), format!("No entry: '{}' in \n{:?}", s, objs));
+ match objs.get(&s).unwrap() {
+ &Value::Object(ref entry) => {
+ match entry.get("header").unwrap() {
+ &Value::Object(_) => assert!(true),
+ _ => panic!("Wrong type in JSON at 'store.'{}'.header'", s),
+ }
+
+ match entry.get("content").unwrap() {
+ &Value::String(_) => assert!(true),
+ _ => panic!("Wrong type in JSON at 'store.'{}'.content'", s),
+ }
+ },
+ _ => panic!("Wrong type in JSON at 'store.'{}''", s),
+ }
+ },
+ _ => panic!("Wrong type in JSON at 'store'"),
+ }
+ },
+ _ => panic!("Wrong type in JSON at top level"),
+ }
+
+ }
+}
+
diff --git a/lib/core/libimagstore/src/storeid.rs b/lib/core/libimagstore/src/storeid.rs
new file mode 100644
index 0000000..211efbe
--- /dev/null
+++ b/lib/core/libimagstore/src/storeid.rs
@@ -0,0 +1,391 @@
+//
+// 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::ops::Deref;
+use std::path::Path;
+use std::path::PathBuf;
+
+use std::fmt::{Display, Debug, Formatter};
+use std::fmt::Error as FmtError;
+use std::result::Result as RResult;
+use std::path::Components;
+
+use libimagerror::into::IntoError;
+
+use error::StoreErrorKind as SEK;
+use error::MapErrInto;
+use store::Result;
+
+/// The Index into the Store
+#[derive(Debug, Clone, Hash, Eq, PartialOrd, Ord)]
+pub struct StoreId {
+ base: Option<PathBuf>,
+ id: PathBuf,
+}
+
+impl PartialEq for StoreId {
+ fn eq(&self, other: &StoreId) -> bool {
+ self.id == other.id
+ }
+}
+
+impl StoreId {
+
+ pub fn new(base: Option<PathBuf>, id: PathBuf) -> Result<StoreId> {
+ StoreId::new_baseless(id).map(|mut sid| { sid.base = base; sid })
+ }
+
+ /// Try to create a StoreId object from a filesystem-absolute path.
+ ///
+ /// Automatically creates a StoreId object which has a `base` set to `store_part` if stripping
+ /// the `store_part` from the `full_path` succeeded.
+ ///
+ /// Returns a `StoreErrorKind::StoreIdBuildFromFullPathError` if stripping failes.
+ pub fn from_full_path<D>(store_part: &PathBuf, full_path: D) -> Result<StoreId>
+ where D: Deref<Target = Path>
+ {
+ let p = try!(
+ full_path.strip_prefix(store_part).map_err_into(SEK::StoreIdBuildFromFullPathError)
+ );
+ StoreId::new(Some(store_part.clone()), PathBuf::from(p))
+ }
+
+ pub fn new_baseless(id: PathBuf) -> Result<StoreId> {
+ debug!("Trying to get a new baseless id from: {:?}", id);
+ if id.is_absolute() {
+ Err(SEK::StoreIdLocalPartAbsoluteError.into_error())
+ } else {
+ Ok(StoreId {
+ base: None,
+ id: id
+ })
+ }
+ }
+
+ pub fn without_base(mut self) -> StoreId {
+ self.base = None;
+ self
+ }
+
+ pub fn with_base(mut self, base: PathBuf) -> Self {
+ self.base = Some(base);
+ self
+ }
+
+ /// Transform the StoreId object into a PathBuf, error if the base of the StoreId is not
+ /// specified.
+ pub fn into_pathbuf(self) -> Result<PathBuf> {
+ let mut base = try!(self.base.ok_or(SEK::StoreIdHasNoBaseError.into_error()));
+ base.push(self.id);
+ Ok(base)
+ }
+
+ pub fn exists(&self) -> Result<bool> {
+ self.clone().into_pathbuf().map(|pb| pb.exists())
+ }
+
+ pub fn to_str(&self) -> Result<String> {
+ self.base
+ .as_ref()
+ .cloned()
+ .map(|mut base| { base.push(self.id.clone()); base })
+ .unwrap_or_else(|| self.id.clone())
+ .to_str()
+ .map(String::from)
+ .ok_or(SEK::StoreIdHandlingError.into_error())
+ }
+
+ /// Returns the components of the `id` part of the StoreId object.
+ ///
+ /// Can be used to check whether a StoreId points to an entry in a specific collection of
+ /// StoreIds.
+ pub fn components(&self) -> Components {
+ self.id.components()
+ }
+
+ /// Get the _local_ part of a StoreId object, as in "the part from the store root to the entry".
+ pub fn local(&self) -> &PathBuf {
+ &self.id
+ }
+
+ /// Check whether a StoreId points to an entry in a specific collection.
+ ///
+ /// A "collection" here is simply a directory. So `foo/bar/baz` is an entry which is in
+ /// collection ["foo", "bar", "baz"], but also in ["foo", "bar"] and ["foo"].
+ ///
+ /// # Warning
+ ///
+ /// The collection specification _has_ to start with the module name. Otherwise this function
+ /// may return false negatives.
+ ///
+ pub fn is_in_collection(&self, colls: &[&str]) -> bool {
+ use std::path::Component;
+
+ self.id
+ .components()
+ .zip(colls)
+ .map(|(component, pred_coll)| match component {
+ Component::Normal(ref s) => s.to_str().map(|ref s| s == pred_coll).unwrap_or(false),
+ _ => false
+ })
+ .all(|x| x)
+ }
+
+ pub fn local_push<P: AsRef<Path>>(&mut self, path: P) {
+ self.id.push(path)
+ }
+
+}
+
+impl Display for StoreId {
+
+ fn fmt(&self, fmt: &mut Formatter) -> RResult<(), FmtError> {
+ match self.id.to_str() {
+ Some(s) => write!(fmt, "{}", s),
+ None => write!(fmt, "{}", self.id.to_string_lossy()),
+ }
+ }
+
+}
+
+/// This Trait allows you to convert various representations to a single one
+/// suitable for usage in the Store
+pub trait IntoStoreId {
+ fn into_storeid(self) -> Result<StoreId>;
+}
+
+impl IntoStoreId for StoreId {
+ fn into_storeid(self) -> Result<StoreId> {
+ Ok(self)
+ }
+}
+
+impl IntoStoreId for PathBuf {
+ fn into_storeid(self) -> Result<StoreId> {
+ StoreId::new_baseless(self)
+ }
+}
+
+#[macro_export]
+macro_rules! module_entry_path_mod {
+ ($name:expr) => (
+ #[deny(missing_docs,
+ missing_copy_implementations,
+ trivial_casts, trivial_numeric_casts,
+ unsafe_code,
+ unstable_features,
+ unused_import_braces, unused_qualifications,
+ unused_imports)]
+ /// A helper module to create valid module entry paths
+ pub mod module_path {
+ use std::convert::AsRef;
+ use std::path::Path;
+ use std::path::PathBuf;
+
+ use $crate::storeid::StoreId;
+ use $crate::store::Result;
+
+ /// A Struct giving you the ability to choose store entries assigned
+ /// to it.
+ ///
+ /// It is created through a call to `new`.
+ pub struct ModuleEntryPath(PathBuf);
+
+ impl ModuleEntryPath {
+ /// Path has to be a valid UTF-8 string or this will panic!
+ pub fn new<P: AsRef<Path>>(pa: P) -> ModuleEntryPath {
+ let mut path = PathBuf::new();
+ path.push(format!("{}", $name));
+ path.push(pa.as_ref().clone());
+ let name = pa.as_ref().file_name().unwrap()
+ .to_str().unwrap();
+ path.set_file_name(name);
+ ModuleEntryPath(path)
+ }
+ }
+
+ impl $crate::storeid::IntoStoreId for ModuleEntryPath {
+ fn into_storeid(self) -> Result<$crate::storeid::StoreId> {
+ StoreId::new(None, self.0)
+ }
+ }
+ }
+ )
+}
+
+pub struct StoreIdIterator {
+ iter: Box<Iterator<Item = StoreId>>,
+}
+
+impl Debug for StoreIdIterator {
+
+ fn fmt(&self, fmt: &mut Formatter) -> RResult<(), FmtError> {
+ write!(fmt, "StoreIdIterator")
+ }
+
+}
+
+impl StoreIdIterator {
+
+ pub fn new(iter: Box<Iterator<Item = StoreId>>) -> StoreIdIterator {
+ StoreIdIterator {
+ iter: iter,
+ }
+ }
+
+}
+
+impl Iterator for StoreIdIterator {
+ type Item = StoreId;
+
+ fn next(&mut self) -> Option<StoreId> {
+ self.iter.next()
+ }
+
+}
+
+#[cfg(test)]
+mod test {
+ use std::path::PathBuf;
+
+ use storeid::StoreId;
+ use storeid::IntoStoreId;
+ use error::StoreErrorKind as SEK;
+
+ module_entry_path_mod!("test");
+
+ #[test]
+ fn test_correct_path() {
+ let p = module_path::ModuleEntryPath::new("test");
+
+ assert_eq!(p.into_storeid().unwrap().to_str().unwrap(), "test/test");
+ }
+
+ #[test]
+ fn test_baseless_path() {
+ let id = StoreId::new_baseless(PathBuf::from("test"));
+ assert!(id.is_ok());
+ assert_eq!(id.unwrap(), StoreId {
+ base: None,
+ id: PathBuf::from("test")
+ });
+ }
+
+ #[test]
+ fn test_base_path() {
+ let id = StoreId::from_full_path(&PathBuf::from("/tmp/"), PathBuf::from("/tmp/test"));
+ assert!(id.is_ok());
+ assert_eq!(id.unwrap(), StoreId {
+ base: Some(PathBuf::from("/tmp/")),
+ id: PathBuf::from("test")
+ });
+ }
+
+ #[test]
+ fn test_adding_base_to_baseless_path() {
+ let id = StoreId::new_baseless(PathBuf::from("test"));
+
+ assert!(id.is_ok());
+ let id = id.unwrap();
+
+ assert_eq!(id, StoreId { base: None, id: PathBuf::from("test") });
+
+ let id = id.with_base(PathBuf::from("/tmp/"));
+ assert_eq!(id, StoreId {
+ base: Some(PathBuf::from("/tmp/")),
+ id: PathBuf::from("test")
+ });
+ }
+
+ #[test]
+ fn test_removing_base_from_base_path() {
+ let id = StoreId::from_full_path(&PathBuf::from("/tmp/"), PathBuf::from("/tmp/test"));
+
+ assert!(id.is_ok());
+ let id = id.unwrap();
+
+ assert_eq!(id, StoreId {
+ base: Some(PathBuf::from("/tmp/")),
+ id: PathBuf::from("test")
+ });
+
+ let id = id.without_base();
+ assert_eq!(id, StoreId {
+ base: None,
+ id: PathBuf::from("test")
+ });
+ }
+
+ #[test]
+ fn test_baseless_into_pathbuf_is_err() {
+ let id = StoreId::new_baseless(PathBuf::from("test"));
+ assert!(id.is_ok());
+ assert!(id.unwrap().into_pathbuf().is_err());
+ }
+
+ #[test]
+ fn test_baseless_into_pathbuf_is_storeidhasnobaseerror() {
+ let id = StoreId::new_baseless(PathBuf::from("test"));
+ assert!(id.is_ok());
+
+ let pb = id.unwrap().into_pathbuf();
+ assert!(pb.is_err());
+
+ assert_eq!(pb.unwrap_err().err_type(), SEK::StoreIdHasNoBaseError);
+ }
+
+ #[test]
+ fn test_basefull_into_pathbuf_is_ok() {
+ let id = StoreId::from_full_path(&PathBuf::from("/tmp/"), PathBuf::from("/tmp/test"));
+ assert!(id.is_ok());
+ assert!(id.unwrap().into_pathbuf().is_ok());
+ }
+
+ #[test]
+ fn test_basefull_into_pathbuf_is_correct() {
+ let id = StoreId::from_full_path(&PathBuf::from("/tmp/"), PathBuf::from("/tmp/test"));
+ assert!(id.is_ok());
+
+ let pb = id.unwrap().into_pathbuf();
+ assert!(pb.is_ok());
+
+ assert_eq!(pb.unwrap(), PathBuf::from("/tmp/test"));
+ }
+
+ #[test]
+ fn storeid_in_collection() {
+ let p = module_path::ModuleEntryPath::new("1/2/3/4/5/6/7/8/9/0").into_storeid().unwrap();
+
+ assert!(p.is_in_collection(&["test", "1"]));
+ assert!(p.is_in_collection(&["test", "1", "2"]));
+ assert!(p.is_in_collection(&["test", "1", "2", "3"]));
+ assert!(p.is_in_collection(&["test", "1", "2", "3", "4"]));
+ assert!(p.is_in_collection(&["test", "1", "2", "3", "4", "5"]));
+ assert!(p.is_in_collection(&["test", "1", "2", "3", "4", "5", "6"]));
+ assert!(p.is_in_collection(&["test", "1", "2", "3", "4", "5", "6", "7"]));
+ assert!(p.is_in_collection(&["test", "1", "2", "3", "4", "5", "6", "7", "8"]));
+ assert!(p.is_in_collection(&["test", "1", "2", "3", "4", "5", "6", "7", "8", "9"]));
+ assert!(p.is_in_collection(&["test", "1", "2", "3", "4", "5", "6", "7", "8", "9", "0"]));
+
+ assert!(!p.is_in_collection(&["test", "0", "2", "3", "4", "5", "6", "7", "8", "9", "0"]));
+ assert!(!p.is_in_collection(&["test", "1", "2", "3", "4", "5", "6", "8"]));
+ assert!(!p.is_in_collection(&["test", "1", "2", "3", "leet", "5", "6", "7"]));
+ }
+
+}
diff --git a/lib/core/libimagstore/src/toml_ext.rs b/lib/core/libimagstore/src/toml_ext.rs
new file mode 100644
index 0000000..9c0ae54
--- /dev/null
+++ b/lib/core/libimagstore/src/toml_ext.rs
@@ -0,0 +1,894 @@
+//
+// 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!(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 h = create_header();
+
+ 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 mut h = create_header();
+
+ 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 mut h = create_header();
+
+ 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 mut h = create_header();
+
+ 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 mut h = create_header();
+
+ 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 mut h = create_header();
+
+ 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 });
+
+ }
+
+}
+
diff --git a/lib/core/libimagstore/src/util.rs b/lib/core/libimagstore/src/util.rs
new file mode 100644
index 0000000..51462ab
--- /dev/null
+++ b/lib/core/libimagstore/src/util.rs
@@ -0,0 +1,70 @@
+//
+// imag - the personal information management suite for the commandline
+// Copyright (C) 2015, 2016 Matthias Beyer <mail@beyermatthias.de> and contributors
+//
+// This library is free software; you can redistribute it and/or
+// modify it under the terms of the GNU Lesser General Public
+// License as published by the Free Software Foundation; version
+// 2.1 of the License.
+//
+// This library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+// Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public
+// License along with this library; if not, write to the Free Software
+// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+//
+
+use regex::Regex;
+use toml::Value;
+
+use libimagerror::into::IntoError;
+
+use store::Result;
+use error::StoreErrorKind as SEK;
+use toml_ext::Header;
+
+#[cfg(feature = "early-panic")]
+#[macro_export]
+macro_rules! if_cfg_panic {
+ () => { panic!() };
+ ($msg:expr) => { panic!($msg) };
+ ($fmt:expr, $($arg:tt)+) => { panic!($fmt, $($($arg),*)) };
+}
+
+#[cfg(not(feature = "early-panic"))]
+#[macro_export]
+macro_rules! if_cfg_panic {
+ () => { };
+ ($msg:expr) => { };
+ ($fmt:expr, $($arg:tt)+) => { };
+}
+
+pub fn entry_buffer_to_header_content(buf: &str) -> Result<(Value, String)> {
+ debug!("Building entry from string");
+ lazy_static! {
+ static ref RE: Regex = Regex::new(r"(?smx)
+ ^---$
+ (?P<header>.*) # Header
+ ^---$\n
+ (?P<content>.*) # Content
+ ").unwrap();
+ }
+
+ let matches = match RE.captures(buf) {
+ None => return Err(SEK::MalformedEntry.into_error()),
+ Some(s) => s,
+ };
+
+ let header = match matches.name("header") {
+ None => return Err(SEK::MalformedEntry.into_error()),
+ Some(s) => s
+ };
+
+ let content = matches.name("content").map(|r| r.as_str()).unwrap_or("");
+
+ Ok((try!(Value::parse(header.as_str())), String::from(content)))
+}
+
diff --git a/lib/domain/libimagbookmark/Cargo.toml b/lib/domain/libimagbookmark/Cargo.toml
new file mode 100644
index 0000000..d81cb9d
--- /dev/null
+++ b/lib/domain/libimagbookmark/Cargo.toml
@@ -0,0 +1,24 @@
+[package]
+name = "libimagbookmark"
+version = "0.4.0"
+authors = ["Matthias Beyer <mail@beyermatthias.de>"]
+
+description = "Library for the imag core distribution"
+
+keywords = ["imag", "PIM", "personal", "information", "management"]
+readme = "../README.md"
+license = "LGPL-2.1"
+
+documentation = "https://matthiasbeyer.github.io/imag/imag_documentation/index.html"
+repository = "https://github.com/matthiasbeyer/imag"
+homepage = "http://imag-pim.org"
+
+[dependencies]
+semver = "0.5"
+url = "1.2"
+regex = "0.1"
+
+libimagstore = { version = "0.4.0", path = "../../../lib/core/libimagstore" }
+libimagerror = { version = "0.4.0", path = "../../../lib/core/libimagerror" }
+libimagentrylink = { version = "0.4.0", path = "../../../lib/entry/libimagentrylink" }
+
diff --git a/lib/domain/libimagbookmark/README.md b/lib/domain/libimagbookmark/README.md
new file mode 120000
index 0000000..f7c5cd0
--- /dev/null
+++ b/lib/domain/libimagbookmark/README.md
@@ -0,0 +1 @@
+../doc/src/05100-lib-bookmark.md \ No newline at end of file
diff --git a/lib/domain/libimagbookmark/src/collection.rs b/lib/domain/libimagbookmark/src/collection.rs
new file mode 100644
index 0000000..206e31c
--- /dev/null
+++ b/lib/domain/libimagbookmark/src/collection.rs
@@ -0,0 +1,222 @@
+//
+// 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
+//
+
+//! BookmarkCollection module
+//!
+//! A BookmarkCollection is nothing more than a simple store entry. One can simply call functions
+//! from the libimagentrylink::external::ExternalLinker trait on this to generate external links.
+//!
+//! The BookmarkCollection type offers helper functions to get all links or such things.
+use std::ops::Deref;
+use std::ops::DerefMut;
+
+use regex::Regex;
+
+use error::BookmarkErrorKind as BEK;
+use error::MapErrInto;
+use result::Result;
+use module_path::ModuleEntryPath;
+
+use libimagstore::store::Store;
+use libimagstore::storeid::IntoStoreId;
+use libimagstore::store::FileLockEntry;
+use libimagentrylink::external::ExternalLinker;
+use libimagentrylink::external::iter::UrlIter;
+use libimagentrylink::internal::InternalLinker;
+use libimagentrylink::internal::Link as StoreLink;
+use libimagerror::into::IntoError;
+
+use link::Link;
+
+use self::iter::LinksMatchingRegexIter;
+
+pub struct BookmarkCollection<'a> {
+ fle: FileLockEntry<'a>,
+ store: &'a Store,
+}
+
+/// {Internal, External}Linker is implemented as Deref is implemented
+impl<'a> Deref for BookmarkCollection<'a> {
+ type Target = FileLockEntry<'a>;
+
+ fn deref(&self) -> &FileLockEntry<'a> {
+ &self.fle
+ }
+
+}
+
+impl<'a> DerefMut for BookmarkCollection<'a> {
+
+ fn deref_mut(&mut self) -> &mut FileLockEntry<'a> {
+ &mut self.fle
+ }
+
+}
+
+impl<'a> BookmarkCollection<'a> {
+
+ pub fn new(store: &'a Store, name: &str) -> Result<BookmarkCollection<'a>> {
+ ModuleEntryPath::new(name)
+ .into_storeid()
+ .and_then(|id| store.create(id))
+ .map(|fle| {
+ BookmarkCollection {
+ fle: fle,
+ store: store,
+ }
+ })
+ .map_err_into(BEK::StoreReadError)
+ }
+
+ pub fn get(store: &'a Store, name: &str) -> Result<BookmarkCollection<'a>> {
+ ModuleEntryPath::new(name)
+ .into_storeid()
+ .and_then(|id| store.get(id))
+ .map_err_into(BEK::StoreReadError)
+ .and_then(|fle| {
+ match fle {
+ None => Err(BEK::CollectionNotFound.into_error()),
+ Some(e) => Ok(BookmarkCollection {
+ fle: e,
+ store: store,
+ }),
+ }
+ })
+ }
+
+ pub fn delete(store: &Store, name: &str) -> Result<()> {
+ ModuleEntryPath::new(name)
+ .into_storeid()
+ .and_then(|id| store.delete(id))
+ .map_err_into(BEK::StoreReadError)
+ }
+
+ pub fn links(&self) -> Result<UrlIter> {
+ self.fle.get_external_links(&self.store).map_err_into(BEK::LinkError)
+ }
+
+ pub fn link_entries(&self) -> Result<Vec<StoreLink>> {
+ use libimagentrylink::external::is_external_link_storeid;
+
+ self.fle
+ .get_internal_links()
+ .map(|v| v.filter(|id| is_external_link_storeid(id)).collect())
+ .map_err_into(BEK::StoreReadError)
+ }
+
+ pub fn add_link(&mut self, l: Link) -> Result<()> {
+ use link::IntoUrl;
+
+ l.into_url()
+ .and_then(|url| self.add_external_link(self.store, url).map_err_into(BEK::LinkingError))
+ .map_err_into(BEK::LinkError)
+ }
+
+ pub fn get_links_matching(&self, r: Regex) -> Result<LinksMatchingRegexIter<'a>> {
+ use self::iter::IntoLinksMatchingRegexIter;
+
+ self.get_external_links(self.store)
+ .map_err_into(BEK::LinkError)
+ .map(|iter| iter.matching_regex(r))
+ }
+
+ pub fn remove_link(&mut self, l: Link) -> Result<()> {
+ use link::IntoUrl;
+
+ l.into_url()
+ .and_then(|url| {
+ self.remove_external_link(self.store, url).map_err_into(BEK::LinkingError)
+ })
+ .map_err_into(BEK::LinkError)
+ }
+
+}
+
+pub mod iter {
+ use link::Link;
+ use result::Result;
+ use error::{MapErrInto, BookmarkErrorKind as BEK};
+
+ pub struct LinkIter<I>(I)
+ where I: Iterator<Item = Link>;
+
+ impl<I: Iterator<Item = Link>> LinkIter<I> {
+ pub fn new(i: I) -> LinkIter<I> {
+ LinkIter(i)
+ }
+ }
+
+ impl<I: Iterator<Item = Link>> Iterator for LinkIter<I> {
+ type Item = Link;
+
+ fn next(&mut self) -> Option<Self::Item> {
+ self.0.next()
+ }
+ }
+
+ impl<I> From<I> for LinkIter<I> where I: Iterator<Item = Link> {
+ fn from(i: I) -> LinkIter<I> {
+ LinkIter(i)
+ }
+ }
+
+ use libimagentrylink::external::iter::UrlIter;
+ use regex::Regex;
+
+ pub struct LinksMatchingRegexIter<'a>(UrlIter<'a>, Regex);
+
+ impl<'a> LinksMatchingRegexIter<'a> {
+ pub fn new(i: UrlIter<'a>, r: Regex) -> LinksMatchingRegexIter<'a> {
+ LinksMatchingRegexIter(i, r)
+ }
+ }
+
+ impl<'a> Iterator for LinksMatchingRegexIter<'a> {
+ type Item = Result<Link>;
+
+ fn next(&mut self) -> Option<Self::Item> {
+ loop {
+ let n = match self.0.next() {
+ Some(Ok(n)) => n,
+ Some(Err(e)) => return Some(Err(e).map_err_into(BEK::LinkError)),
+ None => return None,
+ };
+
+ let s = n.into_string();
+ if self.1.is_match(&s[..]) {
+ return Some(Ok(Link::from(s)))
+ } else {
+ continue;
+ }
+ }
+ }
+ }
+
+ pub trait IntoLinksMatchingRegexIter<'a> {
+ fn matching_regex(self, Regex) -> LinksMatchingRegexIter<'a>;
+ }
+
+ impl<'a> IntoLinksMatchingRegexIter<'a> for UrlIter<'a> {
+ fn matching_regex(self, r: Regex) -> LinksMatchingRegexIter<'a> {
+ LinksMatchingRegexIter(self, r)
+ }
+ }
+
+}
+
diff --git a/lib/domain/libimagbookmark/src/error.rs b/lib/domain/libimagbookmark/src/error.rs
new file mode 100644
index 0000000..9b52a16
--- /dev/null
+++ b/lib/domain/libimagbookmark/src/error.rs
@@ -0,0 +1,33 @@
+//
+// 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
+//
+
+generate_error_module!(
+ generate_error_types!(BookmarkError, BookmarkErrorKind,
+ StoreReadError => "Store read error",
+ LinkError => "Link error",
+ LinkParsingError => "Link parsing error",
+ LinkingError => "Error while linking",
+ CollectionNotFound => "Link-Collection not found"
+ );
+);
+
+pub use self::error::BookmarkError;
+pub use self::error::BookmarkErrorKind;
+pub use self::error::MapErrInto;
+
diff --git a/lib/domain/libimagbookmark/src/lib.rs b/lib/domain/libimagbookmark/src/lib.rs
new file mode 100644
index 0000000..e576ff1
--- /dev/null
+++ b/lib/domain/libimagbookmark/src/lib.rs
@@ -0,0 +1,47 @@
+//
+// 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
+//
+
+#![deny(
+ non_camel_case_types,
+ non_snake_case,
+ path_statements,
+ trivial_numeric_casts,
+ unstable_features,
+ unused_allocation,
+ unused_import_braces,
+ unused_imports,
+ unused_mut,
+ unused_qualifications,
+ while_true,
+)]
+
+extern crate semver;
+extern crate url;
+extern crate regex;
+
+#[macro_use] extern crate libimagstore;
+#[macro_use] extern crate libimagerror;
+extern crate libimagentrylink;
+
+module_entry_path_mod!("bookmark");
+
+pub mod collection;
+pub mod error;
+pub mod link;
+pub mod result;
diff --git a/lib/domain/libimagbookmark/src/link.rs b/lib/domain/libimagbookmark/src/link.rs
new file mode 100644
index 0000000..1da91c8
--- /dev/null
+++ b/lib/domain/libimagbookmark/src/link.rs
@@ -0,0 +1,76 @@
+//
+// 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::ops::{Deref, DerefMut};
+
+use result::Result;
+
+use url::Url;
+
+#[derive(Debug, Clone)]
+pub struct Link(String);
+
+impl From<String> for Link {
+
+ fn from(s: String) -> Link {
+ Link(s)
+ }
+
+}
+
+impl<'a> From<&'a str> for Link {
+
+ fn from(s: &'a str) -> Link {
+ Link(String::from(s))
+ }
+
+}
+
+impl Deref for Link {
+ type Target = String;
+
+ fn deref(&self) -> &String {
+ &self.0
+ }
+
+}
+
+impl DerefMut for Link {
+
+ fn deref_mut(&mut self) -> &mut String {
+ &mut self.0
+ }
+
+}
+
+pub trait IntoUrl {
+ fn into_url(self) -> Result<Url>;
+}
+
+impl IntoUrl for Link {
+
+ fn into_url(self) -> Result<Url> {
+ use error::BookmarkErrorKind as BEK;
+ use error::MapErrInto;
+
+ Url::parse(&self[..]).map_err_into(BEK::LinkParsingError)
+ }
+
+}
+
diff --git a/lib/domain/libimagbookmark/src/result.rs b/lib/domain/libimagbookmark/src/result.rs
new file mode 100644
index 0000000..780a803
--- /dev/null
+++ b/lib/domain/libimagbookmark/src/result.rs
@@ -0,0 +1,25 @@
+//
+// 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 error::BookmarkError;
+
+pub type Result<T> = RResult<T, BookmarkError>;
+
diff --git a/lib/domain/libimagcounter/Cargo.toml b/lib/domain/libimagcounter/Cargo.toml
new file mode 100644
index 0000000..01519e1
--- /dev/null
+++ b/lib/domain/libimagcounter/Cargo.toml
@@ -0,0 +1,23 @@
+[package]
+name = "libimagcounter"
+version = "0.4.0"
+authors = ["Matthias Beyer <mail@beyermatthias.de>"]
+
+description = "Library for the imag core distribution"
+
+keywords = ["imag", "PIM", "personal", "information", "management"]
+readme = "../README.md"
+license = "LGPL-2.1"
+
+documentation = "https://matthiasbeyer.github.io/imag/imag_documentation/index.html"
+repository = "https://github.com/matthiasbeyer/imag"
+homepage = "http://imag-pim.org"
+
+[dependencies]
+log = "0.3"
+toml = "0.4.*"
+toml-query = "0.3.*"
+semver = "0.5"
+
+libimagstore = { version = "0.4.0", path = "../../../lib/core/libimagstore" }
+libimagerror = { version = "0.4.0", path = "../../../lib/core/libimagerror" }
diff --git a/lib/domain/libimagcounter/README.md b/lib/domain/libimagcounter/README.md
new file mode 120000
index 0000000..90221b8
--- /dev/null
+++ b/lib/domain/libimagcounter/README.md
@@ -0,0 +1 @@
+../doc/src/05100-lib-counter.md \ No newline at end of file
diff --git a/lib/domain/libimagcounter/src/counter.rs b/lib/domain/libimagcounter/src/counter.rs
new file mode 100644
index 0000000..7b6c1d3
--- /dev/null
+++ b/lib/domain/libimagcounter/src/counter.rs
@@ -0,0 +1,266 @@
+//
+// 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::ops::DerefMut;
+
+use toml::Value;
+use toml_query::read::TomlValueReadExt;
+use toml_query::set::TomlValueSetExt;
+
+use std::collections::BTreeMap;
+use std::fmt;
+use std::fmt::Display;
+
+use libimagstore::store::Store;
+use libimagstore::storeid::StoreIdIterator;
+use libimagstore::store::FileLockEntry;
+use libimagstore::storeid::StoreId;
+use libimagstore::storeid::IntoStoreId;
+use libimagerror::into::IntoError;
+
+use module_path::ModuleEntryPath;
+use result::Result;
+use error::CounterError as CE;
+use error::CounterErrorKind as CEK;
+use error::error::MapErrInto;
+
+pub type CounterName = String;
+
+#[derive(Clone, Debug, Eq, PartialEq, Ord, PartialOrd)]
+pub struct CounterUnit(String);
+
+impl Display for CounterUnit {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ write!(f, "({})", self.0)
+ }
+}
+
+impl CounterUnit {
+ pub fn new<S: Into<String>>(unit: S) -> CounterUnit {
+ CounterUnit(unit.into())
+ }
+}
+
+pub struct Counter<'a> {
+ fle: FileLockEntry<'a>,
+ unit: Option<CounterUnit>,
+}
+
+impl<'a> Counter<'a> {
+
+ pub fn new(store: &Store, name: CounterName, init: i64) -> Result<Counter> {
+ use std::ops::DerefMut;
+
+ debug!("Creating new counter: '{}' with value: {}", name, init);
+ let fle = {
+ let id = try!(ModuleEntryPath::new(name.clone())
+ .into_storeid()
+ .map_err_into(CEK::StoreWriteError));
+ let mut lockentry = try!(store.create(id).map_err_into(CEK::StoreWriteError));
+
+ {
+ let mut entry = lockentry.deref_mut();
+ let mut header = entry.get_header_mut();
+ let setres = header.set(&String::from("counter"), Value::Table(BTreeMap::new()));
+ if setres.is_err() {
+ return Err(CEK::StoreWriteError.into_error());
+ }
+
+ let setres = header.set(&String::from("counter.name"), Value::String(name));
+ if setres.is_err() {
+ return Err(CEK::StoreWriteError.into_error())
+ }
+
+ let setres = header.set(&String::from("counter.value"), Value::Integer(init));
+ if setres.is_err() {
+ return Err(CEK::StoreWriteError.into_error())
+ }
+ }
+
+ lockentry
+ };
+
+ Ok(Counter { fle: fle, unit: None })
+ }
+
+ pub fn with_unit(mut self, unit: Option<CounterUnit>) -> Result<Counter<'a>> {
+ self.unit = unit;
+
+ if let Some(u) = self.unit.clone() {
+ let mut header = self.fle.deref_mut().get_header_mut();
+ let setres = header.set(&String::from("counter.unit"), Value::String(u.0));
+ if setres.is_err() {
+ self.unit = None;
+ return Err(CEK::StoreWriteError.into_error())
+ }
+ };
+ Ok(self)
+ }
+
+ pub fn inc(&mut self) -> Result<()> {
+ let mut header = self.fle.deref_mut().get_header_mut();
+ let query = String::from("counter.value");
+ match try!(header.read(&query).map_err_into(CEK::StoreReadError)) {
+ Some(&Value::Integer(i)) => {
+ header.set(&query, Value::Integer(i + 1))
+ .map_err_into(CEK::StoreWriteError)
+ .map(|_| ())
+ },
+ _ => Err(CE::new(CEK::StoreReadError, None)),
+ }
+ }
+
+ pub fn dec(&mut self) -> Result<()> {
+ let mut header = self.fle.deref_mut().get_header_mut();
+ let query = String::from("counter.value");
+ match try!(header.read(&query).map_err_into(CEK::StoreReadError)) {
+ Some(&Value::Integer(i)) => {
+ header.set(&query, Value::Integer(i - 1))
+ .map_err_into(CEK::StoreWriteError)
+ .map(|_| ())
+ },
+ _ => Err(CE::new(CEK::StoreReadError, None)),
+ }
+ }
+
+ pub fn reset(&mut self) -> Result<()> {
+ self.set(0)
+ }
+
+ pub fn set(&mut self, v: i64) -> Result<()> {
+ let mut header = self.fle.deref_mut().get_header_mut();
+ header.set(&String::from("counter.value"), Value::Integer(v))
+ .map_err_into(CEK::StoreWriteError)
+ .map(|_| ())
+ }
+
+ pub fn name(&self) -> Result<CounterName> {
+ self.read_header_at("counter.name", |v| match v {
+ Some(&Value::String(ref s)) => Ok(s.clone()),
+ Some(_) => Err(CEK::HeaderTypeError.into_error()),
+ _ => Err(CEK::StoreReadError.into_error()),
+ })
+ }
+
+ pub fn value(&self) -> Result<i64> {
+ self.read_header_at("counter.value", |v| match v {
+ Some(&Value::Integer(i)) => Ok(i),
+ Some(_) => Err(CEK::HeaderTypeError.into_error()),
+ _ => Err(CEK::StoreReadError.into_error()),
+ })
+ }
+
+ pub fn unit(&self) -> Option<&CounterUnit> {
+ self.unit.as_ref()
+ }
+
+ pub fn read_unit(&self) -> Result<Option<CounterUnit>> {
+ self.read_header_at("counter.unit", |s| match s {
+ Some(&Value::String(ref s)) => Ok(Some(CounterUnit::new(s.clone()))),
+ Some(_) => Err(CEK::HeaderTypeError.into_error()),
+ _ => Err(CEK::StoreReadError.into_error()),
+ })
+ }
+
+ fn read_header_at<T, F>(&self, name: &str, f: F) -> Result<T>
+ where F: FnOnce(Option<&Value>) -> Result<T>
+ {
+
+ self.fle
+ .get_header()
+ .read(&String::from(name))
+ .map_err_into(CEK::StoreWriteError)
+ .and_then(f)
+ }
+
+ pub fn load(name: CounterName, store: &Store) -> Result<Counter> {
+ debug!("Loading counter: '{}'", name);
+ let id = try!(ModuleEntryPath::new(name)
+ .into_storeid()
+ .map_err_into(CEK::StoreWriteError));
+ Counter::from_storeid(store, id)
+ }
+
+ pub fn delete(name: CounterName, store: &Store) -> Result<()> {
+ debug!("Deleting counter: '{}'", name);
+ let id = try!(ModuleEntryPath::new(name)
+ .into_storeid()
+ .map_err_into(CEK::StoreWriteError));
+ store.delete(id).map_err_into(CEK::StoreWriteError)
+ }
+
+ pub fn all_counters(store: &Store) -> Result<CounterIterator> {
+ store.retrieve_for_module("counter")
+ .map(|iter| CounterIterator::new(store, iter))
+ .map_err_into(CEK::StoreReadError)
+ }
+
+}
+
+trait FromStoreId {
+ fn from_storeid(&Store, StoreId) -> Result<Counter>;
+}
+
+impl<'a> FromStoreId for Counter<'a> {
+
+ fn from_storeid(store: &Store, id: StoreId) -> Result<Counter> {
+ debug!("Loading counter from storeid: '{:?}'", id);
+ match store.retrieve(id) {
+ Err(e) => Err(CE::new(CEK::StoreReadError, Some(Box::new(e)))),
+ Ok(c) => {
+ let mut counter = Counter { fle: c, unit: None };
+ counter.read_unit()
+ .map_err_into(CEK::StoreReadError)
+ .and_then(|u| {
+ counter.unit = u;
+ Ok(counter)
+ })
+ }
+ }
+ }
+
+}
+
+pub struct CounterIterator<'a> {
+ store: &'a Store,
+ iditer: StoreIdIterator,
+}
+
+impl<'a> CounterIterator<'a> {
+
+ pub fn new(store: &'a Store, iditer: StoreIdIterator) -> CounterIterator<'a> {
+ CounterIterator {
+ store: store,
+ iditer: iditer,
+ }
+ }
+
+}
+
+impl<'a> Iterator for CounterIterator<'a> {
+ type Item = Result<Counter<'a>>;
+
+ fn next(&mut self) -> Option<Result<Counter<'a>>> {
+ self.iditer
+ .next()
+ .map(|id| Counter::from_storeid(self.store, id))
+ }
+
+}
+
diff --git a/lib/domain/libimagcounter/src/error.rs b/lib/domain/libimagcounter/src/error.rs
new file mode 100644
index 0000000..c886d90
--- /dev/null
+++ b/lib/domain/libimagcounter/src/error.rs
@@ -0,0 +1,32 @@
+//
+// 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
+//
+
+generate_error_module!(
+ generate_error_types!(CounterError, CounterErrorKind,
+ StoreIdError => "StoreId error",
+ StoreReadError => "Store read error",
+ StoreWriteError => "Store write error",
+ HeaderTypeError => "Header type error",
+ HeaderFieldMissingError => "Header field missing error"
+ );
+);
+
+pub use self::error::CounterError;
+pub use self::error::CounterErrorKind;
+
diff --git a/lib/domain/libimagcounter/src/lib.rs b/lib/domain/libimagcounter/src/lib.rs
new file mode 100644
index 0000000..09ba178
--- /dev/null
+++ b/lib/domain/libimagcounter/src/lib.rs
@@ -0,0 +1,47 @@
+//
+// 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
+//
+
+#![deny(
+ non_camel_case_types,
+ non_snake_case,
+ path_statements,
+ trivial_numeric_casts,
+ unstable_features,
+ unused_allocation,
+ unused_import_braces,
+ unused_imports,
+ unused_mut,
+ unused_qualifications,
+ while_true,
+)]
+
+extern crate toml;
+extern crate toml_query;
+#[macro_use] extern crate log;
+extern crate semver;
+
+#[macro_use] extern crate libimagstore;
+#[macro_use] extern crate libimagerror;
+
+module_entry_path_mod!("counter");
+
+pub mod counter;
+pub mod error;
+pub mod result;
+
diff --git a/lib/domain/libimagcounter/src/result.rs b/lib/domain/libimagcounter/src/result.rs
new file mode 100644
index 0000000..e24b90c
--- /dev/null
+++ b/lib/domain/libimagcounter/src/result.rs
@@ -0,0 +1,25 @@
+//
+// 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 error::CounterError;
+
+pub type Result<T> = RResult<T, CounterError>;
+
diff --git a/lib/domain/libimagdiary/Cargo.toml b/lib/domain/libimagdiary/Cargo.toml
new file mode 100644
index 0000000..1da26c7
--- /dev/null
+++ b/lib/domain/libimagdiary/Cargo.toml
@@ -0,0 +1,30 @@
+[package]
+name = "libimagdiary"
+version = "0.4.0"
+authors = ["Matthias Beyer <mail@beyermatthias.de>"]
+
+description = "Library for the imag core distribution"
+
+keywords = ["imag", "PIM", "personal", "information", "management"]
+readme = "../README.md"
+license = "LGPL-2.1"
+
+documentation = "https://matthiasbeyer.github.io/imag/imag_documentation/index.html"
+repository = "https://github.com/matthiasbeyer/imag"
+homepage = "http://imag-pim.org"
+
+[dependencies]
+chrono = "0.4"
+log = "0.3"
+semver = "0.5"
+toml = "0.4.*"
+toml-query = "0.3.*"
+regex = "0.1"
+itertools = "0.5"
+
+libimagstore = { version = "0.4.0", path = "../../../lib/core/libimagstore" }
+libimagerror = { version = "0.4.0", path = "../../../lib/core/libimagerror" }
+libimagrt = { version = "0.4.0", path = "../../../lib/core/libimagrt" }
+libimagutil = { version = "0.4.0", path = "../../../lib/etc/libimagutil" }
+libimagentryedit = { version = "0.4.0", path = "../../../lib/entry/libimagentryedit" }
+libimagentryview = { version = "0.4.0", path = "../../../lib/entry/libimagentryview" }
diff --git a/lib/domain/libimagdiary/README.md b/lib/domain/libimagdiary/README.md
new file mode 120000
index 0000000..50c0ff3
--- /dev/null
+++ b/lib/domain/libimagdiary/README.md
@@ -0,0 +1 @@
+../doc/src/05100-lib-diary.md \ No newline at end of file
diff --git a/lib/domain/libimagdiary/src/config.rs b/lib/domain/libimagdiary/src/config.rs
new file mode 100644
index 0000000..c2a6a98
--- /dev/null
+++ b/lib/domain/libimagdiary/src/config.rs
@@ -0,0 +1,43 @@
+//
+// 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 libimagrt::runtime::Runtime;
+
+use toml_query::read::TomlValueReadExt;
+
+pub fn get_default_diary_name(rt: &Runtime) -> Option<String> {
+ get_diary_config_section(rt)
+ .and_then(|config| {
+ match config.read(&String::from("default_diary")) {
+ Ok(Some(&Value::String(ref s))) => Some(s.clone()),
+ _ => None,
+ }
+ })
+}
+
+pub fn get_diary_config_section<'a>(rt: &'a Runtime) -> Option<&'a Value> {
+ rt.config()
+ .map(|config| config.config())
+ .and_then(|config| match config.read(&String::from("diary")) {
+ Ok(x) => x,
+ Err(_) => None,
+ })
+}
diff --git a/lib/domain/libimagdiary/src/diary.rs b/lib/domain/libimagdiary/src/diary.rs
new file mode 100644
index 0000000..431b632
--- /dev/null
+++ b/lib/domain/libimagdiary/src/diary.rs
@@ -0,0 +1,128 @@
+//
+// 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::cmp::Ordering;
+
+use libimagstore::store::Store;
+use libimagstore::storeid::IntoStoreId;
+use libimagerror::trace::trace_error;
+
+use chrono::offset::Local;
+use chrono::Datelike;
+use itertools::Itertools;
+use chrono::naive::NaiveDateTime;
+
+use entry::Entry;
+use diaryid::DiaryId;
+use error::DiaryError as DE;
+use error::DiaryErrorKind as DEK;
+use result::Result;
+use iter::DiaryEntryIterator;
+use is_in_diary::IsInDiary;
+
+#[derive(Debug)]
+pub struct Diary<'a> {
+ store: &'a Store,
+ name: &'a str,
+}
+
+impl<'a> Diary<'a> {
+
+ pub fn open(store: &'a Store, name: &'a str) -> Diary<'a> {
+ Diary {
+ store: store,
+ name: name,
+ }
+ }
+
+ // create or get a new entry for today
+ pub fn new_entry_today(&self) -> Result<Entry> {
+ let dt = Local::now();
+ let ndt = dt.naive_local();
+ let id = DiaryId::new(String::from(self.name), ndt.year(), ndt.month(), ndt.day(), 0, 0);
+ self.new_entry_by_id(id)
+ }
+
+ pub fn new_entry_by_id(&self, id: DiaryId) -> Result<Entry> {
+ self.retrieve(id.with_diary_name(String::from(self.name)))
+ }
+
+ pub fn retrieve(&self, id: DiaryId) -> Result<Entry> {
+ id.into_storeid()
+ .and_then(|id| self.store.retrieve(id))
+ .map(|fle| Entry::new(fle))
+ .map_err(|e| DE::new(DEK::StoreWriteError, Some(Box::new(e))))
+ }
+
+ // Get an iterator for iterating over all entries
+ pub fn entries(&self) -> Result<DiaryEntryIterator<'a>> {
+ self.store
+ .retrieve_for_module("diary")
+ .map(|iter| DiaryEntryIterator::new(self.name, self.store, iter))
+ .map_err(|e| DE::new(DEK::StoreReadError, Some(Box::new(e))))
+ }
+
+ pub fn delete_entry(&self, entry: Entry) -> Result<()> {
+ if !entry.is_in_diary(self.name) {
+ return Err(DE::new(DEK::EntryNotInDiary, None));
+ }
+ let id = entry.get_location().clone();
+ drop(entry);
+
+ self.store.delete(id)
+ .map_err(|e| DE::new(DEK::StoreWriteError, Some(Box::new(e))))
+ }
+
+ pub fn get_youngest_entry(&self) -> Option<Result<Entry>> {
+ match self.entries() {
+ Err(e) => Some(Err(e)),
+ Ok(entries) => {
+ entries.sorted_by(|a, b| {
+ match (a, b) {
+ (&Ok(ref a), &Ok(ref b)) => {
+ let a : NaiveDateTime = a.diary_id().into();
+ let b : NaiveDateTime = b.diary_id().into();
+
+ a.cmp(&b)
+ },
+
+ (&Ok(_), &Err(ref e)) => {
+ trace_error(e);
+ Ordering::Less
+ },
+ (&Err(ref e), &Ok(_)) => {
+ trace_error(e);
+ Ordering::Greater
+ },
+ (&Err(ref e1), &Err(ref e2)) => {
+ trace_error(e1);
+ trace_error(e2);
+ Ordering::Equal
+ },
+ }
+ }).into_iter().next()
+ }
+ }
+ }
+
+ pub fn name(&self) -> &'a str {
+ &self.name
+ }
+}
+
diff --git a/lib/domain/libimagdiary/src/diaryid.rs b/lib/domain/libimagdiary/src/diaryid.rs
new file mode 100644
index 0000000..da1afc4
--- /dev/null
+++ b/lib/domain/libimagdiary/src/diaryid.rs
@@ -0,0 +1,257 @@
+//
+// 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::convert::Into;
+use std::fmt::{Display, Formatter, Error as FmtError};
+
+use chrono::naive::NaiveDateTime;
+use chrono::naive::NaiveTime;
+use chrono::naive::NaiveDate;
+use chrono::Datelike;
+use chrono::Timelike;
+
+use libimagstore::storeid::StoreId;
+use libimagstore::storeid::IntoStoreId;
+use libimagstore::store::Result as StoreResult;
+
+use error::DiaryError as DE;
+use error::DiaryErrorKind as DEK;
+use error::MapErrInto;
+use libimagerror::into::IntoError;
+
+use module_path::ModuleEntryPath;
+
+#[derive(Debug, Clone)]
+pub struct DiaryId {
+ name: String,
+ year: i32,
+ month: u32,
+ day: u32,
+ hour: u32,
+ minute: u32,
+}
+
+impl DiaryId {
+
+ pub fn new(name: String, y: i32, m: u32, d: u32, h: u32, min: u32) -> DiaryId {
+ DiaryId {
+ name: name,
+ year: y,
+ month: m,
+ day: d,
+ hour: h,
+ minute: min,
+ }
+ }
+
+ pub fn from_datetime<DT: Datelike + Timelike>(diary_name: String, dt: DT) -> DiaryId {
+ DiaryId::new(diary_name, dt.year(), dt.month(), dt.day(), dt.hour(), dt.minute())
+ }
+
+ pub fn diary_name(&self) -> &String {
+ &self.name
+ }
+
+ pub fn year(&self) -> i32 {
+ self.year
+ }
+
+ pub fn month(&self) -> u32 {
+ self.month
+ }
+
+ pub fn day(&self) -> u32 {
+ self.day
+ }
+
+ pub fn hour(&self) -> u32 {
+ self.hour
+ }
+
+ pub fn minute(&self) -> u32 {
+ self.minute
+ }
+
+ pub fn with_diary_name(mut self, name: String) -> DiaryId {
+ self.name = name;
+ self
+ }
+
+ pub fn with_year(mut self, year: i32) -> DiaryId {
+ self.year = year;
+ self
+ }
+
+ pub fn with_month(mut self, month: u32) -> DiaryId {
+ self.month = month;
+ self
+ }
+
+ pub fn with_day(mut self, day: u32) -> DiaryId {
+ self.day = day;
+ self
+ }
+
+ pub fn with_hour(mut self, hour: u32) -> DiaryId {
+ self.hour = hour;
+ self
+ }
+
+ pub fn with_minute(mut self, minute: u32) -> DiaryId {
+ self.minute = minute;
+ self
+ }
+
+ pub fn now(name: String) -> DiaryId {
+ use chrono::offset::Local;
+
+ let now = Local::now();
+ let now_date = now.date().naive_local();
+ let now_time = now.time();
+ let dt = NaiveDateTime::new(now_date, now_time);
+
+ DiaryId::new(name, dt.year(), dt.month(), dt.day(), dt.hour(), dt.minute())
+ }
+
+}
+
+impl Default for DiaryId {
+
+ /// Create a default DiaryId which is a diaryid for a diary named "default" with
+ /// time = 0000-00-00 00:00:00
+ fn default() -> DiaryId {
+ let dt = NaiveDateTime::new(NaiveDate::from_ymd(0, 0, 0), NaiveTime::from_hms(0, 0, 0));
+ DiaryId::from_datetime(String::from("default"), dt)
+ }
+}
+
+impl IntoStoreId for DiaryId {
+
+ fn into_storeid(self) -> StoreResult<StoreId> {
+ let s : String = self.into();
+ ModuleEntryPath::new(s).into_storeid()
+ }
+
+}
+
+impl Into<String> for DiaryId {
+
+ fn into(self) -> String {
+ format!("{}/{:0>4}/{:0>2}/{:0>2}/{:0>2}:{:0>2}",
+ self.name, self.year, self.month, self.day, self.hour, self.minute)
+ }
+
+}
+
+impl Display for DiaryId {
+
+ fn fmt(&self, fmt: &mut Formatter) -> Result<(), FmtError> {
+ write!(fmt, "{}/{:0>4}/{:0>2}/{:0>2}/{:0>2}:{:0>2}",
+ self.name, self.year, self.month, self.day, self.hour, self.minute)
+ }
+
+}
+
+impl Into<NaiveDateTime> for DiaryId {
+
+ fn into(self) -> NaiveDateTime {
+ let d = NaiveDate::from_ymd(self.year, self.month, self.day);
+ let t = NaiveTime::from_hms(self.hour, self.minute, 0);
+ NaiveDateTime::new(d, t)
+ }
+
+}
+
+pub trait FromStoreId : Sized {
+
+ fn from_storeid(&StoreId) -> Result<Self, DE>;
+
+}
+
+use std::path::Component;
+
+fn component_to_str<'a>(com: Component<'a>) -> Result<&'a str, DE> {
+ match com {
+ Component::Normal(s) => Some(s),
+ _ => None,
+ }.and_then(|s| s.to_str())
+ .ok_or(DEK::IdParseError.into_error())
+}
+
+impl FromStoreId for DiaryId {
+
+ fn from_storeid(s: &StoreId) -> Result<DiaryId, DE> {
+ use std::str::FromStr;
+
+ use std::path::Components;
+ use std::iter::Rev;
+
+ fn next_component<'a>(components: &'a mut Rev<Components>) -> Result<&'a str, DE> {
+ components.next()
+ .ok_or(DEK::IdParseError.into_error())
+ .and_then(component_to_str)
+ }
+
+ let mut cmps = s.components().rev();
+
+ let (hour, minute) = try!(next_component(&mut cmps).and_then(|time| {
+ let mut time = time.split(":");
+ let hour = time.next().and_then(|s| FromStr::from_str(s).ok());
+ let minute = time.next()
+ .and_then(|s| s.split("~").next())
+ .and_then(|s| FromStr::from_str(s).ok());
+
+ debug!("Hour = {:?}", hour);
+ debug!("Minute = {:?}", minute);
+
+ match (hour, minute) {
+ (Some(h), Some(m)) => Ok((h, m)),
+ _ => return Err(DE::new(DEK::IdParseError, None)),
+ }
+ }));
+
+ let day: Result<u32,_> = next_component(&mut cmps)
+ .and_then(|s| s.parse::<u32>()
+ .map_err_into(DEK::IdParseError));
+
+ let month: Result<u32,_> = next_component(&mut cmps)
+ .and_then(|s| s.parse::<u32>()
+ .map_err_into(DEK::IdParseError));
+
+ let year: Result<i32,_> = next_component(&mut cmps)
+ .and_then(|s| s.parse::<i32>()
+ .map_err_into(DEK::IdParseError));
+
+ let name = next_component(&mut cmps).map(String::from);
+
+ debug!("Day = {:?}", day);
+ debug!("Month = {:?}", month);
+ debug!("Year = {:?}", year);
+ debug!("Name = {:?}", name);
+
+ let day = try!(day);
+ let month = try!(month);
+ let year = try!(year);
+ let name = try!(name);
+
+ Ok(DiaryId::new(name, year, month, day, hour, minute))
+ }
+
+}
+
diff --git a/lib/domain/libimagdiary/src/entry.rs b/lib/domain/libimagdiary/src/entry.rs
new file mode 100644
index 0000000..0148b59
--- /dev/null
+++ b/lib/domain/libimagdiary/src/entry.rs
@@ -0,0 +1,90 @@
+//
+// 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::ops::Deref;
+use std::ops::DerefMut;
+
+use libimagstore::store::FileLockEntry;
+use libimagentryedit::edit::Edit;
+use libimagentryedit::result::Result as EditResult;
+use libimagrt::runtime::Runtime;
+
+use diaryid::DiaryId;
+use diaryid::FromStoreId;
+
+#[derive(Debug)]
+pub struct Entry<'a>(FileLockEntry<'a>);
+
+impl<'a> Deref for Entry<'a> {
+ type Target = FileLockEntry<'a>;
+
+ fn deref(&self) -> &FileLockEntry<'a> {
+ &self.0
+ }
+
+}
+
+impl<'a> DerefMut for Entry<'a> {
+
+ fn deref_mut(&mut self) -> &mut FileLockEntry<'a> {
+ &mut self.0
+ }
+
+}
+
+impl<'a> Entry<'a> {
+
+ pub fn new(fle: FileLockEntry<'a>) -> Entry<'a> {
+ Entry(fle)
+ }
+
+ /// Get the diary id for this entry.
+ ///
+ /// TODO: calls Option::unwrap() as it assumes that an existing Entry has an ID that is parsable
+ pub fn diary_id(&self) -> DiaryId {
+ DiaryId::from_storeid(&self.0.get_location().clone()).unwrap()
+ }
+
+}
+
+impl<'a> Into<FileLockEntry<'a>> for Entry<'a> {
+
+ fn into(self) -> FileLockEntry<'a> {
+ self.0
+ }
+
+}
+
+impl<'a> From<FileLockEntry<'a>> for Entry<'a> {
+
+ fn from(fle: FileLockEntry<'a>) -> Entry<'a> {
+ Entry::new(fle)
+ }
+
+}
+
+impl<'a> Edit for Entry<'a> {
+
+ fn edit_content(&mut self, rt: &Runtime) -> EditResult<()> {
+ self.0.edit_content(rt)
+ }
+
+}
+
+
diff --git a/lib/domain/libimagdiary/src/error.rs b/lib/domain/libimagdiary/src/error.rs
new file mode 100644
index 0000000..b5406b1
--- /dev/null
+++ b/lib/domain/libimagdiary/src/error.rs
@@ -0,0 +1,38 @@
+//
+// 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
+//
+
+generate_error_module!(
+ generate_error_types!(DiaryError, DiaryErrorKind,
+ StoreWriteError => "Error writing store",
+ StoreReadError => "Error reading store",
+ CannotFindDiary => "Cannot find diary",
+ CannotCreateNote => "Cannot create Note object for diary entry",
+ DiaryEditError => "Cannot edit diary entry",
+ PathConversionError => "Error while converting paths internally",
+ EntryNotInDiary => "Entry not in Diary",
+ IOError => "IO Error",
+ ViewError => "Error viewing diary entry",
+ IdParseError => "Error while parsing ID"
+ );
+);
+
+pub use self::error::DiaryError;
+pub use self::error::DiaryErrorKind;
+pub use self::error::MapErrInto;
+
diff --git a/lib/domain/libimagdiary/src/is_in_diary.rs b/lib/domain/libimagdiary/src/is_in_diary.rs
new file mode 100644
index 0000000..0922807
--- /dev/null
+++ b/lib/domain/libimagdiary/src/is_in_diary.rs
@@ -0,0 +1,44 @@
+//
+// 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 libimagstore::store::Entry;
+use libimagstore::storeid::StoreId;
+
+pub trait IsInDiary {
+
+ fn is_in_diary(&self, name: &str) -> bool;
+
+}
+
+impl IsInDiary for Entry {
+
+ fn is_in_diary(&self, name: &str) -> bool {
+ self.get_location().clone().is_in_diary(name)
+ }
+
+}
+
+impl IsInDiary for StoreId {
+
+ fn is_in_diary(&self, name: &str) -> bool {
+ self.local().starts_with(format!("diary/{}", name))
+ }
+
+}
+
diff --git a/lib/domain/libimagdiary/src/iter.rs b/lib/domain/libimagdiary/src/iter.rs
new file mode 100644
index 0000000..de84987
--- /dev/null
+++ b/lib/domain/libimagdiary/src/iter.rs
@@ -0,0 +1,132 @@
+//
+// 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::fmt::{Debug, Formatter, Error as FmtError};
+use std::result::Result as RResult;
+
+use libimagstore::store::Store;
+use libimagstore::storeid::StoreIdIterator;
+
+use diaryid::DiaryId;
+use diaryid::FromStoreId;
+use is_in_diary::IsInDiary;
+use entry::Entry as DiaryEntry;
+use error::DiaryError as DE;
+use error::DiaryErrorKind as DEK;
+use result::Result;
+use libimagerror::trace::trace_error;
+
+/// A iterator for iterating over diary entries
+pub struct DiaryEntryIterator<'a> {
+ store: &'a Store,
+ name: &'a str,
+ iter: StoreIdIterator,
+
+ year: Option<i32>,
+ month: Option<u32>,
+ day: Option<u32>,
+}
+
+impl<'a> Debug for DiaryEntryIterator<'a> {
+
+ fn fmt(&self, fmt: &mut Formatter) -> RResult<(), FmtError> {
+ write!(fmt, "DiaryEntryIterator<name = {}, year = {:?}, month = {:?}, day = {:?}>",
+ self.name, self.year, self.month, self.day)
+ }
+
+}
+
+impl<'a> DiaryEntryIterator<'a> {
+
+ pub fn new(diaryname: &'a str, store: &'a Store, iter: StoreIdIterator) -> DiaryEntryIterator<'a> {
+ DiaryEntryIterator {
+ store: store,
+ name: diaryname,
+ iter: iter,
+
+ year: None,
+ month: None,
+ day: None,
+ }
+ }
+
+ // Filter by year, get all diary entries for this year
+ pub fn year(mut self, year: i32) -> DiaryEntryIterator<'a> {
+ self.year = Some(year);
+ self
+ }
+
+ // Filter by month, get all diary entries for this month (every year)
+ pub fn month(mut self, month: u32) -> DiaryEntryIterator<'a> {
+ self.month = Some(month);
+ self
+ }
+
+ // Filter by day, get all diary entries for this day (every year, every year)
+ pub fn day(mut self, day: u32) -> DiaryEntryIterator<'a> {
+ self.day = Some(day);
+ self
+ }
+
+}
+
+impl<'a> Iterator for DiaryEntryIterator<'a> {
+ type Item = Result<DiaryEntry<'a>>;
+
+ fn next(&mut self) -> Option<Result<DiaryEntry<'a>>> {
+ loop {
+ let next = match self.iter.next() {
+ Some(s) => s,
+ None => return None,
+ };
+ debug!("Next element: {:?}", next);
+
+ if next.is_in_diary(self.name) {
+ debug!("Seems to be in diary: {:?}", next);
+ let id = match DiaryId::from_storeid(&next) {
+ Ok(i) => i,
+ Err(e) => {
+ trace_error(&e);
+ debug!("Couldn't parse {:?} into DiaryId: {:?}", next, e);
+ continue;
+ }
+ };
+ debug!("Success parsing id = {:?}", id);
+
+ let y = match self.year { None => true, Some(y) => y == id.year() };
+ let m = match self.month { None => true, Some(m) => m == id.month() };
+ let d = match self.day { None => true, Some(d) => d == id.day() };
+
+ if y && m && d {
+ debug!("Return = {:?}", id);
+ return Some(self
+ .store
+ .retrieve(next)
+ .map(|fle| DiaryEntry::new(fle))
+ .map_err(|e| DE::new(DEK::StoreReadError, Some(Box::new(e))))
+ );
+ }
+ } else {
+ debug!("Not in the requested diary ({}): {:?}", self.name, next);
+ }
+ }
+ }
+
+}
+
diff --git a/lib/domain/libimagdiary/src/lib.rs b/lib/domain/libimagdiary/src/lib.rs
new file mode 100644
index 0000000..fcabc94
--- /dev/null
+++ b/lib/domain/libimagdiary/src/lib.rs
@@ -0,0 +1,62 @@
+//
+// 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
+//
+
+#![deny(
+ dead_code,
+ non_camel_case_types,
+ non_snake_case,
+ path_statements,
+ trivial_numeric_casts,
+ unstable_features,
+ unused_allocation,
+ unused_import_braces,
+ unused_imports,
+ unused_must_use,
+ unused_mut,
+ unused_qualifications,
+ while_true,
+)]
+
+extern crate chrono;
+#[macro_use] extern crate log;
+extern crate semver;
+extern crate toml;
+extern crate toml_query;
+extern crate regex;
+extern crate itertools;
+
+#[macro_use] extern crate libimagstore;
+#[macro_use] extern crate libimagerror;
+extern crate libimagentryedit;
+extern crate libimagentryview;
+extern crate libimagrt;
+extern crate libimagutil;
+
+module_entry_path_mod!("diary");
+
+pub mod config;
+pub mod error;
+pub mod diaryid;
+pub mod diary;
+pub mod is_in_diary;
+pub mod entry;
+pub mod iter;
+pub mod result;
+pub mod viewer;
+
diff --git a/lib/domain/libimagdiary/src/result.rs b/lib/domain/libimagdiary/src/result.rs
new file mode 100644
index 0000000..b4f5f38
--- /dev/null
+++ b/lib/domain/libimagdiary/src/result.rs
@@ -0,0 +1,24 @@
+//
+// 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 error::DiaryError;
+
+pub type Result<T> = RResult<T, DiaryError>;
diff --git a/lib/domain/libimagdiary/src/viewer.rs b/lib/domain/libimagdiary/src/viewer.rs
new file mode 100644
index 0000000..93b155a
--- /dev/null
+++ b/lib/domain/libimagdiary/src/viewer.rs
@@ -0,0 +1,64 @@
+//
+// 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
+//
+
+//! A diary viewer built on libimagentryview.
+
+use entry::Entry;
+use error::DiaryErrorKind as DEK;
+use error::MapErrInto;
+use result::Result;
+
+use libimagentryview::viewer::Viewer;
+use libimagentryview::builtin::plain::PlainViewer;
+
+/// This viewer does _not_ implement libimagentryview::viewer::Viewer because we need to be able to
+/// call some diary-type specific functions on the entries passed to this.
+///
+/// This type is mainly just written to be constructed-called-deleted in one go:
+///
+/// ```ignore
+/// DiaryViewer::new(show_header).view_entries(entries);
+/// ```
+///
+pub struct DiaryViewer(PlainViewer);
+
+impl DiaryViewer {
+
+ pub fn new(show_header: bool) -> DiaryViewer {
+ DiaryViewer(PlainViewer::new(show_header))
+ }
+
+ /// View all entries from the iterator, or stop immediately if an error occurs, returning that
+ /// error.
+ pub fn view_entries<'a, I: Iterator<Item = Entry<'a>>>(&self, entries: I) -> Result<()> {
+ for entry in entries {
+ let id = entry.diary_id();
+ println!("{} :\n", id);
+ let _ = try!(self.0
+ .view_entry(&entry)
+ .map_err_into(DEK::ViewError)
+ .map_err_into(DEK::IOError));
+ println!("\n---\n");
+ }
+
+ Ok(())
+ }
+
+}
+
diff --git a/lib/domain/libimagmail/Cargo.toml b/lib/domain/libimagmail/Cargo.toml
new file mode 100644
index 0000000..98d245b
--- /dev/null
+++ b/lib/domain/libimagmail/Cargo.toml
@@ -0,0 +1,25 @@
+[package]
+name = "libimagmail"
+version = "0.4.0"
+authors = ["Matthias Beyer <mail@beyermatthias.de>"]
+
+description = "Library for the imag core distribution"
+
+keywords = ["imag", "PIM", "personal", "information", "management"]
+readme = "../README.md"
+license = "LGPL-2.1"
+
+documentation = "https://matthiasbeyer.github.io/imag/imag_documentation/index.html"
+repository = "https://github.com/matthiasbeyer/imag"
+homepage = "http://imag-pim.org"
+
+[dependencies]
+log = "0.3"
+email = "0.0.17"
+semver = "0.5"
+toml = "0.4.*"
+filters = "0.1.*"
+
+libimagstore = { version = "0.4.0", path = "../../../lib/core/libimagstore" }
+libimagerror = { version = "0.4.0", path = "../../../lib/core/libimagerror" }
+libimagref = { version = "0.4.0", path = "../../../lib/entry/libimagentryref" }
diff --git a/lib/domain/libimagmail/README.md b/lib/domain/libimagmail/README.md
new file mode 120000
index 0000000..d5d8fb0
--- /dev/null
+++ b/lib/domain/libimagmail/README.md
@@ -0,0 +1 @@
+../doc/src/04020-module-mails.md \ No newline at end of file
diff --git a/lib/domain/libimagmail/src/error.rs b/lib/domain/libimagmail/src/error.rs
new file mode 100644
index 0000000..8c616fb
--- /dev/null
+++ b/lib/domain/libimagmail/src/error.rs
@@ -0,0 +1,35 @@
+//
+// 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
+//
+
+generate_error_module!(
+ generate_error_types!(MailError, MailErrorKind,
+ RefCreationError => "Error creating a reference to a file/directory",
+ RefHandlingError => "Error while handling the internal reference object",
+ MailParsingError => "Error while parsing mail",
+
+ FetchByHashError => "Error fetching mail from Store by hash",
+ FetchError => "Error fetching mail from Store",
+ IOError => "IO Error"
+ );
+);
+
+pub use self::error::MailError;
+pub use self::error::MailErrorKind;
+pub use self::error::MapErrInto;
+
diff --git a/lib/domain/libimagmail/src/hasher.rs b/lib/domain/libimagmail/src/hasher.rs
new file mode 100644
index 0000000..8ea3f03
--- /dev/null
+++ b/lib/domain/libimagmail/src/hasher.rs
@@ -0,0 +1,87 @@
+//
+// 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::io::Read;
+use std::path::PathBuf;
+
+use email::MimeMessage;
+
+use libimagref::hasher::Hasher;
+use libimagref::hasher::DefaultHasher;
+use libimagref::error::RefErrorKind as REK;
+use libimagref::error::MapErrInto;
+use libimagref::result::Result as RResult;
+use libimagerror::into::IntoError;
+
+use error::MailErrorKind as MEK;
+
+pub struct MailHasher {
+ defaulthasher: DefaultHasher,
+}
+
+impl MailHasher {
+
+ pub fn new() -> MailHasher {
+ MailHasher { defaulthasher: DefaultHasher::new() }
+ }
+
+}
+
+impl Hasher for MailHasher {
+
+ fn hash_name(&self) -> &'static str {
+ "default_mail_hasher"
+ }
+
+ fn create_hash<R: Read>(&mut self, pb: &PathBuf, c: &mut R) -> RResult<String> {
+ use filters::filter::Filter;
+ use email::Header;
+
+ let mut s = String::new();
+ try!(c.read_to_string(&mut s).map_err_into(REK::UTF8Error).map_err_into(REK::IOError));
+
+ MimeMessage::parse(&s)
+ .map_err(Box::new)
+ .map_err(|e| MEK::MailParsingError.into_error_with_cause(e))
+ .map_err_into(REK::RefHashingError)
+ .and_then(|mail| {
+ let has_key = |hdr: &Header, exp: &str| hdr.name == exp;
+
+ let subject_filter = |hdr: &Header| has_key(hdr, "Subject");
+ let from_filter = |hdr: &Header| has_key(hdr, "From");
+ let to_filter = |hdr: &Header| has_key(hdr, "To");
+
+ let filter = subject_filter.or(from_filter).or(to_filter);
+
+ let mut v : Vec<String> = vec![];
+ for hdr in mail.headers.iter().filter(|item| filter.filter(item)) {
+ let s = try!(hdr
+ .get_value()
+ .map_err(Box::new)
+ .map_err(|e| REK::RefHashingError.into_error_with_cause(e)));
+
+ v.push(s);
+ }
+ let s : String = v.join("");
+
+ self.defaulthasher.create_hash(pb, &mut s.as_bytes())
+ })
+ }
+
+}
diff --git a/lib/domain/libimagmail/src/iter.rs b/lib/domain/libimagmail/src/iter.rs
new file mode 100644
index 0000000..771d45a
--- /dev/null
+++ b/lib/domain/libimagmail/src/iter.rs
@@ -0,0 +1,56 @@
+//
+// 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
+//
+
+//! Module for the MailIter
+//!
+//! MailIter is a iterator which takes an Iterator that yields `Ref` and yields itself
+//! `Result<Mail>`, where `Err(_)` is returned if the Ref is not a Mail or parsing of the
+//! referenced mail file failed.
+//!
+
+use mail::Mail;
+use result::Result;
+
+use libimagref::reference::Ref;
+
+use std::marker::PhantomData;
+
+pub struct MailIter<'a, I: 'a + Iterator<Item = Ref<'a>>> {
+ _marker: PhantomData<&'a I>,
+ i: I,
+}
+
+impl<'a, I: Iterator<Item = Ref<'a>>> MailIter<'a, I> {
+
+ pub fn new(i: I) -> MailIter<'a, I> {
+ MailIter { _marker: PhantomData, i: i }
+ }
+
+}
+
+impl<'a, I: Iterator<Item = Ref<'a>>> Iterator for MailIter<'a, I> {
+
+ type Item = Result<Mail<'a>>;
+
+ fn next(&mut self) -> Option<Result<Mail<'a>>> {
+ self.i.next().map(Mail::from_ref)
+ }
+
+}
+
diff --git a/lib/domain/libimagmail/src/lib.rs b/lib/domain/libimagmail/src/lib.rs
new file mode 100644
index 0000000..cc25d1f
--- /dev/null
+++ b/lib/domain/libimagmail/src/lib.rs
@@ -0,0 +1,35 @@
+//
+// 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
+//
+
+#[macro_use] extern crate log;
+extern crate email;
+extern crate semver;
+extern crate toml;
+extern crate filters;
+
+#[macro_use] extern crate libimagerror;
+extern crate libimagstore;
+extern crate libimagref;
+
+pub mod error;
+pub mod hasher;
+pub mod iter;
+pub mod mail;
+pub mod result;
+
diff --git a/lib/domain/libimagmail/src/mail.rs b/lib/domain/libimagmail/src/mail.rs
new file mode 100644
index 0000000..6ee4d57
--- /dev/null
+++ b/lib/domain/libimagmail/src/mail.rs
@@ -0,0 +1,137 @@
+//
+// 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::path::Path;
+use std::path::PathBuf;
+use std::fs::File;
+use std::io::Read;
+
+use libimagstore::store::Store;
+use libimagref::reference::Ref;
+use libimagref::flags::RefFlags;
+
+use email::MimeMessage;
+use email::results::ParsingResult as EmailParsingResult;
+
+use hasher::MailHasher;
+use result::Result;
+use error::{MapErrInto, MailErrorKind as MEK};
+
+struct Buffer(String);
+
+impl Buffer {
+ pub fn parsed(&self) -> EmailParsingResult<MimeMessage> {
+ MimeMessage::parse(&self.0)
+ }
+}
+
+impl From<String> for Buffer {
+ fn from(data: String) -> Buffer {
+ Buffer(data)
+ }
+}
+
+pub struct Mail<'a>(Ref<'a>, Buffer);
+
+impl<'a> Mail<'a> {
+
+ /// Imports a mail from the Path passed
+ pub fn import_from_path<P: AsRef<Path>>(store: &Store, p: P) -> Result<Mail> {
+ let h = MailHasher::new();
+ let f = RefFlags::default().with_content_hashing(true).with_permission_tracking(false);
+ let p = PathBuf::from(p.as_ref());
+
+ Ref::create_with_hasher(store, p, f, h)
+ .map_err_into(MEK::RefCreationError)
+ .and_then(|reference| {
+ reference.fs_file()
+ .map_err_into(MEK::RefHandlingError)
+ .and_then(|path| File::open(path).map_err_into(MEK::IOError))
+ .and_then(|mut file| {
+ let mut s = String::new();
+ file.read_to_string(&mut s)
+ .map(|_| s)
+ .map_err_into(MEK::IOError)
+ })
+ .map(Buffer::from)
+ .map(|buffer| Mail(reference, buffer))
+ })
+ }
+
+ /// Opens a mail by the passed hash
+ pub fn open<S: AsRef<str>>(store: &Store, hash: S) -> Result<Option<Mail>> {
+ Ref::get_by_hash(store, String::from(hash.as_ref()))
+ .map_err_into(MEK::FetchByHashError)
+ .map_err_into(MEK::FetchError)
+ .and_then(|o| match o {
+ Some(r) => Mail::from_ref(r).map(Some),
+ None => Ok(None),
+ })
+
+ }
+
+ /// Implement me as TryFrom as soon as it is stable
+ pub fn from_ref(r: Ref<'a>) -> Result<Mail> {
+ r.fs_file()
+ .map_err_into(MEK::RefHandlingError)
+ .and_then(|path| File::open(path).map_err_into(MEK::IOError))
+ .and_then(|mut file| {
+ let mut s = String::new();
+ file.read_to_string(&mut s)
+ .map(|_| s)
+ .map_err_into(MEK::IOError)
+ })
+ .map(Buffer::from)
+ .map(|buffer| Mail(r, buffer))
+ }
+
+ pub fn get_field(&self, field: &str) -> Result<Option<String>> {
+ self.1
+ .parsed()
+ .map_err_into(MEK::MailParsingError)
+ .map(|parsed| {
+ parsed.headers
+ .iter()
+ .filter(|hdr| hdr.name == field)
+ .nth(0)
+ .and_then(|field| field.get_value().ok())
+ })
+ }
+
+ pub fn get_from(&self) -> Result<Option<String>> {
+ self.get_field("From")
+ }
+
+ pub fn get_to(&self) -> Result<Option<String>> {
+ self.get_field("To")
+ }
+
+ pub fn get_subject(&self) -> Result<Option<String>> {
+ self.get_field("Subject")
+ }
+
+ pub fn get_message_id(&self) -> Result<Option<String>> {
+ self.get_field("Message-ID")
+ }
+
+ pub fn get_in_reply_to(&self) -> Result<Option<String>> {
+ self.get_field("In-Reply-To")
+ }
+
+}
diff --git a/lib/domain/libimagmail/src/result.rs b/lib/domain/libimagmail/src/result.rs
new file mode 100644
index 0000000..7f745ed
--- /dev/null
+++ b/lib/domain/libimagmail/src/result.rs
@@ -0,0 +1,25 @@
+//
+// 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 error::MailError;
+
+pub type Result<T> = RResult<T, MailError>;
+
diff --git a/lib/domain/libimagnotes/Cargo.toml b/lib/domain/libimagnotes/Cargo.toml
new file mode 100644
index 0000000..d3616cd
--- /dev/null
+++ b/lib/domain/libimagnotes/Cargo.toml
@@ -0,0 +1,25 @@
+[package]
+name = "libimagnotes"
+version = "0.4.0"
+authors = ["Matthias Beyer <mail@beyermatthias.de>"]
+
+description = "Library for the imag core distribution"
+
+keywords = ["imag", "PIM", "personal", "information", "management"]
+readme = "../README.md"
+license = "LGPL-2.1"
+
+documentation = "https://matthiasbeyer.github.io/imag/imag_documentation/index.html"
+repository = "https://github.com/matthiasbeyer/imag"
+homepage = "http://imag-pim.org"
+
+[dependencies]
+semver = "0.5"
+log = "0.3"
+toml = "^0.4"
+
+libimagstore = { version = "0.4.0", path = "../../../lib/core/libimagstore" }
+libimagerror = { version = "0.4.0", path = "../../../lib/core/libimagerror" }
+libimagrt = { version = "0.4.0", path = "../../../lib/core/libimagrt" }
+libimagentryedit = { version = "0.4.0", path = "../../../lib/entry/libimagentryedit" }
+libimagentrytag = { version = "0.4.0", path = "../../../lib/entry/libimagentrytag" }
diff --git a/lib/domain/libimagnotes/src/error.rs b/lib/domain/libimagnotes/src/error.rs
new file mode 100644
index 0000000..9fd5121
--- /dev/null
+++ b/lib/domain/libimagnotes/src/error.rs
@@ -0,0 +1,32 @@
+//
+// 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
+//
+
+generate_error_module!(
+ generate_error_types!(NoteError, NoteErrorKind,
+ StoreWriteError => "Error writing store",
+ StoreReadError => "Error reading store",
+ HeaderTypeError => "Header type error",
+ NoteToEntryConversion => "Error converting Note instance to Entry instance"
+ );
+);
+
+pub use self::error::NoteError;
+pub use self::error::NoteErrorKind;
+pub use self::error::MapErrInto;
+
diff --git a/lib/domain/libimagnotes/src/lib.rs b/lib/domain/libimagnotes/src/lib.rs
new file mode 100644
index 0000000..10ef952
--- /dev/null
+++ b/lib/domain/libimagnotes/src/lib.rs
@@ -0,0 +1,51 @@
+//
+// 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
+//
+
+#![deny(
+ dead_code,
+ non_camel_case_types,
+ non_snake_case,
+ path_statements,
+ trivial_numeric_casts,
+ unstable_features,
+ unused_allocation,
+ unused_import_braces,
+ unused_imports,
+ unused_must_use,
+ unused_mut,
+ unused_qualifications,
+ while_true,
+)]
+
+#[macro_use] extern crate log;
+extern crate semver;
+extern crate toml;
+
+extern crate libimagrt;
+#[macro_use] extern crate libimagstore;
+#[macro_use] extern crate libimagerror;
+extern crate libimagentryedit;
+extern crate libimagentrytag;
+
+module_entry_path_mod!("notes");
+
+pub mod error;
+pub mod note;
+pub mod result;
+
diff --git a/lib/domain/libimagnotes/src/note.rs b/lib/domain/libimagnotes/src/note.rs
new file mode 100644
index 0000000..cc50bab
--- /dev/null
+++ b/lib/domain/libimagnotes/src/note.rs
@@ -0,0 +1,204 @@
+//
+// 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::collections::BTreeMap;
+use std::ops::Deref;
+
+use toml::Value;
+
+use libimagrt::runtime::Runtime;
+use libimagentryedit::edit::Edit;
+use libimagentryedit::result::Result as EditResult;
+use libimagstore::storeid::IntoStoreId;
+use libimagstore::storeid::StoreId;
+use libimagstore::storeid::StoreIdIterator;
+use libimagstore::store::FileLockEntry;
+use libimagstore::store::Store;
+use libimagstore::toml_ext::TomlValueExt;
+
+use module_path::ModuleEntryPath;
+use result::Result;
+use error::NoteError as NE;
+use error::NoteErrorKind as NEK;
+use error::MapErrInto;
+
+#[derive(Debug)]
+pub struct Note<'a> {
+ entry: FileLockEntry<'a>,
+}
+
+impl<'a> Note<'a> {
+
+ pub fn new(store: &Store, name: String, text: String) -> Result<Note> {
+ use std::ops::DerefMut;
+
+ debug!("Creating new Note: '{}'", name);
+ let fle = {
+ let mut lockentry = try!(ModuleEntryPath::new(name.clone())
+ .into_storeid()
+ .and_then(|id| store.create(id))
+ .map_err_into(NEK::StoreWriteError));
+
+ {
+ let mut entry = lockentry.deref_mut();
+
+ {
+ let mut header = entry.get_header_mut();
+ let setres = header.set("note", Value::Table(BTreeMap::new()));
+ if setres.is_err() {
+ let kind = NEK::StoreWriteError;
+ return Err(NE::new(kind, Some(Box::new(setres.unwrap_err()))));
+ }
+
+ let setres = header.set("note.name", Value::String(name));
+ if setres.is_err() {
+ let kind = NEK::StoreWriteError;
+ return Err(NE::new(kind, Some(Box::new(setres.unwrap_err()))));
+ }
+ }
+
+ *entry.get_content_mut() = text;
+ }
+
+ lockentry
+ };
+
+ Ok(Note { entry: fle })
+ }
+
+ pub fn set_name(&mut self, n: String) -> Result<()> {
+ let mut header = self.entry.get_header_mut();
+ header.set("note.name", Value::String(n))
+ .map_err(|e| NE::new(NEK::StoreWriteError, Some(Box::new(e))))
+ .map(|_| ())
+ }
+
+ pub fn get_name(&self) -> Result<String> {
+ let header = self.entry.get_header();
+ match header.read("note.name") {
+ Ok(Some(Value::String(s))) => Ok(String::from(s)),
+ Ok(_) => {
+ let e = NE::new(NEK::HeaderTypeError, None);
+ Err(NE::new(NEK::StoreReadError, Some(Box::new(e))))
+ },
+ Err(e) => Err(NE::new(NEK::StoreReadError, Some(Box::new(e))))
+ }
+ }
+
+ pub fn set_text(&mut self, n: String) {
+ *self.entry.get_content_mut() = n
+ }
+
+ pub fn get_text(&self) -> &String {
+ self.entry.get_content()
+ }
+
+ pub fn delete(store: &Store, name: String) -> Result<()> {
+ ModuleEntryPath::new(name)
+ .into_storeid()
+ .and_then(|id| store.delete(id))
+ .map_err_into(NEK::StoreWriteError)
+ }
+
+ pub fn retrieve(store: &Store, name: String) -> Result<Note> {
+ ModuleEntryPath::new(name)
+ .into_storeid()
+ .and_then(|id| store.retrieve(id))
+ .map_err_into(NEK::StoreWriteError)
+ .map(|entry| Note { entry: entry })
+ }
+
+ pub fn get(store: &Store, name: String) -> Result<Option<Note>> {
+ ModuleEntryPath::new(name)
+ .into_storeid()
+ .and_then(|id| store.get(id))
+ .map_err_into(NEK::StoreWriteError)
+ .map(|o| o.map(|entry| Note { entry: entry }))
+ }
+
+ pub fn all_notes(store: &Store) -> Result<NoteIterator> {
+ store.retrieve_for_module("notes")
+ .map(|iter| NoteIterator::new(store, iter))
+ .map_err(|e| NE::new(NEK::StoreReadError, Some(Box::new(e))))
+ }
+
+}
+
+impl<'a> Edit for Note<'a> {
+
+ fn edit_content(&mut self, rt: &Runtime) -> EditResult<()> {
+ self.entry.edit_content(rt)
+ }
+
+}
+
+trait FromStoreId {
+ fn from_storeid(&Store, StoreId) -> Result<Note>;
+}
+
+impl<'a> FromStoreId for Note<'a> {
+
+ fn from_storeid(store: &Store, id: StoreId) -> Result<Note> {
+ debug!("Loading note from storeid: '{:?}'", id);
+ match store.retrieve(id) {
+ Err(e) => Err(NE::new(NEK::StoreReadError, Some(Box::new(e)))),
+ Ok(entry) => Ok(Note { entry: entry }),
+ }
+ }
+
+}
+
+impl<'a> Deref for Note<'a> {
+
+ type Target = FileLockEntry<'a>;
+
+ fn deref(&self) -> &FileLockEntry<'a> {
+ &self.entry
+ }
+
+}
+
+#[derive(Debug)]
+pub struct NoteIterator<'a> {
+ store: &'a Store,
+ iditer: StoreIdIterator,
+}
+
+impl<'a> NoteIterator<'a> {
+
+ pub fn new(store: &'a Store, iditer: StoreIdIterator) -> NoteIterator<'a> {
+ NoteIterator {
+ store: store,
+ iditer: iditer,
+ }
+ }
+
+}
+
+impl<'a> Iterator for NoteIterator<'a> {
+ type Item = Result<Note<'a>>;
+
+ fn next(&mut self) -> Option<Result<Note<'a>>> {
+ self.iditer
+ .next()
+ .map(|id| Note::from_storeid(self.store, id))
+ }
+
+}
+
diff --git a/lib/domain/libimagnotes/src/result.rs b/lib/domain/libimagnotes/src/result.rs
new file mode 100644
index 0000000..a11ba89
--- /dev/null
+++ b/lib/domain/libimagnotes/src/result.rs
@@ -0,0 +1,25 @@
+//
+// 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 error::NoteError;
+
+pub type Result<T> = RResult<T, NoteError>;
+
diff --git a/lib/domain/libimagtodo/Cargo.toml b/lib/domain/libimagtodo/Cargo.toml
new file mode 100644
index 0000000..65b4ee2
--- /dev/null
+++ b/lib/domain/libimagtodo/Cargo.toml
@@ -0,0 +1,28 @@
+[package]
+name = "libimagtodo"
+version = "0.4.0"
+authors = ["mario <mario-krehl@gmx.de>"]
+
+description = "Library for the imag core distribution"
+
+keywords = ["imag", "PIM", "personal", "information", "management"]
+readme = "../README.md"
+license = "LGPL-2.1"
+
+documentation = "https://matthiasbeyer.github.io/imag/imag_documentation/index.html"
+repository = "https://github.com/matthiasbeyer/imag"
+homepage = "http://imag-pim.org"
+
+[dependencies]
+semver = "0.2"
+task-hookrs = "0.2.2"
+uuid = "0.3"
+toml = "0.4.*"
+toml-query = "0.3.*"
+is-match = "0.1.*"
+log = "0.3"
+serde_json = "0.8"
+
+libimagstore = { version = "0.4.0", path = "../../../lib/core/libimagstore" }
+libimagerror = { version = "0.4.0", path = "../../../lib/core/libimagerror" }
+libimagutil = { version = "0.4.0", path = "../../../lib/etc/libimagutil" }
diff --git a/lib/domain/libimagtodo/src/error.rs b/lib/domain/libimagtodo/src/error.rs
new file mode 100644
index 0000000..c19a6e5
--- /dev/null
+++ b/lib/domain/libimagtodo/src/error.rs
@@ -0,0 +1,32 @@
+//
+// 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
+//
+
+generate_error_module!(
+ generate_error_types!(TodoError, TodoErrorKind,
+ ConversionError => "Conversion Error",
+ StoreError => "Store Error",
+ StoreIdError => "Store Id handling error",
+ ImportError => "Error importing"
+ );
+);
+
+pub use self::error::TodoError;
+pub use self::error::TodoErrorKind;
+pub use self::error::MapErrInto;
+
diff --git a/lib/domain/libimagtodo/src/lib.rs b/lib/domain/libimagtodo/src/lib.rs
new file mode 100644
index 0000000..16fcc11
--- /dev/null
+++ b/lib/domain/libimagtodo/src/lib.rs
@@ -0,0 +1,52 @@
+//
+// 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
+//
+
+#![deny(
+ non_camel_case_types,
+ non_snake_case,
+ path_statements,
+ trivial_numeric_casts,
+ unstable_features,
+ unused_allocation,
+ unused_import_braces,
+ unused_imports,
+ unused_mut,
+ unused_qualifications,
+ while_true,
+)]
+
+extern crate semver;
+extern crate uuid;
+extern crate toml;
+extern crate toml_query;
+#[macro_use] extern crate is_match;
+#[macro_use] extern crate log;
+extern crate serde_json;
+
+#[macro_use] extern crate libimagstore;
+#[macro_use] extern crate libimagerror;
+extern crate libimagutil;
+extern crate task_hookrs;
+
+module_entry_path_mod!("todo");
+
+pub mod error;
+pub mod result;
+pub mod task;
+
diff --git a/lib/domain/libimagtodo/src/result.rs b/lib/domain/libimagtodo/src/result.rs
new file mode 100644
index 0000000..7962851
--- /dev/null
+++ b/lib/domain/libimagtodo/src/result.rs
@@ -0,0 +1,24 @@
+//
+// 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 error::TodoError;
+
+use std::result::Result as RResult;
+
+pub type Result<T> = RResult<T, TodoError>;
diff --git a/lib/domain/libimagtodo/src/task.rs b/lib/domain/libimagtodo/src/task.rs
new file mode 100644
index 0000000..5e31d95
--- /dev/null
+++ b/lib/domain/libimagtodo/src/task.rs
@@ -0,0 +1,294 @@
+//
+// 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::collections::BTreeMap;
+use std::ops::{Deref, DerefMut};
+use std::io::BufRead;
+use std::result::Result as RResult;
+
+use toml::Value;
+use uuid::Uuid;
+
+use task_hookrs::task::Task as TTask;
+use task_hookrs::import::{import_task, import_tasks};
+
+use libimagstore::store::{FileLockEntry, Store};
+use libimagstore::storeid::{IntoStoreId, StoreIdIterator, StoreId};
+use module_path::ModuleEntryPath;
+
+use error::{TodoErrorKind as TEK, MapErrInto};
+use result::Result;
+
+/// Task struct containing a `FileLockEntry`
+#[derive(Debug)]
+pub struct Task<'a>(FileLockEntry<'a>);
+
+impl<'a> Task<'a> {
+
+ /// Concstructs a new `Task` with a `FileLockEntry`
+ pub fn new(fle: FileLockEntry<'a>) -> Task<'a> {
+ Task(fle)
+ }
+
+ pub fn import<R: BufRead>(store: &'a Store, mut r: R) -> Result<(Task<'a>, String, Uuid)> {
+ let mut line = String::new();
+ r.read_line(&mut line);
+ import_task(&line.as_str())
+ .map_err_into(TEK::ImportError)
+ .and_then(|t| {
+ let uuid = t.uuid().clone();
+ t.into_task(store).map(|t| (t, line, uuid))
+ })
+ }
+
+ /// Get a task from an import string. That is: read the imported string, get the UUID from it
+ /// and try to load this UUID from store.
+ ///
+ /// Possible return values are:
+ ///
+ /// * Ok(Ok(Task))
+ /// * Ok(Err(String)) - where the String is the String read from the `r` parameter
+ /// * Err(_) - where the error is an error that happened during evaluation
+ ///
+ pub fn get_from_import<R>(store: &'a Store, mut r: R) -> Result<RResult<Task<'a>, String>>
+ where R: BufRead
+ {
+ let mut line = String::new();
+ r.read_line(&mut line);
+ Task::get_from_string(store, line)
+ }
+
+ /// Get a task from a String. The String is expected to contain the JSON-representation of the
+ /// Task to get from the store (only the UUID really matters in this case)
+ ///
+ /// For an explanation on the return values see `Task::get_from_import()`.
+ pub fn get_from_string(store: &'a Store, s: String) -> Result<RResult<Task<'a>, String>> {
+ import_task(s.as_str())
+ .map_err_into(TEK::ImportError)
+ .map(|t| t.uuid().clone())
+ .and_then(|uuid| Task::get_from_uuid(store, uuid))
+ .and_then(|o| match o {
+ None => Ok(Err(s)),
+ Some(t) => Ok(Ok(t)),
+ })
+ }
+
+ /// Get a task from an UUID.
+ ///
+ /// If there is no task with this UUID, this returns `Ok(None)`.
+ pub fn get_from_uuid(store: &'a Store, uuid: Uuid) -> Result<Option<Task<'a>>> {
+ ModuleEntryPath::new(format!("taskwarrior/{}", uuid))
+ .into_storeid()
+ .and_then(|store_id| store.get(store_id))
+ .map(|o| o.map(Task::new))
+ .map_err_into(TEK::StoreError)
+ }
+
+ /// Same as Task::get_from_import() but uses Store::retrieve() rather than Store::get(), to
+ /// implicitely create the task if it does not exist.
+ pub fn retrieve_from_import<R: BufRead>(store: &'a Store, mut r: R) -> Result<Task<'a>> {
+ let mut line = String::new();
+ r.read_line(&mut line);
+ Task::retrieve_from_string(store, line)
+ }
+
+ /// Retrieve a task from a String. The String is expected to contain the JSON-representation of
+ /// the Task to retrieve from the store (only the UUID really matters in this case)
+ pub fn retrieve_from_string(store: &'a Store, s: String) -> Result<Task<'a>> {
+ Task::get_from_string(store, s)
+ .and_then(|opt| match opt {
+ Ok(task) => Ok(task),
+ Err(string) => import_task(string.as_str())
+ .map_err_into(TEK::ImportError)
+ .and_then(|t| t.into_task(store)),
+ })
+ }
+
+ pub fn delete_by_imports<R: BufRead>(store: &Store, r: R) -> Result<()> {
+ use serde_json::ser::to_string as serde_to_string;
+ use task_hookrs::status::TaskStatus;
+
+ for (counter, res_ttask) in import_tasks(r).into_iter().enumerate() {
+ match res_ttask {
+ Ok(ttask) => {
+ if counter % 2 == 1 {
+ // Only every second task is needed, the first one is the
+ // task before the change, and the second one after
+ // the change. The (maybe modified) second one is
+ // expected by taskwarrior.
+ match serde_to_string(&ttask).map_err_into(TEK::ImportError) {
+ // use println!() here, as we talk with TW
+ Ok(val) => println!("{}", val),
+ Err(e) => return Err(e),
+ }
+
+ // Taskwarrior does not have the concept of deleted tasks, but only modified
+ // ones.
+ //
+ // Here we check if the status of a task is deleted and if yes, we delete it
+ // from the store.
+ if *ttask.status() == TaskStatus::Deleted {
+ match Task::delete_by_uuid(store, *ttask.uuid()) {
+ Ok(_) => info!("Deleted task {}", *ttask.uuid()),
+ Err(e) => return Err(e),
+ }
+ }
+ } // end if c % 2
+ },
+ Err(e) => return Err(e).map_err_into(TEK::ImportError),
+ }
+ }
+ Ok(())
+ }
+
+ pub fn delete_by_uuid(store: &Store, uuid: Uuid) -> Result<()> {
+ ModuleEntryPath::new(format!("taskwarrior/{}", uuid))
+ .into_storeid()
+ .and_then(|id| store.delete(id))
+ .map_err_into(TEK::StoreError)
+ }
+
+ pub fn all_as_ids(store: &Store) -> Result<StoreIdIterator> {
+ store.retrieve_for_module("todo/taskwarrior")
+ .map_err_into(TEK::StoreError)
+ }
+
+ pub fn all(store: &Store) -> Result<TaskIterator> {
+ Task::all_as_ids(store)
+ .map(|iter| TaskIterator::new(store, iter))
+ }
+
+}
+
+impl<'a> Deref for Task<'a> {
+ type Target = FileLockEntry<'a>;
+
+ fn deref(&self) -> &FileLockEntry<'a> {
+ &self.0
+ }
+
+}
+
+impl<'a> DerefMut for Task<'a> {
+
+ fn deref_mut(&mut self) -> &mut FileLockEntry<'a> {
+ &mut self.0
+ }
+
+}
+
+/// A trait to get a `libimagtodo::task::Task` out of the implementing object.
+pub trait IntoTask<'a> {
+
+ /// # Usage
+ /// ```ignore
+ /// use std::io::stdin;
+ ///
+ /// use task_hookrs::task::Task;
+ /// use task_hookrs::import::import;
+ /// use libimagstore::store::{Store, FileLockEntry};
+ ///
+ /// if let Ok(task_hookrs_task) = import(stdin()) {
+ /// // Store is given at runtime
+ /// let task = task_hookrs_task.into_filelockentry(store);
+ /// println!("Task with uuid: {}", task.flentry.get_header().get("todo.uuid"));
+ /// }
+ /// ```
+ fn into_task(self, store : &'a Store) -> Result<Task<'a>>;
+
+}
+
+impl<'a> IntoTask<'a> for TTask {
+
+ fn into_task(self, store : &'a Store) -> Result<Task<'a>> {
+ use toml_query::read::TomlValueReadExt;
+ use toml_query::set::TomlValueSetExt;
+
+ // Helper for toml_query::read::TomlValueReadExt::read() return value, which does only
+ // return Result<T> instead of Result<Option<T>>, which is a real inconvenience.
+ //
+ let no_identifier = |e: &::toml_query::error::Error| -> bool {
+ is_match!(e.kind(), &::toml_query::error::ErrorKind::IdentifierNotFoundInDocument(_))
+ };
+
+ let uuid = self.uuid();
+ ModuleEntryPath::new(format!("taskwarrior/{}", uuid))
+ .into_storeid()
+ .map_err_into(TEK::StoreIdError)
+ .and_then(|id| {
+ store.retrieve(id)
+ .map_err_into(TEK::StoreError)
+ .and_then(|mut fle| {
+ {
+ let mut hdr = fle.get_header_mut();
+ if try!(hdr.read("todo").map_err_into(TEK::StoreError)).is_none() {
+ try!(hdr
+ .set("todo", Value::Table(BTreeMap::new()))
+ .map_err_into(TEK::StoreError));
+ }
+
+ try!(hdr.set("todo.uuid", Value::String(format!("{}",uuid)))
+ .map_err_into(TEK::StoreError));
+ }
+
+ // If none of the errors above have returned the function, everything is fine
+ Ok(Task::new(fle))
+ })
+ })
+ }
+
+}
+
+trait FromStoreId {
+ fn from_storeid<'a>(&'a Store, StoreId) -> Result<Task<'a>>;
+}
+
+impl<'a> FromStoreId for Task<'a> {
+
+ fn from_storeid<'b>(store: &'b Store, id: StoreId) -> Result<Task<'b>> {
+ store.retrieve(id)
+ .map_err_into(TEK::StoreError)
+ .map(Task::new)
+ }
+}
+
+pub struct TaskIterator<'a> {
+ store: &'a Store,
+ iditer: StoreIdIterator,
+}
+
+impl<'a> TaskIterator<'a> {
+
+ pub fn new(store: &'a Store, iditer: StoreIdIterator) -> TaskIterator<'a> {
+ TaskIterator {
+ store: store,
+ iditer: iditer,
+ }
+ }
+
+}
+
+impl<'a> Iterator for TaskIterator<'a> {
+ type Item = Result<Task<'a>>;
+
+ fn next(&mut self) -> Option<Result<Task<'a>>> {
+ self.iditer.next().map(|id| Task::from_storeid(self.store, id))
+ }
+}
+
diff --git a/lib/entry/libimagentryannotation/Cargo.toml b/lib/entry/libimagentryannotation/Cargo.toml
new file mode 100644
index 0000000..1b8716f
--- /dev/null
+++ b/lib/entry/libimagentryannotation/Cargo.toml
@@ -0,0 +1,21 @@
+[package]
+name = "libimagannotation"
+version = "0.4.0"
+authors = ["Matthias Beyer <mail@beyermatthias.de>"]
+
+description = "Library for the imag core distribution"
+
+keywords = ["imag", "PIM", "personal", "information", "management"]
+readme = "../README.md"
+license = "LGPL-2.1"
+
+[dependencies]
+uuid = { version = "0.3.1", features = ["v4"] }
+lazy_static = "0.1.15"
+toml = "^0.4"
+
+libimagstore = { version = "0.4.0", path = "../../../lib/core/libimagstore" }
+libimagerror = { version = "0.4.0", path = "../../../lib/core/libimagerror" }
+libimagnotes = { version = "0.4.0", path = "../../../lib/domain/libimagnotes" }
+libimagentrylink = { version = "0.4.0", path = "../../../lib/entry/libimagentrylink" }
+libimagutil = { version = "0.4.0", path = "../../../lib/etc/libimagutil" }
diff --git a/lib/entry/libimagentryannotation/README.md b/lib/entry/libimagentryannotation/README.md
new file mode 120000
index 0000000..5c4afa4
--- /dev/null
+++ b/lib/entry/libimagentryannotation/README.md
@@ -0,0 +1 @@
+../doc/src/05100-lib-annotation.md \ No newline at end of file
diff --git a/lib/entry/libimagentryannotation/src/annotateable.rs b/lib/entry/libimagentryannotation/src/annotateable.rs
new file mode 100644
index 0000000..8827916
--- /dev/null
+++ b/lib/entry/libimagentryannotation/src/annotateable.rs
@@ -0,0 +1,84 @@
+//
+// 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::path::PathBuf;
+
+use toml::Value;
+
+use libimagstore::store::Entry;
+use libimagstore::store::FileLockEntry;
+use libimagstore::store::Store;
+use libimagstore::toml_ext::TomlValueExt;
+use libimagentrylink::internal::InternalLinker;
+use libimagerror::into::IntoError;
+
+use result::Result;
+use error::AnnotationErrorKind as AEK;
+use error::MapErrInto;
+
+pub trait Annotateable {
+
+ /// Add an annotation to `Self`, that is a `FileLockEntry` which is linked to `Self` (link as in
+ /// libimagentrylink).
+ ///
+ /// A new annotation also has the field `annotation.is_annotation` set to `true`.
+ fn annotate<'a>(&mut self, store: &'a Store, ann_name: &str) -> Result<FileLockEntry<'a>>;
+
+ /// Check whether an entry is an annotation
+ fn is_annotation(&self) -> Result<bool>;
+
+}
+
+impl Annotateable for Entry {
+
+ fn annotate<'a>(&mut self, store: &'a Store, ann_name: &str) -> Result<FileLockEntry<'a>> {
+ store.retrieve(PathBuf::from(ann_name))
+ .map_err_into(AEK::StoreWriteError)
+ .and_then(|mut anno| {
+ anno.get_header_mut()
+ .insert("annotation.is_annotation", Value::Boolean(true))
+ .map_err_into(AEK::StoreWriteError)
+ .and_then(|succeeded| {
+ if succeeded {
+ Ok(anno)
+ } else {
+ Err(AEK::HeaderWriteError.into_error())
+ }
+ })
+ })
+ .and_then(|mut anno| {
+ anno.add_internal_link(self)
+ .map_err_into(AEK::LinkingError)
+ .map(|_| anno)
+ })
+ }
+
+ fn is_annotation(&self) -> Result<bool> {
+ self.get_header()
+ .read("annotation.is_annotation")
+ .map_err_into(AEK::StoreReadError)
+ .and_then(|res| match res {
+ Some(Value::Boolean(b)) => Ok(b),
+ None => Ok(false),
+ _ => Err(AEK::HeaderTypeError.into_error()),
+ })
+ }
+
+}
+
diff --git a/lib/entry/libimagentryannotation/src/annotation_fetcher.rs b/lib/entry/libimagentryannotation/src/annotation_fetcher.rs
new file mode 100644
index 0000000..18c4a8b
--- /dev/null
+++ b/lib/entry/libimagentryannotation/src/annotation_fetcher.rs
@@ -0,0 +1,115 @@
+//
+// 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 libimagstore::store::Entry;
+use libimagstore::store::Store;
+use libimagentrylink::internal::InternalLinker;
+use libimagnotes::note::Note;
+use libimagnotes::note::NoteIterator;
+use libimagstore::storeid::StoreIdIterator;
+
+use result::Result;
+use error::AnnotationErrorKind as AEK;
+use error::MapErrInto;
+
+use self::iter::*;
+
+pub trait AnnotationFetcher<'a> {
+
+ fn all_annotations(&'a self) -> Result<AnnotationIter<'a>>;
+
+ fn annotations_for_entry(&'a self, entry: &Entry) -> Result<AnnotationIter<'a>>;
+
+}
+
+impl<'a> AnnotationFetcher<'a> for Store {
+
+ /// Wrapper around `Note::all_notes()` of `libimagnotes` which filters out normal notes and
+ /// leaves only annotations in the iterator.
+ fn all_annotations(&'a self) -> Result<AnnotationIter<'a>> {
+ Note::all_notes(self)
+ .map(|iter| AnnotationIter::new(iter))
+ .map_err_into(AEK::StoreReadError)
+ }
+
+ /// Get all annotations (in an iterator) for an entry
+ ///
+ /// Internally, this fetches the links of the entry, fetches all the entries behind the links
+ /// and filters them for annotations.
+ ///
+ /// This might result in some heavy IO operations if a lot of stuff is linked to a single
+ /// entry, but should normally be not that heavy.
+ fn annotations_for_entry(&'a self, entry: &Entry) -> Result<AnnotationIter<'a>> {
+ entry.get_internal_links()
+ .map_err_into(AEK::StoreReadError)
+ .map(|iter| StoreIdIterator::new(Box::new(iter.map(|e| e.get_store_id().clone()))))
+ .map(|iter| NoteIterator::new(self, iter))
+ .map(|iter| AnnotationIter::new(iter))
+ }
+
+}
+
+pub mod iter {
+ use toml::Value;
+
+ use libimagstore::toml_ext::TomlValueExt;
+ use libimagerror::into::IntoError;
+ use libimagnotes::note::Note;
+ use libimagnotes::note::NoteIterator;
+
+ use result::Result;
+ use error::AnnotationErrorKind as AEK;
+ use error::MapErrInto;
+
+ #[derive(Debug)]
+ pub struct AnnotationIter<'a>(NoteIterator<'a>);
+
+ impl<'a> AnnotationIter<'a> {
+
+ pub fn new(noteiter: NoteIterator<'a>) -> AnnotationIter<'a> {
+ AnnotationIter(noteiter)
+ }
+
+ }
+
+ impl<'a> Iterator for AnnotationIter<'a> {
+ type Item = Result<Note<'a>>;
+
+ fn next(&mut self) -> Option<Result<Note<'a>>> {
+ loop {
+ match self.0.next() {
+ Some(Ok(note)) => {
+ let hdr = note.get_header().read("annotation.is_annotation");
+ match hdr {
+ Ok(None) => continue, // not an annotation
+ Ok(Some(Value::Boolean(true))) => return Some(Ok(note)),
+ Ok(Some(_)) => return Some(Err(AEK::HeaderTypeError.into_error())),
+ Err(e) => return Some(Err(e).map_err_into(AEK::HeaderReadError)),
+ }
+ },
+ Some(Err(e)) => return Some(Err(e).map_err_into(AEK::StoreReadError)),
+ None => return None, // iterator consumed
+ }
+ }
+ }
+
+ }
+
+}
+
diff --git a/lib/entry/libimagentryannotation/src/error.rs b/lib/entry/libimagentryannotation/src/error.rs
new file mode 100644
index 0000000..25fc004
--- /dev/null
+++ b/lib/entry/libimagentryannotation/src/error.rs
@@ -0,0 +1,35 @@
+//
+// 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
+//
+
+generate_error_module!(
+ generate_error_types!(AnnotationError, AnnotationErrorKind,
+ StoreReadError => "Store read error",
+ StoreWriteError => "Store write error",
+
+ LinkingError => "Error while linking",
+ HeaderWriteError => "Couldn't write Header for annotation",
+ HeaderReadError => "Couldn't read Header of Entry",
+ HeaderTypeError => "Header field has unexpected type"
+ );
+);
+
+pub use self::error::AnnotationError;
+pub use self::error::AnnotationErrorKind;
+pub use self::error::MapErrInto;
+
diff --git a/lib/entry/libimagentryannotation/src/lib.rs b/lib/entry/libimagentryannotation/src/lib.rs
new file mode 100644
index 0000000..0cb4e77
--- /dev/null
+++ b/lib/entry/libimagentryannotation/src/lib.rs
@@ -0,0 +1,33 @@
+//
+// 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
+//
+
+extern crate uuid;
+extern crate toml;
+
+#[macro_use] extern crate libimagerror;
+#[macro_use] extern crate libimagstore;
+extern crate libimagentrylink;
+extern crate libimagnotes;
+extern crate libimagutil;
+
+pub mod annotateable;
+pub mod annotation_fetcher;
+pub mod error;
+pub mod result;
+
diff --git a/lib/entry/libimagentryannotation/src/result.rs b/lib/entry/libimagentryannotation/src/result.rs
new file mode 100644
index 0000000..292fc0e
--- /dev/null
+++ b/lib/entry/libimagentryannotation/src/result.rs
@@ -0,0 +1,26 @@
+//
+// 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 error::AnnotationError;
+
+pub type Result<T> = RResult<T, AnnotationError>;
+
+
diff --git a/lib/entry/libimagentrycategory/Cargo.toml b/lib/entry/libimagentrycategory/Cargo.toml
new file mode 100644
index 0000000..d3f1618
--- /dev/null
+++ b/lib/entry/libimagentrycategory/Cargo.toml
@@ -0,0 +1,27 @@
+[package]
+name = "libimagentrycategory"
+version = "0.4.0"
+authors = ["Matthias Beyer <mail@beyermatthias.de>"]
+
+description = "Library for the imag core distribution"
+
+keywords = ["imag", "PIM", "personal", "information", "management"]
+readme = "../README.md"
+license = "LGPL-2.1"
+
+documentation = "https://matthiasbeyer.github.io/imag/imag_documentation/index.html"
+repository = "https://github.com/matthiasbeyer/imag"
+homepage = "http://imag-pim.org"
+
+[dependencies]
+log = "0.3"
+toml = "0.4"
+toml-query = "0.3"
+is-match = "0.1"
+
+libimagerror = { version = "0.4.0", path = "../../../lib/core/libimagerror" }
+libimagstore = { version = "0.4.0", path = "../../../lib/core/libimagstore" }
+
+[dev-dependencies]
+env_logger = "0.4"
+
diff --git a/lib/entry/libimagentrycategory/src/category.rs b/lib/entry/libimagentrycategory/src/category.rs
new file mode 100644
index 0000000..1e77495
--- /dev/null
+++ b/lib/entry/libimagentrycategory/src/category.rs
@@ -0,0 +1,103 @@
+//
+// 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_query::insert::TomlValueInsertExt;
+use toml_query::read::TomlValueReadExt;
+use toml_query::error::ErrorKind as TQEK;
+use toml::Value;
+
+use libimagstore::store::Entry;
+use libimagerror::into::IntoError;
+
+use error::CategoryErrorKind as CEK;
+use error::MapErrInto;
+use result::Result;
+use register::CategoryRegister;
+
+#[derive(Clone, Debug, Eq, PartialEq, Ord, PartialOrd)]
+pub struct Category(String);
+
+impl From<String> for Category {
+
+ fn from(s: String) -> Category {
+ Category(s)
+ }
+
+}
+
+impl Into<String> for Category {
+ fn into(self) -> String {
+ self.0
+ }
+}
+
+pub trait EntryCategory {
+
+ fn set_category(&mut self, s: Category) -> Result<()>;
+
+ fn set_category_checked(&mut self, register: &CategoryRegister, s: Category) -> Result<()>;
+
+ fn get_category(&self) -> Result<Option<Category>>;
+
+ fn has_category(&self) -> Result<bool>;
+
+}
+
+impl EntryCategory for Entry {
+
+ fn set_category(&mut self, s: Category) -> Result<()> {
+ self.get_header_mut()
+ .insert(&String::from("category.value"), Value::String(s.into()))
+ .map_err_into(CEK::HeaderWriteError)
+ .map(|_| ())
+ }
+
+ /// Check whether a category exists before setting it.
+ ///
+ /// This function should be used by default over EntryCategory::set_category()!
+ fn set_category_checked(&mut self, register: &CategoryRegister, s: Category) -> Result<()> {
+ register.category_exists(&s.0)
+ .and_then(|bl| if bl {
+ self.set_category(s)
+ } else {
+ Err(CEK::CategoryDoesNotExist.into_error())
+ })
+ }
+
+ fn get_category(&self) -> Result<Option<Category>> {
+ match self.get_header().read(&String::from("category.value")) {
+ Err(res) => match res.kind() {
+ &TQEK::IdentifierNotFoundInDocument(_) => Ok(None),
+ _ => Err(res),
+ }
+ .map_err_into(CEK::HeaderReadError),
+
+ Ok(Some(&Value::String(ref s))) => Ok(Some(s.clone().into())),
+ Ok(None) => Err(CEK::StoreReadError.into_error()).map_err_into(CEK::HeaderReadError),
+ Ok(_) => Err(CEK::TypeError.into_error()).map_err_into(CEK::HeaderReadError),
+ }
+ }
+
+ fn has_category(&self) -> Result<bool> {
+ self.get_header().read(&String::from("category.value"))
+ .map_err_into(CEK::HeaderReadError)
+ .map(|e| e.is_some())
+ }
+
+}
diff --git a/lib/entry/libimagentrycategory/src/error.rs b/lib/entry/libimagentrycategory/src/error.rs
new file mode 100644
index 0000000..7282433
--- /dev/null
+++ b/lib/entry/libimagentrycategory/src/error.rs
@@ -0,0 +1,36 @@
+//
+// 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
+//
+
+generate_error_module!(
+ generate_error_types!(CategoryError, CategoryErrorKind,
+ StoreReadError => "Store Read error",
+ StoreWriteError => "Store Write error",
+ StoreIdHandlingError => "StoreId handling error",
+ HeaderReadError => "Header read error",
+ HeaderWriteError => "Header write error",
+ TypeError => "Found wrong type in header",
+
+ CategoryDoesNotExist => "Category does not exist"
+ );
+);
+
+pub use self::error::CategoryError;
+pub use self::error::CategoryErrorKind;
+pub use self::error::MapErrInto;
+
diff --git a/lib/entry/libimagentrycategory/src/lib.rs b/lib/entry/libimagentrycategory/src/lib.rs
new file mode 100644
index 0000000..8ffaafc
--- /dev/null
+++ b/lib/entry/libimagentrycategory/src/lib.rs
@@ -0,0 +1,38 @@
+//
+// 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
+//
+
+extern crate toml_query;
+extern crate toml;
+#[macro_use]
+extern crate is_match;
+#[macro_use]
+extern crate log;
+
+#[macro_use]
+extern crate libimagerror;
+#[macro_use]
+extern crate libimagstore;
+
+pub mod category;
+pub mod error;
+pub mod register;
+pub mod result;
+
+module_entry_path_mod!("category");
+
diff --git a/lib/entry/libimagentrycategory/src/register.rs b/lib/entry/libimagentrycategory/src/register.rs
new file mode 100644
index 0000000..e885363
--- /dev/null
+++ b/lib/entry/libimagentrycategory/src/register.rs
@@ -0,0 +1,290 @@
+//
+// 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::path::PathBuf;
+
+use toml_query::insert::TomlValueInsertExt;
+use toml_query::read::TomlValueReadExt;
+use toml::Value;
+
+use libimagstore::store::Store;
+use libimagstore::store::FileLockEntry;
+use libimagstore::storeid::StoreId;
+use libimagstore::storeid::StoreIdIterator;
+use libimagerror::into::IntoError;
+
+use category::Category;
+use error::CategoryErrorKind as CEK;
+use error::MapErrInto;
+use result::Result;
+
+pub const CATEGORY_REGISTER_NAME_FIELD_PATH : &'static str = "category.register.name";
+
+/// Extension on the Store to make it a register for categories
+///
+/// The register writes files to the
+pub trait CategoryRegister {
+
+ fn category_exists(&self, name: &str) -> Result<bool>;
+
+ fn create_category(&self, name: &str) -> Result<bool>;
+
+ fn delete_category(&self, name: &str) -> Result<()>;
+
+ fn all_category_names(&self) -> Result<CategoryNameIter>;
+
+ fn get_category_by_name(&self, name: &str) -> Result<Option<FileLockEntry>>;
+
+}
+
+impl CategoryRegister for Store {
+
+ /// Check whether a category exists
+ fn category_exists(&self, name: &str) -> Result<bool> {
+ let sid = try!(mk_category_storeid(self.path().clone(), name));
+ represents_category(self, sid, name)
+ }
+
+ /// Create a category
+ ///
+ /// Fails if the category already exists (returns false then)
+ fn create_category(&self, name: &str) -> Result<bool> {
+ use libimagstore::error::StoreErrorKind as SEK;
+
+ let sid = try!(mk_category_storeid(self.path().clone(), name));
+
+
+ match self.create(sid) {
+ Ok(mut entry) => {
+ let val = Value::String(String::from(name));
+ entry.get_header_mut()
+ .insert(CATEGORY_REGISTER_NAME_FIELD_PATH, val)
+ .map(|opt| if opt.is_none() {
+ debug!("Setting category header worked")
+ } else {
+ warn!("Setting category header replaced existing value: {:?}", opt);
+ })
+ .map(|_| true)
+ .map_err_into(CEK::HeaderWriteError)
+ .map_err_into(CEK::StoreWriteError)
+ }
+ Err(store_error) => if is_match!(store_error.err_type(), SEK::EntryAlreadyExists) {
+ Ok(false)
+ } else {
+ Err(store_error).map_err_into(CEK::StoreWriteError)
+ }
+ }
+ }
+
+ /// Delete a category
+ fn delete_category(&self, name: &str) -> Result<()> {
+ let sid = try!(mk_category_storeid(self.path().clone(), name));
+
+ self.delete(sid).map_err_into(CEK::StoreWriteError)
+ }
+
+ /// Get all category names
+ fn all_category_names(&self) -> Result<CategoryNameIter> {
+ self.retrieve_for_module("category")
+ .map_err_into(CEK::StoreReadError)
+ .map(|iter| CategoryNameIter::new(self, iter))
+ }
+
+ /// Get a category by its name
+ ///
+ /// Returns the FileLockEntry which represents the category, so one can link to it and use it
+ /// like a normal file in the store (which is exactly what it is).
+ fn get_category_by_name(&self, name: &str) -> Result<Option<FileLockEntry>> {
+ let sid = try!(mk_category_storeid(self.path().clone(), name));
+
+ self.get(sid)
+ .map_err_into(CEK::StoreWriteError)
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ extern crate env_logger;
+ use std::path::PathBuf;
+
+ use super::*;
+
+ use libimagstore::store::Store;
+
+ pub fn get_store() -> Store {
+ use libimagstore::store::InMemoryFileAbstraction;
+ let backend = Box::new(InMemoryFileAbstraction::new());
+ Store::new_with_backend(PathBuf::from("/"), None, backend).unwrap()
+ }
+
+ #[test]
+ fn test_non_existing_category_exists() {
+ let exists = get_store().category_exists("nonexistent");
+
+ assert!(exists.is_ok(), format!("Expected Ok(_), got: {:?}", exists));
+ let exists = exists.unwrap();
+
+ assert!(!exists);
+ }
+
+ #[test]
+ fn test_creating_category() {
+ let category_name = "examplecategory";
+ let store = get_store();
+ let res = store.create_category(category_name);
+
+ assert!(res.is_ok(), format!("Expected Ok(_), got: {:?}", res));
+ let res = res.unwrap();
+ assert!(res);
+ }
+
+ #[test]
+ fn test_creating_category_creates_store_entry() {
+ let category_name = "examplecategory";
+ let store = get_store();
+
+ let res = store.create_category(category_name);
+
+ assert!(res.is_ok(), format!("Expected Ok(_), got: {:?}", res));
+ let res = res.unwrap();
+ assert!(res);
+
+ let category = store.get(PathBuf::from(format!("category/{}", category_name)));
+
+ assert!(category.is_ok(), format!("Expected Ok(_), got: {:?}", category));
+ let category = category.unwrap();
+
+ assert!(category.is_some());
+ }
+
+ #[test]
+ fn test_creating_category_creates_store_entry_with_header_field_set() {
+ let _ = env_logger::init();
+ let category_name = "examplecategory";
+ let store = get_store();
+ let res = store.create_category(category_name);
+
+ assert!(res.is_ok(), format!("Expected Ok(_), got: {:?}", res));
+ let res = res.unwrap();
+ assert!(res);
+
+ let id = PathBuf::from(format!("category/{}", category_name));
+ println!("Trying: {:?}", id);
+ let category = store.get(id);
+
+ assert!(category.is_ok(), format!("Expected Ok(_), got: {:?}", category));
+ let category = category.unwrap();
+
+ assert!(category.is_some());
+ let category = category.unwrap();
+
+ let header_field = category.get_header().read(CATEGORY_REGISTER_NAME_FIELD_PATH);
+ assert!(header_field.is_ok(), format!("Expected Ok(_), got: {:?}", header_field));
+ let header_field = header_field.unwrap();
+
+ match header_field {
+ Some(&Value::String(ref s)) => assert_eq!(category_name, s),
+ Some(_) => assert!(false, "Header field has wrong type"),
+ None => assert!(false, "Header field not present"),
+ }
+ }
+}
+
+#[inline]
+fn mk_category_storeid(base: PathBuf, s: &str) -> Result<StoreId> {
+ use libimagstore::storeid::IntoStoreId;
+ ::module_path::ModuleEntryPath::new(s)
+ .into_storeid()
+ .map(|id| id.with_base(base))
+ .map_err_into(CEK::StoreIdHandlingError)
+}
+
+#[inline]
+fn represents_category(store: &Store, sid: StoreId, name: &str) -> Result<bool> {
+ sid.exists()
+ .map_err_into(CEK::StoreIdHandlingError)
+ .and_then(|bl| {
+ if bl {
+ store.get(sid)
+ .map_err_into(CEK::StoreReadError)
+ .and_then(|fle| {
+ if let Some(fle) = fle {
+ match fle.get_header()
+ .read(&String::from(CATEGORY_REGISTER_NAME_FIELD_PATH))
+ .map_err_into(CEK::HeaderReadError)
+ {
+ Ok(Some(&Value::String(ref s))) => Ok(s == name),
+ Ok(_) => Err(CEK::TypeError.into_error()),
+ Err(e) => Err(e).map_err_into(CEK::HeaderReadError),
+ }
+ } else {
+ Ok(false)
+ }
+ })
+ } else {
+ Ok(bl) // false
+ }
+ })
+}
+
+/// Iterator for Category names
+///
+/// Iterates over Result<Category>
+///
+/// # Return values
+///
+/// In each iteration, a Option<Result<Category>> is returned. Error kinds are as follows:
+///
+/// * CategoryErrorKind::StoreReadError if a name could not be fetched from the store
+/// * CategoryErrorKind::HeaderReadError if the header of the fetched item couldn't be read
+/// * CategoryErrorKind::TypeError if the name could not be fetched because it is not a String
+///
+pub struct CategoryNameIter<'a>(&'a Store, StoreIdIterator);
+
+impl<'a> CategoryNameIter<'a> {
+
+ fn new(store: &'a Store, sidit: StoreIdIterator) -> CategoryNameIter<'a> {
+ CategoryNameIter(store, sidit)
+ }
+
+}
+
+impl<'a> Iterator for CategoryNameIter<'a> {
+ type Item = Result<Category>;
+
+ fn next(&mut self) -> Option<Self::Item> {
+ // TODO: Optimize me with lazy_static
+ let query = String::from(CATEGORY_REGISTER_NAME_FIELD_PATH);
+
+ self.1
+ .next()
+ .map(|sid| {
+ self.0
+ .get(sid)
+ .map_err_into(CEK::StoreReadError)
+ .and_then(|fle| fle.ok_or(CEK::StoreReadError.into_error()))
+ .and_then(|fle| match fle.get_header().read(&query) {
+ Ok(Some(&Value::String(ref s))) => Ok(Category::from(s.clone())),
+ Ok(_) => Err(CEK::TypeError.into_error()),
+ Err(e) => Err(e).map_err_into(CEK::HeaderReadError),
+ })
+ })
+ }
+}
+
diff --git a/lib/entry/libimagentrycategory/src/result.rs b/lib/entry/libimagentrycategory/src/result.rs
new file mode 100644
index 0000000..517d0e9
--- /dev/null
+++ b/lib/entry/libimagentrycategory/src/result.rs
@@ -0,0 +1,26 @@
+
+//
+// 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 error::CategoryError;
+
+pub type Result<T> = RResult<T, CategoryError>;
+
diff --git a/lib/entry/libimagentrydatetime/Cargo.toml b/lib/entry/libimagentrydatetime/Cargo.toml
new file mode 100644
index 0000000..93e43b8
--- /dev/null
+++ b/lib/entry/libimagentrydatetime/Cargo.toml
@@ -0,0 +1,28 @@
+[package]
+name = "libimagentrydatetime"
+version = "0.4.0"
+authors = ["Matthias Beyer <mail@beyermatthias.de>"]
+
+description = "Library for the imag core distribution"
+
+keywords = ["imag", "PIM", "personal", "information", "management"]
+readme = "../README.md"
+license = "LGPL-2.1"
+
+documentation = "https://matthiasbeyer.github.io/imag/imag_documentation/index.html"
+repository = "https://github.com/matthiasbeyer/imag"
+homepage = "http://imag-pim.org"
+
+[dependencies]
+chrono = "0.4"
+toml-query = "0.3"
+lazy_static = "0.2"
+toml = "0.4"
+
+libimagerror = { version = "0.4.0", path = "../../../lib/core/libimagerror" }
+libimagstore = { version = "0.4.0", path = "../../../lib/core/libimagstore" }
+libimagutil = { version = "0.4.0", path = "../../../lib/etc/libimagutil" }
+
+[dev-dependencies]
+is-match = "0.1"
+
diff --git a/lib/entry/libimagentrydatetime/src/datepath/accuracy.rs b/lib/entry/libimagentrydatetime/src/datepath/accuracy.rs
new file mode 100644
index 0000000..47299c1
--- /dev/null
+++ b/lib/entry/libimagentrydatetime/src/datepath/accuracy.rs
@@ -0,0 +1,112 @@
+//
+// 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
+//
+
+/// The accuracy with which the compiler should compile the time specification
+#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Debug)]
+pub enum Accuracy {
+ Year,
+ Month,
+ Day,
+ Hour,
+ Minute,
+ Second
+}
+
+impl Accuracy {
+
+ /// Check whether the current setting includes a year.
+ pub fn has_year_accuracy(&self) -> bool {
+ match *self {
+ Accuracy::Year => true,
+ Accuracy::Month => true,
+ Accuracy::Day => true,
+ Accuracy::Hour => true,
+ Accuracy::Minute => true,
+ Accuracy::Second => true,
+ }
+ }
+
+ /// Check whether the current setting includes a month.
+ pub fn has_month_accuracy(&self) -> bool {
+ match *self {
+ Accuracy::Year => false,
+ Accuracy::Month => true,
+ Accuracy::Day => true,
+ Accuracy::Hour => true,
+ Accuracy::Minute => true,
+ Accuracy::Second => true,
+ }
+ }
+
+ /// Check whether the current setting includes a day.
+ pub fn has_day_accuracy(&self) -> bool {
+ match *self {
+ Accuracy::Year => false,
+ Accuracy::Month => false,
+ Accuracy::Day => true,
+ Accuracy::Hour => true,
+ Accuracy::Minute => true,
+ Accuracy::Second => true,
+ }
+ }
+
+ /// Check whether the current setting includes a hour.
+ pub fn has_hour_accuracy(&self) -> bool {
+ match *self {
+ Accuracy::Year => false,
+ Accuracy::Month => false,
+ Accuracy::Day => false,
+ Accuracy::Hour => true,
+ Accuracy::Minute => true,
+ Accuracy::Second => true,
+ }
+ }
+
+ /// Check whether the current setting includes a minute.
+ pub fn has_minute_accuracy(&self) -> bool {
+ match *self {
+ Accuracy::Year => false,
+ Accuracy::Month => false,
+ Accuracy::Day => false,
+ Accuracy::Hour => false,
+ Accuracy::Minute => true,
+ Accuracy::Second => true,
+ }
+ }
+
+ /// Check whether the current setting includes a second.
+ pub fn has_second_accuracy(&self) -> bool {
+ match *self {
+ Accuracy::Year => false,
+ Accuracy::Month => false,
+ Accuracy::Day => false,
+ Accuracy::Hour => false,
+ Accuracy::Minute => false,
+ Accuracy::Second => true,
+ }
+ }
+
+}
+
+impl Default for Accuracy {
+ fn default() -> Accuracy {
+ Accuracy::Second
+ }
+}
+
diff --git a/lib/entry/libimagentrydatetime/src/datepath/compiler.rs b/lib/entry/libimagentrydatetime/src/datepath/compiler.rs
new file mode 100644
index 0000000..742b594
--- /dev/null
+++ b/lib/entry/libimagentrydatetime/src/datepath/compiler.rs
@@ -0,0 +1,196 @@
+//
+// 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::path::PathBuf;
+
+use chrono::naive::NaiveDateTime;
+use chrono::Datelike;
+use chrono::Timelike;
+
+use libimagstore::storeid::StoreId;
+
+use datepath::accuracy::Accuracy;
+use datepath::format::Format;
+use datepath::result::Result;
+use datepath::error::DatePathCompilerErrorKind as DPCEK;
+use datepath::error::MapErrInto;
+
+pub struct DatePathCompiler {
+ accuracy : Accuracy,
+ format : Format,
+}
+
+impl DatePathCompiler {
+
+ pub fn new(accuracy: Accuracy, format: Format) -> DatePathCompiler {
+ DatePathCompiler {
+ accuracy : accuracy,
+ format : format,
+ }
+ }
+
+ /// Compile a NaiveDateTime object into a StoreId object.
+ ///
+ /// # More information
+ ///
+ /// See the documentations of the `Format` and the `Accuracy` types as well.
+ ///
+ /// # Warnings
+ ///
+ /// This does _not_ guarantee that the StoreId can be created, is not yet in the store or
+ /// anything else. Overall, this is just a `spec->path` compiler which is really stupid.
+ ///
+ /// # Return value
+ ///
+ /// The `StoreId` object on success.
+ ///
+ pub fn compile(&self, module_name: &str, datetime: &NaiveDateTime) -> Result<StoreId> {
+ let mut s = format!("{}/", module_name);
+
+ if self.accuracy.has_year_accuracy() /* always true */ {
+ s.push_str(&format!("{:04}", datetime.year()));
+ }
+
+ if self.accuracy.has_month_accuracy() {
+ match self.format {
+ Format::ElementIsFolder
+ | Format::DaysAreFolder
+ | Format::MonthIsFolder
+ | Format::YearIsFolder
+ => s.push_str(&format!("/{:02}", datetime.month())),
+ }
+ }
+
+ if self.accuracy.has_day_accuracy() {
+ match self.format {
+ Format::ElementIsFolder
+ | Format::DaysAreFolder
+ | Format::MonthIsFolder
+ => s.push_str(&format!("/{:02}", datetime.day())),
+ Format::YearIsFolder
+ => s.push_str(&format!("-{:02}", datetime.day())),
+ }
+ }
+
+ if self.accuracy.has_hour_accuracy() {
+ match self.format {
+ Format::ElementIsFolder
+ | Format::DaysAreFolder
+ => s.push_str(&format!("/{:02}", datetime.hour())),
+ Format::YearIsFolder
+ | Format::MonthIsFolder
+ => s.push_str(&format!("-{:02}", datetime.hour())),
+ }
+ }
+
+ if self.accuracy.has_minute_accuracy() {
+ match self.format {
+ Format::ElementIsFolder
+ => s.push_str(&format!("/{:02}", datetime.minute())),
+ Format::YearIsFolder
+ | Format::MonthIsFolder
+ | Format::DaysAreFolder
+ => s.push_str(&format!("-{:02}", datetime.minute())),
+ }
+ }
+
+ if self.accuracy.has_second_accuracy() {
+ match self.format {
+ Format::ElementIsFolder
+ => s.push_str(&format!("/{:02}", datetime.second())),
+ Format::YearIsFolder
+ | Format::MonthIsFolder
+ | Format::DaysAreFolder
+ => s.push_str(&format!("-{:02}", datetime.second())),
+ }
+ }
+
+ StoreId::new_baseless(PathBuf::from(s))
+ .map_err_into(DPCEK::StoreIdBuildFailed)
+ }
+
+}
+
+#[cfg(test)]
+mod test {
+ use super::*;
+ use datepath::accuracy::Accuracy;
+ use datepath::format::Format;
+
+ use chrono::naive::NaiveDate;
+ use chrono::naive::NaiveDateTime;
+
+ #[test]
+ fn test_compiler_compile_simple() {
+ let dt = NaiveDate::from_ymd(2000, 1, 1).and_hms(0, 0, 0);
+ let compiler = DatePathCompiler::new(Accuracy::default(), Format::default());
+ let res = compiler.compile("testmodule", &dt);
+
+ assert!(res.is_ok());
+ let res = res.unwrap();
+
+ let s = res.to_str();
+
+ assert!(s.is_ok());
+ let s = s.unwrap();
+
+ assert_eq!("testmodule/2000/01/01/00/00/00", s);
+ }
+
+ fn test_accuracy(acc: Accuracy, dt: NaiveDateTime, modname: &str, matchstr: &str) {
+ let compiler = DatePathCompiler::new(acc, Format::default());
+ let res = compiler.compile(modname, &dt);
+
+ assert!(res.is_ok());
+ let res = res.unwrap();
+
+ let s = res.to_str();
+
+ assert!(s.is_ok());
+ let s = s.unwrap();
+
+ assert_eq!(matchstr, s);
+ }
+
+ #[test]
+ fn test_compiler_compile_year_accuracy() {
+ let dt = NaiveDate::from_ymd(2000, 1, 1).and_hms(0, 0, 0);
+ test_accuracy(Accuracy::Year, dt, "module", "module/2000");
+ }
+
+ #[test]
+ fn test_compiler_compile_month_accuracy() {
+ let dt = NaiveDate::from_ymd(2000, 1, 1).and_hms(0, 0, 0);
+ test_accuracy(Accuracy::Month, dt, "module", "module/2000/01");
+ }
+
+ #[test]
+ fn test_compiler_compile_day_accuracy() {
+ let dt = NaiveDate::from_ymd(2000, 1, 1).and_hms(0, 0, 0);
+ test_accuracy(Accuracy::Day, dt, "module", "module/2000/01/01");
+ }
+
+ #[test]
+ fn test_compiler_compile_year_paddning() {
+ let dt = NaiveDate::from_ymd(1, 1, 1).and_hms(0, 0, 0);
+ test_accuracy(Accuracy::Day, dt, "module", "module/0001/01/01");
+ }
+
+}
+
diff --git a/lib/entry/libimagentrydatetime/src/datepath/error.rs b/lib/entry/libimagentrydatetime/src/datepath/error.rs
new file mode 100644
index 0000000..1a16320
--- /dev/null
+++ b/lib/entry/libimagentrydatetime/src/datepath/error.rs
@@ -0,0 +1,30 @@
+//
+// 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
+//
+
+/// Error module for the DatePathCompiler type
+generate_error_module! {
+ generate_error_types!(DatePathCompilerError, DatePathCompilerErrorKind,
+ UnknownDatePathCompilerError => "Unknown DatePathCompiler error",
+ StoreIdBuildFailed => "Failed building StoreId object"
+ );
+}
+pub use self::error::DatePathCompilerError;
+pub use self::error::DatePathCompilerErrorKind;
+pub use self::error::MapErrInto;
+
diff --git a/lib/entry/libimagentrydatetime/src/datepath/format.rs b/lib/entry/libimagentrydatetime/src/datepath/format.rs
new file mode 100644
index 0000000..cc9e8c7
--- /dev/null
+++ b/lib/entry/libimagentrydatetime/src/datepath/format.rs
@@ -0,0 +1,92 @@
+//
+// 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
+//
+
+/// The format which should be used to compile the datetime spec into a StoreId object.
+///
+/// # Warning
+///
+/// These settings depend on the Accuracy settings of the compiler as well.
+///
+/// If the compiler settings only specify an accuracy of `Accuracy::Month`, a setting of
+/// `Format::ElementIsFolder` will result in the `month` beeing the file name.
+///
+#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Debug)]
+pub enum Format {
+ /// Each element of the Path is one folder level.
+ ///
+ /// This is the default.
+ ///
+ /// # Example
+ ///
+ /// The date "1st May of 2017, 14:15:16" will be compiled to `2017/05/01/14/15/16`.
+ ///
+ /// The second is the filename, then.
+ ///
+ /// # Usecase
+ ///
+ /// When expecting a lot of entries, this makes sure that the tree is fast-traversible and has
+ /// few files per folder (maximum 60).
+ ///
+ ElementIsFolder,
+
+ /// Each element from Year to Day is folder, below is filename.
+ ///
+ /// # Example
+ ///
+ /// The date "1st May of 2017, 14:15:16" will be compiled to `2017/05/01/14-15-16`.
+ ///
+ /// # Usecase
+ ///
+ /// When expecting few entries per day.
+ ///
+ DaysAreFolder,
+
+ /// Each element from Year to Month is folder, below is filename.
+ ///
+ /// # Example
+ ///
+ /// The date "1st May of 2017, 14:15:16" will be compiled to `2017/05/01T14-15-16`.
+ ///
+ /// # Usecase
+ ///
+ /// When expecting few entries per month.
+ ///
+ MonthIsFolder,
+
+ /// Each element from Year to Month is folder, below is filename.
+ ///
+ /// # Example
+ ///
+ /// The date "1st May of 2017, 14:15:16" will be compiled to `2017/05-01T14-15-16`.
+ ///
+ /// # Usecase
+ ///
+ /// When expecting few entries per year.
+ /// Might be never used.
+ ///
+ YearIsFolder,
+
+}
+
+impl Default for Format {
+ fn default() -> Format {
+ Format::ElementIsFolder
+ }
+}
+
diff --git a/lib/entry/libimagentrydatetime/src/datepath/mod.rs b/lib/entry/libimagentrydatetime/src/datepath/mod.rs
new file mode 100644
index 0000000..c964abf
--- /dev/null
+++ b/lib/entry/libimagentrydatetime/src/datepath/mod.rs
@@ -0,0 +1,26 @@
+//
+// 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
+//
+
+pub mod accuracy;
+pub mod compiler;
+pub mod error;
+pub mod format;
+pub mod result;
+pub mod to_store_id;
+
diff --git a/lib/entry/libimagentrydatetime/src/datepath/result.rs b/lib/entry/libimagentrydatetime/src/datepath/result.rs
new file mode 100644
index 0000000..2fc3350
--- /dev/null
+++ b/lib/entry/libimagentrydatetime/src/datepath/result.rs
@@ -0,0 +1,25 @@
+//
+// 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
+//
+
+/// Result type for this module.
+use super::error::DatePathCompilerError as DPCE;
+use std::result::Result as RResult;
+
+pub type Result<T> = RResult<T, DPCE>;
+
diff --git a/lib/entry/libimagentrydatetime/src/datepath/to_store_id.rs b/lib/entry/libimagentrydatetime/src/datepath/to_store_id.rs
new file mode 100644
index 0000000..c781595
--- /dev/null
+++ b/lib/entry/libimagentrydatetime/src/datepath/to_store_id.rs
@@ -0,0 +1,39 @@
+//
+// 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 chrono::naive::NaiveDateTime;
+
+use libimagstore::storeid::StoreId;
+use datepath::result::Result;
+use datepath::compiler::DatePathCompiler;
+
+//
+// Extension Trait for NaiveDateTime
+//
+
+pub trait ToStoreId {
+ fn to_store_id(&self, modname: &str, compiler: &DatePathCompiler) -> Result<StoreId>;
+}
+
+impl ToStoreId for NaiveDateTime {
+ fn to_store_id(&self, modname: &str, compiler: &DatePathCompiler) -> Result<StoreId> {
+ compiler.compile(modname, self)
+ }
+}
+
diff --git a/lib/entry/libimagentrydatetime/src/datetime.rs b/lib/entry/libimagentrydatetime/src/datetime.rs
new file mode 100644
index 0000000..73c2962
--- /dev/null
+++ b/lib/entry/libimagentrydatetime/src/datetime.rs
@@ -0,0 +1,330 @@
+//
+// 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 chrono::naive::NaiveDateTime;
+use toml_query::delete::TomlValueDeleteExt;
+use toml_query::insert::TomlValueInsertExt;
+use toml_query::read::TomlValueReadExt;
+use toml::Value;
+
+use libimagstore::store::Entry;
+use libimagerror::into::IntoError;
+
+use error::DateErrorKind as DEK;
+use error::*;
+use result::Result;
+use range::DateTimeRange;
+
+pub trait EntryDate {
+
+ fn delete_date(&mut self) -> Result<()>;
+ fn read_date(&self) -> Result<NaiveDateTime>;
+ fn set_date(&mut self, d: NaiveDateTime) -> Result<Option<Result<NaiveDateTime>>>;
+
+ fn delete_date_range(&mut self) -> Result<()>;
+ fn read_date_range(&self) -> Result<DateTimeRange>;
+ fn set_date_range(&mut self, start: NaiveDateTime, end: NaiveDateTime) -> Result<Option<Result<DateTimeRange>>>;
+
+}
+
+lazy_static! {
+ static ref DATE_HEADER_LOCATION : &'static str = "datetime.value";
+ static ref DATE_RANGE_START_HEADER_LOCATION : &'static str = "datetime.range.start";
+ static ref DATE_RANGE_END_HEADER_LOCATION : &'static str = "datetime.range.end";
+ static ref DATE_FMT : &'static str = "%Y-%m-%dT%H:%M:%S";
+}
+
+impl EntryDate for Entry {
+
+ fn delete_date(&mut self) -> Result<()> {
+ self.get_header_mut()
+ .delete(&DATE_HEADER_LOCATION)
+ .map(|_| ())
+ .map_err_into(DEK::DeleteDateError)
+ }
+
+ fn read_date(&self) -> Result<NaiveDateTime> {
+ self.get_header()
+ .read(&DATE_HEADER_LOCATION)
+ .map_err_into(DEK::ReadDateError)
+ .and_then(|v| {
+ match v {
+ Some(&Value::String(ref s)) => s.parse::<NaiveDateTime>()
+ .map_err_into(DEK::DateTimeParsingError),
+ Some(_) => Err(DEK::DateHeaderFieldTypeError.into_error()),
+ _ => Err(DEK::ReadDateError.into_error()),
+ }
+ })
+ }
+
+ /// Set a Date for this entry
+ ///
+ /// # Return value
+ ///
+ /// This function returns funny things, I know. But I find it more attractive to be explicit
+ /// what failed when here, instead of beeing nice to the user here.
+ ///
+ /// So here's a list how things are returned:
+ ///
+ /// - Err(_) if the inserting failed
+ /// - Ok(None) if the inserting succeeded and _did not replace an existing value_.
+ /// - Ok(Some(Ok(_))) if the inserting succeeded, but replaced an existing value which then got
+ /// parsed into a NaiveDateTime object
+ /// - Ok(Some(Err(_))) if the inserting succeeded, but replaced an existing value which then
+ /// got parsed into a NaiveDateTime object, where the parsing failed for some reason.
+ ///
+ fn set_date(&mut self, d: NaiveDateTime) -> Result<Option<Result<NaiveDateTime>>> {
+ let date = d.format(&DATE_FMT).to_string();
+
+ self.get_header_mut()
+ .insert(&DATE_HEADER_LOCATION, Value::String(date))
+ .map(|opt| opt.map(|stri| {
+ match stri {
+ Value::String(ref s) => s.parse::<NaiveDateTime>()
+ .map_err_into(DEK::DateTimeParsingError),
+ _ => Err(DEK::DateHeaderFieldTypeError.into_error()),
+ }
+ }))
+ .map_err_into(DEK::SetDateError)
+ }
+
+
+ /// Deletes the date range
+ ///
+ /// # Warning
+ ///
+ /// First deletes the start, then the end. If the first operation fails, this might leave the
+ /// header in an inconsistent state.
+ ///
+ fn delete_date_range(&mut self) -> Result<()> {
+ let _ = try!(self
+ .get_header_mut()
+ .delete(&DATE_RANGE_START_HEADER_LOCATION)
+ .map(|_| ())
+ .map_err_into(DEK::DeleteDateTimeRangeError));
+
+ self.get_header_mut()
+ .delete(&DATE_RANGE_END_HEADER_LOCATION)
+ .map(|_| ())
+ .map_err_into(DEK::DeleteDateTimeRangeError)
+ }
+
+ fn read_date_range(&self) -> Result<DateTimeRange> {
+ let start = try!(self
+ .get_header()
+ .read(&DATE_RANGE_START_HEADER_LOCATION)
+ .map_err_into(DEK::ReadDateTimeRangeError)
+ .and_then(|v| {
+ match v {
+ Some(&Value::String(ref s)) => s.parse::<NaiveDateTime>()
+ .map_err_into(DEK::DateTimeParsingError),
+ Some(_) => Err(DEK::DateHeaderFieldTypeError.into_error()),
+ _ => Err(DEK::ReadDateError.into_error()),
+ }
+ }));
+
+ let end = try!(self
+ .get_header()
+ .read(&DATE_RANGE_START_HEADER_LOCATION)
+ .map_err_into(DEK::ReadDateTimeRangeError)
+ .and_then(|v| {
+ match v {
+ Some(&Value::String(ref s)) => s.parse::<NaiveDateTime>()
+ .map_err_into(DEK::DateTimeParsingError),
+ Some(_) => Err(DEK::DateHeaderFieldTypeError.into_error()),
+ _ => Err(DEK::ReadDateError.into_error()),
+ }
+ }));
+
+ DateTimeRange::new(start, end)
+ .map_err_into(DEK::DateTimeRangeError)
+ }
+
+ /// Set the date range
+ ///
+ /// # Warning
+ ///
+ /// This first sets the start, then the end. If the first operation fails, this might leave the
+ /// header in an inconsistent state.
+ ///
+ fn set_date_range(&mut self, start: NaiveDateTime, end: NaiveDateTime)
+ -> Result<Option<Result<DateTimeRange>>>
+ {
+ let start = start.format(&DATE_FMT).to_string();
+ let end = end.format(&DATE_FMT).to_string();
+
+ let opt_old_start = try!(self
+ .get_header_mut()
+ .insert(&DATE_RANGE_START_HEADER_LOCATION, Value::String(start))
+ .map(|opt| opt.map(|stri| {
+ match stri {
+ Value::String(ref s) => s.parse::<NaiveDateTime>()
+ .map_err_into(DEK::DateTimeParsingError),
+ _ => Err(DEK::DateHeaderFieldTypeError.into_error()),
+ }
+ }))
+ .map_err_into(DEK::SetDateTimeRangeError));
+
+ let opt_old_end = try!(self
+ .get_header_mut()
+ .insert(&DATE_RANGE_END_HEADER_LOCATION, Value::String(end))
+ .map(|opt| opt.map(|stri| {
+ match stri {
+ Value::String(ref s) => s.parse::<NaiveDateTime>()
+ .map_err_into(DEK::DateTimeParsingError),
+ _ => Err(DEK::DateHeaderFieldTypeError.into_error()),
+ }
+ }))
+ .map_err_into(DEK::SetDateTimeRangeError));
+
+ match (opt_old_start, opt_old_end) {
+ (Some(Ok(old_start)), Some(Ok(old_end))) => {
+ let dr = DateTimeRange::new(old_start, old_end)
+ .map_err_into(DEK::DateTimeRangeError);
+
+ Ok(Some(dr))
+ },
+
+ (Some(Err(e)), _) => Err(e),
+ (_, Some(Err(e))) => Err(e),
+ _ => {
+ Ok(None)
+ },
+ }
+ }
+
+}
+
+#[cfg(test)]
+mod tests {
+ use std::path::PathBuf;
+
+ use super::*;
+
+ use libimagstore::store::Store;
+
+ use chrono::naive::NaiveDateTime;
+ use chrono::naive::NaiveDate;
+ use chrono::naive::NaiveTime;
+
+ pub fn get_store() -> Store {
+ Store::new(PathBuf::from("/"), None).unwrap()
+ }
+
+ #[test]
+ fn test_set_date() {
+ let store = get_store();
+
+ let date = {
+ let date = NaiveDate::from_ymd(2000, 01, 02);
+ let time = NaiveTime::from_hms(03, 04, 05);
+
+ NaiveDateTime::new(date, time)
+ };
+
+ let mut entry = store.create(PathBuf::from("test")).unwrap();
+ let res = entry.set_date(date);
+
+ assert!(res.is_ok(), format!("Error: {:?}", res));
+ let res = res.unwrap();
+
+ assert!(res.is_none()); // There shouldn't be an existing value
+
+ // Check whether the header is set correctly
+
+ let hdr_field = entry.get_header().read(&DATE_HEADER_LOCATION);
+
+ assert!(hdr_field.is_ok());
+ let hdr_field = hdr_field.unwrap();
+
+ assert!(hdr_field.is_some());
+ let hdr_field = hdr_field.unwrap();
+
+ match *hdr_field {
+ Value::String(ref s) => assert_eq!("2000-01-02T03:04:05", s),
+ _ => assert!(false, "Wrong header type"),
+ }
+ }
+
+ #[test]
+ fn test_read_date() {
+ use chrono::Datelike;
+ use chrono::Timelike;
+
+ let store = get_store();
+
+ let date = {
+ let date = NaiveDate::from_ymd(2000, 01, 02);
+ let time = NaiveTime::from_hms(03, 04, 05);
+
+ NaiveDateTime::new(date, time)
+ };
+
+ let mut entry = store.create(PathBuf::from("test")).unwrap();
+ let res = entry.set_date(date);
+
+ assert!(res.is_ok(), format!("Expected Ok(_), got: {:?}", res));
+ let res = res.unwrap();
+
+ assert!(res.is_none()); // There shouldn't be an existing value
+
+ // same as the test above ...
+
+ let d = entry.read_date();
+
+ assert!(d.is_ok(), format!("Expected Ok(_), got: {:?}", d));
+ let d = d.unwrap();
+
+ assert_eq!(d.date().year() , 2000);
+ assert_eq!(d.date().month() , 01);
+ assert_eq!(d.date().day() , 02);
+ assert_eq!(d.time().hour() , 03);
+ assert_eq!(d.time().minute() , 04);
+ assert_eq!(d.time().second() , 05);
+ }
+
+ #[test]
+ fn test_delete_date() {
+ let store = get_store();
+
+ let date = {
+ let date = NaiveDate::from_ymd(2000, 01, 02);
+ let time = NaiveTime::from_hms(03, 04, 05);
+
+ NaiveDateTime::new(date, time)
+ };
+
+ let mut entry = store.create(PathBuf::from("test")).unwrap();
+ let res = entry.set_date(date);
+
+ assert!(res.is_ok(), format!("Expected Ok(_), got: {:?}", res));
+ let res = res.unwrap();
+ assert!(res.is_none()); // There shouldn't be an existing value
+
+ assert!(entry.delete_date().is_ok());
+
+ let hdr_field = entry.get_header().read(&DATE_HEADER_LOCATION);
+
+ assert!(hdr_field.is_ok());
+ let hdr_field = hdr_field.unwrap();
+
+ assert!(hdr_field.is_none());
+ }
+}
+
diff --git a/lib/entry/libimagentrydatetime/src/error.rs b/lib/entry/libimagentrydatetime/src/error.rs
new file mode 100644
index 0000000..0f0166a
--- /dev/null
+++ b/lib/entry/libimagentrydatetime/src/error.rs
@@ -0,0 +1,39 @@
+//
+// 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
+//
+
+generate_error_module!(
+ generate_error_types!(DateError, DateErrorKind,
+ DeleteDateError => "Error deleting date",
+ ReadDateError => "Error reading date",
+ SetDateError => "Error setting date",
+ DeleteDateTimeRangeError => "Error deleting date-time range",
+ ReadDateTimeRangeError => "Error reading date-time range",
+ SetDateTimeRangeError => "Error setting date-time range",
+
+ DateTimeRangeError => "DateTime Range error",
+
+ DateHeaderFieldTypeError => "Expected the header field in the entry to have type 'String', but have other type",
+ DateTimeParsingError => "Error parsing DateTime"
+ );
+);
+
+pub use self::error::DateError;
+pub use self::error::DateErrorKind;
+pub use self::error::MapErrInto;
+
diff --git a/lib/entry/libimagentrydatetime/src/lib.rs b/lib/entry/libimagentrydatetime/src/lib.rs
new file mode 100644
index 0000000..fb0d73f
--- /dev/null
+++ b/lib/entry/libimagentrydatetime/src/lib.rs
@@ -0,0 +1,37 @@
+//
+// 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
+//
+
+#[macro_use] extern crate lazy_static;
+extern crate chrono;
+extern crate toml_query;
+extern crate toml;
+
+#[macro_use] extern crate libimagerror;
+extern crate libimagstore;
+extern crate libimagutil;
+
+#[cfg(test)]
+#[macro_use] extern crate is_match;
+
+pub mod datepath;
+pub mod datetime;
+pub mod error;
+pub mod range;
+pub mod result;
+
diff --git a/lib/entry/libimagentrydatetime/src/range.rs b/lib/entry/libimagentrydatetime/src/range.rs
new file mode 100644
index 0000000..8820b7a
--- /dev/null
+++ b/lib/entry/libimagentrydatetime/src/range.rs
@@ -0,0 +1,111 @@
+//
+// 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
+//
+
+/// Error types for range module
+pub mod error {
+ generate_error_module!(
+ generate_error_types!(DateTimeRangeError, DateTimeRangeErrorKind,
+ EndDateTimeBeforeStartDateTime => "End datetime is before start datetime"
+ );
+ );
+
+ pub use self::error::DateTimeRangeError;
+ pub use self::error::DateTimeRangeErrorKind;
+ pub use self::error::MapErrInto;
+}
+
+/// Result type for range module
+pub mod result {
+ use std::result::Result as RResult;
+ use super::error::DateTimeRangeError;
+
+ pub type Result<T> = RResult<T, DateTimeRangeError>;
+}
+
+use chrono::naive::NaiveDateTime;
+use libimagerror::into::IntoError;
+use self::result::Result;
+
+/// A Range between two dates
+#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
+pub struct DateTimeRange(NaiveDateTime, NaiveDateTime);
+
+impl DateTimeRange {
+
+ /// Create a new DateTimeRange object
+ ///
+ /// # Return value
+ ///
+ /// Ok(DateTimeRange) if start is before end,
+ /// else Err(DateTimeRangeError)
+ ///
+ pub fn new(start: NaiveDateTime, end: NaiveDateTime) -> Result<DateTimeRange> {
+ use self::error::DateTimeRangeErrorKind as DTREK;
+ if start < end {
+ Ok(DateTimeRange(start, end))
+ } else {
+ Err(DTREK::EndDateTimeBeforeStartDateTime.into_error())
+ }
+ }
+
+}
+
+#[cfg(test)]
+mod tests {
+
+ use chrono::naive::NaiveDateTime;
+ use chrono::naive::NaiveDate;
+ use chrono::naive::NaiveTime;
+
+ use super::DateTimeRange;
+
+ #[test]
+ fn test_new_returns_error_if_start_after_end_date() {
+ let start = NaiveDateTime::new(
+ NaiveDate::from_ymd(2000, 02, 02),
+ NaiveTime::from_hms(12, 00, 02)
+ );
+
+ let end = NaiveDateTime::new(
+ NaiveDate::from_ymd(2000, 02, 02),
+ NaiveTime::from_hms(12, 00, 01)
+ );
+
+ let res = DateTimeRange::new(start, end);
+
+ assert!(res.is_err());
+ }
+
+ #[test]
+ fn test_new_returns_ok_if_start_is_before_end() {
+ let start = NaiveDateTime::new(
+ NaiveDate::from_ymd(2000, 02, 02),
+ NaiveTime::from_hms(12, 00, 01)
+ );
+
+ let end = NaiveDateTime::new(
+ NaiveDate::from_ymd(2000, 02, 02),
+ NaiveTime::from_hms(12, 00, 02)
+ );
+
+ let res = DateTimeRange::new(start, end);
+
+ assert!(res.is_ok());
+ }
+}
diff --git a/lib/entry/libimagentrydatetime/src/result.rs b/lib/entry/libimagentrydatetime/src/result.rs
new file mode 100644
index 0000000..d6d0a5a
--- /dev/null
+++ b/lib/entry/libimagentrydatetime/src/result.rs
@@ -0,0 +1,25 @@
+//
+// 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 error::DateError;
+
+pub type Result<T> = RResult<T, DateError>;
+
diff --git a/lib/entry/libimagentryedit/Cargo.toml b/lib/entry/libimagentryedit/Cargo.toml
new file mode 100644
index 0000000..ae241d2
--- /dev/null
+++ b/lib/entry/libimagentryedit/Cargo.toml
@@ -0,0 +1,21 @@
+[package]
+name = "libimagentryedit"
+version = "0.4.0"
+authors = ["Matthias Beyer <mail@beyermatthias.de>"]
+
+description = "Library for the imag core distribution"
+
+keywords = ["imag", "PIM", "personal", "information", "management"]
+readme = "../README.md"
+license = "LGPL-2.1"
+
+documentation = "https://matthiasbeyer.github.io/imag/imag_documentation/index.html"
+repository = "https://github.com/matthiasbeyer/imag"
+homepage = "http://imag-pim.org"
+
+[dependencies]
+
+libimagerror = { version = "0.4.0", path = "../../../lib/core/libimagerror" }
+libimagrt = { version = "0.4.0", path = "../../../lib/core/libimagrt" }
+libimagstore = { version = "0.4.0", path = "../../../lib/core/libimagstore" }
+libimagutil = { version = "0.4.0", path = "../../../lib/etc/libimagutil" }
diff --git a/lib/entry/libimagentryedit/src/edit.rs b/lib/entry/libimagentryedit/src/edit.rs
new file mode 100644
index 0000000..cbd1773
--- /dev/null
+++ b/lib/entry/libimagentryedit/src/edit.rs
@@ -0,0 +1,66 @@
+//
+// 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 libimagerror::into::IntoError;
+use libimagrt::runtime::Runtime;
+use libimagstore::store::Entry;
+
+use result::Result;
+use error::EditErrorKind;
+use error::MapErrInto;
+
+pub trait Edit {
+ fn edit_content(&mut self, rt: &Runtime) -> Result<()>;
+}
+
+impl Edit for String {
+
+ fn edit_content(&mut self, rt: &Runtime) -> Result<()> {
+ edit_in_tmpfile(rt, self).map(|_| ())
+ }
+
+}
+
+impl Edit for Entry {
+
+ fn edit_content(&mut self, rt: &Runtime) -> Result<()> {
+ edit_in_tmpfile(rt, self.get_content_mut())
+ .map(|_| ())
+ }
+
+}
+
+pub fn edit_in_tmpfile(rt: &Runtime, s: &mut String) -> Result<()> {
+ use libimagutil::edit::edit_in_tmpfile_with_command;
+
+ rt.editor()
+ .ok_or(EditErrorKind::NoEditor.into_error())
+ .and_then(|editor| {
+ edit_in_tmpfile_with_command(editor, s)
+ .map_err_into(EditErrorKind::IOError)
+ .and_then(|worked| {
+ if !worked {
+ Err(EditErrorKind::ProcessExitFailure.into())
+ } else {
+ Ok(())
+ }
+ })
+ })
+}
+
diff --git a/lib/entry/libimagentryedit/src/error.rs b/lib/entry/libimagentryedit/src/error.rs
new file mode 100644
index 0000000..478d5d2
--- /dev/null
+++ b/lib/entry/libimagentryedit/src/error.rs
@@ -0,0 +1,32 @@
+//
+// 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
+//
+
+generate_error_module!(
+ generate_error_types!(EditError, EditErrorKind,
+ IOError => "IO Error",
+ NoEditor => "No editor set",
+ ProcessExitFailure => "Process did not exit properly",
+ InstantiateError => "Instantation error"
+ );
+);
+
+pub use self::error::EditError;
+pub use self::error::EditErrorKind;
+pub use self::error::MapErrInto;
+
diff --git a/lib/entry/libimagentryedit/src/lib.rs b/lib/entry/libimagentryedit/src/lib.rs
new file mode 100644
index 0000000..00013d5
--- /dev/null
+++ b/lib/entry/libimagentryedit/src/lib.rs
@@ -0,0 +1,27 @@
+//
+// 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
+//
+
+#[macro_use] extern crate libimagerror;
+extern crate libimagstore;
+extern crate libimagrt;
+extern crate libimagutil;
+
+pub mod edit;
+pub mod error;
+pub mod result;
diff --git a/lib/entry/libimagentryedit/src/result.rs b/lib/entry/libimagentryedit/src/result.rs
new file mode 100644
index 0000000..1d917c0
--- /dev/null
+++ b/lib/entry/libimagentryedit/src/result.rs
@@ -0,0 +1,25 @@
+//
+// 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 error::EditError;
+
+pub type Result<T> = RResult<T, EditError>;
+
diff --git a/lib/entry/libimagentryfilter/Cargo.toml b/lib/entry/libimagentryfilter/Cargo.toml
new file mode 100644
index 0000000..decdc73
--- /dev/null
+++ b/lib/entry/libimagentryfilter/Cargo.toml
@@ -0,0 +1,26 @@
+[package]
+name = "libimagentryfilter"
+version = "0.4.0"
+authors = ["Matthias Beyer <mail@beyermatthias.de>"]
+
+description = "Library for the imag core distribution"
+
+keywords = ["imag", "PIM", "personal", "information", "management"]
+readme = "../README.md"
+license = "LGPL-2.1"
+
+documentation = "https://matthiasbeyer.github.io/imag/imag_documentation/index.html"
+repository = "https://github.com/matthiasbeyer/imag"
+homepage = "http://imag-pim.org"
+
+[dependencies]
+clap = ">=2.17"
+filters = "0.1.*"
+itertools = "0.5"
+log = "0.3"
+regex = "0.2"
+semver = "0.5.*"
+toml = "^0.4"
+
+libimagstore = { version = "0.4.0", path = "../../../lib/core/libimagstore" }
+libimagentrytag = { version = "0.4.0", path = "../../../lib/entry/libimagentrytag" }
diff --git a/lib/entry/libimagentryfilter/README.md b/lib/entry/libimagentryfilter/README.md
new file mode 120000
index 0000000..505ed66
--- /dev/null
+++ b/lib/entry/libimagentryfilter/README.md
@@ -0,0 +1 @@
+../doc/src/05100-lib-entryfilter.md \ No newline at end of file
diff --git a/lib/entry/libimagentryfilter/src/builtin/bool_filter.rs b/lib/entry/libimagentryfilter/src/builtin/bool_filter.rs
new file mode 100644
index 0000000..e903e34
--- /dev/null
+++ b/lib/entry/libimagentryfilter/src/builtin/bool_filter.rs
@@ -0,0 +1,41 @@
+//
+// 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 libimagstore::store::Entry;
+
+use filters::filter::Filter;
+
+pub struct BoolFilter(bool);
+
+impl BoolFilter {
+
+ pub fn new(b: bool) -> BoolFilter {
+ BoolFilter(b)
+ }
+
+}
+
+impl Filter<Entry> for BoolFilter {
+
+ fn filter(&self, _: &Entry) -> bool {
+ self.0
+ }
+
+}
+
diff --git a/lib/entry/libimagentryfilter/src/builtin/content/grep.rs b/lib/entry/libimagentryfilter/src/builtin/content/grep.rs
new file mode 100644
index 0000000..02039fb
--- /dev/null
+++ b/lib/entry/libimagentryfilter/src/builtin/content/grep.rs
@@ -0,0 +1,72 @@
+//
+// 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 filters::filter::Filter;
+use regex::Regex;
+use regex::Error as RError;
+
+use libimagstore::store::Entry;
+
+pub trait IntoRegex {
+
+ fn into_regex(self) -> Result<Regex, RError>;
+
+}
+
+impl<'a> IntoRegex for &'a str {
+
+ fn into_regex(self) -> Result<Regex, RError> {
+ Regex::new(self)
+ }
+}
+
+impl<'a> IntoRegex for Regex {
+
+ fn into_regex(self) -> Result<Regex, RError> {
+ Ok(self)
+ }
+}
+
+pub struct ContentGrep {
+ regex: Regex,
+}
+
+impl ContentGrep {
+
+ pub fn new<IR>(regex: IR) -> Result<ContentGrep, RError>
+ where IR: IntoRegex
+ {
+ regex.into_regex()
+ .map(|reg| {
+ ContentGrep {
+ regex: reg,
+ }
+ })
+ }
+
+}
+
+impl Filter<Entry> for ContentGrep {
+
+ fn filter(&self, e: &Entry) -> bool {
+ self.regex.captures(&e.get_content()[..]).is_some()
+ }
+
+}
+
diff --git a/lib/entry/libimagentryfilter/src/builtin/content/length/is_over.rs b/lib/entry/libimagentryfilter/src/builtin/content/length/is_over.rs
new file mode 100644
index 0000000..96f22da
--- /dev/null
+++ b/lib/entry/libimagentryfilter/src/builtin/content/length/is_over.rs
@@ -0,0 +1,45 @@
+//
+// 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 filters::filter::Filter;
+use libimagstore::store::Entry;
+
+pub struct ContentLengthIsOver {
+ val: usize
+}
+
+impl ContentLengthIsOver {
+
+ pub fn new(value: usize) -> ContentLengthIsOver {
+ ContentLengthIsOver {
+ val: value,
+ }
+ }
+
+}
+
+impl Filter<Entry> for ContentLengthIsOver {
+
+ fn filter(&self, e: &Entry) -> bool {
+ e.get_content().len() > self.val
+ }
+
+}
+
+
diff --git a/lib/entry/libimagentryfilter/src/builtin/content/length/is_under.rs b/lib/entry/libimagentryfilter/src/builtin/content/length/is_under.rs
new file mode 100644
index 0000000..a365438
--- /dev/null
+++ b/lib/entry/libimagentryfilter/src/builtin/content/length/is_under.rs
@@ -0,0 +1,45 @@
+//
+// 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 filters::filter::Filter;
+use libimagstore::store::Entry;
+
+pub struct ContentLengthIsUnder {
+ val: usize
+}
+
+impl ContentLengthIsUnder {
+
+ pub fn new(value: usize) -> ContentLengthIsUnder {
+ ContentLengthIsUnder {
+ val: value,
+ }
+ }
+
+}
+
+impl Filter<Entry> for ContentLengthIsUnder {
+
+ fn filter(&self, e: &Entry) -> bool {
+ e.get_content().len() < self.val
+ }
+
+}
+
+
diff --git a/lib/entry/libimagentryfilter/src/builtin/content/length/mod.rs b/lib/entry/libimagentryfilter/src/builtin/content/length/mod.rs
new file mode 100644
index 0000000..2ebb18c
--- /dev/null
+++ b/lib/entry/libimagentryfilter/src/builtin/content/length/mod.rs
@@ -0,0 +1,21 @@
+//
+// 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
+//
+
+pub mod is_over;
+pub mod is_under;
diff --git a/lib/entry/libimagentryfilter/src/builtin/content/mod.rs b/lib/entry/libimagentryfilter/src/builtin/content/mod.rs
new file mode 100644
index 0000000..2ccbdf6
--- /dev/null
+++ b/lib/entry/libimagentryfilter/src/builtin/content/mod.rs
@@ -0,0 +1,21 @@
+//
+// 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
+//
+
+pub mod grep;
+pub mod length;
diff --git a/lib/entry/libimagentryfilter/src/builtin/header/field_eq.rs b/lib/entry/libimagentryfilter/src/builtin/header/field_eq.rs
new file mode 100644
index 0000000..f10f3d0
--- /dev/null
+++ b/lib/entry/libimagentryfilter/src/builtin/header/field_eq.rs
@@ -0,0 +1,63 @@
+//
+// 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 libimagstore::store::Entry;
+
+use builtin::header::field_path::FieldPath;
+use builtin::header::field_predicate::FieldPredicate;
+use builtin::header::field_predicate::Predicate;
+use filters::filter::Filter;
+
+use toml::Value;
+
+struct EqPred {
+ expected: Value
+}
+
+impl Predicate for EqPred {
+
+ fn evaluate(&self, v: Value) -> bool {
+ self.expected == v
+ }
+
+}
+
+/// Check whether certain header field in a entry is equal to a value
+pub struct FieldEq {
+ filter: FieldPredicate<EqPred>,
+}
+
+impl FieldEq {
+
+ pub fn new(path: FieldPath, expected_value: Value) -> FieldEq {
+ FieldEq {
+ filter: FieldPredicate::new(path, Box::new(EqPred { expected: expected_value })),
+ }
+ }
+
+}
+
+impl Filter<Entry> for FieldEq {
+
+ fn filter(&self, e: &Entry) -> bool {
+ self.filter.filter(e)
+ }
+
+}
+
diff --git a/lib/entry/libimagentryfilter/src/builtin/header/field_exists.rs b/lib/entry/libimagentryfilter/src/builtin/header/field_exists.rs
new file mode 100644
index 0000000..5fe672d
--- /dev/null
+++ b/lib/entry/libimagentryfilter/src/builtin/header/field_exists.rs
@@ -0,0 +1,48 @@
+//
+// 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 libimagstore::store::Entry;
+use libimagstore::toml_ext::TomlValueExt;
+
+use builtin::header::field_path::FieldPath;
+use filters::filter::Filter;
+
+pub struct FieldExists {
+ header_field_path: FieldPath,
+}
+
+impl FieldExists {
+
+ pub fn new(path: FieldPath) -> FieldExists {
+ FieldExists {
+ header_field_path: path,
+ }
+ }
+
+}
+
+impl Filter<Entry> for FieldExists {
+
+ fn filter(&self, e: &Entry) -> bool {
+ e.get_header().read(&self.header_field_path[..]).is_ok()
+ }
+
+}
+
+
diff --git a/lib/entry/libimagentryfilter/src/builtin/header/field_grep.rs b/lib/entry/libimagentryfilter/src/builtin/header/field_grep.rs
new file mode 100644
index 0000000..799fee6
--- /dev/null
+++ b/lib/entry/libimagentryfilter/src/builtin/header/field_grep.rs
@@ -0,0 +1,68 @@
+//
+// imag - the personal information management suite for the commandline
+// Copyright (C) 2015, 2016 Matthias Beyer <mail@beyermatthias.de> and contributors
+//
+// This library is free software; you can redistribute it and/or
+// modify it under the terms of the GNU Lesser General Public
+// License as published by the Free Software Foundation; version
+// 2.1 of the License.
+//
+// This library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+// Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public
+// License along with this library; if not, write to the Free Software
+// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+//
+
+use regex::Regex;
+use toml::Value;
+
+use libimagstore::store::Entry;
+
+use builtin::header::field_path::FieldPath;
+use builtin::header::field_predicate::FieldPredicate;
+use builtin::header::field_predicate::Predicate;
+use filters::filter::Filter;
+
+struct EqGrep{
+ regex: Regex
+}
+
+impl Predicate for EqGrep {
+
+ fn evaluate(&self, v: Value) -> bool {
+ match v {
+ Value::String(s) => self.regex.captures(&s[..]).is_some(),
+ _ => false,
+ }
+ }
+
+}
+
+/// Check whether certain header field in a entry is equal to a value
+pub struct FieldGrep {
+ filter: FieldPredicate<EqGrep>,
+}
+
+impl FieldGrep {
+
+ pub fn new(path: FieldPath, grep: Regex) -> FieldGrep {
+ FieldGrep {
+ filter: FieldPredicate::new(path, Box::new(EqGrep { regex: grep})),
+ }
+ }
+
+}
+
+impl Filter<Entry> for FieldGrep {
+
+ fn filter(&self, e: &Entry) -> bool {
+ self.filter.filter(e)
+ }
+
+}
+
+
diff --git a/lib/entry/libimagentryfilter/src/builtin/header/field_gt.rs b/lib/entry/libimagentryfilter/src/builtin/header/field_gt.rs
new file mode 100644
index 0000000..2c1bcf8
--- /dev/null
+++ b/lib/entry/libimagentryfilter/src/builtin/header/field_gt.rs
@@ -0,0 +1,79 @@
+//
+// 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 libimagstore::store::Entry;
+
+use builtin::header::field_path::FieldPath;
+use builtin::header::field_predicate::FieldPredicate;
+use builtin::header::field_predicate::Predicate;
+use filters::filter::Filter;
+
+use toml::Value;
+
+struct EqGt {
+ comp: Value
+}
+
+impl Predicate for EqGt {
+
+ fn evaluate(&self, v: Value) -> bool {
+ match self.comp {
+ Value::Integer(i) => {
+ match v {
+ Value::Integer(j) => i > j,
+ Value::Float(f) => (i as f64) > f,
+ _ => false,
+ }
+ },
+ Value::Float(f) => {
+ match v {
+ Value::Integer(i) => f > (i as f64),
+ Value::Float(d) => f > d,
+ _ => false,
+ }
+ },
+ _ => false,
+ }
+ }
+
+}
+
+/// Check whether certain header field in a entry is equal to a value
+pub struct FieldGt {
+ filter: FieldPredicate<EqGt>,
+}
+
+impl FieldGt {
+
+ pub fn new(path: FieldPath, expected_value: Value) -> FieldGt {
+ FieldGt {
+ filter: FieldPredicate::new(path, Box::new(EqGt { comp: expected_value })),
+ }
+ }
+
+}
+
+impl Filter<Entry> for FieldGt {
+
+ fn filter(&self, e: &Entry) -> bool {
+ self.filter.filter(e)
+ }
+
+}
+
diff --git a/lib/entry/libimagentryfilter/src/builtin/header/field_isempty.rs b/lib/entry/libimagentryfilter/src/builtin/header/field_isempty.rs
new file mode 100644
index 0000000..7a9c652
--- /dev/null
+++ b/lib/entry/libimagentryfilter/src/builtin/header/field_isempty.rs
@@ -0,0 +1,64 @@
+//
+// 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 libimagstore::store::Entry;
+use libimagstore::toml_ext::TomlValueExt;
+
+use builtin::header::field_path::FieldPath;
+use filters::filter::Filter;
+
+use toml::Value;
+
+pub struct FieldIsEmpty {
+ header_field_path: FieldPath,
+}
+
+impl FieldIsEmpty {
+
+ pub fn new(path: FieldPath) -> FieldIsEmpty {
+ FieldIsEmpty {
+ header_field_path: path,
+ }
+ }
+
+}
+
+impl Filter<Entry> for FieldIsEmpty {
+
+ fn filter(&self, e: &Entry) -> bool {
+ e.get_header()
+ .read(&self.header_field_path[..])
+ .map(|v| {
+ match v {
+ Some(Value::Array(a)) => a.is_empty(),
+ Some(Value::String(s)) => s.is_empty(),
+ Some(Value::Table(t)) => t.is_empty(),
+ Some(Value::Boolean(_)) |
+ Some(Value::Float(_)) |
+ Some(Value::Integer(_)) => false,
+ _ => true,
+ }
+ })
+ .unwrap_or(false)
+ }
+
+}
+
+
+
diff --git a/lib/entry/libimagentryfilter/src/builtin/header/field_istype.rs b/lib/entry/libimagentryfilter/src/builtin/header/field_istype.rs
new file mode 100644
index 0000000..889c832
--- /dev/null
+++ b/lib/entry/libimagentryfilter/src/builtin/header/field_istype.rs
@@ -0,0 +1,89 @@
+//
+// 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 libimagstore::store::Entry;
+
+use builtin::header::field_path::FieldPath;
+use builtin::header::field_predicate::FieldPredicate;
+use builtin::header::field_predicate::Predicate;
+use filters::filter::Filter;
+
+use toml::Value;
+
+pub enum Type {
+ Array,
+ Boolean,
+ Float,
+ Integer,
+ None,
+ String,
+ Table,
+}
+
+impl Type {
+
+ fn matches(&self, v: &Value) -> bool {
+ match (self, v) {
+ (&Type::String, &Value::String(_)) |
+ (&Type::Integer, &Value::Integer(_)) |
+ (&Type::Float, &Value::Float(_)) |
+ (&Type::Boolean, &Value::Boolean(_)) |
+ (&Type::Array, &Value::Array(_)) |
+ (&Type::Table, &Value::Table(_)) => true,
+ _ => false,
+ }
+ }
+
+}
+
+struct IsTypePred {
+ ty: Type
+}
+
+impl Predicate for IsTypePred {
+
+ fn evaluate(&self, v: Value) -> bool {
+ self.ty.matches(&v)
+ }
+
+}
+
+pub struct FieldIsType {
+ filter: FieldPredicate<IsTypePred>,
+}
+
+impl FieldIsType {
+
+ pub fn new(path: FieldPath, expected_type: Type) -> FieldIsType {
+ FieldIsType {
+ filter: FieldPredicate::new(path, Box::new(IsTypePred { ty: expected_type })),
+ }
+ }
+
+}
+
+impl Filter<Entry> for FieldIsType {
+
+ fn filter(&self, e: &Entry) -> bool {
+ self.filter.filter(e)
+ }
+
+}
+
+
diff --git a/lib/entry/libimagentryfilter/src/builtin/header/field_lt.rs b/lib/entry/libimagentryfilter/src/builtin/header/field_lt.rs
new file mode 100644
index 0000000..0316bf8
--- /dev/null
+++ b/lib/entry/libimagentryfilter/src/builtin/header/field_lt.rs
@@ -0,0 +1,79 @@
+//
+// 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 libimagstore::store::Entry;
+
+use builtin::header::field_path::FieldPath;
+use builtin::header::field_predicate::FieldPredicate;
+use builtin::header::field_predicate::Predicate;
+use filters::filter::Filter;
+
+use toml::Value;
+
+struct EqLt {
+ comp: Value
+}
+
+impl Predicate for EqLt {
+
+ fn evaluate(&self, v: Value) -> bool {
+ match self.comp {
+ Value::Integer(i) => {
+ match v {
+ Value::Integer(j) => i < j,
+ Value::Float(f) => (i as f64) < f,
+ _ => false,
+ }
+ },
+ Value::Float(f) => {
+ match v {
+ Value::Integer(i) => f < (i as f64),
+ Value::Float(d) => f < d,
+ _ => false,
+ }
+ },
+ _ => false,
+ }
+ }
+
+}
+
+/// Check whether certain header field in a entry is equal to a value
+pub struct FieldLt {
+ filter: FieldPredicate<EqLt>,
+}
+
+impl FieldLt {
+
+ pub fn new(path: FieldPath, expected_value: Value) -> FieldLt {
+ FieldLt {
+ filter: FieldPredicate::new(path, Box::new(EqLt { comp: expected_value })),
+ }
+ }
+
+}
+
+impl Filter<Entry> for FieldLt {
+
+ fn filter(&self, e: &Entry) -> bool {
+ self.filter.filter(e)
+ }
+
+}
+
diff --git a/lib/entry/libimagentryfilter/src/builtin/header/field_path.rs b/lib/entry/libimagentryfilter/src/builtin/header/field_path.rs
new file mode 100644
index 0000000..42ba65a
--- /dev/null
+++ b/lib/entry/libimagentryfilter/src/builtin/header/field_path.rs
@@ -0,0 +1,20 @@
+//
+// 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
+//
+
+pub type FieldPath = String;
diff --git a/lib/entry/libimagentryfilter/src/builtin/header/field_predicate.rs b/lib/entry/libimagentryfilter/src/builtin/header/field_predicate.rs
new file mode 100644
index 0000000..45aa332
--- /dev/null
+++ b/lib/entry/libimagentryfilter/src/builtin/header/field_predicate.rs
@@ -0,0 +1,65 @@
+//
+// 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 libimagstore::store::Entry;
+use libimagstore::toml_ext::TomlValueExt;
+
+use builtin::header::field_path::FieldPath;
+use filters::filter::Filter;
+
+use toml::Value;
+
+pub trait Predicate {
+ fn evaluate(&self, Value) -> bool;
+}
+
+/// Check whether certain header field in a entry is equal to a value
+pub struct FieldPredicate<P: Predicate> {
+ header_field_path: FieldPath,
+ predicate: Box<P>,
+}
+
+impl<P: Predicate> FieldPredicate<P> {
+
+ pub fn new(path: FieldPath, predicate: Box<P>) -> FieldPredicate<P> {
+ FieldPredicate {
+ header_field_path: path,
+ predicate: predicate,
+ }
+ }
+
+}
+
+impl<P: Predicate> Filter<Entry> for FieldPredicate<P> {
+
+ fn filter(&self, e: &Entry) -> bool {
+ e.get_header()
+ .read(&self.header_field_path[..])
+ .map(|val| {
+ match val {
+ None => false,
+ Some(v) => (*self.predicate).evaluate(v),
+ }
+ })
+ .unwrap_or(false)
+ }
+
+}
+
+
diff --git a/lib/entry/libimagentryfilter/src/builtin/header/mod.rs b/lib/entry/libimagentryfilter/src/builtin/header/mod.rs
new file mode 100644
index 0000000..cb363c2
--- /dev/null
+++ b/lib/entry/libimagentryfilter/src/builtin/header/mod.rs
@@ -0,0 +1,29 @@
+//
+// 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
+//
+
+pub mod field_eq;
+pub mod field_exists;
+pub mod field_grep;
+pub mod field_gt;
+pub mod field_isempty;
+pub mod field_istype;
+pub mod field_lt;
+pub mod field_path;
+pub mod field_predicate;
+pub mod version;
diff --git a/lib/entry/libimagentryfilter/src/builtin/header/version/eq.rs b/lib/entry/libimagentryfilter/src/builtin/header/version/eq.rs
new file mode 100644
index 0000000..1ca38f8
--- /dev/null
+++ b/lib/entry/libimagentryfilter/src/builtin/header/version/eq.rs
@@ -0,0 +1,62 @@
+//
+// 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 semver::Version;
+use toml::Value;
+
+use libimagstore::store::Entry;
+use libimagstore::toml_ext::TomlValueExt;
+
+use filters::filter::Filter;
+
+pub struct VersionEq {
+ version: Version,
+}
+
+impl VersionEq {
+
+ pub fn new(version: Version) -> VersionEq {
+ VersionEq { version: version }
+ }
+
+}
+
+impl Filter<Entry> for VersionEq {
+
+ fn filter(&self, e: &Entry) -> bool {
+ e.get_header()
+ .read("imag.version")
+ .map(|val| {
+ val.map_or(false, |v| {
+ match v {
+ Value::String(s) => {
+ match Version::parse(&s[..]) {
+ Ok(v) => v == self.version,
+ _ => false
+ }
+ },
+ _ => false,
+ }
+ })
+ })
+ .unwrap_or(false)
+ }
+
+}
+
diff --git a/lib/entry/libimagentryfilter/src/builtin/header/version/gt.rs b/lib/entry/libimagentryfilter/src/builtin/header/version/gt.rs
new file mode 100644
index 0000000..8e3873f
--- /dev/null
+++ b/lib/entry/libimagentryfilter/src/builtin/header/version/gt.rs
@@ -0,0 +1,64 @@
+//
+// 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 semver::Version;
+use toml::Value;
+
+use libimagstore::store::Entry;
+use libimagstore::toml_ext::TomlValueExt;
+
+use filters::filter::Filter;
+
+pub struct VersionGt {
+ version: Version,
+}
+
+impl VersionGt {
+
+ pub fn new(version: Version) -> VersionGt {
+ VersionGt { version: version }
+ }
+
+}
+
+impl Filter<Entry> for VersionGt {
+
+ fn filter(&self, e: &Entry) -> bool {
+ e.get_header()
+ .read("imag.version")
+ .map(|val| {
+ val.map_or(false, |v| {
+ match v {
+ Value::String(s) => {
+ match Version::parse(&s[..]) {
+ Ok(v) => v > self.version,
+ _ => false
+ }
+ },
+ _ => false,
+ }
+ })
+ })
+ .unwrap_or(false)
+ }
+
+}
+
+
+
diff --git a/lib/entry/libimagentryfilter/src/builtin/header/version/lt.rs b/lib/entry/libimagentryfilter/src/builtin/header/version/lt.rs
new file mode 100644
index 0000000..c475b43
--- /dev/null
+++ b/lib/entry/libimagentryfilter/src/builtin/header/version/lt.rs
@@ -0,0 +1,63 @@
+//
+// 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 semver::Version;
+use toml::Value;
+
+use libimagstore::store::Entry;
+use libimagstore::toml_ext::TomlValueExt;
+
+use filters::filter::Filter;
+
+pub struct VersionLt {
+ version: Version,
+}
+
+impl VersionLt {
+
+ pub fn new(version: Version) -> VersionLt {
+ VersionLt { version: version }
+ }
+
+}
+
+impl Filter<Entry> for VersionLt {
+
+ fn filter(&self, e: &Entry) -> bool {
+ e.get_header()
+ .read("imag.version")
+ .map(|val| {
+ val.map_or(false, |v| {
+ match v {
+ Value::String(s) => {
+ match Version::parse(&s[..]) {
+ Ok(v) => v < self.version,
+ _ => false
+ }
+ },
+ _ => false,
+ }
+ })
+ })
+ .unwrap_or(false)
+ }
+
+}
+
+
diff --git a/lib/entry/libimagentryfilter/src/builtin/header/version/mod.rs b/lib/entry/libimagentryfilter/src/builtin/header/version/mod.rs
new file mode 100644
index 0000000..e214e32
--- /dev/null
+++ b/lib/entry/libimagentryfilter/src/builtin/header/version/mod.rs
@@ -0,0 +1,23 @@
+//
+// 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
+//
+
+pub mod eq;
+pub mod gt;
+pub mod lt;
+pub mod range;
diff --git a/lib/entry/libimagentryfilter/src/builtin/header/version/range.rs b/lib/entry/libimagentryfilter/src/builtin/header/version/range.rs
new file mode 100644
index 0000000..59f3a1d
--- /dev/null
+++ b/lib/entry/libimagentryfilter/src/builtin/header/version/range.rs
@@ -0,0 +1,69 @@
+//
+// 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 semver::Version;
+
+use libimagstore::store::Entry;
+
+use builtin::header::version::gt::VersionGt;
+use builtin::header::version::lt::VersionLt;
+use filters::filter::Filter;
+use filters::ops::and::And;
+use filters::ops::not::Not;
+
+pub struct VersionInRange {
+ and: And<VersionGt, VersionLt>,
+}
+
+impl VersionInRange {
+
+ pub fn new(lowerbound: Version, upperbound: Version) -> VersionInRange {
+ VersionInRange { and: VersionGt::new(lowerbound).and(VersionLt::new(upperbound)) }
+ }
+
+}
+
+impl Filter<Entry> for VersionInRange {
+
+ fn filter(&self, e: &Entry) -> bool {
+ self.and.filter(e)
+ }
+
+}
+
+pub struct VersionOutOfRange {
+ not: Not<VersionInRange>
+}
+
+impl VersionOutOfRange {
+
+ pub fn new(lowerbound: Version, upperbound: Version) -> VersionOutOfRange {
+ VersionOutOfRange { not: VersionInRange::new(lowerbound, upperbound).not() }
+ }
+
+}
+
+impl Filter<Entry> for VersionOutOfRange {
+
+ fn filter(&self, e: &Entry) -> bool {
+ self.not.filter(e)
+ }
+
+}
+
diff --git a/lib/entry/libimagentryfilter/src/builtin/mod.rs b/lib/entry/libimagentryfilter/src/builtin/mod.rs
new file mode 100644
index 0000000..68c819b
--- /dev/null
+++ b/lib/entry/libimagentryfilter/src/builtin/mod.rs
@@ -0,0 +1,24 @@
+//
+// 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
+//
+
+pub mod content;
+pub mod header;
+
+pub mod bool_filter;
+
diff --git a/lib/entry/libimagentryfilter/src/cli.rs b/lib/entry/libimagentryfilter/src/cli.rs
new file mode 100644
index 0000000..ee8b838
--- /dev/null
+++ b/lib/entry/libimagentryfilter/src/cli.rs
@@ -0,0 +1,19 @@
+//
+// 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
+//
+
diff --git a/lib/entry/libimagentryfilter/src/lib.rs b/lib/entry/libimagentryfilter/src/lib.rs
new file mode 100644
index 0000000..ad99db6
--- /dev/null
+++ b/lib/entry/libimagentryfilter/src/lib.rs
@@ -0,0 +1,54 @@
+//
+// 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
+//
+
+#![deny(
+ non_camel_case_types,
+ non_snake_case,
+ path_statements,
+ trivial_numeric_casts,
+ unstable_features,
+ unused_allocation,
+ unused_import_braces,
+ unused_imports,
+ unused_must_use,
+ unused_mut,
+ unused_qualifications,
+ while_true,
+)]
+
+extern crate filters;
+extern crate itertools;
+extern crate regex;
+extern crate semver;
+extern crate toml;
+
+extern crate libimagstore;
+extern crate libimagentrytag;
+
+// core functionality modules of the crate,
+// these depend only on libimagstore
+
+pub mod cli;
+pub mod builtin;
+
+// extended functionality of the crate
+// these depend on other internal libraries than libimagstore and use the upper core modules for
+// their functionality
+
+pub mod tags;
diff --git a/lib/entry/libimagentryfilter/src/tags/mod.rs b/lib/entry/libimagentryfilter/src/tags/mod.rs
new file mode 100644
index 0000000..cef11e5
--- /dev/null
+++ b/lib/entry/libimagentryfilter/src/tags/mod.rs
@@ -0,0 +1,96 @@
+//
+// 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 libimagstore::store::Entry;
+use libimagentrytag::tagable::Tagable;
+use libimagentrytag::tag::Tag;
+
+use filters::filter::Filter;
+
+/// Check whether an Entry has a certain tag
+pub struct HasTag {
+ tag: Tag,
+}
+
+impl HasTag {
+
+ pub fn new(tag: Tag) -> HasTag {
+ HasTag {
+ tag: tag,
+ }
+ }
+
+}
+
+impl Filter<Entry> for HasTag {
+
+ fn filter(&self, e: &Entry) -> bool {
+ e.has_tag(&self.tag).ok().unwrap_or(false)
+ }
+
+}
+
+
+/// Check whether an Entry has all of these tags
+pub struct HasAllTags {
+ tags: Vec<Tag>,
+}
+
+impl HasAllTags {
+
+ pub fn new(tags: Vec<Tag>) -> HasAllTags {
+ HasAllTags {
+ tags: tags,
+ }
+ }
+
+}
+
+impl Filter<Entry> for HasAllTags {
+
+ fn filter(&self, e: &Entry) -> bool {
+ e.has_tags(&self.tags).ok().unwrap_or(false)
+ }
+
+}
+
+
+/// Check whether an Entry has any of these tags
+pub struct HasAnyTags {
+ tags: Vec<Tag>,
+}
+
+impl HasAnyTags {
+
+ pub fn new(tags: Vec<Tag>) -> HasAnyTags {
+ HasAnyTags {
+ tags: tags,
+ }
+ }
+
+}
+
+impl Filter<Entry> for HasAnyTags {
+
+ fn filter(&self, e: &Entry) -> bool {
+ self.tags.iter().any(|tag| e.has_tag(tag).ok().unwrap_or(false))
+ }
+
+}
+
diff --git a/lib/entry/libimagentrylink/Cargo.toml b/lib/entry/libimagentrylink/Cargo.toml
new file mode 100644
index 0000000..32e8687
--- /dev/null
+++ b/lib/entry/libimagentrylink/Cargo.toml
@@ -0,0 +1,28 @@
+[package]
+name = "libimagentrylink"
+version = "0.4.0"
+authors = ["Matthias Beyer <mail@beyermatthias.de>"]
+
+description = "Library for the imag core distribution"
+
+keywords = ["imag", "PIM", "personal", "information", "management"]
+readme = "../README.md"
+license = "LGPL-2.1"
+
+documentation = "https://matthiasbeyer.github.io/imag/imag_documentation/index.html"
+repository = "https://github.com/matthiasbeyer/imag"
+homepage = "http://imag-pim.org"
+
+[dependencies]
+itertools = "0.5"
+log = "0.3"
+toml = "^0.4"
+semver = "0.5"
+url = "1.2"
+rust-crypto = "0.2"
+env_logger = "0.3"
+is-match = "0.1"
+
+libimagstore = { version = "0.4.0", path = "../../../lib/core/libimagstore" }
+libimagerror = { version = "0.4.0", path = "../../../lib/core/libimagerror" }
+libimagutil = { version = "0.4.0", path = "../../../lib/etc/libimagutil" }
diff --git a/lib/entry/libimagentrylink/README.md b/lib/entry/libimagentrylink/README.md
new file mode 120000
index 0000000..d30d8ab
--- /dev/null
+++ b/lib/entry/libimagentrylink/README.md
@@ -0,0 +1 @@
+../doc/src/05100-lib-entrylink.md \ No newline at end of file
diff --git a/lib/entry/libimagentrylink/src/error.rs b/lib/entry/libimagentrylink/src/error.rs
new file mode 100644
index 0000000..10d83f9
--- /dev/null
+++ b/lib/entry/libimagentrylink/src/error.rs
@@ -0,0 +1,40 @@
+//
+// 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
+//
+
+generate_error_module!(
+ generate_error_types!(LinkError, LinkErrorKind,
+ EntryHeaderReadError => "Error while reading an entry header",
+ EntryHeaderWriteError => "Error while writing an entry header",
+ ExistingLinkTypeWrong => "Existing link entry has wrong type",
+ LinkTargetDoesNotExist => "Link target does not exist in the store",
+ LinkParserError => "Link cannot be parsed",
+ LinkParserFieldMissingError => "Link cannot be parsed: Field missing",
+ LinkParserFieldTypeError => "Link cannot be parsed: Field type wrong",
+ InternalConversionError => "Error while converting values internally",
+ InvalidUri => "URI is not valid",
+ StoreReadError => "Store read error",
+ StoreWriteError => "Store write error",
+ StoreIdError => "StoreId handling error"
+ );
+);
+
+pub use self::error::LinkError;
+pub use self::error::LinkErrorKind;
+pub use self::error::MapErrInto;
+
diff --git a/lib/entry/libimagentrylink/src/external.rs b/lib/entry/libimagentrylink/src/external.rs
new file mode 100644
index 0000000..daed7a1
--- /dev/null
+++ b/lib/entry/libimagentrylink/src/external.rs
@@ -0,0 +1,416 @@
+//
+// 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
+//
+
+/// External linking is a complex implementation to be able to serve a clean and easy-to-use
+/// interface.
+///
+/// Internally, there are no such things as "external links" (plural). Each Entry in the store can
+/// only have _one_ external link.
+///
+/// This library does the following therefor: It allows you to have several external links with one
+/// entry, which are internally one file in the store for each link, linked with "internal
+/// linking".
+///
+/// This helps us greatly with deduplication of URLs.
+///
+
+use std::ops::DerefMut;
+use std::collections::BTreeMap;
+use std::fmt::Debug;
+
+use libimagstore::store::Entry;
+use libimagstore::store::FileLockEntry;
+use libimagstore::store::Store;
+use libimagstore::storeid::StoreId;
+use libimagstore::storeid::IntoStoreId;
+use libimagstore::toml_ext::TomlValueExt;
+use libimagutil::debug_result::*;
+
+use error::LinkError as LE;
+use error::LinkErrorKind as LEK;
+use error::MapErrInto;
+use result::Result;
+use internal::InternalLinker;
+use module_path::ModuleEntryPath;
+
+use self::iter::*;
+
+use toml::Value;
+use url::Url;
+use crypto::sha1::Sha1;
+use crypto::digest::Digest;
+
+/// "Link" Type, just an abstraction over `FileLockEntry` to have some convenience internally.
+pub struct Link<'a> {
+ link: FileLockEntry<'a>
+}
+
+impl<'a> Link<'a> {
+
+ pub fn new(fle: FileLockEntry<'a>) -> Link<'a> {
+ Link { link: fle }
+ }
+
+ /// Get a link Url object from a `FileLockEntry`, ignore errors.
+ fn get_link_uri_from_filelockentry(file: &FileLockEntry<'a>) -> Option<Url> {
+ file.get_header()
+ .read("imag.content.url")
+ .ok()
+ .and_then(|opt| match opt {
+ Some(Value::String(s)) => {
+ debug!("Found url, parsing: {:?}", s);
+ Url::parse(&s[..]).ok()
+ },
+ _ => None
+ })
+ }
+
+ pub fn get_url(&self) -> Result<Option<Url>> {
+ let opt = self.link
+ .get_header()
+ .read("imag.content.url");
+
+ match opt {
+ Ok(Some(Value::String(s))) => {
+ Url::parse(&s[..])
+ .map(Some)
+ .map_err(|e| LE::new(LEK::EntryHeaderReadError, Some(Box::new(e))))
+ },
+ Ok(None) => Ok(None),
+ _ => Err(LE::new(LEK::EntryHeaderReadError, None))
+ }
+ }
+
+}
+
+pub trait ExternalLinker : InternalLinker {
+
+ /// Get the external links from the implementor object
+ fn get_external_links<'a>(&self, store: &'a Store) -> Result<UrlIter<'a>>;
+
+ /// Set the external links for the implementor object
+ fn set_external_links(&mut self, store: &Store, links: Vec<Url>) -> Result<()>;
+
+ /// Add an external link to the implementor object
+ fn add_external_link(&mut self, store: &Store, link: Url) -> Result<()>;
+
+ /// Remove an external link from the implementor object
+ fn remove_external_link(&mut self, store: &Store, link: Url) -> Result<()>;
+
+}
+
+pub mod iter {
+ //! Iterator helpers for external linking stuff
+ //!
+ //! Contains also helpers to filter iterators for external/internal links
+ //!
+ //!
+ //! # Warning
+ //!
+ //! This module uses `internal::Link` as link type, so we operate on _store ids_ here.
+ //!
+ //! Not to confuse with `external::Link` which is a real `FileLockEntry` under the hood.
+ //!
+
+ use libimagutil::debug_result::*;
+ use libimagstore::store::Store;
+
+ use internal::Link;
+ use internal::iter::LinkIter;
+ use error::LinkErrorKind as LEK;
+ use error::MapErrInto;
+ use result::Result;
+
+ use url::Url;
+
+ /// Helper for building `OnlyExternalIter` and `NoExternalIter`
+ ///
+ /// The boolean value defines, how to interpret the `is_external_link_storeid()` return value
+ /// (here as "pred"):
+ ///
+ /// pred | bool | xor | take?
+ /// ---- | ---- | --- | ----
+ /// 0 | 0 | 0 | 1
+ /// 0 | 1 | 1 | 0
+ /// 1 | 0 | 1 | 0
+ /// 1 | 1 | 0 | 1
+ ///
+ /// If `bool` says "take if return value is false", we take the element if the `pred` returns
+ /// false... and so on.
+ ///
+ /// As we can see, the operator between these two operants is `!(a ^ b)`.
+ pub struct ExternalFilterIter(LinkIter, bool);
+
+ impl Iterator for ExternalFilterIter {
+ type Item = Link;
+
+ fn next(&mut self) -> Option<Self::Item> {
+ use super::is_external_link_storeid;
+
+ while let Some(elem) = self.0.next() {
+ if !(self.1 ^ is_external_link_storeid(&elem)) {
+ return Some(elem);
+ }
+ }
+ None
+ }
+ }
+
+ /// Helper trait to be implemented on `LinkIter` to select or deselect all external links
+ ///
+ /// # See also
+ ///
+ /// Also see `OnlyExternalIter` and `NoExternalIter` and the helper traits/functions
+ /// `OnlyInteralLinks`/`only_internal_links()` and `OnlyExternalLinks`/`only_external_links()`.
+ pub trait SelectExternal {
+ fn select_external_links(self, b: bool) -> ExternalFilterIter;
+ }
+
+ impl SelectExternal for LinkIter {
+ fn select_external_links(self, b: bool) -> ExternalFilterIter {
+ ExternalFilterIter(self, b)
+ }
+ }
+
+
+ pub struct OnlyExternalIter(ExternalFilterIter);
+
+ impl OnlyExternalIter {
+ pub fn new(li: LinkIter) -> OnlyExternalIter {
+ OnlyExternalIter(ExternalFilterIter(li, true))
+ }
+
+ pub fn urls<'a>(self, store: &'a Store) -> UrlIter<'a> {
+ UrlIter(self, store)
+ }
+ }
+
+ impl Iterator for OnlyExternalIter {
+ type Item = Link;
+
+ fn next(&mut self) -> Option<Self::Item> {
+ self.0.next()
+ }
+ }
+
+ pub struct NoExternalIter(ExternalFilterIter);
+
+ impl NoExternalIter {
+ pub fn new(li: LinkIter) -> NoExternalIter {
+ NoExternalIter(ExternalFilterIter(li, false))
+ }
+ }
+
+ impl Iterator for NoExternalIter {
+ type Item = Link;
+
+ fn next(&mut self) -> Option<Self::Item> {
+ self.0.next()
+ }
+ }
+
+ pub trait OnlyExternalLinks : Sized {
+ fn only_external_links(self) -> OnlyExternalIter ;
+
+ fn no_internal_links(self) -> OnlyExternalIter {
+ self.only_external_links()
+ }
+ }
+
+ impl OnlyExternalLinks for LinkIter {
+ fn only_external_links(self) -> OnlyExternalIter {
+ OnlyExternalIter::new(self)
+ }
+ }
+
+ pub trait OnlyInternalLinks : Sized {
+ fn only_internal_links(self) -> NoExternalIter;
+
+ fn no_external_links(self) -> NoExternalIter {
+ self.only_internal_links()
+ }
+ }
+
+ impl OnlyInternalLinks for LinkIter {
+ fn only_internal_links(self) -> NoExternalIter {
+ NoExternalIter::new(self)
+ }
+ }
+
+ pub struct UrlIter<'a>(OnlyExternalIter, &'a Store);
+
+ impl<'a> Iterator for UrlIter<'a> {
+ type Item = Result<Url>;
+
+ fn next(&mut self) -> Option<Self::Item> {
+ use super::get_external_link_from_file;
+
+ self.0
+ .next()
+ .map(|id| {
+ debug!("Retrieving entry for id: '{:?}'", id);
+ self.1
+ .retrieve(id.clone())
+ .map_err_into(LEK::StoreReadError)
+ .map_dbg_err(|_| format!("Retrieving entry for id: '{:?}' failed", id))
+ .and_then(|f| {
+ debug!("Store::retrieve({:?}) succeeded", id);
+ debug!("getting external link from file now");
+ get_external_link_from_file(&f)
+ .map_dbg_err(|e| format!("URL -> Err = {:?}", e))
+ })
+ })
+ }
+
+ }
+
+}
+
+
+/// Check whether the StoreId starts with `/link/external/`
+pub fn is_external_link_storeid<A: AsRef<StoreId> + Debug>(id: A) -> bool {
+ debug!("Checking whether this is a 'links/external/': '{:?}'", id);
+ id.as_ref().local().starts_with("links/external")
+}
+
+fn get_external_link_from_file(entry: &FileLockEntry) -> Result<Url> {
+ Link::get_link_uri_from_filelockentry(entry) // TODO: Do not hide error by using this function
+ .ok_or(LE::new(LEK::StoreReadError, None))
+}
+
+/// Implement `ExternalLinker` for `Entry`, hiding the fact that there is no such thing as an external
+/// link in an entry, but internal links to other entries which serve as external links, as one
+/// entry in the store can only have one external link.
+impl ExternalLinker for Entry {
+
+ /// Get the external links from the implementor object
+ fn get_external_links<'a>(&self, store: &'a Store) -> Result<UrlIter<'a>> {
+ // Iterate through all internal links and filter for FileLockEntries which live in
+ // /link/external/<SHA> -> load these files and get the external link from their headers,
+ // put them into the return vector.
+ self.get_internal_links()
+ .map_err(|e| LE::new(LEK::StoreReadError, Some(Box::new(e))))
+ .map(|iter| {
+ debug!("Getting external links");
+ iter.only_external_links().urls(store)
+ })
+ }
+
+ /// Set the external links for the implementor object
+ fn set_external_links(&mut self, store: &Store, links: Vec<Url>) -> Result<()> {
+ // Take all the links, generate a SHA sum out of each one, filter out the already existing
+ // store entries and store the other URIs in the header of one FileLockEntry each, in
+ // the path /link/external/<SHA of the URL>
+
+ debug!("Iterating {} links = {:?}", links.len(), links);
+ for link in links { // for all links
+ let hash = {
+ let mut s = Sha1::new();
+ s.input_str(&link.as_str()[..]);
+ s.result_str()
+ };
+ let file_id = try!(
+ ModuleEntryPath::new(format!("external/{}", hash)).into_storeid()
+ .map_err_into(LEK::StoreWriteError)
+ .map_dbg_err(|_| {
+ format!("Failed to build StoreId for this hash '{:?}'", hash)
+ })
+ );
+
+ debug!("Link = '{:?}'", link);
+ debug!("Hash = '{:?}'", hash);
+ debug!("StoreId = '{:?}'", file_id);
+
+ // retrieve the file from the store, which implicitely creates the entry if it does not
+ // exist
+ let mut file = try!(store
+ .retrieve(file_id.clone())
+ .map_err_into(LEK::StoreWriteError)
+ .map_dbg_err(|_| {
+ format!("Failed to create or retrieve an file for this link '{:?}'", link)
+ }));
+
+ debug!("Generating header content!");
+ {
+ let mut hdr = file.deref_mut().get_header_mut();
+
+ let mut table = match hdr.read("imag.content") {
+ Ok(Some(Value::Table(table))) => table,
+ Ok(Some(_)) => {
+ warn!("There is a value at 'imag.content' which is not a table.");
+ warn!("Going to override this value");
+ BTreeMap::new()
+ },
+ Ok(None) => BTreeMap::new(),
+ Err(e) => return Err(LE::new(LEK::StoreWriteError, Some(Box::new(e)))),
+ };
+
+ let v = Value::String(link.into_string());
+
+ debug!("setting URL = '{:?}", v);
+ table.insert(String::from("url"), v);
+
+ if let Err(e) = hdr.set("imag.content", Value::Table(table)) {
+ return Err(LE::new(LEK::StoreWriteError, Some(Box::new(e))));
+ } else {
+ debug!("Setting URL worked");
+ }
+ }
+
+ // then add an internal link to the new file or return an error if this fails
+ if let Err(e) = self.add_internal_link(file.deref_mut()) {
+ debug!("Error adding internal link");
+ return Err(LE::new(LEK::StoreWriteError, Some(Box::new(e))));
+ }
+ }
+ debug!("Ready iterating");
+ Ok(())
+ }
+
+ /// Add an external link to the implementor object
+ fn add_external_link(&mut self, store: &Store, link: Url) -> Result<()> {
+ // get external links, add this one, save them
+ debug!("Getting links");
+ self.get_external_links(store)
+ .and_then(|links| {
+ // TODO: Do not ignore errors here
+ let mut links = links.filter_map(Result::ok).collect::<Vec<_>>();
+ debug!("Adding link = '{:?}' to links = {:?}", link, links);
+ links.push(link);
+ debug!("Setting {} links = {:?}", links.len(), links);
+ self.set_external_links(store, links)
+ })
+ }
+
+ /// Remove an external link from the implementor object
+ fn remove_external_link(&mut self, store: &Store, link: Url) -> Result<()> {
+ // get external links, remove this one, save them
+ self.get_external_links(store)
+ .and_then(|links| {
+ debug!("Removing link = '{:?}'", link);
+ let links = links
+ .filter_map(Result::ok)
+ .filter(|l| l.as_str() != link.as_str())
+ .collect::<Vec<_>>();
+ self.set_external_links(store, links)
+ })
+ }
+
+}
+
diff --git a/lib/entry/libimagentrylink/src/internal.rs b/lib/entry/libimagentrylink/src/internal.rs
new file mode 100644
index 0000000..6200377
--- /dev/null
+++ b/lib/entry/libimagentrylink/src/internal.rs
@@ -0,0 +1,1060 @@
+//
+// 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::collections::BTreeMap;
+#[cfg(test)]
+use std::path::PathBuf;
+
+use libimagstore::storeid::StoreId;
+use libimagstore::storeid::IntoStoreId;
+use libimagstore::store::Entry;
+use libimagstore::store::Result as StoreResult;
+use libimagstore::toml_ext::TomlValueExt;
+use libimagerror::into::IntoError;
+
+use error::LinkErrorKind as LEK;
+use error::MapErrInto;
+use result::Result;
+use self::iter::LinkIter;
+use self::iter::IntoValues;
+
+use toml::Value;
+
+#[derive(Eq, PartialOrd, Ord, Hash, Debug, Clone)]
+pub enum Link {
+ Id { link: StoreId },
+ Annotated { link: StoreId, annotation: String },
+}
+
+impl Link {
+
+ pub fn exists(&self) -> Result<bool> {
+ match *self {
+ Link::Id { ref link } => link.exists(),
+ Link::Annotated { ref link, .. } => link.exists(),
+ }
+ .map_err_into(LEK::StoreIdError)
+ }
+
+ pub fn to_str(&self) -> Result<String> {
+ match *self {
+ Link::Id { ref link } => link.to_str(),
+ Link::Annotated { ref link, .. } => link.to_str(),
+ }
+ .map_err_into(LEK::StoreReadError)
+ }
+
+
+ fn eq_store_id(&self, id: &StoreId) -> bool {
+ match self {
+ &Link::Id { link: ref s } => s.eq(id),
+ &Link::Annotated { link: ref s, .. } => s.eq(id),
+ }
+ }
+
+ /// Get the StoreId inside the Link, which is always present
+ pub fn get_store_id(&self) -> &StoreId {
+ match self {
+ &Link::Id { link: ref s } => s,
+ &Link::Annotated { link: ref s, .. } => s,
+ }
+ }
+
+ /// Helper wrapper around Link for StoreId
+ fn without_base(self) -> Link {
+ match self {
+ Link::Id { link: s } => Link::Id { link: s.without_base() },
+ Link::Annotated { link: s, annotation: ann } =>
+ Link::Annotated { link: s.without_base(), annotation: ann },
+ }
+ }
+
+ /// Helper wrapper around Link for StoreId
+ #[cfg(test)]
+ fn with_base(self, pb: PathBuf) -> Link {
+ match self {
+ Link::Id { link: s } => Link::Id { link: s.with_base(pb) },
+ Link::Annotated { link: s, annotation: ann } =>
+ Link::Annotated { link: s.with_base(pb), annotation: ann },
+ }
+ }
+
+ fn to_value(&self) -> Result<Value> {
+ match self {
+ &Link::Id { link: ref s } =>
+ s.to_str().map(Value::String).map_err_into(LEK::InternalConversionError),
+ &Link::Annotated { ref link, annotation: ref anno } => {
+ link.to_str()
+ .map(Value::String)
+ .map_err_into(LEK::InternalConversionError)
+ .map(|link| {
+ let mut tab = BTreeMap::new();
+
+ tab.insert("link".to_owned(), link);
+ tab.insert("annotation".to_owned(), Value::String(anno.clone()));
+ Value::Table(tab)
+ })
+ }
+ }
+ }
+
+}
+
+impl ::std::cmp::PartialEq for Link {
+ fn eq(&self, other: &Self) -> bool {
+ match (self, other) {
+ (&Link::Id { link: ref a }, &Link::Id { link: ref b }) => a.eq(&b),
+ (&Link::Annotated { link: ref a, annotation: ref ann1 },
+ &Link::Annotated { link: ref b, annotation: ref ann2 }) =>
+ (a, ann1).eq(&(b, ann2)),
+ _ => false,
+ }
+ }
+}
+
+impl From<StoreId> for Link {
+
+ fn from(s: StoreId) -> Link {
+ Link::Id { link: s }
+ }
+}
+
+impl Into<StoreId> for Link {
+ fn into(self) -> StoreId {
+ match self {
+ Link::Id { link } => link,
+ Link::Annotated { link, .. } => link,
+ }
+ }
+}
+
+impl IntoStoreId for Link {
+ fn into_storeid(self) -> StoreResult<StoreId> {
+ match self {
+ Link::Id { link } => Ok(link),
+ Link::Annotated { link, .. } => Ok(link),
+ }
+ }
+}
+
+impl AsRef<StoreId> for Link {
+ fn as_ref(&self) -> &StoreId {
+ match self {
+ &Link::Id { ref link } => &link,
+ &Link::Annotated { ref link, .. } => &link,
+ }
+ }
+}
+
+pub trait InternalLinker {
+
+ /// Get the internal links from the implementor object
+ fn get_internal_links(&self) -> Result<LinkIter>;
+
+ /// Set the internal links for the implementor object
+ fn set_internal_links(&mut self, links: Vec<&mut Entry>) -> Result<LinkIter>;
+
+ /// Add an internal link to the implementor object
+ fn add_internal_link(&mut self, link: &mut Entry) -> Result<()>;
+
+ /// Remove an internal link from the implementor object
+ fn remove_internal_link(&mut self, link: &mut Entry) -> Result<()>;
+
+ /// Add internal annotated link
+ fn add_internal_annotated_link(&mut self, link: &mut Entry, annotation: String) -> Result<()>;
+}
+
+pub mod iter {
+ use std::vec::IntoIter;
+ use super::Link;
+
+ use error::LinkErrorKind as LEK;
+ use error::MapErrInto;
+ use result::Result;
+
+ use toml::Value;
+ use itertools::Itertools;
+
+ use libimagstore::store::Store;
+ use libimagstore::store::FileLockEntry;
+
+ pub struct LinkIter(IntoIter<Link>);
+
+ impl LinkIter {
+
+ pub fn new(v: Vec<Link>) -> LinkIter {
+ LinkIter(v.into_iter())
+ }
+
+ pub fn into_getter(self, store: &Store) -> GetIter {
+ GetIter(self.0, store)
+ }
+
+ }
+
+ impl Iterator for LinkIter {
+ type Item = Link;
+
+ fn next(&mut self) -> Option<Self::Item> {
+ self.0.next()
+ }
+ }
+
+ pub trait IntoValues {
+ fn into_values(self) -> Vec<Result<Value>>;
+ }
+
+ impl<I: Iterator<Item = Link>> IntoValues for I {
+ fn into_values(self) -> Vec<Result<Value>> {
+ self.map(|s| s.without_base())
+ .unique()
+ .sorted()
+ .into_iter() // Cannot sort toml::Value, hence uglyness here
+ .map(|link| link.to_value().map_err_into(LEK::InternalConversionError))
+ .collect()
+ }
+ }
+
+ /// An Iterator that `Store::get()`s the Entries from the store while consumed
+ pub struct GetIter<'a>(IntoIter<Link>, &'a Store);
+
+ impl<'a> GetIter<'a> {
+ pub fn new(i: IntoIter<Link>, store: &'a Store) -> GetIter<'a> {
+ GetIter(i, store)
+ }
+
+ /// Turn this iterator into a LinkGcIter, which `Store::delete()`s entries that are not
+ /// linked to any other entry.
+ pub fn delete_unlinked(self) -> DeleteUnlinkedIter<'a> {
+ DeleteUnlinkedIter(self)
+ }
+
+ /// Turn this iterator into a FilterLinksIter that removes all entries that are not linked
+ /// to any other entry, by filtering them out the iterator.
+ ///
+ /// This does _not_ remove the entries from the store.
+ pub fn without_unlinked(self) -> FilterLinksIter<'a> {
+ FilterLinksIter::new(self, Box::new(|links: &[Link]| links.len() > 0))
+ }
+
+ /// Turn this iterator into a FilterLinksIter that removes all entries that have less than
+ /// `n` links to any other entries.
+ ///
+ /// This does _not_ remove the entries from the store.
+ pub fn with_less_than_n_links(self, n: usize) -> FilterLinksIter<'a> {
+ FilterLinksIter::new(self, Box::new(move |links: &[Link]| links.len() < n))
+ }
+
+ /// Turn this iterator into a FilterLinksIter that removes all entries that have more than
+ /// `n` links to any other entries.
+ ///
+ /// This does _not_ remove the entries from the store.
+ pub fn with_more_than_n_links(self, n: usize) -> FilterLinksIter<'a> {
+ FilterLinksIter::new(self, Box::new(move |links: &[Link]| links.len() > n))
+ }
+
+ /// Turn this iterator into a FilterLinksIter that removes all entries where the predicate
+ /// `F` returns false
+ ///
+ /// This does _not_ remove the entries from the store.
+ pub fn filtered_for_links(self, f: Box<Fn(&[Link]) -> bool>) -> FilterLinksIter<'a> {
+ FilterLinksIter::new(self, f)
+ }
+
+ pub fn store(&self) -> &Store {
+ self.1
+ }
+ }
+
+ impl<'a> Iterator for GetIter<'a> {
+ type Item = Result<FileLockEntry<'a>>;
+
+ fn next(&mut self) -> Option<Self::Item> {
+ self.0.next().and_then(|id| match self.1.get(id).map_err_into(LEK::StoreReadError) {
+ Ok(None) => None,
+ Ok(Some(x)) => Some(Ok(x)),
+ Err(e) => Some(Err(e)),
+ })
+ }
+
+ }
+
+ /// An iterator helper that has a function F.
+ ///
+ /// If the function F returns `false` for the number of links, the entry is ignored, else it is
+ /// taken.
+ pub struct FilterLinksIter<'a>(GetIter<'a>, Box<Fn(&[Link]) -> bool>);
+
+ impl<'a> FilterLinksIter<'a> {
+ pub fn new(gi: GetIter<'a>, f: Box<Fn(&[Link]) -> bool>) -> FilterLinksIter<'a> {
+ FilterLinksIter(gi, f)
+ }
+ }
+
+ impl<'a> Iterator for FilterLinksIter<'a> {
+ type Item = Result<FileLockEntry<'a>>;
+
+ fn next(&mut self) -> Option<Self::Item> {
+ use internal::InternalLinker;
+
+ loop {
+ match self.0.next() {
+ Some(Ok(fle)) => {
+ let links = match fle.get_internal_links().map_err_into(LEK::StoreReadError)
+ {
+ Err(e) => return Some(Err(e)),
+ Ok(links) => links.collect::<Vec<_>>(),
+ };
+ if !(self.1)(&links) {
+ continue;
+ } else {
+ return Some(Ok(fle));
+ }
+ },
+ Some(Err(e)) => return Some(Err(e)),
+ None => break,
+ }
+ }
+ None
+ }
+
+ }
+
+ /// An iterator that removes all Items from the iterator that are not linked anymore by calling
+ /// `Store::delete()` on them.
+ ///
+ /// It yields only items which are somehow linked to another entry
+ ///
+ /// # Warning
+ ///
+ /// Deletes entries from the store.
+ ///
+ pub struct DeleteUnlinkedIter<'a>(GetIter<'a>);
+
+ impl<'a> Iterator for DeleteUnlinkedIter<'a> {
+ type Item = Result<FileLockEntry<'a>>;
+
+ fn next(&mut self) -> Option<Self::Item> {
+ use internal::InternalLinker;
+
+ loop {
+ match self.0.next() {
+ Some(Ok(fle)) => {
+ let links = match fle.get_internal_links().map_err_into(LEK::StoreReadError)
+ {
+ Err(e) => return Some(Err(e)),
+ Ok(links) => links,
+ };
+ if links.count() == 0 {
+ match self.0
+ .store()
+ .delete(fle.get_location().clone())
+ .map_err_into(LEK::StoreWriteError)
+ {
+ Ok(x) => x,
+ Err(e) => return Some(Err(e)),
+ }
+ } else {
+ return Some(Ok(fle));
+ }
+ },
+ Some(Err(e)) => return Some(Err(e)),
+ None => break,
+ }
+ }
+ None
+ }
+
+ }
+
+}
+
+impl InternalLinker for Entry {
+
+ fn get_internal_links(&self) -> Result<LinkIter> {
+ process_rw_result(self.get_header().read("imag.links"))
+ }
+
+ /// Set the links in a header and return the old links, if any.
+ fn set_internal_links(&mut self, links: Vec<&mut Entry>) -> Result<LinkIter> {
+ use internal::iter::IntoValues;
+
+ let self_location = self.get_location().clone();
+ let mut new_links = vec![];
+
+ for link in links {
+ if let Err(e) = add_foreign_link(link, self_location.clone()) {
+ return Err(e);
+ }
+ new_links.push(link.get_location().clone().into());
+ }
+
+ let new_links = try!(LinkIter::new(new_links)
+ .into_values()
+ .into_iter()
+ .fold(Ok(vec![]), |acc, elem| {
+ acc.and_then(move |mut v| {
+ elem.map_err_into(LEK::InternalConversionError)
+ .map(|e| {
+ v.push(e);
+ v
+ })
+ })
+ }));
+ process_rw_result(self.get_header_mut().set("imag.links", Value::Array(new_links)))
+ }
+
+ fn add_internal_link(&mut self, link: &mut Entry) -> Result<()> {
+ let location = link.get_location().clone().into();
+ add_internal_link_with_instance(self, link, location)
+ }
+
+ fn remove_internal_link(&mut self, link: &mut Entry) -> Result<()> {
+ let own_loc = self.get_location().clone().without_base();
+ let other_loc = link.get_location().clone().without_base();
+
+ debug!("Removing internal link from {:?} to {:?}", own_loc, other_loc);
+
+ link.get_internal_links()
+ .and_then(|links| {
+ debug!("Rewriting own links for {:?}, without {:?}", other_loc, own_loc);
+ let links = links.filter(|l| !l.eq_store_id(&own_loc));
+ rewrite_links(link.get_header_mut(), links)
+ })
+ .and_then(|_| {
+ self.get_internal_links()
+ .and_then(|links| {
+ debug!("Rewriting own links for {:?}, without {:?}", own_loc, other_loc);
+ let links = links.filter(|l| !l.eq_store_id(&other_loc));
+ rewrite_links(self.get_header_mut(), links)
+ })
+ })
+ }
+
+ fn add_internal_annotated_link(&mut self, link: &mut Entry, annotation: String) -> Result<()> {
+ let new_link = Link::Annotated {
+ link: link.get_location().clone(),
+ annotation: annotation,
+ };
+
+ add_internal_link_with_instance(self, link, new_link)
+ }
+
+}
+
+fn add_internal_link_with_instance(this: &mut Entry, link: &mut Entry, instance: Link) -> Result<()> {
+ debug!("Adding internal link from {:?} to {:?}", this.get_location(), instance);
+
+ add_foreign_link(link, this.get_location().clone())
+ .and_then(|_| {
+ this.get_internal_links()
+ .and_then(|links| {
+ let links = links.chain(LinkIter::new(vec![instance]));
+ rewrite_links(this.get_header_mut(), links)
+ })
+ })
+}
+
+fn rewrite_links<I: Iterator<Item = Link>>(header: &mut Value, links: I) -> Result<()> {
+ let links = try!(links.into_values()
+ .into_iter()
+ .fold(Ok(vec![]), |acc, elem| {
+ acc.and_then(move |mut v| {
+ elem.map_err_into(LEK::InternalConversionError)
+ .map(|e| {
+ v.push(e);
+ v
+ })
+ })
+ }));
+
+ debug!("Setting new link array: {:?}", links);
+ let process = header.set("imag.links", Value::Array(links));
+ process_rw_result(process).map(|_| ())
+}
+
+/// When Linking A -> B, the specification wants us to link back B -> A.
+/// This is a helper function which does this.
+fn add_foreign_link(target: &mut Entry, from: StoreId) -> Result<()> {
+ debug!("Linking back from {:?} to {:?}", target.get_location(), from);
+ target.get_internal_links()
+ .and_then(|links| {
+ let links = try!(links
+ .chain(LinkIter::new(vec![from.into()]))
+ .into_values()
+ .into_iter()
+ .fold(Ok(vec![]), |acc, elem| {
+ acc.and_then(move |mut v| {
+ elem.map_err_into(LEK::InternalConversionError)
+ .map(|e| {
+ v.push(e);
+ v
+ })
+ })
+ }));
+ debug!("Setting links in {:?}: {:?}", target.get_location(), links);
+ process_rw_result(target.get_header_mut().set("imag.links", Value::Array(links)))
+ .map(|_| ())
+ })
+}
+
+fn process_rw_result(links: StoreResult<Option<Value>>) -> Result<LinkIter> {
+ use std::path::PathBuf;
+
+ let links = match links {
+ Err(e) => {
+ debug!("RW action on store failed. Generating LinkError");
+ return Err(LEK::EntryHeaderReadError.into_error_with_cause(Box::new(e)))
+ },
+ Ok(None) => {
+ debug!("We got no value from the header!");
+ return Ok(LinkIter::new(vec![]))
+ },
+ Ok(Some(Value::Array(l))) => l,
+ Ok(Some(_)) => {
+ debug!("We expected an Array for the links, but there was a non-Array!");
+ return Err(LEK::ExistingLinkTypeWrong.into());
+ }
+ };
+
+ if !links.iter().all(|l| is_match!(*l, Value::String(_)) || is_match!(*l, Value::Table(_))) {
+ debug!("At least one of the Values which were expected in the Array of links is not a String or a Table!");
+ debug!("Generating LinkError");
+ return Err(LEK::ExistingLinkTypeWrong.into());
+ }
+
+ let links : Vec<Link> = try!(links.into_iter()
+ .map(|link| {
+ debug!("Matching the link: {:?}", link);
+ match link {
+ Value::String(s) => StoreId::new_baseless(PathBuf::from(s))
+ .map_err_into(LEK::StoreIdError)
+ .map(|s| Link::Id { link: s })
+ ,
+ Value::Table(mut tab) => {
+ debug!("Destructuring table");
+ if !tab.contains_key("link")
+ || !tab.contains_key("annotation") {
+ debug!("Things missing... returning Error instance");
+ Err(LEK::LinkParserError.into_error())
+ } else {
+ let link = try!(tab.remove("link")
+ .ok_or(LEK::LinkParserFieldMissingError.into_error()));
+
+ let anno = try!(tab.remove("annotation")
+ .ok_or(LEK::LinkParserFieldMissingError.into_error()));
+
+ debug!("Ok, here we go with building a Link::Annotated");
+ match (link, anno) {
+ (Value::String(link), Value::String(anno)) => {
+ StoreId::new_baseless(PathBuf::from(link))
+ .map_err_into(LEK::StoreIdError)
+ .map(|link| {
+ Link::Annotated {
+ link: link,
+ annotation: anno,
+ }
+ })
+ },
+ _ => Err(LEK::LinkParserFieldTypeError.into_error()),
+ }
+ }
+ }
+ _ => unreachable!(),
+ }
+ })
+ .collect());
+
+ debug!("Ok, the RW action was successful, returning link vector now!");
+ Ok(LinkIter::new(links))
+}
+
+pub mod store_check {
+ use libimagstore::store::Store;
+
+ pub mod error {
+ generate_error_imports!();
+
+ use libimagstore::storeid::StoreId;
+
+ #[derive(Debug)]
+ pub enum StoreLinkConsistencyErrorCustomData {
+ DeadLink {
+ target: StoreId
+ },
+ OneDirectionalLink {
+ source: StoreId,
+ target: StoreId
+ },
+ }
+
+ impl Display for StoreLinkConsistencyErrorCustomData {
+
+ fn fmt(&self, fmt: &mut Formatter) -> Result<(), FmtError> {
+ use self::StoreLinkConsistencyErrorCustomData as SLCECD;
+ match self {
+ &SLCECD::DeadLink { ref target } => {
+ try!(write!(fmt, "Dead Link to '{}'", target))
+ },
+
+ &SLCECD::OneDirectionalLink { ref source, ref target } => {
+ try!(write!(fmt,
+ "Link from '{}' to '{}' does exist, but not other way round",
+ source, target))
+ }
+ };
+ Ok(())
+ }
+
+ }
+
+ generate_custom_error_types!(
+ StoreLinkConsistencyError,
+ StoreLinkConsistencyErrorKind,
+ StoreLinkConsistencyErrorCustomData,
+ StoreLinkConsistencyError => "Links in the store are not consistent",
+ LinkHandlingError => "Error in link handling",
+ StoreError => "Error while talking to the store"
+ );
+
+ generate_result_helper!(StoreLinkConsistencyError, StoreLinkConsistencyErrorKind);
+ generate_option_helper!(StoreLinkConsistencyError, StoreLinkConsistencyErrorKind);
+ }
+
+ pub use self::error::StoreLinkConsistencyError;
+ pub use self::error::StoreLinkConsistencyErrorKind;
+ pub use self::error::MapErrInto;
+
+ pub mod result {
+ use std::result::Result as RResult;
+ use internal::store_check::error::StoreLinkConsistencyError as SLCE;
+
+ pub type Result<T> = RResult<T, SLCE>;
+ }
+
+ use self::result::Result;
+
+ pub trait StoreLinkConsistentExt {
+ fn check_link_consistency(&self) -> Result<()>;
+ }
+
+ impl StoreLinkConsistentExt for Store {
+ fn check_link_consistency(&self) -> Result<()> {
+ use std::collections::HashMap;
+
+ use self::error::StoreLinkConsistencyErrorKind as SLCEK;
+ use self::error::StoreLinkConsistencyError as SLCE;
+ use self::error::StoreLinkConsistencyErrorCustomData as SLCECD;
+ use error::LinkErrorKind as LEK;
+ use result::Result as LResult;
+ use internal::InternalLinker;
+
+ use libimagstore::store::StoreObject;
+ use libimagstore::storeid::StoreId;
+ use libimagerror::iter::TraceIterator;
+ use libimagerror::into::IntoError;
+ use libimagutil::iter::FoldResult;
+
+ // Helper data structure to collect incoming and outgoing links for each StoreId
+ #[derive(Debug, Default)]
+ struct Linking {
+ outgoing: Vec<StoreId>,
+ incoming: Vec<StoreId>,
+ }
+
+ /// Helper function to aggregate the Link network
+ ///
+ /// This function aggregates a HashMap which maps each StoreId object in the store onto
+ /// a Linking object, which contains a list of StoreIds which this entry links to and a
+ /// list of StoreIds which link to the current one.
+ ///
+ /// The lambda returns an error if something fails
+ let aggregate_link_network = |store: &Store| -> Result<HashMap<StoreId, Linking>> {
+ store
+ .walk("") // this is a hack... I know...
+ .filter_map(|obj: StoreObject| match obj {
+ StoreObject::Id(id) => Some(id),
+ _ => None
+ }) // Only ids are interesting
+ .fold(Ok(HashMap::new()), |acc, sid| {
+ acc.and_then(|mut state| {
+ debug!("Checking entry: '{}'", sid);
+
+ match try!(self.get(sid).map_err_into(SLCEK::StoreError)) {
+ Some(fle) => {
+ debug!("Found FileLockEntry");
+
+ let fle_loc = fle.get_location();
+
+ let internal_links = fle
+ .get_internal_links()
+ .map_err_into(SLCEK::StoreError)?
+ .into_getter(self) // get the FLEs from the Store
+ .trace_unwrap(); // trace all Err(e)s and get the Ok(fle)s
+
+ for internal_link in internal_links {
+ let il_loc = internal_link.get_location();
+
+ state
+ .entry(il_loc.clone())
+ .or_insert(Linking::default())
+ .incoming
+ .push(fle_loc.clone());
+
+ // Make sure an empty linking object is present for the
+ // current StoreId object
+ state
+ .entry(fle_loc.clone())
+ .or_insert(Linking::default())
+ .outgoing
+ .push(il_loc.clone());
+ }
+
+ Ok(state)
+ },
+ None => {
+ debug!("No entry");
+ Ok(state)
+ }
+ }
+ })
+ })
+ };
+
+ /// Helper to check whethre all StoreIds in the network actually exists
+ ///
+ /// Because why not?
+ let all_collected_storeids_exist = |network: &HashMap<StoreId, Linking>| -> LResult<()> {
+ network
+ .iter()
+ .fold_result(|(id, _)| {
+ if is_match!(self.get(id.clone()), Ok(Some(_))) {
+ debug!("Exists in store: {:?}", id);
+
+ let exists = {
+ use error::MapErrInto as MEI;
+ try!(MEI::map_err_into(id.exists(), LEK::StoreReadError))
+ };
+
+ if !exists {
+ warn!("Does exist in store but not on FS: {:?}", id);
+ Err(LEK::LinkTargetDoesNotExist.into_error())
+ } else {
+ Ok(())
+ }
+ } else {
+ warn!("Does not exist in store: {:?}", id);
+ Err(LEK::LinkTargetDoesNotExist.into_error())
+ }
+ })
+ };
+
+ /// Helper function to create a SLCECD::OneDirectionalLink error object
+ #[inline]
+ let mk_one_directional_link_err = |src: StoreId, target: StoreId| -> SLCE {
+ // construct the error
+ let custom = SLCECD::OneDirectionalLink {
+ source: src,
+ target: target,
+ };
+
+ SLCEK::StoreLinkConsistencyError
+ .into_error()
+ .with_custom_data(custom)
+ };
+
+ /// Helper lambda to check whether the _incoming_ links of each entry actually also
+ /// appear in the _outgoing_ list of the linked entry
+ let incoming_links_exists_as_outgoing_links =
+ |src: &StoreId, linking: &Linking, network: &HashMap<StoreId, Linking>| -> Result<()> {
+ linking
+ .incoming
+ .iter()
+ .fold_result(|link| {
+
+ // Check whether the links which are _incoming_ on _src_ are outgoing
+ // in each of the links in the incoming list.
+ let incoming_consistent = network.get(link)
+ .map(|l| l.outgoing.contains(src))
+ .unwrap_or(false);
+
+ if !incoming_consistent {
+ Err(mk_one_directional_link_err(src.clone(), link.clone()))
+ } else {
+ Ok(())
+ }
+ })
+ };
+
+ /// Helper lambda to check whether the _outgoing links of each entry actually also
+ /// appear in the _incoming_ list of the linked entry
+ let outgoing_links_exist_as_incoming_links =
+ |src: &StoreId, linking: &Linking, network: &HashMap<StoreId, Linking>| -> Result<()> {
+ linking
+ .outgoing
+ .iter()
+ .fold_result(|link| {
+
+ // Check whether the links which are _outgoing_ on _src_ are incoming
+ // in each of the links in the outgoing list.
+ let outgoing_consistent = network.get(link)
+ .map(|l| l.incoming.contains(src))
+ .unwrap_or(false);
+
+ if !outgoing_consistent {
+ Err(mk_one_directional_link_err(link.clone(), src.clone()))
+ } else {
+ Ok(())
+ }
+ })
+ };
+
+ aggregate_link_network(&self)
+ .and_then(|nw| {
+ all_collected_storeids_exist(&nw)
+ .map(|_| nw)
+ .map_err_into(SLCEK::LinkHandlingError)
+ })
+ .and_then(|nw| {
+ nw.iter().fold_result(|(id, linking)| {
+ try!(incoming_links_exists_as_outgoing_links(id, linking, &nw));
+ try!(outgoing_links_exist_as_incoming_links(id, linking, &nw));
+ Ok(())
+ })
+ })
+ .map(|_| ())
+ }
+ }
+
+}
+
+#[cfg(test)]
+mod test {
+ use std::path::PathBuf;
+
+ use libimagstore::store::Store;
+
+ use super::InternalLinker;
+
+ fn setup_logging() {
+ use env_logger;
+ let _ = env_logger::init().unwrap_or(());
+ }
+
+ pub fn get_store() -> Store {
+ Store::new(PathBuf::from("/"), None).unwrap()
+ }
+
+ #[test]
+ fn test_new_entry_no_links() {
+ setup_logging();
+ let store = get_store();
+ let entry = store.create(PathBuf::from("test_new_entry_no_links")).unwrap();
+ let links = entry.get_internal_links();
+ assert!(links.is_ok());
+ let links = links.unwrap();
+ assert_eq!(links.collect::<Vec<_>>().len(), 0);
+ }
+
+ #[test]
+ fn test_link_two_entries() {
+ setup_logging();
+ let store = get_store();
+ let mut e1 = store.create(PathBuf::from("test_link_two_entries1")).unwrap();
+ assert!(e1.get_internal_links().is_ok());
+
+ let mut e2 = store.create(PathBuf::from("test_link_two_entries2")).unwrap();
+ assert!(e2.get_internal_links().is_ok());
+
+ {
+ assert!(e1.add_internal_link(&mut e2).is_ok());
+
+ let e1_links = e1.get_internal_links().unwrap().collect::<Vec<_>>();
+ let e2_links = e2.get_internal_links().unwrap().collect::<Vec<_>>();
+
+ debug!("1 has links: {:?}", e1_links);
+ debug!("2 has links: {:?}", e2_links);
+
+ assert_eq!(e1_links.len(), 1);
+ assert_eq!(e2_links.len(), 1);
+
+ assert!(e1_links.first().map(|l| l.clone().with_base(store.path().clone()).eq_store_id(e2.get_location())).unwrap_or(false));
+ assert!(e2_links.first().map(|l| l.clone().with_base(store.path().clone()).eq_store_id(e1.get_location())).unwrap_or(false));
+ }
+
+ {
+ assert!(e1.remove_internal_link(&mut e2).is_ok());
+
+ println!("{:?}", e2.to_str());
+ let e2_links = e2.get_internal_links().unwrap().collect::<Vec<_>>();
+ assert_eq!(e2_links.len(), 0, "Expected [], got: {:?}", e2_links);
+
+ println!("{:?}", e1.to_str());
+ let e1_links = e1.get_internal_links().unwrap().collect::<Vec<_>>();
+ assert_eq!(e1_links.len(), 0, "Expected [], got: {:?}", e1_links);
+
+ }
+ }
+
+ #[test]
+ fn test_multiple_links() {
+ setup_logging();
+ let store = get_store();
+
+ let mut e1 = store.retrieve(PathBuf::from("1")).unwrap();
+ let mut e2 = store.retrieve(PathBuf::from("2")).unwrap();
+ let mut e3 = store.retrieve(PathBuf::from("3")).unwrap();
+ let mut e4 = store.retrieve(PathBuf::from("4")).unwrap();
+ let mut e5 = store.retrieve(PathBuf::from("5")).unwrap();
+
+ assert!(e1.add_internal_link(&mut e2).is_ok());
+
+ assert_eq!(e1.get_internal_links().unwrap().collect::<Vec<_>>().len(), 1);
+ assert_eq!(e2.get_internal_links().unwrap().collect::<Vec<_>>().len(), 1);
+ assert_eq!(e3.get_internal_links().unwrap().collect::<Vec<_>>().len(), 0);
+ assert_eq!(e4.get_internal_links().unwrap().collect::<Vec<_>>().len(), 0);
+ assert_eq!(e5.get_internal_links().unwrap().collect::<Vec<_>>().len(), 0);
+
+ assert!(e1.add_internal_link(&mut e3).is_ok());
+
+ assert_eq!(e1.get_internal_links().unwrap().collect::<Vec<_>>().len(), 2);
+ assert_eq!(e2.get_internal_links().unwrap().collect::<Vec<_>>().len(), 1);
+ assert_eq!(e3.get_internal_links().unwrap().collect::<Vec<_>>().len(), 1);
+ assert_eq!(e4.get_internal_links().unwrap().collect::<Vec<_>>().len(), 0);
+ assert_eq!(e5.get_internal_links().unwrap().collect::<Vec<_>>().len(), 0);
+
+ assert!(e1.add_internal_link(&mut e4).is_ok());
+
+ assert_eq!(e1.get_internal_links().unwrap().collect::<Vec<_>>().len(), 3);
+ assert_eq!(e2.get_internal_links().unwrap().collect::<Vec<_>>().len(), 1);
+ assert_eq!(e3.get_internal_links().unwrap().collect::<Vec<_>>().len(), 1);
+ assert_eq!(e4.get_internal_links().unwrap().collect::<Vec<_>>().len(), 1);
+ assert_eq!(e5.get_internal_links().unwrap().collect::<Vec<_>>().len(), 0);
+
+ assert!(e1.add_internal_link(&mut e5).is_ok());
+
+ assert_eq!(e1.get_internal_links().unwrap().collect::<Vec<_>>().len(), 4);
+ assert_eq!(e2.get_internal_links().unwrap().collect::<Vec<_>>().len(), 1);
+ assert_eq!(e3.get_internal_links().unwrap().collect::<Vec<_>>().len(), 1);
+ assert_eq!(e4.get_internal_links().unwrap().collect::<Vec<_>>().len(), 1);
+ assert_eq!(e5.get_internal_links().unwrap().collect::<Vec<_>>().len(), 1);
+
+ assert!(e5.remove_internal_link(&mut e1).is_ok());
+
+ assert_eq!(e1.get_internal_links().unwrap().collect::<Vec<_>>().len(), 3);
+ assert_eq!(e2.get_internal_links().unwrap().collect::<Vec<_>>().len(), 1);
+ assert_eq!(e3.get_internal_links().unwrap().collect::<Vec<_>>().len(), 1);
+ assert_eq!(e4.get_internal_links().unwrap().collect::<Vec<_>>().len(), 1);
+ assert_eq!(e5.get_internal_links().unwrap().collect::<Vec<_>>().len(), 0);
+
+ assert!(e4.remove_internal_link(&mut e1).is_ok());
+
+ assert_eq!(e1.get_internal_links().unwrap().collect::<Vec<_>>().len(), 2);
+ assert_eq!(e2.get_internal_links().unwrap().collect::<Vec<_>>().len(), 1);
+ assert_eq!(e3.get_internal_links().unwrap().collect::<Vec<_>>().len(), 1);
+ assert_eq!(e4.get_internal_links().unwrap().collect::<Vec<_>>().len(), 0);
+ assert_eq!(e5.get_internal_links().unwrap().collect::<Vec<_>>().len(), 0);
+
+ assert!(e3.remove_internal_link(&mut e1).is_ok());
+
+ assert_eq!(e1.get_internal_links().unwrap().collect::<Vec<_>>().len(), 1);
+ assert_eq!(e2.get_internal_links().unwrap().collect::<Vec<_>>().len(), 1);
+ assert_eq!(e3.get_internal_links().unwrap().collect::<Vec<_>>().len(), 0);
+ assert_eq!(e4.get_internal_links().unwrap().collect::<Vec<_>>().len(), 0);
+ assert_eq!(e5.get_internal_links().unwrap().collect::<Vec<_>>().len(), 0);
+
+ assert!(e2.remove_internal_link(&mut e1).is_ok());
+
+ assert_eq!(e1.get_internal_links().unwrap().collect::<Vec<_>>().len(), 0);
+ assert_eq!(e2.get_internal_links().unwrap().collect::<Vec<_>>().len(), 0);
+ assert_eq!(e3.get_internal_links().unwrap().collect::<Vec<_>>().len(), 0);
+ assert_eq!(e4.get_internal_links().unwrap().collect::<Vec<_>>().len(), 0);
+ assert_eq!(e5.get_internal_links().unwrap().collect::<Vec<_>>().len(), 0);
+
+ }
+
+ #[test]
+ fn test_link_deleting() {
+ setup_logging();
+ let store = get_store();
+
+ let mut e1 = store.retrieve(PathBuf::from("1")).unwrap();
+ let mut e2 = store.retrieve(PathBuf::from("2")).unwrap();
+
+ assert_eq!(e1.get_internal_links().unwrap().collect::<Vec<_>>().len(), 0);
+ assert_eq!(e2.get_internal_links().unwrap().collect::<Vec<_>>().len(), 0);
+
+ assert!(e1.add_internal_link(&mut e2).is_ok());
+
+ assert_eq!(e1.get_internal_links().unwrap().collect::<Vec<_>>().len(), 1);
+ assert_eq!(e2.get_internal_links().unwrap().collect::<Vec<_>>().len(), 1);
+
+ assert!(e1.remove_internal_link(&mut e2).is_ok());
+
+ assert_eq!(e1.get_internal_links().unwrap().collect::<Vec<_>>().len(), 0);
+ assert_eq!(e2.get_internal_links().unwrap().collect::<Vec<_>>().len(), 0);
+ }
+
+ #[test]
+ fn test_link_deleting_multiple_links() {
+ setup_logging();
+ let store = get_store();
+
+ let mut e1 = store.retrieve(PathBuf::from("1")).unwrap();
+ let mut e2 = store.retrieve(PathBuf::from("2")).unwrap();
+ let mut e3 = store.retrieve(PathBuf::from("3")).unwrap();
+
+ assert_eq!(e1.get_internal_links().unwrap().collect::<Vec<_>>().len(), 0);
+ assert_eq!(e2.get_internal_links().unwrap().collect::<Vec<_>>().len(), 0);
+ assert_eq!(e3.get_internal_links().unwrap().collect::<Vec<_>>().len(), 0);
+
+ assert!(e1.add_internal_link(&mut e2).is_ok()); // 1-2
+ assert!(e1.add_internal_link(&mut e3).is_ok()); // 1-2, 1-3
+
+ assert_eq!(e1.get_internal_links().unwrap().collect::<Vec<_>>().len(), 2);
+ assert_eq!(e2.get_internal_links().unwrap().collect::<Vec<_>>().len(), 1);
+ assert_eq!(e3.get_internal_links().unwrap().collect::<Vec<_>>().len(), 1);
+
+ assert!(e2.add_internal_link(&mut e3).is_ok()); // 1-2, 1-3, 2-3
+
+ assert_eq!(e1.get_internal_links().unwrap().collect::<Vec<_>>().len(), 2);
+ assert_eq!(e2.get_internal_links().unwrap().collect::<Vec<_>>().len(), 2);
+ assert_eq!(e3.get_internal_links().unwrap().collect::<Vec<_>>().len(), 2);
+
+ assert!(e1.remove_internal_link(&mut e2).is_ok()); // 1-3, 2-3
+
+ assert_eq!(e1.get_internal_links().unwrap().collect::<Vec<_>>().len(), 1);
+ assert_eq!(e2.get_internal_links().unwrap().collect::<Vec<_>>().len(), 1);
+ assert_eq!(e3.get_internal_links().unwrap().collect::<Vec<_>>().len(), 2);
+
+ assert!(e1.remove_internal_link(&mut e3).is_ok()); // 2-3
+
+ assert_eq!(e1.get_internal_links().unwrap().collect::<Vec<_>>().len(), 0);
+ assert_eq!(e2.get_internal_links().unwrap().collect::<Vec<_>>().len(), 1);
+ assert_eq!(e3.get_internal_links().unwrap().collect::<Vec<_>>().len(), 1);
+
+ assert!(e2.remove_internal_link(&mut e3).is_ok());
+
+ assert_eq!(e1.get_internal_links().unwrap().collect::<Vec<_>>().len(), 0);
+ assert_eq!(e2.get_internal_links().unwrap().collect::<Vec<_>>().len(), 0);
+ assert_eq!(e3.get_internal_links().unwrap().collect::<Vec<_>>().len(), 0);
+ }
+
+}
+
diff --git a/lib/entry/libimagentrylink/src/lib.rs b/lib/entry/libimagentrylink/src/lib.rs
new file mode 100644
index 0000000..2ed8b6b
--- /dev/null
+++ b/lib/entry/libimagentrylink/src/lib.rs
@@ -0,0 +1,55 @@
+//
+// 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
+//
+
+#![deny(
+ non_camel_case_types,
+ non_snake_case,
+ path_statements,
+ trivial_numeric_casts,
+ unstable_features,
+ unused_allocation,
+ unused_import_braces,
+ unused_imports,
+ unused_mut,
+ unused_qualifications,
+ while_true,
+)]
+
+extern crate itertools;
+#[macro_use] extern crate log;
+extern crate toml;
+extern crate semver;
+extern crate url;
+extern crate crypto;
+#[macro_use] extern crate is_match;
+
+#[cfg(test)]
+extern crate env_logger;
+
+#[macro_use] extern crate libimagstore;
+#[macro_use] extern crate libimagerror;
+extern crate libimagutil;
+
+module_entry_path_mod!("links");
+
+pub mod error;
+pub mod external;
+pub mod internal;
+pub mod result;
+
diff --git a/lib/entry/libimagentrylink/src/result.rs b/lib/entry/libimagentrylink/src/result.rs
new file mode 100644
index 0000000..23e5036
--- /dev/null
+++ b/lib/entry/libimagentrylink/src/result.rs
@@ -0,0 +1,25 @@
+//
+// 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 error::LinkError;
+
+pub type Result<T> = RResult<T, LinkError>;
+
diff --git a/lib/entry/libimagentrylist/Cargo.toml b/lib/entry/libimagentrylist/Cargo.toml
new file mode 100644
index 0000000..c28354d
--- /dev/null
+++ b/lib/entry/libimagentrylist/Cargo.toml
@@ -0,0 +1,24 @@
+[package]
+name = "libimagentrylist"
+version = "0.4.0"
+authors = ["Matthias Beyer <mail@beyermatthias.de>"]
+
+description = "Library for the imag core distribution"
+
+keywords = ["imag", "PIM", "personal", "information", "management"]
+readme = "../README.md"
+license = "LGPL-2.1"
+
+documentation = "https://matthiasbeyer.github.io/imag/imag_documentation/index.html"
+repository = "https://github.com/matthiasbeyer/imag"
+homepage = "http://imag-pim.org"
+
+[dependencies]
+clap = ">=2.17"
+log = "0.3"
+toml = "^0.4"
+prettytable-rs = "0.6.*"
+
+libimagstore = { version = "0.4.0", path = "../../../lib/core/libimagstore" }
+libimagerror = { version = "0.4.0", path = "../../../lib/core/libimagerror" }
+libimagutil = { version = "0.4.0", path = "../../../lib/etc/libimagutil" }
diff --git a/lib/entry/libimagentrylist/README.md b/lib/entry/libimagentrylist/README.md
new file mode 120000
index 0000000..7fd3298
--- /dev/null
+++ b/lib/entry/libimagentrylist/README.md
@@ -0,0 +1 @@
+../doc/src/05100-lib-entrylist.md \ No newline at end of file
diff --git a/lib/entry/libimagentrylist/src/cli.rs b/lib/entry/libimagentrylist/src/cli.rs
new file mode 100644
index 0000000..b52a92f
--- /dev/null
+++ b/lib/entry/libimagentrylist/src/cli.rs
@@ -0,0 +1,101 @@
+//
+// 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 clap::{Arg, ArgMatches, App, SubCommand};
+
+use libimagstore::store::FileLockEntry;
+
+use result::Result;
+use listers::line::LineLister;
+use listers::path::PathLister;
+use lister::Lister;
+use error::{ListError, ListErrorKind};
+
+pub fn build_list_cli_component<'a, 'b>() -> App<'a, 'b> {
+ SubCommand::with_name(list_subcommand_name())
+ .author("Matthias Beyer <mail@beyermatthias.de>")
+ .version("0.1")
+ .about("List entries")
+
+ .arg(Arg::with_name(list_backend_line())
+ .short("l")
+ .long("line")
+ .takes_value(false)
+ .required(false)
+ .multiple(false)
+ .help("Use backend: Line"))
+
+ .arg(Arg::with_name(list_backend_path())
+ .short("p")
+ .long("path")
+ .takes_value(false)
+ .required(false)
+ .multiple(false)
+ .help("Use backend: Path"))
+
+ .arg(Arg::with_name(list_backend_path_absolute())
+ .short("P")
+ .long("path-absolute")
+ .takes_value(false)
+ .required(false)
+ .multiple(false)
+ .help("Use backend: Path (absolute)"))
+
+}
+
+pub fn list_subcommand_name() -> &'static str {
+ "list"
+}
+
+pub fn list_backend_line() -> &'static str {
+ "line"
+}
+
+pub fn list_backend_path() -> &'static str {
+ "path"
+}
+
+pub fn list_backend_path_absolute() -> &'static str {
+ "path-absolute"
+}
+
+// TODO: Add Registry for listers where a HashMap name->lister is in and where we can fetch the
+// lister from.
+pub fn list_entries_with_lister<'a, I>(m: &ArgMatches, entries: I) -> Result<()>
+ where I: Iterator<Item = FileLockEntry<'a>>
+{
+ if let Some(matches) = m.subcommand_matches(list_subcommand_name()) {
+ if matches.is_present(list_backend_line()) {
+ return LineLister::new("<unknown>").list(entries)
+ };
+
+ if matches.is_present(list_backend_path()) {
+ return PathLister::new(false).list(entries)
+ }
+
+
+ if matches.is_present(list_backend_path_absolute()) {
+ return PathLister::new(true).list(entries)
+ }
+
+ Ok(())
+ } else {
+ Err(ListError::new(ListErrorKind::CLIError, None))
+ }
+}
diff --git a/lib/entry/libimagentrylist/src/error.rs b/lib/entry/libimagentrylist/src/error.rs
new file mode 100644
index 0000000..9c3b1cc
--- /dev/null
+++ b/lib/entry/libimagentrylist/src/error.rs
@@ -0,0 +1,33 @@
+//
+// 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
+//
+
+generate_error_module!(
+ generate_error_types!(ListError, ListErrorKind,
+ IOError => "IO Error",
+ FormatError => "FormatError",
+ EntryError => "EntryError",
+ IterationError => "IterationError",
+ CLIError => "No CLI subcommand for listing entries"
+ );
+);
+
+pub use self::error::ListError;
+pub use self::error::ListErrorKind;
+pub use self::error::MapErrInto;
+
diff --git a/lib/entry/libimagentrylist/src/lib.rs b/lib/entry/libimagentrylist/src/lib.rs
new file mode 100644
index 0000000..ee97d72
--- /dev/null
+++ b/lib/entry/libimagentrylist/src/lib.rs
@@ -0,0 +1,50 @@
+//
+// 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
+//
+
+#![deny(
+ dead_code,
+ non_camel_case_types,
+ non_snake_case,
+ path_statements,
+ trivial_numeric_casts,
+ unstable_features,
+ unused_allocation,
+ unused_import_braces,
+ unused_imports,
+ unused_must_use,
+ unused_mut,
+ unused_qualifications,
+ while_true,
+)]
+
+extern crate clap;
+#[macro_use] extern crate log;
+extern crate toml;
+extern crate prettytable;
+
+extern crate libimagstore;
+extern crate libimagutil;
+#[macro_use] extern crate libimagerror;
+
+pub mod cli;
+pub mod error;
+pub mod lister;
+pub mod listers;
+pub mod result;
+
diff --git a/lib/entry/libimagentrylist/src/lister.rs b/lib/entry/libimagentrylist/src/lister.rs
new file mode 100644
index 0000000..798061b
--- /dev/null
+++ b/lib/entry/libimagentrylist/src/lister.rs
@@ -0,0 +1,29 @@
+//
+// 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 libimagstore::store::FileLockEntry;
+
+use result::Result;
+
+pub trait Lister : Sized {
+
+ fn list<'a, I: Iterator<Item = FileLockEntry<'a>>>(&self, entries: I) -> Result<()>;
+
+}
+
diff --git a/lib/entry/libimagentrylist/src/listers/core.rs b/lib/entry/libimagentrylist/src/listers/core.rs
new file mode 100644
index 0000000..733ba71
--- /dev/null
+++ b/lib/entry/libimagentrylist/src/listers/core.rs
@@ -0,0 +1,65 @@
+//
+// 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::io::stdout;
+use std::io::Write;
+
+use lister::Lister;
+use result::Result;
+
+use libimagstore::store::FileLockEntry;
+use libimagstore::store::Entry;
+
+pub struct CoreLister<T: Fn(&Entry) -> String> {
+ lister: Box<T>,
+}
+
+impl<T: Fn(&Entry) -> String> CoreLister<T> {
+
+ pub fn new(lister: T) -> CoreLister<T> {
+ CoreLister {
+ lister: Box::new(lister),
+ }
+ }
+
+}
+
+impl<T: Fn(&Entry) -> String> Lister for CoreLister<T> {
+
+ fn list<'b, I: Iterator<Item = FileLockEntry<'b>>>(&self, entries: I) -> Result<()> {
+ use error::ListError as LE;
+ use error::ListErrorKind as LEK;
+
+ debug!("Called list()");
+ let (r, n) = entries
+ .fold((Ok(()), 0), |(accu, i), entry| {
+ debug!("fold({:?}, {:?})", accu, entry);
+ let r = accu.and_then(|_| {
+ debug!("Listing Entry: {:?}", entry);
+ write!(stdout(), "{:?}\n", (self.lister)(&entry))
+ .map_err(|e| LE::new(LEK::FormatError, Some(Box::new(e))))
+ });
+ (r, i + 1)
+ });
+ debug!("Iterated over {} entries", n);
+ r
+ }
+
+}
+
diff --git a/lib/entry/libimagentrylist/src/listers/line.rs b/lib/entry/libimagentrylist/src/listers/line.rs
new file mode 100644
index 0000000..8c43993
--- /dev/null
+++ b/lib/entry/libimagentrylist/src/listers/line.rs
@@ -0,0 +1,55 @@
+//
+// 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::io::stdout;
+use std::io::Write;
+
+use lister::Lister;
+use result::Result;
+
+use libimagstore::store::FileLockEntry;
+use libimagutil::iter::FoldResult;
+
+pub struct LineLister<'a> {
+ unknown_output: &'a str,
+}
+
+impl<'a> LineLister<'a> {
+
+ pub fn new(unknown_output: &'a str) -> LineLister<'a> {
+ LineLister {
+ unknown_output: unknown_output,
+ }
+ }
+
+}
+
+impl<'a> Lister for LineLister<'a> {
+
+ fn list<'b, I: Iterator<Item = FileLockEntry<'b>>>(&self, entries: I) -> Result<()> {
+ use error::ListError as LE;
+ use error::ListErrorKind as LEK;
+
+ entries.fold_result(|entry| {
+ let s = entry.get_location().to_str().unwrap_or(String::from(self.unknown_output));
+ write!(stdout(), "{:?}\n", s).map_err(|e| LE::new(LEK::FormatError, Some(Box::new(e))))
+ })
+ }
+
+}
diff --git a/lib/entry/libimagentrylist/src/listers/mod.rs b/lib/entry/libimagentrylist/src/listers/mod.rs
new file mode 100644
index 0000000..78a6e8f
--- /dev/null
+++ b/lib/entry/libimagentrylist/src/listers/mod.rs
@@ -0,0 +1,23 @@
+//
+// 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
+//
+
+pub mod core;
+pub mod line;
+pub mod path;
+pub mod table;
diff --git a/lib/entry/libimagentrylist/src/listers/path.rs b/lib/entry/libimagentrylist/src/listers/path.rs
new file mode 100644
index 0000000..15a2df5
--- /dev/null
+++ b/lib/entry/libimagentrylist/src/listers/path.rs
@@ -0,0 +1,75 @@
+//
+// 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::io::stdout;
+use std::io::Write;
+
+use lister::Lister;
+use result::Result;
+use error::MapErrInto;
+
+use libimagstore::store::FileLockEntry;
+use libimagutil::iter::FoldResult;
+
+pub struct PathLister {
+ absolute: bool,
+}
+
+impl PathLister {
+
+ pub fn new(absolute: bool) -> PathLister {
+ PathLister {
+ absolute: absolute,
+ }
+ }
+
+}
+
+impl Lister for PathLister {
+
+ fn list<'a, I: Iterator<Item = FileLockEntry<'a>>>(&self, entries: I) -> Result<()> {
+ use error::ListError as LE;
+ use error::ListErrorKind as LEK;
+
+ entries.fold_result(|entry| {
+ Ok(entry.get_location().clone())
+ .and_then(|pb| pb.into_pathbuf().map_err_into(LEK::FormatError))
+ .and_then(|pb| {
+ if self.absolute {
+ pb.canonicalize().map_err(|e| LE::new(LEK::FormatError, Some(Box::new(e))))
+ } else {
+ Ok(pb.into())
+ }
+ })
+ .and_then(|pb| {
+ write!(stdout(), "{:?}\n", pb)
+ .map_err(|e| LE::new(LEK::FormatError, Some(Box::new(e))))
+ })
+ .map_err(|e| {
+ if e.err_type() == LEK::FormatError {
+ e
+ } else {
+ LE::new(LEK::FormatError, Some(Box::new(e)))
+ }
+ })
+ })
+ }
+
+}
+
diff --git a/lib/entry/libimagentrylist/src/listers/table.rs b/lib/entry/libimagentrylist/src/listers/table.rs
new file mode 100644
index 0000000..e9358ce
--- /dev/null
+++ b/lib/entry/libimagentrylist/src/listers/table.rs
@@ -0,0 +1,110 @@
+//
+// 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::io::stdout;
+
+use lister::Lister;
+use result::Result;
+use error::MapErrInto;
+
+use libimagstore::store::FileLockEntry;
+use libimagerror::into::IntoError;
+
+use prettytable::Table;
+use prettytable::cell::Cell;
+use prettytable::row::Row;
+
+pub struct TableLister<F: Fn(&FileLockEntry) -> Vec<String>> {
+ line_generator: F,
+ header: Option<Vec<String>>,
+
+ with_idx: bool,
+}
+
+impl<F: Fn(&FileLockEntry) -> Vec<String>> TableLister<F> {
+
+ pub fn new(gen: F) -> TableLister<F> {
+ TableLister {
+ line_generator: gen,
+ header: None,
+ with_idx: true,
+ }
+ }
+
+ pub fn with_header(mut self, hdr: Vec<String>) -> TableLister<F> {
+ self.header = Some(hdr);
+ self
+ }
+
+ pub fn with_idx(mut self, b: bool) -> TableLister<F> {
+ self.with_idx = b;
+ self
+ }
+
+}
+
+impl<F: Fn(&FileLockEntry) -> Vec<String>> Lister for TableLister<F> {
+
+ fn list<'b, I: Iterator<Item = FileLockEntry<'b>>>(&self, entries: I) -> Result<()> {
+ use error::ListErrorKind as LEK;
+
+ let mut table = Table::new();
+ let mut header_len : Option<usize> = None;
+ match self.header {
+ Some(ref s) => {
+ debug!("We have a header... preparing");
+ let mut cells : Vec<Cell> = s.iter().map(|s| Cell::new(s)).collect();
+ if self.with_idx {
+ cells.insert(0, Cell::new("#"));
+ }
+ table.set_titles(Row::new(cells));
+ header_len = Some(s.len());
+ },
+ None => {
+ debug!("No header for table found... continuing without");
+ },
+ }
+
+ entries.fold(Ok(table), |table, entry| {
+ table.and_then(|mut table| {
+ let mut v = (self.line_generator)(&entry);
+ {
+ let v_len = v.len();
+ if header_len.is_none() {
+ header_len = Some(v_len);
+ }
+ if header_len.map(|l| v_len > l).unwrap_or(false) {
+ return Err(LEK::FormatError.into_error());
+ }
+ while header_len.map(|l| v.len() != l).unwrap_or(false) {
+ v.push(String::from(""));
+ }
+ }
+
+ table.add_row(v.iter().map(|s| Cell::new(s)).collect());
+ Ok(table)
+ })
+ })
+ .and_then(|tbl| {
+ let mut io = stdout();
+ tbl.print(&mut io).map_err_into(LEK::IOError)
+ })
+ }
+
+}
diff --git a/lib/entry/libimagentrylist/src/result.rs b/lib/entry/libimagentrylist/src/result.rs
new file mode 100644
index 0000000..e29d7d3
--- /dev/null
+++ b/lib/entry/libimagentrylist/src/result.rs
@@ -0,0 +1,25 @@
+//
+// 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 error::ListError;
+
+pub type Result<T> = RResult<T, ListError>;
+
diff --git a/lib/entry/libimagentrymarkdown/Cargo.toml b/lib/entry/libimagentrymarkdown/Cargo.toml
new file mode 100644
index 0000000..5951f36
--- /dev/null
+++ b/lib/entry/libimagentrymarkdown/Cargo.toml
@@ -0,0 +1,24 @@
+[package]
+name = "libimagentrymarkdown"
+version = "0.4.0"
+authors = ["Matthias Beyer <mail@beyermatthias.de>"]
+
+description = "Library for the imag core distribution"
+
+keywords = ["imag", "PIM", "personal", "information", "management"]
+readme = "../README.md"
+license = "LGPL-2.1"
+
+documentation = "https://matthiasbeyer.github.io/imag/imag_documentation/index.html"
+repository = "https://github.com/matthiasbeyer/imag"
+homepage = "http://imag-pim.org"
+
+[dependencies]
+log = "0.3"
+hoedown = "5.0.0"
+crossbeam = "0.2"
+url = "1.2"
+
+libimagstore = { version = "0.4.0", path = "../../../lib/core/libimagstore" }
+libimagerror = { version = "0.4.0", path = "../../../lib/core/libimagerror" }
+
diff --git a/lib/entry/libimagentrymarkdown/README.md b/lib/entry/libimagentrymarkdown/README.md
new file mode 120000
index 0000000..617d700
--- /dev/null
+++ b/lib/entry/libimagentrymarkdown/README.md
@@ -0,0 +1 @@
+../doc/src/05100-lib-entrymarkdown.md \ No newline at end of file
diff --git a/lib/entry/libimagentrymarkdown/src/error.rs b/lib/entry/libimagentrymarkdown/src/error.rs
new file mode 100644
index 0000000..1186d5f
--- /dev/null
+++ b/lib/entry/libimagentrymarkdown/src/error.rs
@@ -0,0 +1,30 @@
+//
+// 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
+//
+
+generate_error_module!(
+ generate_error_types!(MarkdownError, MarkdownErrorKind,
+ MarkdownRenderError => "Markdown render error",
+ LinkParsingError => "Link parsing error"
+ );
+);
+
+pub use self::error::MarkdownError;
+pub use self::error::MarkdownErrorKind;
+
+
diff --git a/lib/entry/libimagentrymarkdown/src/html.rs b/lib/entry/libimagentrymarkdown/src/html.rs
new file mode 100644
index 0000000..1fcd12c
--- /dev/null
+++ b/lib/entry/libimagentrymarkdown/src/html.rs
@@ -0,0 +1,101 @@
+//
+// 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 hoedown::{Markdown, Html as MdHtml};
+use hoedown::renderer::html::Flags as HtmlFlags;
+use hoedown::renderer::Render;
+
+use result::Result;
+use error::MarkdownErrorKind;
+use libimagerror::into::IntoError;
+
+pub type HTML = String;
+
+pub fn to_html(buffer: &str) -> Result<HTML> {
+ let md = Markdown::new(buffer);
+ let mut html = MdHtml::new(HtmlFlags::empty(), 0);
+ html.render(&md)
+ .to_str()
+ .map(String::from)
+ .map_err(Box::new)
+ .map_err(|e| MarkdownErrorKind::MarkdownRenderError.into_error_with_cause(e))
+}
+
+pub mod iter {
+ use result::Result;
+ use libimagstore::store::Entry;
+ use super::HTML;
+ use super::to_html;
+
+ pub struct ToHtmlIterator<I: Iterator<Item = Entry>> {
+ i: I
+ }
+
+ impl<I: Iterator<Item = Entry>> ToHtmlIterator<I> {
+
+ pub fn new(i: I) -> ToHtmlIterator<I> {
+ ToHtmlIterator { i: i }
+ }
+
+ }
+
+ impl<I: Iterator<Item = Entry>> Iterator for ToHtmlIterator<I> {
+ type Item = Result<HTML>;
+
+ fn next(&mut self) -> Option<Self::Item> {
+ self.i.next().map(|entry| to_html(&entry.get_content()[..]))
+ }
+
+ }
+
+ impl<I: Iterator<Item = Entry>> From<I> for ToHtmlIterator<I> {
+
+ fn from(obj: I) -> ToHtmlIterator<I> {
+ ToHtmlIterator::new(obj)
+ }
+
+ }
+
+
+ /// Iterate over `(Entry, Result<HTML>)` tuples
+ pub struct WithHtmlIterator<I: Iterator<Item = Entry>> {
+ i: I
+ }
+
+ impl<I: Iterator<Item = Entry>> WithHtmlIterator<I> {
+
+ pub fn new(i: I) -> WithHtmlIterator<I> {
+ WithHtmlIterator { i: i }
+ }
+
+ }
+
+ impl<I: Iterator<Item = Entry>> Iterator for WithHtmlIterator<I> {
+ type Item = (Entry, Result<HTML>);
+
+ fn next(&mut self) -> Option<Self::Item> {
+ self.i.next().map(|entry| {
+ let html = to_html(&entry.get_content()[..]);
+ (entry, html)
+ })
+ }
+
+ }
+
+}
diff --git a/lib/entry/libimagentrymarkdown/src/lib.rs b/lib/entry/libimagentrymarkdown/src/lib.rs
new file mode 100644
index 0000000..2efc609
--- /dev/null
+++ b/lib/entry/libimagentrymarkdown/src/lib.rs
@@ -0,0 +1,44 @@
+//
+// 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
+//
+
+#![deny(
+ non_camel_case_types,
+ non_snake_case,
+ path_statements,
+ trivial_numeric_casts,
+ unstable_features,
+ unused_allocation,
+ unused_import_braces,
+ unused_imports,
+ unused_mut,
+ unused_qualifications,
+ while_true,
+)]
+
+extern crate crossbeam;
+extern crate hoedown;
+extern crate url;
+extern crate libimagstore;
+#[macro_use] extern crate libimagerror;
+
+pub mod error;
+pub mod html;
+pub mod link;
+pub mod result;
+
diff --git a/lib/entry/libimagentrymarkdown/src/link.rs b/lib/entry/libimagentrymarkdown/src/link.rs
new file mode 100644
index 0000000..9f13fe6
--- /dev/null
+++ b/lib/entry/libimagentrymarkdown/src/link.rs
@@ -0,0 +1,162 @@
+//
+// 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 error::MarkdownErrorKind as MEK;
+use result::Result;
+
+use hoedown::renderer::Render;
+use hoedown::Buffer;
+use hoedown::Markdown;
+use url::Url;
+
+use libimagerror::into::IntoError;
+
+#[derive(Debug, Clone, PartialEq, Eq)]
+pub struct Link {
+ pub title: String,
+ pub link: String,
+}
+
+impl Link {
+
+ /// Translate a `Link` into a `UrlLink`
+ pub fn into_urllink(self) -> Result<UrlLink> {
+ Url::parse(&self.link[..])
+ .map(move |link| UrlLink { title: self.title, link: link, })
+ .map_err(Box::new)
+ .map_err(|e| MEK::LinkParsingError.into_error_with_cause(e))
+ }
+
+}
+
+pub struct UrlLink {
+ pub title: String,
+ pub link: Url,
+}
+
+struct LinkExtractor {
+ links: Vec<Link>,
+}
+
+impl LinkExtractor {
+
+ pub fn new() -> LinkExtractor {
+ LinkExtractor { links: vec![] }
+ }
+
+ pub fn links(self) -> Vec<Link> {
+ self.links
+ }
+
+}
+
+impl Render for LinkExtractor {
+
+ fn link(&mut self,
+ _: &mut Buffer,
+ content: Option<&Buffer>,
+ link: Option<&Buffer>,
+ _: Option<&Buffer>)
+ -> bool
+ {
+ let link = link.and_then(|l| l.to_str().ok()).map(String::from);
+ let content = content.and_then(|l| l.to_str().ok()).map(String::from);
+
+ match (link, content) {
+ (Some(link), Some(content)) => {
+ self.links.push(Link { link: link, title: content });
+ false
+ },
+
+ (_, _) => {
+ false
+ },
+ }
+
+ }
+
+}
+
+pub fn extract_links(buf: &str) -> Vec<Link> {
+ let mut le = LinkExtractor::new();
+ le.render(&Markdown::new(buf));
+ le.links()
+}
+
+#[cfg(test)]
+mod test {
+ use super::{Link, extract_links};
+
+ #[test]
+ fn test_one_link() {
+ let testtext = "Some [example text](http://example.com).";
+
+ let exp = Link {
+ title: String::from("example text"),
+ link: String::from("http://example.com"),
+ };
+
+ let mut links = extract_links(testtext);
+ assert_eq!(1, links.len());
+ assert_eq!(exp, links.pop().unwrap())
+ }
+
+ #[test]