Merge pull request #996 from matthiasbeyer/imag-timetrack

imag timetrack
This commit is contained in:
Matthias Beyer 2017-08-25 17:36:55 +02:00 committed by GitHub
commit 057d919239
19 changed files with 1380 additions and 101 deletions

View file

@ -11,6 +11,7 @@ members = [
"imag-ref",
"imag-store",
"imag-tag",
"imag-timetrack",
"imag-todo",
"imag-view",
"libimagannotation",

40
imag-timetrack/Cargo.toml Normal file
View file

@ -0,0 +1,40 @@
[package]
name = "imag-timetrack"
version = "0.3.0"
authors = ["Matthias Beyer <mail@beyermatthias.de>"]
description = "Part of the imag core distribution: imag-tag command"
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.*"
log = "0.3"
version = "2.0.1"
semver = "0.2"
toml = "^0.4"
chrono = "^0.4"
filters = "0.1.1"
itertools = "0.6"
[dependencies.libimagstore]
path = "../libimagstore"
[dependencies.libimagrt]
path = "../libimagrt"
[dependencies.libimagerror]
path = "../libimagerror"
[dependencies.libimagentrytimetrack]
path = "../libimagentrytimetrack"
[dependencies.libimagutil]
path = "../libimagutil"

112
imag-timetrack/src/cont.rs Normal file
View file

@ -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
//
use std::cmp::Ord;
use std::cmp::Ordering;
use filters::ops::not::Not;
use filters::filter::Filter;
use itertools::Itertools;
use itertools::MinMaxResult;
use chrono::NaiveDateTime;
use libimagerror::trace::trace_error;
use libimagerror::trace::MapErrTrace;
use libimagerror::iter::TraceIterator;
use libimagentrytimetrack::timetrackingstore::TimeTrackStore;
use libimagentrytimetrack::timetracking::TimeTracking;
use libimagentrytimetrack::iter::filter::*;
use libimagrt::runtime::Runtime;
pub fn cont(rt: &Runtime) -> i32 {
rt.store()
.get_timetrackings()
.and_then(|iter| {
let groups = iter
// unwrap everything, trace errors
.trace_unwrap()
// I want all entries with an end time
.filter(|e| has_end_time.filter(&e))
// Now group them by the end time
.group_by(|elem| match elem.get_end_datetime() {
Ok(Some(dt)) => dt,
Ok(None) => {
// error. We expect all of them having an end-time.
error!("Has no end time, but should be filtered out: {:?}", elem);
error!("This is a bug. Please report.");
error!("Will panic now");
panic!("Unknown bug")
}
Err(e) => {
trace_error(&e);
NaiveDateTime::from_timestamp(0, 0) // placeholder
}
});
// sort the trackings by key, so by end datetime
let elements = {
let mut v = vec![];
for (key, value) in groups.into_iter() {
v.push((key, value));
}
v.into_iter()
.sorted_by(|t1, t2| {
let (k1, _) = *t1;
let (k2, _) = *t2;
Ord::cmp(&k1, &k2)
})
.into_iter()
// get the last one, which should be the highest one
.last() // -> Option<_>
};
match elements {
Some((_, trackings)) => {
// and then, for all trackings
trackings
.fold(Ok(0), |acc, tracking| {
debug!("Having tracking: {:?}", tracking);
acc.and_then(|_| {
// create a new tracking with the same tag
tracking
.get_timetrack_tag()
.and_then(|tag| rt.store().create_timetracking_now(&tag))
.map(|_| 0)
.map_err_trace()
})
})
},
None => {
info!("No trackings to continue");
Ok(1)
},
}
})
.map(|_| 0)
.map_err_trace()
.unwrap_or(1)
}

119
imag-timetrack/src/day.rs Normal file
View file

@ -0,0 +1,119 @@
//
// 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::Ord;
use std::cmp::Ordering;
use std::str::FromStr;
use filters::ops::not::Not;
use filters::filter::Filter;
use itertools::Itertools;
use itertools::MinMaxResult;
use chrono::NaiveDateTime;
use libimagerror::trace::trace_error;
use libimagerror::trace::MapErrTrace;
use libimagerror::iter::TraceIterator;
use libimagstore::store::FileLockEntry;
use libimagentrytimetrack::timetrackingstore::TimeTrackStore;
use libimagentrytimetrack::timetracking::TimeTracking;
use libimagentrytimetrack::tag::TimeTrackingTag;
use libimagentrytimetrack::iter::filter::*;
use libimagrt::runtime::Runtime;
pub fn day(rt: &Runtime) -> i32 {
let (_, cmd) = rt.cli().subcommand();
let cmd = cmd.unwrap(); // checked in main()
let filter = {
let start = match cmd.value_of("start").map(::chrono::naive::NaiveDateTime::from_str) {
None => ::chrono::offset::Local::today().and_hms(0, 0, 0).naive_local(),
Some(Ok(dt)) => dt,
Some(Err(e)) => {
trace_error(&e);
return 1
}
};
let end = match cmd.value_of("end").map(::chrono::naive::NaiveDateTime::from_str) {
None => ::chrono::offset::Local::today().and_hms(23, 59, 59).naive_local(),
Some(Ok(dt)) => dt,
Some(Err(e)) => {
trace_error(&e);
return 1
}
};
let tags = cmd
.values_of("tags")
.map(|ts| ts.into_iter().map(String::from).map(TimeTrackingTag::from).collect());
let start_time_filter = has_start_time_where(move |dt: &NaiveDateTime| {
start <= *dt
});
let end_time_filter = has_end_time_where(move |dt: &NaiveDateTime| {
end >= *dt
});
let tags_filter = move |fle: &FileLockEntry| {
match tags {
Some(ref tags) => has_one_of_tags(&tags).filter(fle),
None => true,
}
};
tags_filter.and(start_time_filter).and(end_time_filter)
};
rt.store()
.get_timetrackings()
.and_then(|iter| {
iter.trace_unwrap()
.filter(|e| filter.filter(e))
.fold(Ok(()), |acc, e| {
acc.and_then(|_| {
debug!("Processing {:?}", e.get_location());
let tag = try!(e.get_timetrack_tag());
debug!(" -> tag = {:?}", tag);
let start = try!(e.get_start_datetime());
debug!(" -> start = {:?}", start);
let end = try!(e.get_end_datetime());
debug!(" -> end = {:?}", end);
match (start, end) {
(None, _) => println!("{} has no start time.", tag),
(Some(s), None) => println!("{} | {} - ...", tag, s),
(Some(s), Some(e)) => println!("{} | {} - {}", tag, s, e),
}
Ok(())
})
})
})
.map(|_| 0)
.map_err_trace()
.unwrap_or(1)
}

124
imag-timetrack/src/list.rs Normal file
View file

@ -0,0 +1,124 @@
//
// 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::Ord;
use std::cmp::Ordering;
use std::str::FromStr;
use filters::ops::not::Not;
use filters::filter::Filter;
use itertools::Itertools;
use itertools::MinMaxResult;
use chrono::NaiveDateTime;
use libimagerror::trace::trace_error;
use libimagerror::trace::MapErrTrace;
use libimagerror::iter::TraceIterator;
use libimagstore::store::FileLockEntry;
use libimagentrytimetrack::timetrackingstore::TimeTrackStore;
use libimagentrytimetrack::timetracking::TimeTracking;
use libimagentrytimetrack::iter::filter::*;
use libimagrt::runtime::Runtime;
pub fn list(rt: &Runtime) -> i32 {
let (_, cmd) = rt.cli().subcommand();
let cmd = cmd.unwrap(); // checked in main()
let start = match cmd.value_of("start-time").map(::chrono::naive::NaiveDateTime::from_str) {
None => None,
Some(Ok(dt)) => Some(dt),
Some(Err(e)) => {
trace_error(&e);
None
}
};
let end = match cmd.value_of("end-time").map(::chrono::naive::NaiveDateTime::from_str) {
None => None,
Some(Ok(dt)) => Some(dt),
Some(Err(e)) => {
trace_error(&e);
None
}
};
let list_not_ended = cmd.is_present("list-not-ended");
let start_time_filter = |timetracking: &FileLockEntry| {
start.map(|s| match timetracking.get_start_datetime() {
Ok(Some(dt)) => dt >= s,
Ok(None) => {
warn!("Funny things are happening: Timetracking has no start time");
false
}
Err(e) => {
trace_error(&e);
false
}
})
.unwrap_or(true)
};
let end_time_filter = |timetracking: &FileLockEntry| {
start.map(|s| match timetracking.get_end_datetime() {
Ok(Some(dt)) => dt <= s,
Ok(None) => list_not_ended,
Err(e) => {
trace_error(&e);
false
}
})
.unwrap_or(true)
};
let filter = start_time_filter.and(end_time_filter);
rt.store()
.get_timetrackings()
.and_then(|iter| {
iter.trace_unwrap()
.filter(|e| filter.filter(e))
.fold(Ok(()), |acc, e| {
acc.and_then(|_| {
debug!("Processing {:?}", e.get_location());
let tag = try!(e.get_timetrack_tag());
debug!(" -> tag = {:?}", tag);
let start = try!(e.get_start_datetime());
debug!(" -> start = {:?}", start);
let end = try!(e.get_end_datetime());
debug!(" -> end = {:?}", end);
match (start, end) {
(None, _) => println!("{} has no start time.", tag),
(Some(s), None) => println!("{} | {} - ...", tag, s),
(Some(s), Some(e)) => println!("{} | {} - {}", tag, s, e),
}
Ok(())
})
})
})
.map(|_| 0)
.map_err_trace()
.unwrap_or(1)
}

View file

@ -0,0 +1,93 @@
//
// 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;
#[macro_use]
extern crate version;
extern crate clap;
extern crate semver;
extern crate toml;
extern crate chrono;
extern crate filters;
extern crate itertools;
extern crate libimagerror;
extern crate libimagstore;
extern crate libimagrt;
extern crate libimagentrytimetrack;
extern crate libimagutil;
mod cont;
mod day;
mod list;
mod month;
mod start;
mod stop;
mod track;
mod ui;
mod week;
mod year;
use cont::cont;
use day::day;
use list::list;
use month::month;
use start::start;
use stop::stop;
use track::track;
use ui::build_ui;
use week::week;
use year::year;
use libimagrt::setup::generate_runtime_setup;
fn main() {
let rt = generate_runtime_setup("imag-timetrack",
&version!()[..],
"Time tracking module",
build_ui);
let command = rt.cli().subcommand_name();
let retval = if let Some(command) = command {
debug!("Call: {}", command);
match command {
"continue" => cont(&rt),
"day" => day(&rt),
"list" => list(&rt),
"month" => month(&rt),
"start" => start(&rt),
"stop" => stop(&rt),
"track" => track(&rt),
"week" => week(&rt),
"year" => year(&rt),
_ => {
error!("Unknown command");
1
},
}
} else {
error!("No command");
1
};
::std::process::exit(retval);
}

135
imag-timetrack/src/month.rs Normal file
View file

@ -0,0 +1,135 @@
//
// 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::Ord;
use std::cmp::Ordering;
use std::str::FromStr;
use filters::ops::not::Not;
use filters::filter::Filter;
use itertools::Itertools;
use itertools::MinMaxResult;
use chrono::NaiveDateTime;
use libimagerror::trace::trace_error;
use libimagerror::trace::MapErrTrace;
use libimagerror::iter::TraceIterator;
use libimagstore::store::FileLockEntry;
use libimagentrytimetrack::timetrackingstore::TimeTrackStore;
use libimagentrytimetrack::timetracking::TimeTracking;
use libimagentrytimetrack::tag::TimeTrackingTag;
use libimagentrytimetrack::iter::filter::*;
use libimagrt::runtime::Runtime;
pub fn month(rt: &Runtime) -> i32 {
let cmd = rt.cli().subcommand().1.unwrap(); // checked in main
let filter = {
use chrono::offset::Local;
use chrono::naive::NaiveDate;
use chrono::Weekday;
use chrono::Datelike;
let now = Local::now();
let start = match cmd.value_of("start").map(::chrono::naive::NaiveDateTime::from_str) {
None => NaiveDate::from_ymd(now.year(), now.month(), 1).and_hms(0, 0, 0),
Some(Ok(dt)) => dt,
Some(Err(e)) => {
trace_error(&e);
return 1
}
};
let end = match cmd.value_of("end").map(::chrono::naive::NaiveDateTime::from_str) {
None => {
// Is it much harder to go to the last second of the current month than to the first
// second of the next month, right?
let (year, month) = if now.month() == 12 {
(now.year() + 1, 1)
} else {
(now.year(), now.month())
};
NaiveDate::from_ymd(year, month, 1).and_hms(0, 0, 0)
},
Some(Ok(dt)) => dt,
Some(Err(e)) => {
trace_error(&e);
return 1
}
};
let tags = cmd
.values_of("tags")
.map(|ts| ts.into_iter().map(String::from).map(TimeTrackingTag::from).collect());
let start_time_filter = has_start_time_where(move |dt: &NaiveDateTime| {
start <= *dt
});
let end_time_filter = has_end_time_where(move |dt: &NaiveDateTime| {
end >= *dt
});
let tags_filter = move |fle: &FileLockEntry| {
match tags {
Some(ref tags) => has_one_of_tags(&tags).filter(fle),
None => true,
}
};
tags_filter.and(start_time_filter).and(end_time_filter)
};
rt.store()
.get_timetrackings()
.and_then(|iter| {
iter.trace_unwrap()
.filter(|e| filter.filter(e))
.fold(Ok(()), |acc, e| {
acc.and_then(|_| {
debug!("Processing {:?}", e.get_location());
let tag = try!(e.get_timetrack_tag());
debug!(" -> tag = {:?}", tag);
let start = try!(e.get_start_datetime());
debug!(" -> start = {:?}", start);
let end = try!(e.get_end_datetime());
debug!(" -> end = {:?}", end);
match (start, end) {
(None, _) => println!("{} has no start time.", tag),
(Some(s), None) => println!("{} | {} - ...", tag, s),
(Some(s), Some(e)) => println!("{} | {} - {}", tag, s, e),
}
Ok(())
})
})
})
.map(|_| 0)
.map_err_trace()
.unwrap_or(1)
}

View file

@ -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
//
use std::str::FromStr;
use libimagrt::runtime::Runtime;
use libimagerror::trace::trace_error;
use libimagentrytimetrack::tag::TimeTrackingTag;
use libimagentrytimetrack::timetrackingstore::TimeTrackStore;
use libimagerror::trace::MapErrTrace;
pub fn start(rt: &Runtime) -> i32 {
let (_, cmd) = rt.cli().subcommand();
let cmd = cmd.unwrap(); // checked in main()
let start = match cmd.value_of("start-time") {
None | Some("now") => ::chrono::offset::Local::now().naive_local(),
Some(ndt) => match ::chrono::naive::NaiveDateTime::from_str(ndt) {
Ok(ndt) => ndt,
Err(e) => {
trace_error(&e);
error!("Cannot continue, not having start time");
return 1
},
}
};
cmd.values_of("tags")
.unwrap() // enforced by clap
.map(String::from)
.map(TimeTrackingTag::from)
.fold(0, |acc, ttt| {
rt.store()
.create_timetracking_at(&start, &ttt)
.map_err_trace()
.map(|_| acc)
.unwrap_or(1)
})
}

View file

@ -0,0 +1,98 @@
//
// 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::str::FromStr;
use filters::filter::Filter;
use libimagerror::trace::trace_error;
use libimagerror::iter::TraceIterator;
use libimagrt::runtime::Runtime;
use libimagrt::setup::generate_runtime_setup;
use libimagentrytimetrack::timetracking::TimeTracking;
use libimagentrytimetrack::tag::TimeTrackingTag;
use libimagentrytimetrack::timetrackingstore::*;
use libimagentrytimetrack::iter::get::GetTimeTrackIter;
use libimagentrytimetrack::iter::filter::has_end_time;
use libimagentrytimetrack::iter::filter::has_one_of_tags;
pub fn stop(rt: &Runtime) -> i32 {
let (_, cmd) = rt.cli().subcommand();
let cmd = cmd.unwrap(); // checked in main()
let stop_time = match cmd.value_of("stop-time") {
None | Some("now") => ::chrono::offset::Local::now().naive_local(),
Some(ndt) => match ::chrono::naive::NaiveDateTime::from_str(ndt) {
Ok(ndt) => ndt,
Err(e) => {
trace_error(&e);
error!("Cannot continue, not having start time");
return 1
},
}
};
// TODO: We do not yet support stopping all tags by simply calling the "stop" subcommand!
let tags : Vec<TimeTrackingTag> = cmd.values_of("tags")
.unwrap() // enforced by clap
.map(String::from)
.map(TimeTrackingTag::from)
.collect();
let iter : GetTimeTrackIter = match rt.store().get_timetrackings() {
Ok(i) => i,
Err(e) => {
error!("Getting timetrackings failed");
trace_error(&e);
return 1
}
};
let filter = has_end_time.not().and(has_one_of_tags(&tags));
// Filter all timetrackings for the ones that are not yet ended.
iter.trace_unwrap()
.filter_map(|elem| {
if filter.filter(&elem) {
Some(elem)
} else {
None
}
})
// for each of these timetrackings, end them
// for each result, print the backtrace (if any)
.fold(0, |acc, mut elem| match elem.set_end_datetime(stop_time.clone()) {
Err(e) => { // if there was an error
trace_error(&e); // trace
1 // set exit code to 1
},
Ok(_) => {
debug!("Setting end time worked: {:?}", elem);
// Keep the exit code
acc
}
})
}

View file

@ -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::str::FromStr;
use clap::ArgMatches;
use chrono::naive::NaiveDateTime;
use libimagrt::runtime::Runtime;
use libimagerror::trace::trace_error;
use libimagentrytimetrack::tag::TimeTrackingTag;
use libimagentrytimetrack::timetrackingstore::TimeTrackStore;
use libimagerror::trace::MapErrTrace;
pub fn track(rt: &Runtime) -> i32 {
let (_, cmd) = rt.cli().subcommand();
let cmd = cmd.unwrap(); // checked in main()
// Gets the appropriate time from the commandline or None on error (errors already logged, so
// callee can directly return in case of error
fn get_time(cmd: &ArgMatches, clap_name: &str, errname: &str) -> Option<NaiveDateTime> {
let val = cmd
.value_of(clap_name)
.map(::chrono::naive::NaiveDateTime::from_str)
.unwrap(); // clap has our back
match val {
Ok(ndt) => Some(ndt),
Err(e) => {
trace_error(&e);
error!("Cannot continue, not having {} time", errname);
None
},
}
}
let start = match get_time(&cmd, "start-time", "start") {
Some(t) => t,
None => return 1,
};
let stop = match get_time(&cmd, "stop-time", "stop") {
Some(t) => t,
None => return 1,
};
cmd.values_of("tags")
.unwrap() // enforced by clap
.map(String::from)
.map(TimeTrackingTag::from)
.fold(0, |acc, ttt| {
rt.store()
.create_timetracking(&start, &stop, &ttt)
.map_err_trace()
.map(|_| acc)
.unwrap_or(1)
})
}

178
imag-timetrack/src/ui.rs Normal file
View file

@ -0,0 +1,178 @@
//
// 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, App, SubCommand};
pub fn build_ui<'a>(app: App<'a, 'a>) -> App<'a, 'a> {
app
.subcommand(SubCommand::with_name("list")
.about("List time trackings")
.version("0.1")
.arg(Arg::with_name("start-time")
.short("f")
.long("from")
.takes_value(true)
.multiple(false)
.required(false)
.help("Set earliest time from which on time trackings should be shown (use 'now' for current time)"))
.arg(Arg::with_name("end-time")
.short("t")
.long("to")
.takes_value(true)
.multiple(false)
.required(false)
.help("Set latest time of time trackings to be shown (use 'now' for current time)"))
.arg(Arg::with_name("list-not-ended")
.short("l")
.long("list-not-ended")
.takes_value(false)
.multiple(false)
.required(false)
.help("List not yet ended timetrackings even if after 'end-time'"))
)
.subcommand(SubCommand::with_name("start")
.about("Start time tracking")
.version("0.1")
.arg(Arg::with_name("start-time")
.index(1)
.required(true)
.help("Start-time when to start the timetracking (use 'now' for current time)"))
.arg(Arg::with_name("tags")
.index(2)
.required(true)
.multiple(true)
.help("Tags to start"))
)
.subcommand(SubCommand::with_name("stop")
.about("Stop time tracking")
.version("0.1")
.arg(Arg::with_name("end-time")
.index(1)
.required(true)
.help("End-time when to stop the timetracking (use 'now' for current time)"))
.arg(Arg::with_name("tags")
.index(2)
.required(true)
.multiple(true)
.help("Tags to stop"))
)
.subcommand(SubCommand::with_name("track")
.about("Track time in given range")
.version("0.1")
.arg(Arg::with_name("start-time")
.index(1)
.required(true)
.help("Start-time when to start the timetracking"))
.arg(Arg::with_name("end-time")
.index(2)
.required(true)
.help("End-time when to stop the timetracking"))
.arg(Arg::with_name("tags")
.index(3)
.required(true)
.multiple(true)
.help("Tags to stop"))
)
.subcommand(SubCommand::with_name("continue")
.about("Continue last stopped time tracking")
.version("0.1")
)
.subcommand(SubCommand::with_name("day")
.about("Print stats about day")
.version("0.1")
.arg(Arg::with_name("start")
.index(1)
.required(false)
.help("Limit to specific date and time, start time (default: today, 00:00:00)"))
.arg(Arg::with_name("end")
.index(2)
.required(false)
.help("Limit to specific date and time, end time (default: today, 23:59:59)"))
.arg(Arg::with_name("tags")
.long("tags")
.short("t")
.required(false)
.multiple(true)
.help("Limit to certain tags"))
)
.subcommand(SubCommand::with_name("week")
.about("Print stats about week")
.version("0.1")
.arg(Arg::with_name("start")
.index(1)
.required(false)
.help("Limit to specific date and time, start time (default: today, 00:00:00)"))
.arg(Arg::with_name("end")
.index(2)
.required(false)
.help("Limit to specific date and time, end time (default: today, 23:59:59)"))
.arg(Arg::with_name("tags")
.long("tags")
.short("t")
.required(false)
.multiple(true)
.help("Limit to certain tags"))
)
.subcommand(SubCommand::with_name("month")
.about("Print stats about month")
.version("0.1")
.arg(Arg::with_name("start")
.index(1)
.required(false)
.help("Limit to specific date and time, start time (default: today, 00:00:00)"))
.arg(Arg::with_name("end")
.index(2)
.required(false)
.help("Limit to specific date and time, end time (default: today, 23:59:59)"))
.arg(Arg::with_name("tags")
.long("tags")
.short("t")
.required(false)
.multiple(true)
.help("Limit to certain tags"))
)
.subcommand(SubCommand::with_name("year")
.about("Print stats about year")
.version("0.1")
.arg(Arg::with_name("start")
.index(1)
.required(false)
.help("Limit to specific date and time, start time (default: today, 00:00:00)"))
.arg(Arg::with_name("end")
.index(2)
.required(false)
.help("Limit to specific date and time, end time (default: today, 23:59:59)"))
.arg(Arg::with_name("tags")
.long("tags")
.short("t")
.required(false)
.multiple(true)
.help("Limit to certain tags"))
)
}

