Merge pull request #19 from matthiasbeyer/all-extensions-as-traits_imag-todo

All extensions as traits imag todo
This commit is contained in:
mario-kr 2017-09-04 09:48:27 +02:00 committed by GitHub
commit cb9aa19894
5 changed files with 231 additions and 180 deletions

View file

@ -35,7 +35,7 @@ use toml::Value;
use libimagrt::runtime::Runtime;
use libimagrt::setup::generate_runtime_setup;
use libimagtodo::task::TaskStore;
use libimagtodo::taskstore::TaskStore;
use libimagerror::trace::{MapErrTrace, trace_error, trace_error_exit};
mod ui;

View file

@ -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"
);
);

View file

@ -48,4 +48,5 @@ module_entry_path_mod!("todo");
pub mod error;
pub mod result;
pub mod task;
pub mod taskstore;

View file

@ -17,189 +17,31 @@
// 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 error::TodoErrorKind as TEK;
use error::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>>;
use libimagstore::store::Entry;
use libimagerror::into::IntoError;
use uuid::Uuid;
use toml::Value;
use toml_query::read::TomlValueReadExt;
pub trait Task {
fn get_uuid(&self) -> Result<Uuid>;
}
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),
}
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),
}
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)
})
})
}
}

View file

@ -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)
})
})
}
}