From db6d4643cb2d474ac1a046e44abad50879740c01 Mon Sep 17 00:00:00 2001 From: Nikolas Schmidt-Voigt Date: Sun, 5 Sep 2021 21:30:11 +0200 Subject: [PATCH] moved commands to separate modules --- src/lib.rs | 258 +---------------------------------- src/list_commands.rs | 135 ++++++++++++++++++ src/main.rs | 18 +-- src/manipulation_commands.rs | 124 +++++++++++++++++ 4 files changed, 274 insertions(+), 261 deletions(-) create mode 100644 src/list_commands.rs create mode 100644 src/manipulation_commands.rs diff --git a/src/lib.rs b/src/lib.rs index d8c7ba8..67c4575 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,255 +1,9 @@ -use anyhow::{anyhow, bail, Context, Error, Result}; -use chrono::{naive, NaiveDate, NaiveDateTime}; - -use crate::activity::Activity; -use crate::bartib_file::Line; -use std::process::Command; -mod activity; -pub mod bartib_file; pub mod conf; +pub mod list_commands; +pub mod manipulation_commands; + +mod activity; +mod bartib_file; mod format_util; mod output; -mod table; - -pub struct ActivityFilter { - pub number_of_activities: Option, - pub from_date: Option, - pub to_date: Option, - pub date: Option, -} - -// starts a new activity -pub fn start( - file_name: &str, - project_name: &str, - activity_description: &str, - time: Option, -) -> Result<()> { - let mut file_content: Vec = Vec::new(); - - if let Ok(mut previous_file_content) = bartib_file::get_file_content(file_name) { - // if we start a new activities programaticly, we stop all other activities first. - // However, we must not assume that there is always only one activity - // running as the user may have started activities manually - stop_all_running_activities(&mut previous_file_content, time); - - file_content.append(&mut previous_file_content); - } - - let activity = activity::Activity::start( - project_name.to_string(), - activity_description.to_string(), - time, - ); - - save_new_activity(file_name, &mut file_content, activity) -} - -fn save_new_activity( - file_name: &str, - file_content: &mut Vec, - activity: Activity, -) -> Result<(), Error> { - println!( - "Started activity: \"{}\" ({}) at {}", - activity.description, - activity.project, - activity.start.format(conf::FORMAT_DATETIME) - ); - - file_content.push(bartib_file::Line::for_activity(activity)); - bartib_file::write_to_file(file_name, &file_content) - .context(format!("Could not write to file: {}", file_name)) -} - -// stops all currently running activities -pub fn stop(file_name: &str, time: Option) -> Result<()> { - let mut file_content = bartib_file::get_file_content(file_name)?; - stop_all_running_activities(&mut file_content, time); - bartib_file::write_to_file(file_name, &file_content) - .context(format!("Could not write to file: {}", file_name)) -} - -// lists all currently runninng activities. -pub fn list_running(file_name: &str) -> Result<()> { - let file_content = bartib_file::get_file_content(file_name)?; - let running_activities = get_running_activities(&file_content); - output::list_running_activities(&running_activities); - - Ok(()) -} - -// lists tracked activities -// -// the activities will be ordered chronologically. -pub fn list(file_name: &str, filter: ActivityFilter, do_group_activities: bool) -> Result<()> { - let file_content = bartib_file::get_file_content(file_name)?; - let activities = get_activities(&file_content); - let mut filtered_activities: Vec<&activity::Activity> = - filter_activities(activities, &filter).collect(); - - filtered_activities.sort_by_key(|activity| activity.start); - - let first_element = - get_index_of_first_element(filtered_activities.len(), filter.number_of_activities); - - if do_group_activities { - output::list_activities_grouped_by_date( - &filtered_activities[first_element..filtered_activities.len()], - ); - } else { - let with_start_dates = !filter.date.is_some(); - output::list_activities( - &filtered_activities[first_element..filtered_activities.len()], - with_start_dates, - ); - } - - Ok(()) -} - -// lists all projects -pub fn list_projects(file_name: &str) -> Result<()> { - let file_content = bartib_file::get_file_content(file_name)?; - - let mut all_projects: Vec<&String> = get_activities(&file_content) - .map(|activity| &activity.project) - .collect(); - - all_projects.sort_unstable(); - all_projects.dedup(); - - for project in all_projects { - println!("\"{}\"", project); - } - - Ok(()) -} - -// return last finished activity -pub fn display_last_activity(file_name: &str) -> Result<()> { - let file_content = bartib_file::get_file_content(file_name)?; - - let last_activity = get_last_activity(&file_content); - - if let Some(activity) = last_activity { - output::display_single_activity(&activity); - } else { - println!("No activity has been finished yet.") - } - - Ok(()) -} - -// continue last activity -pub fn continue_last_activity( - file_name: &str, - project_name: Option<&str>, - activity_description: Option<&str>, - time: Option, -) -> Result<()> { - let mut file_content = bartib_file::get_file_content(file_name)?; - - let optional_last_activity = - get_last_activity_by_start(&file_content).or(get_last_activity(&file_content)); - - if let Some(last_activity) = optional_last_activity { - let new_activity = activity::Activity::start( - project_name.unwrap_or(&last_activity.project).to_string(), - activity_description - .unwrap_or(&last_activity.description) - .to_string(), - time, - ); - stop_all_running_activities(&mut file_content, time); - save_new_activity(file_name, &mut file_content, new_activity) - } else { - bail!("No activity has been started before.") - } -} - -pub fn start_editor(file_name: &str, optional_editor_command: Option<&str>) -> Result<()> { - let editor_command = optional_editor_command.context("editor command is missing")?; - let command = Command::new(editor_command).arg(file_name).spawn(); - - match command { - Ok(mut child) => { - child.wait().context("editor did not execute")?; - Ok(()) - } - Err(e) => Err(anyhow!(e)), - } -} - -fn get_last_activity(file_content: &Vec) -> Option<&Activity> { - get_activities(&file_content) - .filter(|activity| activity.is_stopped()) - .max_by_key(|activity| activity.end.unwrap_or(naive::MIN_DATE.and_hms(0, 0, 0))) -} - -fn get_last_activity_by_start(file_content: &Vec) -> Option<&Activity> { - get_activities(&file_content).max_by_key(|activity| activity.start) -} - -fn get_index_of_first_element(length: usize, sub: Option) -> usize { - if let Some(s) = sub { - length.saturating_sub(s) - } else { - 0 - } -} - -fn stop_all_running_activities( - file_content: &mut [bartib_file::Line], - time: Option, -) { - for line in file_content { - if let Ok(activity) = &mut line.activity { - if !activity.is_stopped() { - activity.stop(time); - println!( - "Stopped activity: \"{}\" ({}) started at {} ({})", - activity.description, - activity.project, - activity.start.format(conf::FORMAT_DATETIME), - format_util::format_duration(&activity.get_duration()), - ); - - line.set_changed(); - } - } - } -} - -fn get_running_activities(file_content: &[bartib_file::Line]) -> Vec<&activity::Activity> { - get_activities(file_content) - .filter(|activity| !activity.is_stopped()) - .collect() -} - -fn get_activities(file_content: &[bartib_file::Line]) -> impl Iterator { - file_content - .iter() - .map(|line| line.activity.as_ref()) - .filter_map(|activity_result| activity_result.ok()) -} - -fn filter_activities<'a>( - activities: impl Iterator, - filter: &ActivityFilter, -) -> impl Iterator { - let from_date: NaiveDate; - let to_date: NaiveDate; - - if let Some(date) = filter.date { - from_date = date; - to_date = date; - } else { - from_date = filter.from_date.unwrap_or(naive::MIN_DATE); - to_date = filter.to_date.unwrap_or(naive::MAX_DATE); - } - - activities.filter(move |activity| { - activity.start.date() >= from_date && activity.start.date() <= to_date - }) -} +mod table; \ No newline at end of file diff --git a/src/list_commands.rs b/src/list_commands.rs new file mode 100644 index 0000000..77e0a14 --- /dev/null +++ b/src/list_commands.rs @@ -0,0 +1,135 @@ +use anyhow::Result; +use chrono::{naive, NaiveDate}; + +use crate::activity; +use crate::bartib_file; +use crate::output; + +pub struct ActivityFilter { + pub number_of_activities: Option, + pub from_date: Option, + pub to_date: Option, + pub date: Option, +} + +// lists all currently runninng activities. +pub fn list_running(file_name: &str) -> Result<()> { + let file_content = bartib_file::get_file_content(file_name)?; + let running_activities = get_running_activities(&file_content); + output::list_running_activities(&running_activities); + + Ok(()) +} + +// lists tracked activities +// +// the activities will be ordered chronologically. +pub fn list(file_name: &str, filter: ActivityFilter, do_group_activities: bool) -> Result<()> { + let file_content = bartib_file::get_file_content(file_name)?; + let activities = get_activities(&file_content); + let mut filtered_activities: Vec<&activity::Activity> = + filter_activities(activities, &filter).collect(); + + filtered_activities.sort_by_key(|activity| activity.start); + + let first_element = + get_index_of_first_element(filtered_activities.len(), filter.number_of_activities); + + if do_group_activities { + output::list_activities_grouped_by_date( + &filtered_activities[first_element..filtered_activities.len()], + ); + } else { + let with_start_dates = !filter.date.is_some(); + output::list_activities( + &filtered_activities[first_element..filtered_activities.len()], + with_start_dates, + ); + } + + Ok(()) +} + +// lists all projects +pub fn list_projects(file_name: &str) -> Result<()> { + let file_content = bartib_file::get_file_content(file_name)?; + + let mut all_projects: Vec<&String> = get_activities(&file_content) + .map(|activity| &activity.project) + .collect(); + + all_projects.sort_unstable(); + all_projects.dedup(); + + for project in all_projects { + println!("\"{}\"", project); + } + + Ok(()) +} + +// return last finished activity +pub fn display_last_activity(file_name: &str) -> Result<()> { + let file_content = bartib_file::get_file_content(file_name)?; + + let last_activity = get_last_activity_by_end(&file_content); + + if let Some(activity) = last_activity { + output::display_single_activity(&activity); + } else { + println!("No activity has been finished yet.") + } + + Ok(()) +} + +fn get_index_of_first_element(length: usize, sub: Option) -> usize { + if let Some(s) = sub { + length.saturating_sub(s) + } else { + 0 + } +} + +fn get_running_activities(file_content: &[bartib_file::Line]) -> Vec<&activity::Activity> { + get_activities(file_content) + .filter(|activity| !activity.is_stopped()) + .collect() +} + +fn get_activities(file_content: &[bartib_file::Line]) -> impl Iterator { + file_content + .iter() + .map(|line| line.activity.as_ref()) + .filter_map(|activity_result| activity_result.ok()) +} + +fn filter_activities<'a>( + activities: impl Iterator, + filter: &ActivityFilter, +) -> impl Iterator { + let from_date: NaiveDate; + let to_date: NaiveDate; + + if let Some(date) = filter.date { + from_date = date; + to_date = date; + } else { + from_date = filter.from_date.unwrap_or(naive::MIN_DATE); + to_date = filter.to_date.unwrap_or(naive::MAX_DATE); + } + + activities.filter(move |activity| { + activity.start.date() >= from_date && activity.start.date() <= to_date + }) +} + +pub fn get_last_activity_by_end(file_content: &Vec) -> Option<&activity::Activity> { + get_activities(&file_content) + .filter(|activity| activity.is_stopped()) + .max_by_key(|activity| activity.end.unwrap_or(naive::MIN_DATE.and_hms(0, 0, 0))) +} + +pub fn get_last_activity_by_start(file_content: &Vec) -> Option<&activity::Activity> { + get_activities(&file_content).max_by_key(|activity| activity.start) +} \ No newline at end of file diff --git a/src/main.rs b/src/main.rs index 0003106..159eccd 100644 --- a/src/main.rs +++ b/src/main.rs @@ -170,7 +170,7 @@ fn run_subcommand(matches: &ArgMatches, file_name: &str) -> Result<()> { let time = get_time_argument_or_ignore(sub_m.value_of("time"), "-t/--time") .map(|t| Local::today().naive_local().and_time(t)); - bartib::start(file_name, project_name, activity_description, time) + bartib::manipulation_commands::start(file_name, project_name, activity_description, time) } ("continue", Some(sub_m)) => { let project_name = sub_m.value_of("project"); @@ -178,17 +178,17 @@ fn run_subcommand(matches: &ArgMatches, file_name: &str) -> Result<()> { let time = get_time_argument_or_ignore(sub_m.value_of("time"), "-t/--time") .map(|t| Local::today().naive_local().and_time(t)); - bartib::continue_last_activity(file_name, project_name, activity_description, time) + bartib::manipulation_commands::continue_last_activity(file_name, project_name, activity_description, time) } ("stop", Some(sub_m)) => { let time = get_time_argument_or_ignore(sub_m.value_of("time"), "-t/--time") .map(|t| Local::today().naive_local().and_time(t)); - bartib::stop(file_name, time) + bartib::manipulation_commands::stop(file_name, time) } - ("current", Some(_)) => bartib::list_running(file_name), + ("current", Some(_)) => bartib::list_commands::list_running(file_name), ("list", Some(sub_m)) => { - let mut filter = bartib::ActivityFilter { + let mut filter = bartib::list_commands::ActivityFilter { number_of_activities: get_number_argument_or_ignore( sub_m.value_of("number"), "-n/--number", @@ -207,13 +207,13 @@ fn run_subcommand(matches: &ArgMatches, file_name: &str) -> Result<()> { } let do_group_activities = !sub_m.is_present("no_grouping") && !filter.date.is_some(); - bartib::list(file_name, filter, do_group_activities) + bartib::list_commands::list(file_name, filter, do_group_activities) } - ("projects", Some(_)) => bartib::list_projects(file_name), - ("last", Some(_)) => bartib::display_last_activity(file_name), + ("projects", Some(_)) => bartib::list_commands::list_projects(file_name), + ("last", Some(_)) => bartib::list_commands::display_last_activity(file_name), ("edit", Some(sub_m)) => { let optional_editor_command = sub_m.value_of("editor"); - bartib::start_editor(file_name, optional_editor_command) + bartib::manipulation_commands::start_editor(file_name, optional_editor_command) } _ => bail!("Unknown command"), } diff --git a/src/manipulation_commands.rs b/src/manipulation_commands.rs new file mode 100644 index 0000000..3b522bf --- /dev/null +++ b/src/manipulation_commands.rs @@ -0,0 +1,124 @@ +use anyhow::{anyhow, bail, Context, Error, Result}; +use chrono::NaiveDateTime; +use std::process::Command; + +use crate::activity; +use crate::bartib_file; +use crate::conf; +use crate::format_util; +use crate::list_commands; + +// starts a new activity +pub fn start( + file_name: &str, + project_name: &str, + activity_description: &str, + time: Option, +) -> Result<()> { + let mut file_content: Vec = Vec::new(); + + if let Ok(mut previous_file_content) = bartib_file::get_file_content(file_name) { + // if we start a new activities programaticly, we stop all other activities first. + // However, we must not assume that there is always only one activity + // running as the user may have started activities manually + stop_all_running_activities(&mut previous_file_content, time); + + file_content.append(&mut previous_file_content); + } + + let activity = activity::Activity::start( + project_name.to_string(), + activity_description.to_string(), + time, + ); + + save_new_activity(file_name, &mut file_content, activity) +} + +fn save_new_activity( + file_name: &str, + file_content: &mut Vec, + activity: activity::Activity, +) -> Result<(), Error> { + println!( + "Started activity: \"{}\" ({}) at {}", + activity.description, + activity.project, + activity.start.format(conf::FORMAT_DATETIME) + ); + + file_content.push(bartib_file::Line::for_activity(activity)); + bartib_file::write_to_file(file_name, &file_content) + .context(format!("Could not write to file: {}", file_name)) +} + +// stops all currently running activities +pub fn stop(file_name: &str, time: Option) -> Result<()> { + let mut file_content = bartib_file::get_file_content(file_name)?; + stop_all_running_activities(&mut file_content, time); + bartib_file::write_to_file(file_name, &file_content) + .context(format!("Could not write to file: {}", file_name)) +} + + +// continue last activity +pub fn continue_last_activity( + file_name: &str, + project_name: Option<&str>, + activity_description: Option<&str>, + time: Option, +) -> Result<()> { + let mut file_content = bartib_file::get_file_content(file_name)?; + + let optional_last_activity = + list_commands::get_last_activity_by_start(&file_content).or(list_commands::get_last_activity_by_end(&file_content)); + + if let Some(last_activity) = optional_last_activity { + let new_activity = activity::Activity::start( + project_name.unwrap_or(&last_activity.project).to_string(), + activity_description + .unwrap_or(&last_activity.description) + .to_string(), + time, + ); + stop_all_running_activities(&mut file_content, time); + save_new_activity(file_name, &mut file_content, new_activity) + } else { + bail!("No activity has been started before.") + } +} + +pub fn start_editor(file_name: &str, optional_editor_command: Option<&str>) -> Result<()> { + let editor_command = optional_editor_command.context("editor command is missing")?; + let command = Command::new(editor_command).arg(file_name).spawn(); + + match command { + Ok(mut child) => { + child.wait().context("editor did not execute")?; + Ok(()) + } + Err(e) => Err(anyhow!(e)), + } +} + +fn stop_all_running_activities( + file_content: &mut [bartib_file::Line], + time: Option, +) { + for line in file_content { + if let Ok(activity) = &mut line.activity { + if !activity.is_stopped() { + activity.stop(time); + println!( + "Stopped activity: \"{}\" ({}) started at {} ({})", + activity.description, + activity.project, + activity.start.format(conf::FORMAT_DATETIME), + format_util::format_duration(&activity.get_duration()), + ); + + line.set_changed(); + } + } + } +} \ No newline at end of file