126
imag-timetrack/src/week.rs Normal file
View file

@ -0,0 +1,126 @@
//
// 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::Ord;
use std::cmp::Ordering;
use std::str::FromStr;
use filters::ops::not::Not;
use filters::filter::Filter;
use itertools::Itertools;
use itertools::MinMaxResult;
use chrono::NaiveDateTime;
use libimagerror::trace::trace_error;
use libimagerror::trace::MapErrTrace;
use libimagerror::iter::TraceIterator;
use libimagstore::store::FileLockEntry;
use libimagentrytimetrack::timetrackingstore::TimeTrackStore;
use libimagentrytimetrack::timetracking::TimeTracking;
use libimagentrytimetrack::tag::TimeTrackingTag;
use libimagentrytimetrack::iter::filter::*;
use libimagrt::runtime::Runtime;
pub fn week(rt: &Runtime) -> i32 {
let cmd = rt.cli().subcommand().1.unwrap(); // checked in main
let filter = {
use chrono::offset::Local;
use chrono::naive::NaiveDate;
use chrono::Weekday;
use chrono::Datelike;
let this_week = Local::now().iso_week();
let start = match cmd.value_of("start").map(::chrono::naive::NaiveDateTime::from_str) {
None => NaiveDate::from_isoywd(this_week.year(), this_week.week(), Weekday::Mon)
.and_hms(0, 0, 0),
Some(Ok(dt)) => dt,
Some(Err(e)) => {
trace_error(&e);
return 1
}
};
let end = match cmd.value_of("end").map(::chrono::naive::NaiveDateTime::from_str) {
None => NaiveDate::from_isoywd(this_week.year(), this_week.week(), Weekday::Sun)
.and_hms(23, 59, 59),
Some(Ok(dt)) => dt,
Some(Err(e)) => {
trace_error(&e);
return 1
}
};
let tags = cmd
.values_of("tags")
.map(|ts| ts.into_iter().map(String::from).map(TimeTrackingTag::from).collect());
let start_time_filter = has_start_time_where(move |dt: &NaiveDateTime| {
start <= *dt
});
let end_time_filter = has_end_time_where(move |dt: &NaiveDateTime| {
end >= *dt
});
let tags_filter = move |fle: &FileLockEntry| {
match tags {
Some(ref tags) => has_one_of_tags(&tags).filter(fle),
None => true,
}
};
tags_filter.and(start_time_filter).and(end_time_filter)
};
rt.store()
.get_timetrackings()
.and_then(|iter| {
iter.trace_unwrap()
.filter(|e| filter.filter(e))
.fold(Ok(()), |acc, e| {
acc.and_then(|_| {
debug!("Processing {:?}", e.get_location());
let tag = try!(e.get_timetrack_tag());
debug!(" -> tag = {:?}", tag);
let start = try!(e.get_start_datetime());
debug!(" -> start = {:?}", start);
let end = try!(e.get_end_datetime());
debug!(" -> end = {:?}", end);
match (start, end) {
(None, _) => println!("{} has no start time.", tag),
(Some(s), None) => println!("{} | {} - ...", tag, s),
(Some(s), Some(e)) => println!("{} | {} - {}", tag, s, e),
}
Ok(())
})
})
})
.map(|_| 0)
.map_err_trace()
.unwrap_or(1)
}

