Reimplement imag-todo
Parts of this commit were written by Leon, but in the process we needed to squash. Here's his original commit message: > Change todo listing behaviours > > This commit changes the todo binary to have the following behaviour: > - `imag-todo`: Print all non-hidden pending todos > - `imag-todo pending`: Print all non-hidden pending todos > - `imag-todo list`: Print all non-hidden non-done todos > - `--done`: Include done > - `--no-pending`: Exclude pending > > Each and every command respects the hidden attribute only on the view > layer, but still pipes the hidden entries to stdout. > > Internally, this introduces a black- and whitelist todo-state matcher, > that can be configured to match only certain todos and thereby > improves reusability of functions over the domain binary. Signed-off-by: Leon Schuermann <leon@is.currently.online> Signed-off-by: Matthias Beyer <mail@beyermatthias.de>
This commit is contained in:
parent
775d3f0a80
commit
7873d99df5
5 changed files with 507 additions and 139 deletions
|
@ -1,5 +1,5 @@
|
|||
[package]
|
||||
authors = ["mario <mario-krehl@gmx.de>"]
|
||||
authors = ["Matthias Beyer <mail@beyermatthias.de>"]
|
||||
name = "imag-todo"
|
||||
version = "0.10.0"
|
||||
|
||||
|
@ -25,10 +25,18 @@ toml = "0.5.1"
|
|||
toml-query = "0.9.2"
|
||||
is-match = "0.1.0"
|
||||
failure = "0.1.5"
|
||||
chrono = "0.4"
|
||||
filters = "0.3"
|
||||
kairos = "0.3"
|
||||
resiter = "0.4.0"
|
||||
|
||||
libimagrt = { version = "0.10.0", path = "../../../lib/core/libimagrt" }
|
||||
libimagerror = { version = "0.10.0", path = "../../../lib/core/libimagerror" }
|
||||
libimagtodo = { version = "0.10.0", path = "../../../lib/domain/libimagtodo" }
|
||||
libimagrt = { version = "0.10.0", path = "../../../lib/core/libimagrt" }
|
||||
libimagstore = { version = "0.10.0", path = "../../../lib/core/libimagstore" }
|
||||
libimagerror = { version = "0.10.0", path = "../../../lib/core/libimagerror" }
|
||||
libimagentryedit = { version = "0.10.0", path = "../../../lib/entry/libimagentryedit" }
|
||||
libimagtodo = { version = "0.10.0", path = "../../../lib/domain/libimagtodo" }
|
||||
libimagutil = { version = "0.10.0", path = "../../../lib/etc/libimagutil" }
|
||||
libimagentryview = { version = "0.10.0", path = "../../../lib/entry/libimagentryview" }
|
||||
|
||||
[dependencies.clap]
|
||||
version = "2.33.0"
|
||||
|
|
|
@ -1,4 +0,0 @@
|
|||
#/!usr/bin/env bash
|
||||
|
||||
imag todo tw-hook --add
|
||||
|
|
@ -1,4 +0,0 @@
|
|||
#/!usr/bin/env bash
|
||||
|
||||
imag todo tw-hook --delete
|
||||
|
|
@ -35,30 +35,48 @@
|
|||
)]
|
||||
|
||||
extern crate clap;
|
||||
#[macro_use] extern crate log;
|
||||
extern crate toml;
|
||||
extern crate toml_query;
|
||||
#[macro_use] extern crate is_match;
|
||||
extern crate failure;
|
||||
extern crate chrono;
|
||||
extern crate filters;
|
||||
extern crate kairos;
|
||||
#[macro_use] extern crate log;
|
||||
#[macro_use] extern crate failure;
|
||||
extern crate resiter;
|
||||
|
||||
extern crate libimagrt;
|
||||
extern crate libimagstore;
|
||||
extern crate libimagerror;
|
||||
extern crate libimagentryedit;
|
||||
extern crate libimagtodo;
|
||||
extern crate libimagutil;
|
||||
extern crate libimagentryview;
|
||||
|
||||
use std::process::{Command, Stdio};
|
||||
use std::io::stdin;
|
||||
use std::io::Write;
|
||||
use std::result::Result as RResult;
|
||||
|
||||
use clap::ArgMatches;
|
||||
use chrono::NaiveDateTime;
|
||||
use failure::Error;
|
||||
use failure::Fallible as Result;
|
||||
use failure::err_msg;
|
||||
use clap::App;
|
||||
use resiter::AndThen;
|
||||
use resiter::IterInnerOkOrElse;
|
||||
|
||||
use libimagrt::runtime::Runtime;
|
||||
use libimagentryedit::edit::Edit;
|
||||
use libimagentryview::viewer::ViewFromIter;
|
||||
use libimagentryview::viewer::Viewer;
|
||||
use libimagrt::application::ImagApplication;
|
||||
use libimagtodo::taskstore::TaskStore;
|
||||
use libimagerror::trace::{MapErrTrace, trace_error};
|
||||
use libimagerror::iter::TraceIterator;
|
||||
use libimagerror::exit::ExitUnwrap;
|
||||
use libimagerror::io::ToExitCode;
|
||||
use libimagrt::runtime::Runtime;
|
||||
use libimagstore::iter::get::*;
|
||||
use libimagstore::store::Entry;
|
||||
use libimagstore::store::FileLockEntry;
|
||||
use libimagtodo::entry::Todo;
|
||||
use libimagtodo::priority::Priority;
|
||||
use libimagtodo::status::Status;
|
||||
use libimagtodo::store::TodoStore;
|
||||
use libimagutil::date::datetime_to_string;
|
||||
|
||||
mod ui;
|
||||
|
||||
|
@ -70,21 +88,20 @@ pub enum ImagTodo {}
|
|||
impl ImagApplication for ImagTodo {
|
||||
fn run(rt: Runtime) -> Result<()> {
|
||||
match rt.cli().subcommand_name() {
|
||||
Some("tw-hook") => tw_hook(&rt),
|
||||
Some("list") => list(&rt),
|
||||
Some(other) => {
|
||||
Some("create") => create(&rt),
|
||||
Some("show") => show(&rt),
|
||||
Some("mark") => mark(&rt),
|
||||
Some("pending") | None => list_todos(&rt, &StatusMatcher::new().is(Status::Pending), false),
|
||||
Some("list") => list(&rt),
|
||||
Some(other) => {
|
||||
debug!("Unknown command");
|
||||
let _ = rt.handle_unknown_subcommand("imag-todo", other, rt.cli())
|
||||
.map_err_trace_exit_unwrap()
|
||||
.code()
|
||||
.map(::std::process::exit);
|
||||
if rt.handle_unknown_subcommand("imag-todo", other, rt.cli())?.success() {
|
||||
Ok(())
|
||||
} else {
|
||||
Err(err_msg("Failed to handle unknown subcommand"))
|
||||
}
|
||||
}
|
||||
None => {
|
||||
warn!("No command");
|
||||
},
|
||||
};
|
||||
|
||||
Ok(())
|
||||
} // end match scmd
|
||||
}
|
||||
|
||||
fn build_cli<'a>(app: App<'a, 'a>) -> App<'a, 'a> {
|
||||
|
@ -104,99 +121,311 @@ impl ImagApplication for ImagTodo {
|
|||
}
|
||||
}
|
||||
|
||||
fn tw_hook(rt: &Runtime) {
|
||||
let subcmd = rt.cli().subcommand_matches("tw-hook").unwrap();
|
||||
if subcmd.is_present("add") {
|
||||
let stdin = stdin();
|
||||
/// A black- and whitelist for matching statuses of todo entries
|
||||
///
|
||||
/// The blacklist is checked first, followed by the whitelist.
|
||||
/// In case the whitelist is empty, the StatusMatcher works with a
|
||||
/// blacklist-only approach.
|
||||
#[derive(Debug)]
|
||||
pub struct StatusMatcher {
|
||||
is: Vec<Status>,
|
||||
is_not: Vec<Status>,
|
||||
}
|
||||
|
||||
// implements BufRead which is required for `Store::import_task_from_reader()`
|
||||
let stdin = stdin.lock();
|
||||
impl StatusMatcher {
|
||||
pub fn new() -> Self {
|
||||
StatusMatcher {
|
||||
is: Vec::new(),
|
||||
is_not: Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
let (_, line, uuid ) = rt
|
||||
.store()
|
||||
.import_task_from_reader(stdin)
|
||||
.map_err_trace_exit_unwrap();
|
||||
pub fn is(mut self, s: Status) -> Self {
|
||||
self.add_is(s);
|
||||
self
|
||||
}
|
||||
|
||||
writeln!(rt.stdout(), "{}\nTask {} stored in imag", line, uuid)
|
||||
.to_exit_code()
|
||||
.unwrap_or_exit();
|
||||
pub fn add_is(&mut self, s: Status) {
|
||||
self.is.push(s);
|
||||
}
|
||||
|
||||
} 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();
|
||||
rt.store().delete_tasks_by_imports(stdin.lock()).map_err_trace().ok();
|
||||
} else {
|
||||
// Should not be possible, as one argument is required via
|
||||
// ArgGroup
|
||||
unreachable!();
|
||||
pub fn is_not(mut self, s: Status) -> Self {
|
||||
self.add_is_not(s);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn add_is_not(&mut self, s: Status) {
|
||||
self.is_not.push(s);
|
||||
}
|
||||
|
||||
pub fn matches(&self, todo: Status) -> bool {
|
||||
if self.is_not.iter().find(|t| **t == todo).is_some() {
|
||||
// On blacklist
|
||||
false
|
||||
} else if self.is.len() < 1 || self.is.iter().find(|t| **t == todo).is_some() {
|
||||
// No whitelist or on whitelist
|
||||
true
|
||||
} else {
|
||||
// Not on blacklist, but whitelist exists and not on it either
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn list(rt: &Runtime) {
|
||||
use toml_query::read::TomlValueReadTypeExt;
|
||||
fn create(rt: &Runtime) -> Result<()> {
|
||||
debug!("Creating todo");
|
||||
let scmd = rt.cli().subcommand().1.unwrap(); // safe by clap
|
||||
|
||||
let subcmd = rt.cli().subcommand_matches("list").unwrap();
|
||||
let verbose = subcmd.is_present("verbose");
|
||||
let scheduled: Option<NaiveDateTime> = get_datetime_arg(&scmd, "create-scheduled")?;
|
||||
let hidden: Option<NaiveDateTime> = get_datetime_arg(&scmd, "create-hidden")?;
|
||||
let due: Option<NaiveDateTime> = get_datetime_arg(&scmd, "create-due")?;
|
||||
let prio: Option<Priority> = scmd.value_of("create-prio").map(prio_from_str).transpose()?;
|
||||
let status: Status = scmd.value_of("create-status").map(Status::from_str).unwrap()?;
|
||||
let edit = scmd.is_present("create-edit");
|
||||
let text = scmd.value_of("text").unwrap();
|
||||
|
||||
// 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, &::toml_query::error::Error::IdentifierNotFoundInDocument(_))
|
||||
};
|
||||
trace!("Creating todo with these variables:");
|
||||
trace!("scheduled = {:?}", scheduled);
|
||||
trace!("hidden = {:?}", hidden);
|
||||
trace!("due = {:?}", due);
|
||||
trace!("prio = {:?}", prio);
|
||||
trace!("status = {:?}", status);
|
||||
trace!("edit = {}", edit);
|
||||
trace!("text = {:?}", text);
|
||||
|
||||
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.trace_unwrap_exit().filter_map(|storeid| {
|
||||
match rt.store().retrieve(storeid) {
|
||||
Ok(fle) => {
|
||||
match fle.get_header().read_string("todo.uuid") {
|
||||
Ok(Some(ref u)) => Some(u.clone()),
|
||||
Ok(None) => {
|
||||
error!("Header missing field in {}", fle.get_location());
|
||||
None
|
||||
},
|
||||
Err(e) => {
|
||||
if !no_identifier(&e) {
|
||||
trace_error(&Error::from(e));
|
||||
}
|
||||
None
|
||||
}
|
||||
}
|
||||
},
|
||||
Err(e) => {
|
||||
trace_error(&e);
|
||||
None
|
||||
},
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
let mut entry = rt.store().create_todo(status, scheduled, hidden, due, prio, true)?;
|
||||
debug!("Created: todo {}", entry.get_uuid()?);
|
||||
|
||||
// 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| {
|
||||
error!("Failed to execute `task` on the commandline: {:?}. I'm dying now.", e);
|
||||
::std::process::exit(1)
|
||||
})
|
||||
.wait_with_output()
|
||||
.unwrap_or_else(|e| panic!("failed to unwrap output: {}", e));
|
||||
debug!("Setting content");
|
||||
*entry.get_content_mut() = text.to_string();
|
||||
|
||||
String::from_utf8(output.stdout)
|
||||
.unwrap_or_else(|e| panic!("failed to execute: {}", e))
|
||||
} else { // ... else just join them
|
||||
uuids.join("\n")
|
||||
};
|
||||
if edit {
|
||||
debug!("Editing content");
|
||||
entry.edit_content(&rt)?;
|
||||
}
|
||||
|
||||
// and then print that
|
||||
writeln!(rt.stdout(), "{}", outstring).to_exit_code().unwrap_or_exit();
|
||||
});
|
||||
|
||||
res.map_err_trace().ok();
|
||||
rt.report_touched(entry.get_location()).map_err(Error::from)
|
||||
}
|
||||
|
||||
fn mark(rt: &Runtime) -> Result<()> {
|
||||
fn mark_todos_as(rt: &Runtime, status: Status) -> Result<()> {
|
||||
rt.ids::<crate::ui::PathProvider>()?
|
||||
.ok_or_else(|| err_msg("No ids supplied"))?
|
||||
.into_iter()
|
||||
.map(Ok)
|
||||
.into_get_iter(rt.store())
|
||||
.map_inner_ok_or_else(|| err_msg("Did not find one entry"))
|
||||
.and_then_ok(|e| rt.report_touched(e.get_location()).map_err(Error::from).map(|_| e))
|
||||
.and_then_ok(|mut e| e.set_status(status.clone()))
|
||||
.collect()
|
||||
}
|
||||
|
||||
let scmd = rt.cli().subcommand().1.unwrap();
|
||||
match scmd.subcommand_name() {
|
||||
Some("done") => mark_todos_as(rt, Status::Done),
|
||||
Some("delete") => mark_todos_as(rt, Status::Deleted),
|
||||
Some("pending") => mark_todos_as(rt, Status::Pending),
|
||||
Some(other) => Err(format_err!("Unknown mark type selected: {}", other)),
|
||||
None => Err(format_err!("No mark type selected, doing nothing!")),
|
||||
}
|
||||
}
|
||||
|
||||
/// Generic todo listing function
|
||||
///
|
||||
/// Supports filtering of todos by status using the passed in StatusMatcher
|
||||
fn list_todos(rt: &Runtime, matcher: &StatusMatcher, show_hidden: bool) -> Result<()> {
|
||||
use filters::failable::filter::FailableFilter;
|
||||
debug!("Listing todos with status filter {:?}", matcher);
|
||||
|
||||
let now = {
|
||||
let now = chrono::offset::Local::now();
|
||||
NaiveDateTime::new(now.date().naive_local(), now.time())
|
||||
};
|
||||
|
||||
let filter_hidden = |todo: &FileLockEntry<'_>| -> Result<bool> {
|
||||
Ok(todo.get_hidden()?.map(|hid| hid > now).unwrap_or(true))
|
||||
};
|
||||
|
||||
struct TodoViewer {
|
||||
details: bool,
|
||||
}
|
||||
impl Viewer for TodoViewer {
|
||||
fn view_entry<W>(&self, entry: &Entry, sink: &mut W) -> RResult<(), libimagentryview::error::Error>
|
||||
where W: Write
|
||||
{
|
||||
use libimagentryview::error::Error as E;
|
||||
|
||||
if !entry.is_todo().map_err(E::from)? {
|
||||
return Err(format_err!("Not a Todo: {}", entry.get_location())).map_err(E::from);
|
||||
}
|
||||
|
||||
let uuid = entry.get_uuid().map_err(E::from)?;
|
||||
let status = entry.get_status().map_err(E::from)?;
|
||||
let status = status.as_str();
|
||||
let first_line = entry.get_content()
|
||||
.lines()
|
||||
.next()
|
||||
.unwrap_or("<empty description>");
|
||||
|
||||
if !self.details {
|
||||
writeln!(sink, "{uuid} - {status} : {first_line}",
|
||||
uuid = uuid,
|
||||
status = status,
|
||||
first_line = first_line)
|
||||
} else {
|
||||
let sched = get_dt_str(entry.get_scheduled(), "Not scheduled")?;
|
||||
let hidden = get_dt_str(entry.get_hidden(), "Not hidden")?;
|
||||
let due = get_dt_str(entry.get_due(), "No due")?;
|
||||
let priority = entry.get_priority().map_err(E::from)?.map(|p| p.as_str().to_string())
|
||||
.unwrap_or("No prio".to_string());
|
||||
|
||||
writeln!(sink, "{uuid} - {status} - {sched} - {hidden} - {due} - {prio}: {first_line}",
|
||||
uuid = uuid,
|
||||
status = status,
|
||||
sched = sched,
|
||||
hidden = hidden,
|
||||
due = due,
|
||||
prio = priority,
|
||||
first_line = first_line)
|
||||
}
|
||||
.map_err(libimagentryview::error::Error::from)
|
||||
}
|
||||
}
|
||||
|
||||
let viewer = TodoViewer { details: false };
|
||||
|
||||
rt.store()
|
||||
.get_todos()?
|
||||
.into_get_iter()
|
||||
.map_inner_ok_or_else(|| err_msg("Did not find one entry"))
|
||||
.filter_map(|r| {
|
||||
match r.and_then(|e| e.get_status().map(|s| (s, e))) {
|
||||
Err(e) => Some(Err(e)),
|
||||
Ok((st, e)) => if matcher.matches(st) {
|
||||
Some(Ok(e))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
})
|
||||
.and_then_ok(|entry| {
|
||||
if show_hidden || filter_hidden.filter(&entry)? {
|
||||
viewer.view_entry(&entry, &mut rt.stdout())?;
|
||||
}
|
||||
|
||||
rt.report_touched(entry.get_location()).map_err(Error::from)
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
/// Generic todo items list function
|
||||
///
|
||||
/// This sets up filtes based on the command line and prints out a list of todos
|
||||
fn list(rt: &Runtime) -> Result<()> {
|
||||
debug!("Listing todo");
|
||||
let scmd = rt.cli().subcommand().1;
|
||||
let table = scmd.map(|s| s.is_present("list-table")).unwrap_or(true);
|
||||
let hidden = scmd.map(|s| s.is_present("list-hidden")).unwrap_or(false);
|
||||
let done = scmd.map(|s| s.is_present("list-done")).unwrap_or(false);
|
||||
let nopending = scmd.map(|s| s.is_present("list-nopending")).unwrap_or(true);
|
||||
|
||||
trace!("table = {}", table);
|
||||
trace!("hidden = {}", hidden);
|
||||
trace!("done = {}", done);
|
||||
trace!("nopending = {}", nopending);
|
||||
|
||||
let mut matcher = StatusMatcher::new();
|
||||
if !done { matcher.add_is_not(Status::Done); }
|
||||
if nopending { matcher.add_is_not(Status::Pending); }
|
||||
|
||||
// TODO: Support printing as ASCII table
|
||||
list_todos(rt, &matcher, hidden)
|
||||
}
|
||||
|
||||
fn show(rt: &Runtime) -> Result<()> {
|
||||
#[derive(Default)]
|
||||
struct TodoShow;
|
||||
impl Viewer for TodoShow {
|
||||
|
||||
fn view_entry<W>(&self, entry: &Entry, sink: &mut W) -> RResult<(), libimagentryview::error::Error>
|
||||
where W: Write
|
||||
{
|
||||
use libimagentryview::error::Error as E;
|
||||
|
||||
if !entry.is_todo().map_err(E::from)? {
|
||||
return Err(format_err!("Not a Todo: {}", entry.get_location())).map_err(E::from);
|
||||
}
|
||||
|
||||
let uuid = entry.get_uuid().map_err(E::from)?;
|
||||
let status = entry.get_status().map_err(E::from)?;
|
||||
let status = status.as_str();
|
||||
let text = entry.get_content();
|
||||
let sched = get_dt_str(entry.get_scheduled(), "Not scheduled")?;
|
||||
let hidden = get_dt_str(entry.get_hidden(), "Not hidden")?;
|
||||
let due = get_dt_str(entry.get_due(), "No due")?;
|
||||
let priority = entry.get_priority().map_err(E::from)?.map(|p| p.as_str().to_string())
|
||||
.unwrap_or("No prio".to_string());
|
||||
|
||||
writeln!(sink, "{uuid}\nStatus: {status}\nPriority: {prio}\nScheduled: {sched}\nHidden: {hidden}\nDue: {due}\n\n{text}",
|
||||
uuid = uuid,
|
||||
status = status,
|
||||
sched = sched,
|
||||
hidden = hidden,
|
||||
due = due,
|
||||
prio = priority,
|
||||
text = text)
|
||||
.map_err(Error::from)
|
||||
.map_err(libimagentryview::error::Error::from)
|
||||
}
|
||||
}
|
||||
|
||||
rt.ids::<crate::ui::PathProvider>()?
|
||||
.ok_or_else(|| err_msg("No ids supplied"))?
|
||||
.into_iter()
|
||||
.map(Ok)
|
||||
.into_get_iter(rt.store())
|
||||
.map_inner_ok_or_else(|| err_msg("Did not find one entry"))
|
||||
.and_then_ok(|e| rt.report_touched(e.get_location()).map_err(Error::from).map(|_| e))
|
||||
.collect::<Result<Vec<_>>>()?
|
||||
.into_iter()
|
||||
.view::<TodoShow, _>(&mut rt.stdout())
|
||||
.map_err(Error::from)
|
||||
}
|
||||
|
||||
//
|
||||
// utility functions
|
||||
//
|
||||
|
||||
fn get_datetime_arg(scmd: &ArgMatches, argname: &'static str) -> Result<Option<NaiveDateTime>> {
|
||||
use kairos::timetype::TimeType;
|
||||
use kairos::parser;
|
||||
|
||||
match scmd.value_of(argname) {
|
||||
None => Ok(None),
|
||||
Some(v) => match parser::parse(v)? {
|
||||
parser::Parsed::TimeType(TimeType::Moment(moment)) => Ok(Some(moment)),
|
||||
parser::Parsed::TimeType(other) => {
|
||||
Err(format_err!("You did not pass a date, but a {}", other.name()))
|
||||
},
|
||||
parser::Parsed::Iterator(_) => {
|
||||
Err(format_err!("Argument {} results in a list of dates, but we need a single date.", v))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn prio_from_str<S: AsRef<str>>(s: S) -> Result<Priority> {
|
||||
match s.as_ref() {
|
||||
"h" => Ok(Priority::High),
|
||||
"m" => Ok(Priority::Medium),
|
||||
"l" => Ok(Priority::Low),
|
||||
other => Err(format_err!("Unsupported Priority: '{}'", other)),
|
||||
}
|
||||
}
|
||||
|
||||
fn get_dt_str(d: Result<Option<NaiveDateTime>>, s: &str) -> RResult<String, libimagentryview::error::Error> {
|
||||
Ok(d.map_err(libimagentryview::error::Error::from)?
|
||||
.map(|v| datetime_to_string(&v))
|
||||
.unwrap_or(s.to_string()))
|
||||
}
|
||||
|
||||
|
|
|
@ -17,45 +17,184 @@
|
|||
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
//
|
||||
|
||||
use clap::{Arg, App, ArgGroup, SubCommand};
|
||||
use std::path::PathBuf;
|
||||
use clap::{Arg, ArgMatches, App, SubCommand};
|
||||
use failure::Fallible as Result;
|
||||
|
||||
use libimagstore::storeid::StoreId;
|
||||
use libimagstore::storeid::IntoStoreId;
|
||||
use libimagrt::runtime::IdPathProvider;
|
||||
|
||||
|
||||
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")
|
||||
.subcommand(SubCommand::with_name("create")
|
||||
.about("Create task")
|
||||
.version("0.1")
|
||||
|
||||
.arg(Arg::with_name("add")
|
||||
.long("add")
|
||||
.short("a")
|
||||
.takes_value(false)
|
||||
.arg(Arg::with_name("create-scheduled")
|
||||
.long("scheduled")
|
||||
.short("s")
|
||||
.takes_value(true)
|
||||
.required(false)
|
||||
.help("For use in an on-add hook"))
|
||||
.help("Set a 'scheduled' date/time")
|
||||
)
|
||||
|
||||
.arg(Arg::with_name("delete")
|
||||
.long("delete")
|
||||
.arg(Arg::with_name("create-hidden")
|
||||
.long("hidden")
|
||||
.short("h")
|
||||
.takes_value(true)
|
||||
.required(false)
|
||||
.help("Set a 'hidden' date/time")
|
||||
)
|
||||
|
||||
.arg(Arg::with_name("create-due")
|
||||
.long("due")
|
||||
.short("d")
|
||||
.takes_value(true)
|
||||
.required(false)
|
||||
.help("Set a 'due' date/time")
|
||||
)
|
||||
|
||||
.arg(Arg::with_name("create-prio")
|
||||
.long("prio")
|
||||
.short("p")
|
||||
.takes_value(true)
|
||||
.required(false)
|
||||
.help("Set a priority")
|
||||
.possible_values(&["h", "m", "l"])
|
||||
)
|
||||
|
||||
.arg(Arg::with_name("create-status")
|
||||
.long("status")
|
||||
.takes_value(true)
|
||||
.required(false)
|
||||
.help("Set a status, useful if the task is already done")
|
||||
.default_value("pending")
|
||||
.possible_values(&["pending", "done", "deleted"])
|
||||
)
|
||||
|
||||
.arg(Arg::with_name("create-edit")
|
||||
.long("edit")
|
||||
.short("e")
|
||||
.takes_value(false)
|
||||
.required(false)
|
||||
.help("For use in an on-delete hook"))
|
||||
.help("Create and then edit the entry")
|
||||
)
|
||||
|
||||
.group(ArgGroup::with_name("taskwarrior hooks")
|
||||
.args(&[ "add",
|
||||
"delete",
|
||||
])
|
||||
.required(true))
|
||||
.arg(Arg::with_name("text")
|
||||
.index(1)
|
||||
.multiple(true)
|
||||
.required(true)
|
||||
.help("Text for the todo")
|
||||
)
|
||||
)
|
||||
|
||||
.subcommand(SubCommand::with_name("pending")
|
||||
.arg(Arg::with_name("todos")
|
||||
.index(1)
|
||||
.takes_value(true)
|
||||
.required(false)
|
||||
.help("List pending todos (same as 'list' command without arguments)")
|
||||
)
|
||||
)
|
||||
|
||||
.subcommand(SubCommand::with_name("list")
|
||||
.about("List all tasks")
|
||||
.about("List tasks (default)")
|
||||
.version("0.1")
|
||||
|
||||
.arg(Arg::with_name("verbose")
|
||||
.long("verbose")
|
||||
.short("v")
|
||||
.arg(Arg::with_name("list-table")
|
||||
.long("table")
|
||||
.short("T")
|
||||
.takes_value(false)
|
||||
.required(false)
|
||||
.help("Asks taskwarrior for all the details")
|
||||
.help("Print a nice ascii-table")
|
||||
)
|
||||
)
|
||||
|
||||
.arg(Arg::with_name("list-hidden")
|
||||
.long("hidden")
|
||||
.short("H")
|
||||
.takes_value(false)
|
||||
.required(false)
|
||||
.help("Print also hidden todos")
|
||||
)
|
||||
|
||||
.arg(Arg::with_name("list-done")
|
||||
.long("done")
|
||||
.short("D")
|
||||
.takes_value(false)
|
||||
.required(false)
|
||||
.help("Print also done todos")
|
||||
)
|
||||
|
||||
.arg(Arg::with_name("list-nopending")
|
||||
.long("no-pending")
|
||||
.short("P")
|
||||
.takes_value(false)
|
||||
.required(false)
|
||||
.help("Do not print pending tasks")
|
||||
)
|
||||
|
||||
)
|
||||
|
||||
.subcommand(SubCommand::with_name("show")
|
||||
.arg(Arg::with_name("todos")
|
||||
.index(1)
|
||||
.takes_value(true)
|
||||
.required(false)
|
||||
.help("Show the passed todos")
|
||||
)
|
||||
)
|
||||
|
||||
.subcommand(SubCommand::with_name("mark")
|
||||
.about("Mark tasks as pending, done or deleted")
|
||||
.version("0.1")
|
||||
|
||||
.subcommand(SubCommand::with_name("pending")
|
||||
.arg(Arg::with_name("todos")
|
||||
.index(1)
|
||||
.takes_value(true)
|
||||
.required(false)
|
||||
.help("List pending todos (same as 'list' command without arguments)")
|
||||
)
|
||||
)
|
||||
|
||||
.subcommand(SubCommand::with_name("done")
|
||||
.arg(Arg::with_name("todos")
|
||||
.index(1)
|
||||
.takes_value(true)
|
||||
.required(false)
|
||||
.help("Mark the passed todos as done")
|
||||
)
|
||||
)
|
||||
|
||||
.subcommand(SubCommand::with_name("deleted")
|
||||
.arg(Arg::with_name("todos")
|
||||
.index(1)
|
||||
.takes_value(true)
|
||||
.required(false)
|
||||
.help("Mark the passed todos as deleted")
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
}
|
||||
|
||||
pub struct PathProvider;
|
||||
impl IdPathProvider for PathProvider {
|
||||
fn get_ids(matches: &ArgMatches) -> Result<Option<Vec<StoreId>>> {
|
||||
match matches.subcommand() {
|
||||
("show", Some(scmd)) => scmd.values_of("todos"),
|
||||
("show", None) => unimplemented!(),
|
||||
_ => unimplemented!()
|
||||
}
|
||||
.map(|v| v
|
||||
.into_iter()
|
||||
.map(PathBuf::from)
|
||||
.map(|pb| pb.into_storeid())
|
||||
.collect::<Result<Vec<_>>>()
|
||||
)
|
||||
.transpose()
|
||||
}
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in a new issue