summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorMatthias Beyer <mail@beyermatthias.de>2016-08-06 14:26:28 +0200
committerGitHub <noreply@github.com>2016-08-06 14:26:28 +0200
commitfc164a138231ffaa6c0a3f11ec0a6fad5ea7635d (patch)
treed998fbfd717d52e05c180d15b869f08804e25e85
parent0d7f7a3ac1f46bb05f9c2297cb64067b0fd01cd0 (diff)
parente98bc05cfcbf30a943566e955339da0815030dd2 (diff)
Merge pull request #383 from mario-kr/imag-task
libimagtask
-rw-r--r--imag-todo/Cargo.toml26
-rw-r--r--imag-todo/etc/on-add.sh4
-rw-r--r--imag-todo/etc/on-modify.sh4
-rw-r--r--imag-todo/src/main.rs127
-rw-r--r--imag-todo/src/ui.rs42
-rw-r--r--libimagtodo/Cargo.toml19
-rw-r--r--libimagtodo/src/error.rs12
-rw-r--r--libimagtodo/src/lib.rs16
-rw-r--r--libimagtodo/src/result.rs5
-rw-r--r--libimagtodo/src/task.rs268
10 files changed, 523 insertions, 0 deletions
diff --git a/imag-todo/Cargo.toml b/imag-todo/Cargo.toml
new file mode 100644
index 00000000..0580c74f
--- /dev/null
+++ b/imag-todo/Cargo.toml
@@ -0,0 +1,26 @@
+[package]
+authors = ["mario <mario-krehl@gmx.de>"]
+name = "imag-todo"
+version = "0.1.0"
+
+[dependencies]
+clap = "2.4.3"
+glob = "0.2.11"
+log = "0.3.6"
+semver = "0.2.3"
+serde_json = "0.7.4"
+task-hookrs = "0.2.0"
+toml = "0.1.28"
+version = "2.0.1"
+
+[dependencies.libimagrt]
+path = "../libimagrt"
+
+[dependencies.libimagstore]
+path = "../libimagstore"
+
+[dependencies.libimagtodo]
+path = "../libimagtodo"
+
+[dependencies.libimagerror]
+path = "../libimagerror"
diff --git a/imag-todo/etc/on-add.sh b/imag-todo/etc/on-add.sh
new file mode 100644
index 00000000..a58e4989
--- /dev/null
+++ b/imag-todo/etc/on-add.sh
@@ -0,0 +1,4 @@
+#/!usr/bin/env bash
+
+imag todo tw-hook --add
+
diff --git a/imag-todo/etc/on-modify.sh b/imag-todo/etc/on-modify.sh
new file mode 100644
index 00000000..89be96d0
--- /dev/null
+++ b/imag-todo/etc/on-modify.sh
@@ -0,0 +1,4 @@
+#/!usr/bin/env bash
+
+imag todo tw-hook --delete
+
diff --git a/imag-todo/src/main.rs b/imag-todo/src/main.rs
new file mode 100644
index 00000000..1163fe3c
--- /dev/null
+++ b/imag-todo/src/main.rs
@@ -0,0 +1,127 @@
+extern crate clap;
+extern crate glob;
+#[macro_use] extern crate log;
+extern crate serde_json;
+extern crate semver;
+extern crate toml;
+#[macro_use] extern crate version;
+
+extern crate task_hookrs;
+
+extern crate libimagrt;
+extern crate libimagstore;
+extern crate libimagerror;
+extern crate libimagtodo;
+
+use std::process::exit;
+use std::process::{Command, Stdio};
+use std::io::stdin;
+
+use toml::Value;
+
+use libimagrt::runtime::Runtime;
+use libimagrt::setup::generate_runtime_setup;
+use libimagtodo::task::Task;
+use libimagerror::trace::trace_error;
+
+mod ui;
+
+use ui::build_ui;
+fn main() {
+ let rt = generate_runtime_setup("imag-todo",
+ &version!()[..],
+ "Interface with taskwarrior",
+ build_ui);
+
+ match rt.cli().subcommand_name() {
+ Some("tw-hook") => tw_hook(&rt),
+ Some("list") => list(&rt),
+ None => {
+ warn!("No command");
+ },
+ _ => unreachable!(),
+ } // end match scmd
+} // end main
+
+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) {
+ Ok((_, line, uuid)) => println!("{}\nTask {} stored in imag", line, uuid),
+ Err(e) => {
+ trace_error(&e);
+ exit(1);
+ }
+ }
+ } else if subcmd.is_present("delete") {
+ // 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(|e| trace_error(&e))
+ .ok();
+ } else {
+ // Should not be possible, as one argument is required via
+ // ArgGroup
+ unreachable!();
+ }
+}
+
+fn list(rt: &Runtime) {
+ let subcmd = rt.cli().subcommand_matches("list").unwrap();
+ let verbose = subcmd.is_present("verbose");
+
+ let res = Task::all(rt.store()) // 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("todo.uuid") {
+ Ok(Some(Value::String(ref u))) => Some(u.clone()),
+ Ok(Some(_)) => {
+ warn!("Header type error");
+ None
+ },
+ Ok(None) => None,
+ Err(e) => {
+ trace_error(&e);
+ None
+ }
+ },
+ Err(e) => {
+ trace_error(&e);
+ None
+ }
+ })
+ .collect();
+
+ // compose a `task` call with them, ...
+ let outstring = if verbose { // ... if verbose
+ let output = Command::new("task")
+ .stdin(Stdio::null())
+ .args(&uuids)
+ .spawn()
+ .unwrap_or_else(|e| {
+ trace_error(&e);
+ panic!("Failed to execute `task` on the commandline. I'm dying now.");
+ })
+ .wait_with_output()
+ .unwrap_or_else(|e| panic!("failed to unwrap output: {}", e));
+
+ String::from_utf8(output.stdout)
+ .unwrap_or_else(|e| panic!("failed to execute: {}", e))
+ } else { // ... else just join them
+ uuids.join("\n")
+ };
+
+ // and then print that
+ println!("{}", outstring);
+ });
+
+ if let Err(e) = res {
+ trace_error(&e);
+ }
+}
+
diff --git a/imag-todo/src/ui.rs b/imag-todo/src/ui.rs
new file mode 100644
index 00000000..f781673e
--- /dev/null
+++ b/imag-todo/src/ui.rs
@@ -0,0 +1,42 @@
+use clap::{Arg, App, ArgGroup, SubCommand};
+
+pub fn build_ui<'a>(app: App<'a, 'a>) -> App<'a, 'a> {
+ app
+ .subcommand(SubCommand::with_name("tw-hook")
+ .about("For use in a taskwarrior hook")
+ .version("0.1")
+
+ .arg(Arg::with_name("add")
+ .long("add")
+ .short("a")
+ .takes_value(false)
+ .required(false)
+ .help("For use in an on-add hook"))
+
+ .arg(Arg::with_name("delete")
+ .long("delete")
+ .short("d")
+ .takes_value(false)
+ .required(false)
+ .help("For use in an on-delete hook"))
+
+ .group(ArgGroup::with_name("taskwarrior hooks")
+ .args(&[ "add",
+ "delete",
+ ])
+ .required(true))
+ )
+
+ .subcommand(SubCommand::with_name("list")
+ .about("List all tasks")
+ .version("0.1")
+
+ .arg(Arg::with_name("verbose")
+ .long("verbose")
+ .short("v")
+ .takes_value(false)
+ .required(false)
+ .help("Asks taskwarrior for all the details")
+ )
+ )
+}
diff --git a/libimagtodo/Cargo.toml b/libimagtodo/Cargo.toml
new file mode 100644
index 00000000..7ef12711
--- /dev/null
+++ b/libimagtodo/Cargo.toml
@@ -0,0 +1,19 @@
+[package]
+name = "libimagtodo"
+version = "0.1.0"
+authors = ["mario <mario-krehl@gmx.de>"]
+
+[dependencies]
+semver = "0.2"
+task-hookrs = "0.2"
+uuid = "0.2.0"
+toml = "0.1.28"
+log = "0.3.6"
+serde_json = "0.7.3"
+
+[dependencies.libimagstore]
+path = "../libimagstore"
+
+[dependencies.libimagerror]
+path = "../libimagerror"
+
diff --git a/libimagtodo/src/error.rs b/libimagtodo/src/error.rs
new file mode 100644
index 00000000..8aa42410
--- /dev/null
+++ b/libimagtodo/src/error.rs
@@ -0,0 +1,12 @@
+generate_error_module!(
+ generate_error_types!(TodoError, TodoErrorKind,
+ ConversionError => "Conversion Error",
+ StoreError => "Store Error",
+ ImportError => "Error importing"
+ );
+);
+
+pub use self::error::TodoError;
+pub use self::error::TodoErrorKind;
+pub use self::error::MapErrInto;
+
diff --git a/libimagtodo/src/lib.rs b/libimagtodo/src/lib.rs
new file mode 100644
index 00000000..3189021d
--- /dev/null
+++ b/libimagtodo/src/lib.rs
@@ -0,0 +1,16 @@
+extern crate semver;
+extern crate uuid;
+extern crate toml;
+#[macro_use] extern crate log;
+extern crate serde_json;
+
+#[macro_use] extern crate libimagstore;
+#[macro_use] extern crate libimagerror;
+extern crate task_hookrs;
+
+module_entry_path_mod!("todo", "0.1.0");
+
+pub mod error;
+pub mod result;
+pub mod task;
+
diff --git a/libimagtodo/src/result.rs b/libimagtodo/src/result.rs
new file mode 100644
index 00000000..d14bf499
--- /dev/null
+++ b/libimagtodo/src/result.rs
@@ -0,0 +1,5 @@
+use error::TodoError;
+
+use std::result::Result as RResult;
+
+pub type Result<T> = RResult<T, TodoError>;
diff --git a/libimagtodo/src/task.rs b/libimagtodo/src/task.rs
new file mode 100644
index 00000000..1b44259b
--- /dev/null
+++ b/libimagtodo/src/task.rs
@@ -0,0 +1,268 @@
+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::{TodoError, TodoErrorKind, 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(TodoErrorKind::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: BufRead>(store: &'a Store, mut r: R) -> Result<RResult<Task<'a>, String>>
+ {
+ 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(TodoErrorKind::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>>> {
+ let store_id = ModuleEntryPath::new(format!("taskwarrior/{}", uuid)).into_storeid();
+
+ store.get(store_id)
+ .map(|o| o.map(Task::new))
+ .map_err_into(TodoErrorKind::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(TodoErrorKind::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(TodoErrorKind::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(TodoErrorKind::ImportError),
+ }
+ }
+ Ok(())
+ }
+
+ pub fn delete_by_uuid(store: &Store, uuid: Uuid) -> Result<()> {
+ store.delete(ModuleEntryPath::new(format!("taskwarrior/{}", uuid)).into_storeid())
+ .map_err(|e| TodoError::new(TodoErrorKind::StoreError, Some(Box::new(e))))
+ }
+
+ pub fn all_as_ids(store: &Store) -> Result<StoreIdIterator> {
+ store.retrieve_for_module("todo/taskwarrior")
+ .map_err(|e| TodoError::new(TodoErrorKind::StoreError, Some(Box::new(e))))
+ }
+
+ 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>> {
+ let uuid = self.uuid();
+ let store_id = ModuleEntryPath::new(format!("taskwarrior/{}", uuid)).into_storeid();
+
+ match store.retrieve(store_id) {
+ Err(e) => return Err(TodoError::new(TodoErrorKind::StoreError, Some(Box::new(e)))),
+ Ok(mut fle) => {
+ {
+ let mut header = fle.get_header_mut();
+ match header.read("todo") {
+ Ok(None) => {
+ if let Err(e) = header.set("todo", Value::Table(BTreeMap::new())) {
+ return Err(TodoError::new(TodoErrorKind::StoreError, Some(Box::new(e))))
+ }
+ }
+ Ok(Some(_)) => { }
+ Err(e) => {
+ return Err(TodoError::new(TodoErrorKind::StoreError, Some(Box::new(e))))
+ }
+ }
+
+ if let Err(e) = header.set("todo.uuid", Value::String(format!("{}",uuid))) {
+ return Err(TodoError::new(TodoErrorKind::StoreError, Some(Box::new(e))))
+ }
+ }
+
+ // 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>> {
+ match store.retrieve(id) {
+ Err(e) => Err(TodoError::new(TodoErrorKind::StoreError, Some(Box::new(e)))),
+ Ok(c) => Ok(Task::new( c )),
+ }
+ }
+}
+
+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))
+ }
+}
+