126
imag-timetrack/src/year.rs Normal file
View file

@ -0,0 +1,126 @@
//
// 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::Ord;
use std::cmp::Ordering;
use std::str::FromStr;
use filters::ops::not::Not;
use filters::filter::Filter;
use itertools::Itertools;
use itertools::MinMaxResult;
use chrono::NaiveDateTime;
use libimagerror::trace::trace_error;
use libimagerror::trace::MapErrTrace;
use libimagerror::iter::TraceIterator;
use libimagstore::store::FileLockEntry;
use libimagentrytimetrack::timetrackingstore::TimeTrackStore;
use libimagentrytimetrack::timetracking::TimeTracking;
use libimagentrytimetrack::tag::TimeTrackingTag;
use libimagentrytimetrack::iter::filter::*;
use libimagrt::runtime::Runtime;
pub fn year(rt: &Runtime) -> i32 {
let cmd = rt.cli().subcommand().1.unwrap(); // checked in main
let filter = {
use chrono::offset::Local;
use chrono::naive::NaiveDate;
use chrono::Weekday;
use chrono::Datelike;
let now = Local::now();
let start = match cmd.value_of("start").map(::chrono::naive::NaiveDateTime::from_str) {
None => NaiveDate::from_ymd(now.year(), 1, 1).and_hms(0, 0, 0),
Some(Ok(dt)) => dt,
Some(Err(e)) => {
trace_error(&e);
return 1
}
};
let end = match cmd.value_of("end").map(::chrono::naive::NaiveDateTime::from_str) {
None => {
NaiveDate::from_ymd(now.year() + 1, 1, 1).and_hms(0, 0, 0)
},
Some(Ok(dt)) => dt,
Some(Err(e)) => {
trace_error(&e);
return 1
}
};
let tags = cmd
.values_of("tags")
.map(|ts| ts.into_iter().map(String::from).map(TimeTrackingTag::from).collect());
let start_time_filter = has_start_time_where(move |dt: &NaiveDateTime| {
start <= *dt
});
let end_time_filter = has_end_time_where(move |dt: &NaiveDateTime| {
end >= *dt
});
let tags_filter = move |fle: &FileLockEntry| {
match tags {
Some(ref tags) => has_one_of_tags(&tags).filter(fle),
None => true,
}
};
tags_filter.and(start_time_filter).and(end_time_filter)
};
rt.store()
.get_timetrackings()
.and_then(|iter| {
iter.trace_unwrap()
.filter(|e| filter.filter(e))
.fold(Ok(()), |acc, e| {
acc.and_then(|_| {
debug!("Processing {:?}", e.get_location());
let tag = try!(e.get_timetrack_tag());
debug!(" -> tag = {:?}", tag);
let start = try!(e.get_start_datetime());
debug!(" -> start = {:?}", start);
let end = try!(e.get_end_datetime());
debug!(" -> end = {:?}", end);
match (start, end) {
(None, _) => println!("{} has no start time.", tag),
(Some(s), None) => println!("{} | {} - ...", tag, s),
(Some(s), Some(e)) => println!("{} | {} - {}", tag, s, e),
}
Ok(())
})
})
})
.map(|_| 0)
.map_err_trace()
.unwrap_or(1)
}

