Add imag-timetrack shell

This patch adds a subcommand to imag-timetrack which allows a user to
start a $SHELL and start a timetracking with it and as soon as the shell
exits, the timetracking is stopped.

Signed-off-by: Matthias Beyer <mail@beyermatthias.de>
This commit is contained in:
Matthias Beyer 2019-07-25 20:23:51 +02:00
parent d5fd773da8
commit 6ac6db57d1
4 changed files with 141 additions and 1 deletions

View file

@ -55,6 +55,7 @@ mod cont;
mod day; mod day;
mod list; mod list;
mod month; mod month;
mod shell;
mod start; mod start;
mod stop; mod stop;
mod track; mod track;
@ -66,6 +67,7 @@ use crate::cont::cont;
use crate::day::day; use crate::day::day;
use crate::list::{list, list_impl}; use crate::list::{list, list_impl};
use crate::month::month; use crate::month::month;
use crate::shell::shell;
use crate::start::start; use crate::start::start;
use crate::stop::stop; use crate::stop::stop;
use crate::track::track; use crate::track::track;
@ -91,6 +93,7 @@ fn main() {
"day" => day(&rt), "day" => day(&rt),
"list" => list(&rt), "list" => list(&rt),
"month" => month(&rt), "month" => month(&rt),
"shell" => shell(&rt),
"start" => start(&rt), "start" => start(&rt),
"stop" => stop(&rt), "stop" => stop(&rt),
"track" => track(&rt), "track" => track(&rt),

View file

@ -0,0 +1,110 @@
//
// imag - the personal information management suite for the commandline
// Copyright (C) 2015-2019 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::env;
use std::process::Command;
use filters::filter::Filter;
use libimagerror::exit::ExitUnwrap;
use libimagerror::iter::TraceIterator;
use libimagerror::trace::MapErrTrace;
use libimagerror::trace::trace_error;
use libimagrt::runtime::Runtime;
use libimagtimetrack::iter::filter::has_one_of_tags;
use libimagtimetrack::store::TimeTrackStore;
use libimagtimetrack::tag::TimeTrackingTag;
use libimagtimetrack::timetracking::TimeTracking;
use libimagutil::warn_result::*;
pub fn shell(rt: &Runtime) -> i32 {
let (_, cmd) = rt.cli().subcommand();
let cmd = cmd.unwrap(); // checked in main()
let start = ::chrono::offset::Local::now().naive_local();
let tags = cmd.values_of("tags")
.unwrap() // enforced by clap
.map(String::from)
.map(TimeTrackingTag::from)
.collect::<Vec<_>>();
let mut shellcmd = {
let mkshell = |s: String| {
let mut cmd = Command::new(s);
cmd.stdin(::std::process::Stdio::inherit());
cmd.stdout(::std::process::Stdio::inherit());
cmd.stderr(::std::process::Stdio::inherit());
cmd
};
if let Some(s) = cmd.value_of("shell") {
mkshell(s.to_owned())
} else {
env::var("SHELL")
.map(|s| mkshell(s))
.map_err(|e| match e {
env::VarError::NotPresent => {
error!("No $SHELL variable in environment, cannot work!");
::std::process::exit(1)
},
env::VarError::NotUnicode(_) => {
error!("$SHELL variable is not unicode, cannot work!");
::std::process::exit(1)
}
})
.unwrap()
}
};
for tag in tags.iter() {
match rt.store().create_timetracking_at(&start, tag) {
Err(e) => trace_error(&e),
Ok(entry) => {
let _ = rt.report_touched(entry.get_location()).unwrap_or_exit();
}
}
}
let exit_code = match shellcmd.status() {
Ok(estat) => estat.code().unwrap_or(0),
Err(e) => {
error!("Error starting shell: {:?}", e);
::std::process::exit(2)
},
};
let stop = ::chrono::offset::Local::now().naive_local();
let filter = has_one_of_tags(&tags);
rt.store()
.get_timetrackings()
.map_warn_err_str("Getting timetrackings failed")
.map_err_trace_exit_unwrap()
.trace_unwrap()
.filter(|e| filter.filter(e))
.for_each(|mut elem| if let Err(e) = elem.set_end_datetime(stop.clone()) {
trace_error(&e)
} else {
debug!("Setting end time worked: {:?}", elem);
let _ = rt.report_touched(elem.get_location()).unwrap_or_exit();
});
::std::process::exit(exit_code)
}

View file

@ -98,7 +98,7 @@ pub fn stop(rt: &Runtime) -> i32 {
1 1
} }
Ok(_) => { Ok(_) => {
format!("Setting end time worked: {:?}", elem); debug!("Setting end time worked: {:?}", elem);
let _ = rt.report_touched(elem.get_location()).unwrap_or_exit(); let _ = rt.report_touched(elem.get_location()).unwrap_or_exit();
acc acc
} }

View file

@ -183,4 +183,31 @@ pub fn build_ui<'a>(app: App<'a, 'a>) -> App<'a, 'a> {
.help("Limit to certain tags")) .help("Limit to certain tags"))
) )
.subcommand(SubCommand::with_name("shell")
.about("Start a shell and start timetracking, stop when shell exits")
.version("0.1")
.long_about(r#"
Tries to find the current shell via $SHELL. If none is found, this aborts operation and returns 1.
If a shell is found in the environment variables, the time tracking is created and the shell started.
As soon as the shell exits (no matter what exit code), the timetracking is stopped.
The command exits with the exit code of the shell it started. If there is no exit code, this exits with 0.
If there was a failure during setting the end-time, the command exits with the exit code of the shell anyways, but prints error information.
If the command for the shell could not be executed, this fails with 2.
"#)
.arg(Arg::with_name("shell")
.long("shell")
.short("s")
.required(false)
.multiple(false)
.takes_value(true)
.help("Shell to start, defaults to $SHELL"))
.arg(Arg::with_name("tags")
.index(1)
.required(true)
.multiple(true)
.help("Tags to start"))
)
} }