diff options
author | Matthias Beyer <mail@beyermatthias.de> | 2017-09-04 19:36:38 +0200 |
---|---|---|
committer | GitHub <noreply@github.com> | 2017-09-04 19:36:38 +0200 |
commit | 6583ba04a2151d940d99d4277713df4f50bf9eac (patch) | |
tree | da9bd9d7b7c6f128c03752f87ac461b2bef50672 | |
parent | 1fe5a23331174c469cdc5774f164b4ea8c98697b (diff) | |
parent | 51650bf043cfeb56acca1dda52517033af23a900 (diff) |
Merge pull request #1047 from mario-kr/all-extensions-as-traits_imag-todo
imag-todo/libimagtodo: All extensions as traits
-rw-r--r-- | bin/domain/imag-todo/src/main.rs | 53 | ||||
-rw-r--r-- | lib/domain/libimagtodo/src/error.rs | 5 | ||||
-rw-r--r-- | lib/domain/libimagtodo/src/lib.rs | 1 | ||||
-rw-r--r-- | lib/domain/libimagtodo/src/task.rs | 276 | ||||
-rw-r--r-- | lib/domain/libimagtodo/src/taskstore.rs | 205 |
5 files changed, 258 insertions, 282 deletions
diff --git a/bin/domain/imag-todo/src/main.rs b/bin/domain/imag-todo/src/main.rs index 43545d2a..20ad60e4 100644 --- a/bin/domain/imag-todo/src/main.rs +++ b/bin/domain/imag-todo/src/main.rs @@ -35,7 +35,7 @@ use toml::Value; use libimagrt::runtime::Runtime; use libimagrt::setup::generate_runtime_setup; -use libimagtodo::task::Task; +use libimagtodo::taskstore::TaskStore; use libimagerror::trace::{MapErrTrace, trace_error, trace_error_exit}; mod ui; @@ -61,9 +61,11 @@ fn tw_hook(rt: &Runtime) { let subcmd = rt.cli().subcommand_matches("tw-hook").unwrap(); if subcmd.is_present("add") { let stdin = stdin(); - let stdin = stdin.lock(); // implements BufRead which is required for `Task::import()` - match Task::import(rt.store(), stdin) { + // implements BufRead which is required for `Store::import_task_from_reader()` + let stdin = stdin.lock(); + + match rt.store().import_task_from_reader(stdin) { Ok((_, line, uuid)) => println!("{}\nTask {} stored in imag", line, uuid), Err(e) => trace_error_exit(&e, 1), } @@ -71,7 +73,7 @@ fn tw_hook(rt: &Runtime) { // The used hook is "on-modify". This hook gives two json-objects // per usage und wants one (the second one) back. let stdin = stdin(); - Task::delete_by_imports(rt.store(), stdin.lock()).map_err_trace().ok(); + rt.store().delete_tasks_by_imports(stdin.lock()).map_err_trace().ok(); } else { // Should not be possible, as one argument is required via // ArgGroup @@ -92,30 +94,35 @@ fn list(rt: &Runtime) { is_match!(e.kind(), &::toml_query::error::ErrorKind::IdentifierNotFoundInDocument(_)) }; - let res = Task::all(rt.store()) // get all tasks + let res = rt.store().all_tasks() // get all tasks .map(|iter| { // and if this succeeded // filter out the ones were we can read the uuid - let uuids : Vec<_> = iter.filter_map(|t| match t { - Ok(v) => match v.get_header().read(&String::from("todo.uuid")) { - Ok(Some(&Value::String(ref u))) => Some(u.clone()), - Ok(Some(_)) => { - warn!("Header type error"); - None - }, - Ok(None) => { - warn!("Header missing field"); - None + let uuids : Vec<_> = iter.filter_map(|storeid| { + match rt.store().retrieve(storeid) { + Ok(fle) => { + match fle.get_header().read(&String::from("todo.uuid")) { + Ok(Some(&Value::String(ref u))) => Some(u.clone()), + Ok(Some(_)) => { + error!("Header type error, expected String at 'todo.uuid' in {}", + fle.get_location()); + None + }, + Ok(None) => { + error!("Header missing field in {}", fle.get_location()); + None + }, + Err(e) => { + if !no_identifier(&e) { + trace_error(&e); + } + None + } + } }, Err(e) => { - if !no_identifier(&e) { - trace_error(&e); - } + trace_error(&e); None - } - }, - Err(e) => { - trace_error(&e); - None + }, } }) .collect(); diff --git a/lib/domain/libimagtodo/src/error.rs b/lib/domain/libimagtodo/src/error.rs index 621d3249..de39049f 100644 --- a/lib/domain/libimagtodo/src/error.rs +++ b/lib/domain/libimagtodo/src/error.rs @@ -23,7 +23,10 @@ generate_error_module!( StoreError => "Store Error", StoreIdError => "Store Id handling error", ImportError => "Error importing", - UTF8Error => "Encountered non-UTF8 characters while reading input" + UTF8Error => "Encountered non-UTF8 characters while reading input", + HeaderFieldMissing => "Header field missing", + HeaderTypeError => "Header field type error", + UuidParserError => "Uuid parser error" ); ); diff --git a/lib/domain/libimagtodo/src/lib.rs b/lib/domain/libimagtodo/src/lib.rs index 7961a7a1..6c0c482e 100644 --- a/lib/domain/libimagtodo/src/lib.rs +++ b/lib/domain/libimagtodo/src/lib.rs @@ -48,4 +48,5 @@ module_entry_path_mod!("todo"); pub mod error; pub mod result; pub mod task; +pub mod taskstore; diff --git a/lib/domain/libimagtodo/src/task.rs b/lib/domain/libimagtodo/src/task.rs index 3dbbd04c..b2b70696 100644 --- a/lib/domain/libimagtodo/src/task.rs +++ b/lib/domain/libimagtodo/src/task.rs @@ -17,271 +17,31 @@ // 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 error::TodoErrorKind as TEK; +use error::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(); - try!(r.read_line(&mut line).map_err_into(TEK::UTF8Error)); - 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(); - try!(r.read_line(&mut line).map_err_into(TEK::UTF8Error)); - 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(); - try!(r.read_line(&mut line).map_err_into(TEK::UTF8Error)); - 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>; +use libimagstore::store::Entry; +use libimagerror::into::IntoError; - 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; - - 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 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) - } -} +use uuid::Uuid; +use toml::Value; +use toml_query::read::TomlValueReadExt; -pub struct TaskIterator<'a> { - store: &'a Store, - iditer: StoreIdIterator, +pub trait Task { + fn get_uuid(&self) -> Result<Uuid>; } -impl<'a> TaskIterator<'a> { - - pub fn new(store: &'a Store, iditer: StoreIdIterator) -> TaskIterator<'a> { - TaskIterator { - store: store, - iditer: iditer, +impl Task for Entry { + fn get_uuid(&self) -> Result<Uuid> { + match self.get_header().read("todo.uuid") { + Ok(Some(&Value::String(ref uuid))) => { + Uuid::parse_str(uuid).map_err_into(TEK::UuidParserError) + }, + Ok(Some(_)) => Err(TEK::HeaderTypeError.into_error()), + Ok(None) => Err(TEK::HeaderFieldMissing.into_error()), + Err(e) => Err(e).map_err_into(TEK::StoreError), } } - -} - -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/domain/libimagtodo/src/taskstore.rs b/lib/domain/libimagtodo/src/taskstore.rs new file mode 100644 index 00000000..8e8cd9a5 --- /dev/null +++ b/lib/domain/libimagtodo/src/taskstore.rs @@ -0,0 +1,205 @@ +// +// 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::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}; +use module_path::ModuleEntryPath; + +use error::{TodoErrorKind as TEK, MapErrInto}; +use result::Result; + +/// Task struct containing a `FileLockEntry` +pub trait TaskStore<'a> { + fn import_task_from_reader<R: BufRead>(&'a self, r: R) -> Result<(FileLockEntry<'a>, String, Uuid)>; + fn get_task_from_import<R: BufRead>(&'a self, r: R) -> Result<RResult<FileLockEntry<'a>, String>>; + fn get_task_from_string(&'a self, s: String) -> Result<RResult<FileLockEntry<'a>, String>>; + fn get_task_from_uuid(&'a self, uuid: Uuid) -> Result<Option<FileLockEntry<'a>>>; + fn retrieve_task_from_import<R: BufRead>(&'a self, r: R) -> Result<FileLockEntry<'a>>; + fn retrieve_task_from_string(&'a self, s: String) -> Result<FileLockEntry<'a>>; + fn delete_tasks_by_imports<R: BufRead>(&self, r: R) -> Result<()>; + fn delete_task_by_uuid(&self, uuid: Uuid) -> Result<()>; + fn all_tasks(&self) -> Result<StoreIdIterator>; + fn new_from_twtask(&'a self, task: TTask) -> Result<FileLockEntry<'a>>; +} + +impl<'a> TaskStore<'a> for Store { + + fn import_task_from_reader<R: BufRead>(&'a self, mut r: R) -> Result<(FileLockEntry<'a>, String, Uuid)> { + let mut line = String::new(); + try!(r.read_line(&mut line).map_err_into(TEK::UTF8Error)); + import_task(&line.as_str()) + .map_err_into(TEK::ImportError) + .and_then(|t| { + let uuid = t.uuid().clone(); + self.new_from_twtask(t).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 + /// + fn get_task_from_import<R: BufRead>(&'a self, mut r: R) -> Result<RResult<FileLockEntry<'a>, String>> { + let mut line = String::new(); + try!(r.read_line(&mut line).map_err_into(TEK::UTF8Error)); + self.get_task_from_string(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()`. + fn get_task_from_string(&'a self, s: String) -> Result<RResult<FileLockEntry<'a>, String>> { + import_task(s.as_str()) + .map_err_into(TEK::ImportError) + .map(|t| t.uuid().clone()) + .and_then(|uuid| self.get_task_from_uuid(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)`. + fn get_task_from_uuid(&'a self, uuid: Uuid) -> Result<Option<FileLockEntry<'a>>> { + ModuleEntryPath::new(format!("taskwarrior/{}", uuid)) + .into_storeid() + .and_then(|store_id| self.get(store_id)) + .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. + fn retrieve_task_from_import<R: BufRead>(&'a self, mut r: R) -> Result<FileLockEntry<'a>> { + let mut line = String::new(); + try!(r.read_line(&mut line).map_err_into(TEK::UTF8Error)); + self.retrieve_task_from_string(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) + fn retrieve_task_from_string(&'a self, s: String) -> Result<FileLockEntry<'a>> { + self.get_task_from_string(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| self.new_from_twtask(t)), + }) + } + + fn delete_tasks_by_imports<R: BufRead>(&self, 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 self.delete_task_by_uuid(*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(()) + } + + fn delete_task_by_uuid(&self, uuid: Uuid) -> Result<()> { + ModuleEntryPath::new(format!("taskwarrior/{}", uuid)) + .into_storeid() + .and_then(|id| self.delete(id)) + .map_err_into(TEK::StoreError) + } + + fn all_tasks(&self) -> Result<StoreIdIterator> { + self.retrieve_for_module("todo/taskwarrior") + .map_err_into(TEK::StoreError) + } + + fn new_from_twtask(&'a self, task: TTask) -> Result<FileLockEntry<'a>> { + use toml_query::read::TomlValueReadExt; + use toml_query::set::TomlValueSetExt; + + let uuid = task.uuid(); + ModuleEntryPath::new(format!("taskwarrior/{}", uuid)) + .into_storeid() + .map_err_into(TEK::StoreIdError) + .and_then(|id| { + self.retrieve(id) + .map_err_into(TEK::StoreError) + .and_then(|mut fle| { + { + let 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(fle) + }) + }) + + } + +} + |