View file

@ -19,6 +19,7 @@ chrono = "0.4"
toml = "0.4"
toml-query = "0.3"
lazy_static = "0.2"
is-match = "0.1"
[dependencies.libimagerror]
path = "../libimagerror"

View file

@ -19,123 +19,106 @@
use result::Result;
use chrono::NaiveDateTime;
use filters::filter::Filter;
use libimagstore::store::FileLockEntry;
use tag::TimeTrackingTag as TTT;
use timetracking::TimeTracking;
pub struct WithOneOf<'a, I>
where I: Iterator<Item = Result<FileLockEntry<'a>>>
{
iter: I,
allowed_tags: &'a Vec<TTT>,
pub fn has_start_time(entry: &FileLockEntry) -> bool {
is_match!(entry.get_start_datetime(), Ok(Some(_)))
}
impl<'a, I> WithOneOf<'a, I>
where I: Iterator<Item = Result<FileLockEntry<'a>>>
{
pub fn has_end_time(entry: &FileLockEntry) -> bool {
is_match!(entry.get_end_datetime(), Ok(Some(_)))
}
pub fn new(iter: I, allowed_tags: &'a Vec<TTT>) -> WithOneOf<'a, I> {
WithOneOf {
iter: iter,
allowed_tags: allowed_tags
pub fn has_tag(entry: &FileLockEntry) -> bool {
is_match!(entry.get_timetrack_tag(), Ok(_))
}
pub fn has_start_time_where<F>(f: F) -> HasStartTimeWhere<F>
where F: Fn(&NaiveDateTime) -> bool
{
HasStartTimeWhere::new(f)
}
pub fn has_end_time_where<F>(f: F) -> HasEndTimeWhere<F>
where F: Fn(&NaiveDateTime) -> bool
{
HasEndTimeWhere::new(f)
}
pub fn has_one_of_tags<'a>(tags: &'a Vec<TTT>) -> HasOneOfTags<'a> {
HasOneOfTags::new(tags)
}
mod types {
use chrono::NaiveDateTime;
use filters::filter::Filter;
use tag::TimeTrackingTag as TTT;
use timetracking::TimeTracking;
use libimagstore::store::FileLockEntry;
pub struct HasStartTimeWhere<F>(F)
where F: Fn(&NaiveDateTime) -> bool;
impl<F: Fn(&NaiveDateTime) -> bool> HasStartTimeWhere<F> {
pub fn new(f: F) -> HasStartTimeWhere<F> {
HasStartTimeWhere(f)
}
}
}
impl<'a, I> Iterator for WithOneOf<'a, I>
where I: Iterator<Item = Result<FileLockEntry<'a>>>
{
type Item = Result<FileLockEntry<'a>>;
fn next(&mut self) -> Option<Self::Item> {
loop {
match self.iter.next() {
Some(Ok(fle)) => {
match fle.get_timetrack_tag() {
Err(e) => return Some(Err(e)),
Ok(t) => if self.allowed_tags.contains(&t) {
return Some(Ok(fle))
} else {
// loop
},
}
},
Some(Err(e)) => return Some(Err(e)),
None => return None,
}
impl<'a, F> Filter<FileLockEntry<'a>> for HasStartTimeWhere<F>
where F: Fn(&NaiveDateTime) -> bool
{
fn filter(&self, entry: &FileLockEntry) -> bool {
entry.get_start_datetime()
.map(|o| o.map(|dt| (self.0)(&dt)).unwrap_or(false))
.unwrap_or(false)
}
}
}
pub trait WithOneOfTags<'a> : Sized + Iterator<Item = Result<FileLockEntry<'a>>> {
fn with_timetracking_tags(self, tags: &'a Vec<TTT>) -> WithOneOf<'a, Self>;
}
pub struct HasEndTimeWhere<F>(F)
where F: Fn(&NaiveDateTime) -> bool;
impl<'a, I> WithOneOfTags<'a> for I
where I: Iterator<Item = Result<FileLockEntry<'a>>>,
Self: Sized
{
fn with_timetracking_tags(self, tags: &'a Vec<TTT>) -> WithOneOf<'a, Self> {
WithOneOf::new(self, tags)
}
}
pub struct WithNoneOf<'a, I>
where I: Iterator<Item = Result<FileLockEntry<'a>>>
{
iter: I,
disallowed_tags: &'a Vec<TTT>,
}
impl<'a, I> WithNoneOf<'a, I>
where I: Iterator<Item = Result<FileLockEntry<'a>>>
{
pub fn new(iter: I, disallowed_tags: &'a Vec<TTT>) -> WithNoneOf<'a, I> {
WithNoneOf {
iter: iter,
disallowed_tags: disallowed_tags
impl<F: Fn(&NaiveDateTime) -> bool> HasEndTimeWhere<F> {
pub fn new(f: F) -> HasEndTimeWhere<F> {
HasEndTimeWhere(f)
}
}
}
impl<'a, I> Iterator for WithNoneOf<'a, I>
where I: Iterator<Item = Result<FileLockEntry<'a>>>
{
type Item = Result<FileLockEntry<'a>>;
fn next(&mut self) -> Option<Self::Item> {
loop {
match self.iter.next() {
Some(Ok(fle)) => {
match fle.get_timetrack_tag() {
Err(e) => return Some(Err(e)),
Ok(t) => if !self.disallowed_tags.contains(&t) {
return Some(Ok(fle))
} else {
// loop
},
}
},
Some(Err(e)) => return Some(Err(e)),
None => return None,
}
impl<'a, F> Filter<FileLockEntry<'a>> for HasEndTimeWhere<F>
where F: Fn(&NaiveDateTime) -> bool
{
fn filter(&self, entry: &FileLockEntry) -> bool {
entry.get_end_datetime()
.map(|o| o.map(|dt| (self.0)(&dt)).unwrap_or(false))
.unwrap_or(false)
}
}
}
pub trait WithNoneOfTags<'a> : Sized + Iterator<Item = Result<FileLockEntry<'a>>> {
fn without_timetracking_tags(self, tags: &'a Vec<TTT>) -> WithNoneOf<'a, Self>;
}
pub struct HasOneOfTags<'a>(&'a Vec<TTT>);
impl<'a, I> WithNoneOfTags<'a> for I
where I: Iterator<Item = Result<FileLockEntry<'a>>>,
Self: Sized
{
fn without_timetracking_tags(self, tags: &'a Vec<TTT>) -> WithNoneOf<'a, Self> {
WithNoneOf::new(self, tags)
impl<'a> HasOneOfTags<'a> {
pub fn new(tags: &'a Vec<TTT>) -> HasOneOfTags<'a> {
HasOneOfTags(tags)
}
}
}
impl<'a, 'b> Filter<FileLockEntry<'b>> for HasOneOfTags<'a> {
fn filter(&self, entry: &FileLockEntry) -> bool {
entry.get_timetrack_tag().map(|t| self.0.contains(&t)).unwrap_or(false)
}
}
}
pub use self::types::HasStartTimeWhere;
pub use self::types::HasEndTimeWhere;
pub use self::types::HasOneOfTags;

View file

@ -23,6 +23,8 @@ extern crate toml;
extern crate toml_query;
#[macro_use]
extern crate lazy_static;
#[macro_use]
extern crate is_match;
#[macro_use]
extern crate libimagerror;

View file

@ -17,6 +17,9 @@
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
//
use std::fmt::Display;
use std::fmt::Error as FmtError;
use std::fmt::Formatter;
use std::path::PathBuf;
use libimagstore::store::Result as StoreResult;
@ -34,6 +37,12 @@ impl TimeTrackingTag {
}
}
impl Display for TimeTrackingTag {
fn fmt(&self, f: &mut Formatter) -> Result<(), FmtError> {
self.0.fmt(f)
}
}
impl Into<String> for TimeTrackingTag {
fn into(self) -> String {
self.0

View file

@ -66,9 +66,10 @@ impl TimeTracking for Entry {
self.get_header()
.read(DATE_TIME_TAG_HEADER_PATH)
.map_err_into(TTEK::HeaderReadError)
.map(|value| match value {
Some(&Value::String(ref s)) => s.clone().into(),
_ => unimplemented!(),
.and_then(|value| match value {
Some(&Value::String(ref s)) => Ok(s.clone().into()),
Some(_) => Err(TTEK::HeaderFieldTypeError.into_error()),
_ => Err(TTEK::HeaderReadError.into_error())
})
}

View file

@ -44,7 +44,7 @@ pub trait TimeTrackStore<'a> {
fn create_timetracking_at(&'a self, start: &NDT, ts: &TTT) -> Result<FileLockEntry<'a>>;
fn create_timetracking(&'a self, start: &NDT, end: &NDT, ts: &TTT) -> Result<FileLockEntry<'a>>;
fn get_timetrackings<I>(&'a self) -> Result<GetTimeTrackIter<'a>>;
fn get_timetrackings(&'a self) -> Result<GetTimeTrackIter<'a>>;
}
fn now() -> NDT {
@ -104,7 +104,7 @@ impl<'a> TimeTrackStore<'a> for Store {
})
}
fn get_timetrackings<I>(&'a self) -> Result<GetTimeTrackIter<'a>> {
fn get_timetrackings(&'a self) -> Result<GetTimeTrackIter<'a>> {
self.retrieve_for_module(CRATE_NAME)
.map_err_into(TTEK::StoreReadError)
.map(|iter| GetTimeTrackIter::new(iter, self))