From 8bd6d977181c8e2f7d07a9d17949c265ec327e79 Mon Sep 17 00:00:00 2001 From: meinbaumm Date: Wed, 29 Mar 2023 23:18:25 +0700 Subject: [PATCH] issue-18: fix some typos --- README.md | 51 +++++---- src/conf.rs | 4 +- src/controller/list.rs | 42 ++++---- src/controller/mod.rs | 2 +- src/controller/report.rs | 9 +- src/data/activity.rs | 6 +- src/data/bartib_file.rs | 2 +- src/data/getter.rs | 2 +- src/data/mod.rs | 2 +- src/lib.rs | 2 +- src/main.rs | 112 +++++++++++-------- src/view/list.rs | 82 ++++++++++---- src/view/mod.rs | 2 +- src/view/report.rs | 155 +++++++++++++++++++-------- src/view/table.rs | 226 ++++++++++++++++++++++++++------------- 15 files changed, 464 insertions(+), 235 deletions(-) diff --git a/README.md b/README.md index 7e47697..3535f89 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # Bartib -![Illustration of the White Rabbit from Alice in Wonderland](misc/white-rabbit.png "Oh dear! Oh dear! I shall be too late") +![Illustration of the White Rabbit from Alice in Wonderland](misc/white-rabbit.png "Oh dear! Oh dear! I shall be too late") Bartib is an easy to use time tracking tool for the command line. It saves a log of all tracked activities as a plaintext file and allows you to create flexible reports. @@ -12,19 +12,27 @@ Bartib is an easy to use time tracking tool for the command line. It saves a log ## Contents -1. [Tutorial](#tutorial) -1. [How To ...](#how-to-) - 1. [How to install Bartib](#how-to-install-bartib) - 1. [How to build Bartib](#how-to-build-bartib) - 1. [How to define in which file to save the log of your activities](#how-to-define-in-which-file-to-save-the-log-of-your-activities) - 1. [How to edit or delete tracked activities](#how-to-edit-or-delete-tracked-activities) - 1. [How to activate auto completion](#how-to-activate-auto-completion) -1. [Command overview](#command-overview) - 1. [The essentials](#the-essentials) - 1. [Getting Help](#getting-help) - 1. [Tracking activities](#tracking-activities) - 1. [Reporting and listing activities](#reporting-and-listing-activities) - 1. [Doing other stuff](#doing-other-stuff) +- [Bartib](#bartib) + - [Contents](#contents) + - [Tutorial](#tutorial) + - [How To ...](#how-to-) + - [How to install Bartib](#how-to-install-bartib) + - [Download an executable](#download-an-executable) + - [With Cargo](#with-cargo) + - [From the AUR (Arch Linux)](#from-the-aur-arch-linux) + - [Via homebrew](#via-homebrew) + - [Via apk (Alpine Linux)](#via-apk-alpine-linux) + - [How to build Bartib](#how-to-build-bartib) + - [How to define in which file to save the log of your activities](#how-to-define-in-which-file-to-save-the-log-of-your-activities) + - [How to edit or delete tracked activities](#how-to-edit-or-delete-tracked-activities) + - [How to activate auto completion](#how-to-activate-auto-completion) + - [Command overview](#command-overview) + - [The essentials](#the-essentials) + - [Getting Help](#getting-help) + - [Tracking activities](#tracking-activities) + - [Reporting and listing activities](#reporting-and-listing-activities) + - [Edit activities](#edit-activities) + - [Doing other stuff](#doing-other-stuff) ## Tutorial @@ -68,7 +76,7 @@ Started activity: "More Urgent Task Y" (Just Another Project B) at 2021-10-29 10 See how Bartib just stops the running activity when another one starts? No need to stop it manually. -It is a productive morning. After _More Urgent Task Y_ Alice workes on other projects and other tasks, but now it is time for lunch and Alice lets Bartib list all the activities she has tracked today until now: +It is a productive morning. After _More Urgent Task Y_ Alice works on other projects and other tasks, but now it is time for lunch and Alice lets Bartib list all the activities she has tracked today until now: ```console alice@work: ~ $ bartib list --today @@ -136,7 +144,7 @@ Do you want to be as happy as Alice? Use Bartib! #### Download an executable -Simply download a suitable executable from https://github.com/nikolassv/bartib/releases and copy it in some directory that is listed in your `PATH` (e.g. ~/bin). +Simply download a suitable executable from and copy it in some directory that is listed in your `PATH` (e.g. ~/bin). #### With Cargo @@ -192,7 +200,7 @@ If the specified log file does not exist yet Bartib creates it. ### How to edit or delete tracked activities -Just open your activitiy log in your favorite text editor to edit or delete former activities. You may even add new activities manually in this file. The format is self explanatory. +Just open your activity log in your favorite text editor to edit or delete former activities. You may even add new activities manually in this file. The format is self explanatory. Bartib even offers the `bartib edit` command which opens the log in the editor defined by your `EDITOR` environment variable. If you are unsure whether your edits are readable by bartib, use the `bartib check` command. It will inform you about any parsing errors. @@ -205,6 +213,7 @@ Bartib offers a simple auto completion for project names. This saves you from ty All these commands require that you have set the `BARTIB_FILE` environment variable to the file path of your activity log. Otherwise they require an additional `-f/--file` parameter between `bartib` and the subcommand (see above: [How to define in which file to save the log of your activities](#how-to-define-in-which-file-to-save-the-log-of-your-activities)). ### The essentials + ```bash bartib -h # get help bartib start -p "name of the project" -d "description of the activity" # start a new activity @@ -214,12 +223,14 @@ bartib report --today # create a report for today ``` ### Getting Help + ```bash bartib -h # Print a concise help bartib start -h # Print a help for any subcommand ``` -### Tracking activities +### Tracking activities + ```bash bartib start -p "The name of the associated project" -d "A description of the activity" # Start a new activity with a short description and an associated project bartib start -p "The name of the associated project" -d "A description of the activity" -t 13:45 # Start a new activity at a given time @@ -230,7 +241,7 @@ bartib stop -t 14:00 # Stop the currently running activity at a given time bartib last # Print a list of the ten most recently used projects and descriptions bartib last -n 25 # Prints a list of recently used projects and descriptions with more entries -# All numbers used with the following commands refer to the indizees in the list created with `bartib last` +# All numbers used with the following commands refer to the indexes in the list created with `bartib last` bartib continue 5 # Start an activity with a recently used project and description bartib continue # Continue the latest activity bartib continue 3 -d "Another description" # Continue activity number 3 but overwrite the description @@ -254,7 +265,7 @@ bartib report --project "The most exciting project" # create a report for a g bartib list # list all activities grouped by day bartib list --no_grouping # list all activities but do not group them by day -bartib list --today # list todays' activites +bartib list --today # list todays' activities bartib list --yesterday # list yesterdays' activities bartib list --current_week # list activities of the current week (since monday) bartib list --last_week # list activities of the last week diff --git a/src/conf.rs b/src/conf.rs index c76e6b0..d3a55ca 100644 --- a/src/conf.rs +++ b/src/conf.rs @@ -1,5 +1,5 @@ pub static FORMAT_DATETIME: &str = "%F %R"; pub static FORMAT_TIME: &str = "%R"; pub static FORMAT_DATE: &str = "%F"; -pub static DEFAULT_WIDTH : usize = usize::MAX; -pub static REPORT_INDENTATION : usize = 4; +pub static DEFAULT_WIDTH: usize = usize::MAX; +pub static REPORT_INDENTATION: usize = 4; diff --git a/src/controller/list.rs b/src/controller/list.rs index c29ce01..ee33147 100644 --- a/src/controller/list.rs +++ b/src/controller/list.rs @@ -8,7 +8,7 @@ use crate::data::bartib_file; use crate::data::getter; use crate::view::list; -// lists all currently runninng activities. +// lists all currently running activities. pub fn list_running(file_name: &str) -> Result<()> { let file_content = bartib_file::get_file_content(file_name)?; let running_activities = getter::get_running_activities(&file_content); @@ -52,19 +52,17 @@ pub fn list( // checks the file content for sanity pub fn sanity_check(file_name: &str) -> Result<()> { let file_content = bartib_file::get_file_content(file_name)?; - let mut lines_with_activities : Vec<(Option, Activity)> = file_content + let mut lines_with_activities: Vec<(Option, Activity)> = file_content .into_iter() - .filter_map(|line| { - match line.activity { - Ok(a) => Some((line.line_number, a)), - Err(_) => None - } + .filter_map(|line| match line.activity { + Ok(a) => Some((line.line_number, a)), + Err(_) => None, }) .collect(); lines_with_activities.sort_unstable_by_key(|(_, activity)| activity.start); - let mut has_finding : bool = false; - let mut last_end : Option = None; + let mut has_finding: bool = false; + let mut last_end: Option = None; for (line_number, activity) in lines_with_activities { has_finding = !check_sanity(last_end, &activity, line_number) || has_finding; @@ -87,7 +85,11 @@ pub fn sanity_check(file_name: &str) -> Result<()> { Ok(()) } -fn check_sanity(last_end: Option, activity: &Activity, line_number: Option) -> bool { +fn check_sanity( + last_end: Option, + activity: &Activity, + line_number: Option, +) -> bool { let mut sane = true; if activity.get_duration().num_milliseconds() < 0 { println!("Activity has negative duration"); @@ -96,7 +98,7 @@ fn check_sanity(last_end: Option, activity: &Activity, line_numbe if let Some(e) = last_end { if e > activity.start { - println!("Activity startet before another activity ended"); + println!("Activity started before another activity ended"); sane = false; } } @@ -109,17 +111,19 @@ fn check_sanity(last_end: Option, activity: &Activity, line_numbe } fn print_activity_with_line(activity: &Activity, line_number: usize) { - println!("{} (Started: {}, Ended: {}, Line: {})\n", - activity.description, - activity.start.format(conf::FORMAT_DATETIME), - activity.end - .map(|end| end.format(conf::FORMAT_DATETIME).to_string()) - .unwrap_or_else(|| String::from("--")), - line_number + println!( + "{} (Started: {}, Ended: {}, Line: {})\n", + activity.description, + activity.start.format(conf::FORMAT_DATETIME), + activity + .end + .map(|end| end.format(conf::FORMAT_DATETIME).to_string()) + .unwrap_or_else(|| String::from("--")), + line_number ) } -// prints all errors that occured when reading the bartib file +// prints all errors that occurred when reading the bartib file pub fn check(file_name: &str) -> Result<()> { let file_content = bartib_file::get_file_content(file_name)?; diff --git a/src/controller/mod.rs b/src/controller/mod.rs index 14204ab..e5572b6 100644 --- a/src/controller/mod.rs +++ b/src/controller/mod.rs @@ -1,3 +1,3 @@ pub mod list; pub mod manipulation; -pub mod report; \ No newline at end of file +pub mod report; diff --git a/src/controller/report.rs b/src/controller/report.rs index 9b9840c..0e9e220 100644 --- a/src/controller/report.rs +++ b/src/controller/report.rs @@ -13,10 +13,13 @@ pub fn show_report(file_name: &str, filter: getter::ActivityFilter) -> Result<() filtered_activities.sort_by_key(|activity| activity.start); - - let first_element = filtered_activities.len().saturating_sub(filter.number_of_activities.unwrap_or(filtered_activities.len())); + let first_element = filtered_activities.len().saturating_sub( + filter + .number_of_activities + .unwrap_or(filtered_activities.len()), + ); report::show_activities(&filtered_activities[first_element..filtered_activities.len()]); Ok(()) -} \ No newline at end of file +} diff --git a/src/data/activity.rs b/src/data/activity.rs index 8dea999..7c06b4a 100644 --- a/src/data/activity.rs +++ b/src/data/activity.rs @@ -19,7 +19,7 @@ pub enum ActivityError { #[error("could not parse date or time of activity")] DateTimeParseError, #[error("could not parse activity")] - GeneralParseError + GeneralParseError, } impl Activity { @@ -83,7 +83,7 @@ impl FromStr for Activity { type Err = ActivityError; fn from_str(s: &str) -> Result { - let parts: Vec = split_with_escaped_delimeter(s).collect(); + let parts: Vec = split_with_escaped_delimiter(s).collect(); if parts.len() < 2 { return Err(ActivityError::GeneralParseError); @@ -160,7 +160,7 @@ impl Iterator for StringSplitter<'_> { } } -fn split_with_escaped_delimeter(s: &str) -> StringSplitter { +fn split_with_escaped_delimiter(s: &str) -> StringSplitter { StringSplitter { chars: s.chars() } } diff --git a/src/data/bartib_file.rs b/src/data/bartib_file.rs index 9f9110d..853a2cf 100644 --- a/src/data/bartib_file.rs +++ b/src/data/bartib_file.rs @@ -16,7 +16,7 @@ pub enum LineStatus { #[derive(Debug)] pub struct Line { // the plaintext of the line as it has been read from the file - // we save this to be able write untouched lines back to file without chaning them + // we save this to be able write untouched lines back to file without changing them pub plaintext: Option, // the line number pub line_number: Option, diff --git a/src/data/getter.rs b/src/data/getter.rs index 15ca989..51c1cd0 100644 --- a/src/data/getter.rs +++ b/src/data/getter.rs @@ -141,7 +141,7 @@ mod tests { } #[test] - fn get_descriptions_and_projects_test_restarted_activitiy() { + fn get_descriptions_and_projects_test_restarted_activity() { let a1 = activity::Activity::start("p1".to_string(), "d1".to_string(), None); let a2 = activity::Activity::start("p2".to_string(), "d1".to_string(), None); let a3 = activity::Activity::start("p1".to_string(), "d1".to_string(), None); diff --git a/src/data/mod.rs b/src/data/mod.rs index ddb4629..287e742 100644 --- a/src/data/mod.rs +++ b/src/data/mod.rs @@ -1,3 +1,3 @@ pub mod activity; pub mod bartib_file; -pub mod getter; \ No newline at end of file +pub mod getter; diff --git a/src/lib.rs b/src/lib.rs index 1293f5f..389db34 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -2,4 +2,4 @@ pub mod conf; pub mod controller; pub mod data; -mod view; \ No newline at end of file +mod view; diff --git a/src/main.rs b/src/main.rs index f6a53a5..b968754 100644 --- a/src/main.rs +++ b/src/main.rs @@ -2,9 +2,9 @@ use anyhow::{bail, Context, Result}; use chrono::{Datelike, Duration, Local, NaiveDate, NaiveTime}; use clap::{App, AppSettings, Arg, ArgMatches, SubCommand}; +use bartib::data::getter::ActivityFilter; #[cfg(windows)] use nu_ansi_term::enable_ansi_support; -use bartib::data::getter::ActivityFilter; fn main() -> Result<()> { #[cfg(windows)] @@ -65,7 +65,14 @@ fn main() -> Result<()> { .long("last_week") .help("show activities of the last week") .required(false) - .conflicts_with_all(&["from_date", "to_date", "date", "today", "yesterday", "current_week"]) + .conflicts_with_all(&[ + "from_date", + "to_date", + "date", + "today", + "yesterday", + "current_week", + ]) .takes_value(false); let arg_description = Arg::with_name("description") @@ -99,16 +106,8 @@ fn main() -> Result<()> { .subcommand( SubCommand::with_name("start") .about("starts a new activity") - .arg( - arg_project - .clone() - .required(true) - ) - .arg( - arg_description - .clone() - .required(true) - ) + .arg(arg_project.clone().required(true)) + .arg(arg_description.clone().required(true)) .arg(&arg_time), ) .subcommand( @@ -122,7 +121,7 @@ fn main() -> Result<()> { .help("the number of the activity to continue (see subcommand `last`)") .required(false) .takes_value(true) - .default_value("0") + .default_value("0"), ) .arg(&arg_time), ) @@ -131,7 +130,7 @@ fn main() -> Result<()> { .about("changes the current activity") .arg(&arg_description) .arg(&arg_project) - .arg(&arg_time) + .arg(&arg_time), ) .subcommand( SubCommand::with_name("stop") @@ -139,12 +138,10 @@ fn main() -> Result<()> { .arg(&arg_time), ) .subcommand( - SubCommand::with_name("cancel") - .about("cancels all currently running activities") + SubCommand::with_name("cancel").about("cancels all currently running activities"), ) .subcommand( - SubCommand::with_name("current") - .about("lists all currently running activities") + SubCommand::with_name("current").about("lists all currently running activities"), ) .subcommand( SubCommand::with_name("list") @@ -163,7 +160,7 @@ fn main() -> Result<()> { .value_name("PROJECT") .help("do list activities for this project only") .takes_value(true) - .required(false) + .required(false), ) .arg( Arg::with_name("no_grouping") @@ -197,9 +194,9 @@ fn main() -> Result<()> { .value_name("PROJECT") .help("do report activities for this project only") .takes_value(true) - .required(false) - ) - ) + .required(false), + ), + ) .subcommand( SubCommand::with_name("last") .about("displays the descriptions and projects of recent activities") @@ -211,8 +208,8 @@ fn main() -> Result<()> { .help("maximum number of lines to display") .required(false) .takes_value(true) - .default_value("10") - ) + .default_value("10"), + ), ) .subcommand( SubCommand::with_name("projects") @@ -223,8 +220,8 @@ fn main() -> Result<()> { .long("current") .help("prints currently running projects only") .takes_value(false) - .required(false) - ) + .required(false), + ), ) .subcommand( SubCommand::with_name("edit") @@ -233,7 +230,7 @@ fn main() -> Result<()> { Arg::with_name("editor") .short("e") .value_name("editor") - .help("the command to start your prefered text editor") + .help("the command to start your preferred text editor") .env("EDITOR") .takes_value(true), ), @@ -256,7 +253,12 @@ 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::now().date_naive().and_time(t)); - bartib::controller::manipulation::start(file_name, project_name, activity_description, time) + bartib::controller::manipulation::start( + file_name, + project_name, + activity_description, + time, + ) } ("change", Some(sub_m)) => { let project_name = sub_m.value_of("project"); @@ -264,19 +266,28 @@ 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::now().date_naive().and_time(t)); - bartib::controller::manipulation::change(file_name, project_name, activity_description, time) + bartib::controller::manipulation::change( + file_name, + project_name, + activity_description, + time, + ) } ("continue", Some(sub_m)) => { let project_name = sub_m.value_of("project"); let activity_description = sub_m.value_of("description"); let time = get_time_argument_or_ignore(sub_m.value_of("time"), "-t/--time") .map(|t| Local::now().date_naive().and_time(t)); - let number = get_number_argument_or_ignore( - sub_m.value_of("number"), - "-n/--number", - ).unwrap_or(0); + let number = + get_number_argument_or_ignore(sub_m.value_of("number"), "-n/--number").unwrap_or(0); - bartib::controller::manipulation::continue_last_activity(file_name, project_name, activity_description, time, number) + bartib::controller::manipulation::continue_last_activity( + file_name, + project_name, + activity_description, + time, + number, + ) } ("stop", Some(sub_m)) => { let time = get_time_argument_or_ignore(sub_m.value_of("time"), "-t/--time") @@ -295,12 +306,12 @@ fn run_subcommand(matches: &ArgMatches, file_name: &str) -> Result<()> { let filter = create_filter_for_arguments(sub_m); bartib::controller::report::show_report(file_name, filter) } - ("projects", Some(sub_m)) => bartib::controller::list::list_projects(file_name, sub_m.is_present("current")), + ("projects", Some(sub_m)) => { + bartib::controller::list::list_projects(file_name, sub_m.is_present("current")) + } ("last", Some(sub_m)) => { - let number = get_number_argument_or_ignore( - sub_m.value_of("number"), - "-n/--number", - ).unwrap_or(10); + let number = get_number_argument_or_ignore(sub_m.value_of("number"), "-n/--number") + .unwrap_or(10); bartib::controller::list::list_last_activities(file_name, number) } ("edit", Some(sub_m)) => { @@ -322,7 +333,7 @@ fn create_filter_for_arguments<'a>(sub_m: &'a ArgMatches) -> ActivityFilter<'a> from_date: get_date_argument_or_ignore(sub_m.value_of("from_date"), "--from"), to_date: get_date_argument_or_ignore(sub_m.value_of("to_date"), "--to"), date: get_date_argument_or_ignore(sub_m.value_of("date"), "-d/--date"), - project: sub_m.value_of("project") + project: sub_m.value_of("project"), }; let today = Local::now().naive_local().date(); @@ -335,13 +346,26 @@ fn create_filter_for_arguments<'a>(sub_m: &'a ArgMatches) -> ActivityFilter<'a> } if sub_m.is_present("current_week") { - filter.from_date = Some(today - Duration::days(today.weekday().num_days_from_monday() as i64)); - filter.to_date = Some(today - Duration::days(today.weekday().num_days_from_monday() as i64) + Duration::days(6)); + filter.from_date = + Some(today - Duration::days(today.weekday().num_days_from_monday() as i64)); + filter.to_date = Some( + today - Duration::days(today.weekday().num_days_from_monday() as i64) + + Duration::days(6), + ); } if sub_m.is_present("last_week") { - filter.from_date = Some(today - Duration::days(today.weekday().num_days_from_monday() as i64) - Duration::weeks(1)); - filter.to_date = Some(today - Duration::days(today.weekday().num_days_from_monday() as i64) - Duration::weeks(1) + Duration::days(6) ) + filter.from_date = Some( + today + - Duration::days(today.weekday().num_days_from_monday() as i64) + - Duration::weeks(1), + ); + filter.to_date = Some( + today + - Duration::days(today.weekday().num_days_from_monday() as i64) + - Duration::weeks(1) + + Duration::days(6), + ) } filter diff --git a/src/view/list.rs b/src/view/list.rs index 10a96e3..864c4f9 100644 --- a/src/view/list.rs +++ b/src/view/list.rs @@ -2,8 +2,8 @@ use chrono::NaiveDate; use nu_ansi_term::Color; use std::collections::BTreeMap; -use crate::data::activity; use crate::conf; +use crate::data::activity; use crate::view::format_util; use crate::view::table; @@ -36,7 +36,7 @@ pub fn list_activities_grouped_by_date(activities: &[&activity::Activity]) { group_activities_by_date(activities) .iter() .map(|(date, activity_list)| { - create_activites_group(&format!("{}", date), activity_list.as_slice()) + create_activities_group(&format!("{}", date), activity_list.as_slice()) }) .for_each(|g| activity_table.add_group(g)); @@ -45,15 +45,30 @@ pub fn list_activities_grouped_by_date(activities: &[&activity::Activity]) { fn create_activity_table() -> table::Table { table::Table::new(vec![ - table::Column{ label: "Started".to_string(), wrap: table::Wrap::NoWrap }, - table::Column{ label: "Stopped".to_string(), wrap: table::Wrap::NoWrap }, - table::Column{ label: "Description".to_string(), wrap: table::Wrap::Wrap }, - table::Column{ label: "Project".to_string(), wrap: table::Wrap::Wrap }, - table::Column{ label: "Duration".to_string(), wrap: table::Wrap::NoWrap }, + table::Column { + label: "Started".to_string(), + wrap: table::Wrap::NoWrap, + }, + table::Column { + label: "Stopped".to_string(), + wrap: table::Wrap::NoWrap, + }, + table::Column { + label: "Description".to_string(), + wrap: table::Wrap::Wrap, + }, + table::Column { + label: "Project".to_string(), + wrap: table::Wrap::Wrap, + }, + table::Column { + label: "Duration".to_string(), + wrap: table::Wrap::NoWrap, + }, ]) } -fn create_activites_group(title: &str, activities: &[&activity::Activity]) -> table::Group { +fn create_activities_group(title: &str, activities: &[&activity::Activity]) -> table::Group { let rows = activities .iter() .map(|a| get_activity_table_row(a, false)) @@ -67,10 +82,22 @@ pub fn list_running_activities(activities: &[&activity::Activity]) { println!("No Activity is currently running"); } else { let mut activity_table = table::Table::new(vec![ - table::Column{ label: "Started At".to_string(), wrap: table::Wrap::NoWrap }, - table::Column{ label: "Description".to_string(), wrap: table::Wrap::Wrap }, - table::Column{ label: "Project".to_string(), wrap: table::Wrap::Wrap }, - table::Column{ label: "Duration".to_string(), wrap: table::Wrap::NoWrap }, + table::Column { + label: "Started At".to_string(), + wrap: table::Wrap::NoWrap, + }, + table::Column { + label: "Description".to_string(), + wrap: table::Wrap::Wrap, + }, + table::Column { + label: "Project".to_string(), + wrap: table::Wrap::Wrap, + }, + table::Column { + label: "Duration".to_string(), + wrap: table::Wrap::NoWrap, + }, ]); activities @@ -90,24 +117,35 @@ pub fn list_running_activities(activities: &[&activity::Activity]) { } // display a list of projects and descriptions with index number -pub fn list_descriptions_and_projects(descriptions_and_projects : &[(&String, &String)]) { +pub fn list_descriptions_and_projects(descriptions_and_projects: &[(&String, &String)]) { if descriptions_and_projects.is_empty() { println!("No activities have been tracked yet"); } else { let mut descriptions_and_projects_table = table::Table::new(vec![ - table::Column{ label: " # ".to_string(), wrap: table::Wrap::NoWrap }, - table::Column{ label: "Description".to_string(), wrap: table::Wrap::Wrap }, - table::Column{ label: "Project".to_string(), wrap: table::Wrap::Wrap }, + table::Column { + label: " # ".to_string(), + wrap: table::Wrap::NoWrap, + }, + table::Column { + label: "Description".to_string(), + wrap: table::Wrap::Wrap, + }, + table::Column { + label: "Project".to_string(), + wrap: table::Wrap::Wrap, + }, ]); - let mut i = descriptions_and_projects.len(); + let mut i = descriptions_and_projects.len(); for (description, project) in descriptions_and_projects { i = i.saturating_sub(1); - descriptions_and_projects_table.add_row( - table::Row::new(vec![format!("[{}]", i), description.to_string(), project.to_string()]) - ); + descriptions_and_projects_table.add_row(table::Row::new(vec![ + format!("[{}]", i), + description.to_string(), + project.to_string(), + ])); } println!("\n{}", descriptions_and_projects_table); @@ -118,7 +156,9 @@ pub fn list_descriptions_and_projects(descriptions_and_projects : &[(&String, &S // // the date of the end is shown when it is not the same date as the start fn get_activity_table_row(activity: &activity::Activity, with_start_dates: bool) -> table::Row { - let more_then_one_day = activity.end.map_or(false, |end| activity.start.date() != end.date()); + let more_then_one_day = activity + .end + .map_or(false, |end| activity.start.date() != end.date()); let display_end = activity.end.map_or_else( || "-".to_string(), diff --git a/src/view/mod.rs b/src/view/mod.rs index 7b9a460..a0f905e 100644 --- a/src/view/mod.rs +++ b/src/view/mod.rs @@ -1,4 +1,4 @@ pub mod format_util; pub mod list; pub mod report; -pub mod table; \ No newline at end of file +pub mod table; diff --git a/src/view/report.rs b/src/view/report.rs index 814dd82..7c3f020 100644 --- a/src/view/report.rs +++ b/src/view/report.rs @@ -15,14 +15,14 @@ type ProjectMap<'a> = BTreeMap<&'a str, (Vec<&'a activity::Activity>, Duration)> struct Report<'a> { project_map: ProjectMap<'a>, - total_duration: Duration + total_duration: Duration, } impl<'a> Report<'a> { fn new(activities: &'a [&'a activity::Activity]) -> Report<'a> { - Report { + Report { project_map: create_project_map(activities), - total_duration: sum_duration(activities) + total_duration: sum_duration(activities), } } } @@ -32,7 +32,8 @@ impl<'a> fmt::Display for Report<'a> { let mut longest_line = get_longest_line(&self.project_map).unwrap_or(0); let longest_duration_string = get_longest_duration_string(self).unwrap_or(0); - let terminal_width = term_size::dimensions_stdout().map(|d| d.0) + let terminal_width = term_size::dimensions_stdout() + .map(|d| d.0) .unwrap_or(conf::DEFAULT_WIDTH); if terminal_width < longest_line + longest_duration_string + 1 { @@ -42,7 +43,12 @@ impl<'a> fmt::Display for Report<'a> { for (project, (activities, duration)) in self.project_map.iter() { print_project_heading(f, project, duration, longest_line, longest_duration_string)?; - print_descriptions_with_durations(f, activities, longest_line, longest_duration_string)?; + print_descriptions_with_durations( + f, + activities, + longest_line, + longest_duration_string, + )?; writeln!(f)?; } @@ -61,9 +67,11 @@ fn create_project_map<'a>(activities: &'a [&'a activity::Activity]) -> ProjectMa let mut project_map: ProjectMap = BTreeMap::new(); activities.iter().for_each(|a| { - project_map.entry(&a.project) + project_map + .entry(&a.project) .or_insert_with(|| (Vec::<&'a activity::Activity>::new(), Duration::seconds(0))) - .0.push(a); + .0 + .push(a); }); for (_project, (activities, duration)) in project_map.iter_mut() { @@ -83,7 +91,13 @@ fn sum_duration(activities: &[&activity::Activity]) -> Duration { duration } -fn print_project_heading(f: &mut Formatter, project: &&str, duration: &Duration, longest_line: usize, duration_width: usize) -> fmt::Result { +fn print_project_heading( + f: &mut Formatter, + project: &&str, + duration: &Duration, + longest_line: usize, + duration_width: usize, +) -> fmt::Result { write!(f, "{}", Style::new().bold().prefix())?; let project_lines = textwrap::wrap(project, textwrap::Options::new(longest_line)); @@ -91,11 +105,13 @@ fn print_project_heading(f: &mut Formatter, project: &&str, duration: &Duration, if i + 1 < project_lines.len() { writeln!(f, "{}", line)?; } else { - write!(f, "{line:.duration_width$}", - line = line, - width = longest_line, - duration = format_util::format_duration(duration), - duration_width = duration_width + write!( + f, + "{line:.duration_width$}", + line = line, + width = longest_line, + duration = format_util::format_duration(duration), + duration_width = duration_width )?; } } @@ -103,7 +119,12 @@ fn print_project_heading(f: &mut Formatter, project: &&str, duration: &Duration, writeln!(f, "{}", Style::new().bold().infix(Style::new())) } -fn print_descriptions_with_durations<'a>(f: &mut fmt::Formatter<'_>, activities: &'a [&'a activity::Activity], line_width: usize, duration_width: usize) -> fmt::Result { +fn print_descriptions_with_durations<'a>( + f: &mut fmt::Formatter<'_>, + activities: &'a [&'a activity::Activity], + line_width: usize, + duration_width: usize, +) -> fmt::Result { let description_map = group_activities_by_description(activities); let indent_string = " ".repeat(conf::REPORT_INDENTATION); let wrapping_options = textwrap::Options::new(line_width) @@ -118,11 +139,13 @@ fn print_descriptions_with_durations<'a>(f: &mut fmt::Formatter<'_>, activities: if i + 1 < description_lines.len() { writeln!(f, "{}", line)?; } else { - writeln!(f, "{line:.duration_width$}", - line = line, - width = line_width, - duration = format_util::format_duration(&description_duration), - duration_width = duration_width + writeln!( + f, + "{line:.duration_width$}", + line = line, + width = line_width, + duration = format_util::format_duration(&description_duration), + duration_width = duration_width )?; } } @@ -131,23 +154,32 @@ fn print_descriptions_with_durations<'a>(f: &mut fmt::Formatter<'_>, activities: Ok(()) } -fn print_total_duration(f: &mut fmt::Formatter<'_>, total_duration: Duration, line_width: usize) -> fmt::Result { - writeln!(f, "{prefix}{total:., + total_duration: Duration, + line_width: usize, +) -> fmt::Result { + writeln!( + f, + "{prefix}{total:.(activities: &'a [&'a activity::Activity]) -> BTreeMap<&str, Vec<&'a activity::Activity>> { +fn group_activities_by_description<'a>( + activities: &'a [&'a activity::Activity], +) -> BTreeMap<&str, Vec<&'a activity::Activity>> { let mut activity_map: BTreeMap<&str, Vec<&'a activity::Activity>> = BTreeMap::new(); activities.iter().for_each(|a| { - activity_map.entry(&a.description) + activity_map + .entry(&a.description) .or_insert_with(Vec::<&'a activity::Activity>::new) .push(a); }); @@ -157,23 +189,34 @@ fn group_activities_by_description<'a>(activities: &'a [&'a activity::Activity]) fn get_longest_line(project_map: &ProjectMap) -> Option { let longest_project_line = project_map.keys().map(|p| p.chars().count()).max(); - let longest_activity_line = project_map.values().flat_map(|(a, _d)| a) - .map(|a| a.description.chars().count() + conf::REPORT_INDENTATION).max(); + let longest_activity_line = project_map + .values() + .flat_map(|(a, _d)| a) + .map(|a| a.description.chars().count() + conf::REPORT_INDENTATION) + .max(); get_max_option(longest_project_line, longest_activity_line) } fn get_longest_duration_string(report: &Report) -> Option { - let longest_project_duration = report.project_map.values() + let longest_project_duration = report + .project_map + .values() .map(|(_a, d)| format_util::format_duration(d)) .map(|s| s.chars().count()) .max(); - let longest_activity_duration = report.project_map.values().flat_map(|(a, _d)| a) + let longest_activity_duration = report + .project_map + .values() + .flat_map(|(a, _d)| a) .map(|a| format_util::format_duration(&a.get_duration())) .map(|s| s.chars().count()) .max(); - - let longest_single_duration = get_max_option(longest_project_duration, longest_activity_duration); - let length_of_total_duration = format_util::format_duration(&report.total_duration).chars().count(); + + let longest_single_duration = + get_max_option(longest_project_duration, longest_activity_duration); + let length_of_total_duration = format_util::format_duration(&report.total_duration) + .chars() + .count(); get_max_option(longest_single_duration, Some(length_of_total_duration)) } @@ -181,7 +224,11 @@ fn get_longest_duration_string(report: &Report) -> Option { fn get_max_option(o1: Option, o2: Option) -> Option { if let Some(s1) = o1 { if let Some(s2) = o2 { - if s1 > s2 { o1 } else { o2 } + if s1 > s2 { + o1 + } else { + o2 + } } else { o1 } @@ -201,12 +248,36 @@ mod tests { let mut activities: Vec<&activity::Activity> = Vec::new(); assert_eq!(sum_duration(&activities).num_seconds(), 0); - let mut a1 = activity::Activity::start("p1".to_string(), "d1".to_string(), Some(NaiveDateTime::parse_from_str("2021-09-01 15:00:00", "%Y-%m-%d %H:%M:%S").unwrap())); - a1.end = Some(NaiveDateTime::parse_from_str("2021-09-01 15:20:00", "%Y-%m-%d %H:%M:%S").unwrap()); // 20 * 60 = 1,200 seconds - let mut a2 = activity::Activity::start("p1".to_string(), "d2".to_string(), Some(NaiveDateTime::parse_from_str("2021-09-01 15:21:00", "%Y-%m-%d %H:%M:%S").unwrap())); - a2.end = Some(NaiveDateTime::parse_from_str("2021-09-01 16:21:00", "%Y-%m-%d %H:%M:%S").unwrap()); // 60 * 60 = 3,600 seconds - let mut a3 = activity::Activity::start("p2".to_string(), "d1".to_string(), Some(NaiveDateTime::parse_from_str("2021-09-01 16:21:00", "%Y-%m-%d %H:%M:%S").unwrap())); - a3.end = Some(NaiveDateTime::parse_from_str("2021-09-02 16:21:00", "%Y-%m-%d %H:%M:%S").unwrap()); // 24 * 60 * 60 = 86,400 seconds + let mut a1 = activity::Activity::start( + "p1".to_string(), + "d1".to_string(), + Some( + NaiveDateTime::parse_from_str("2021-09-01 15:00:00", "%Y-%m-%d %H:%M:%S").unwrap(), + ), + ); + a1.end = Some( + NaiveDateTime::parse_from_str("2021-09-01 15:20:00", "%Y-%m-%d %H:%M:%S").unwrap(), + ); // 20 * 60 = 1,200 seconds + let mut a2 = activity::Activity::start( + "p1".to_string(), + "d2".to_string(), + Some( + NaiveDateTime::parse_from_str("2021-09-01 15:21:00", "%Y-%m-%d %H:%M:%S").unwrap(), + ), + ); + a2.end = Some( + NaiveDateTime::parse_from_str("2021-09-01 16:21:00", "%Y-%m-%d %H:%M:%S").unwrap(), + ); // 60 * 60 = 3,600 seconds + let mut a3 = activity::Activity::start( + "p2".to_string(), + "d1".to_string(), + Some( + NaiveDateTime::parse_from_str("2021-09-01 16:21:00", "%Y-%m-%d %H:%M:%S").unwrap(), + ), + ); + a3.end = Some( + NaiveDateTime::parse_from_str("2021-09-02 16:21:00", "%Y-%m-%d %H:%M:%S").unwrap(), + ); // 24 * 60 * 60 = 86,400 seconds activities.push(&a1); activities.push(&a2); diff --git a/src/view/table.rs b/src/view/table.rs index d46054e..8eb85e1 100644 --- a/src/view/table.rs +++ b/src/view/table.rs @@ -1,7 +1,7 @@ +use std::borrow::Cow; use std::cmp; use std::fmt; use std::str; -use std::borrow::Cow; use nu_ansi_term::Style; use textwrap; @@ -10,12 +10,12 @@ use crate::conf; pub enum Wrap { Wrap, - NoWrap + NoWrap, } pub struct Column { pub label: String, - pub wrap: Wrap + pub wrap: Wrap, } pub struct Row { @@ -80,15 +80,18 @@ impl Table { /* Calculates the widths for the columns in a table - If the width of the longest line in the table exceeds the maximum width for the output - all the wrapable columns shrink to an acceptable size. - */ + If the width of the longest line in the table exceeds the maximum width for the output + all the wrapable columns shrink to an acceptable size. + */ fn get_column_width(&self, max_width: usize) -> Vec { let mut max_column_width = self.get_max_column_width(); - let width : usize = max_column_width.iter().sum(); - let columns_wrap : Vec<&Wrap> = self.columns.iter().map(|c| &c.wrap).collect(); - let mut number_of_wrappable_columns : usize = columns_wrap.iter().filter(|w| matches!(w, Wrap::Wrap)).count(); + let width: usize = max_column_width.iter().sum(); + let columns_wrap: Vec<&Wrap> = self.columns.iter().map(|c| &c.wrap).collect(); + let mut number_of_wrappable_columns: usize = columns_wrap + .iter() + .filter(|w| matches!(w, Wrap::Wrap)) + .count(); if width <= max_width || number_of_wrappable_columns == 0 { // we do not need to or can not wrap @@ -96,7 +99,9 @@ impl Table { } // the total width of the columns that we may not wrap - let unwrapable_width : usize = max_column_width.iter().zip(columns_wrap.iter()) + let unwrapable_width: usize = max_column_width + .iter() + .zip(columns_wrap.iter()) .filter(|(_, wrap)| matches!(wrap, Wrap::NoWrap)) .map(|(width, _)| width) .sum(); @@ -107,21 +112,37 @@ impl Table { } // we start with a width of 0 for all the wrapable columns - let mut column_width : Vec = max_column_width.iter().zip(columns_wrap.iter()) - .map(|(width, wrap)| if matches!(wrap, Wrap::NoWrap) { *width } else { 0 }) + let mut column_width: Vec = max_column_width + .iter() + .zip(columns_wrap.iter()) + .map(|(width, wrap)| { + if matches!(wrap, Wrap::NoWrap) { + *width + } else { + 0 + } + }) .collect(); // then we distribute the available width to the wrappable columns let mut available_width_for_wrappable_columns = max_width - unwrapable_width; while available_width_for_wrappable_columns > 0 && number_of_wrappable_columns > 0 { - // the maximum additional width we give each column in this round - let additional_width_for_each = cmp::max(1, available_width_for_wrappable_columns / number_of_wrappable_columns); - let width_data = column_width.iter_mut().zip(max_column_width.iter_mut()).zip(columns_wrap.iter()); + let additional_width_for_each = cmp::max( + 1, + available_width_for_wrappable_columns / number_of_wrappable_columns, + ); + let width_data = column_width + .iter_mut() + .zip(max_column_width.iter_mut()) + .zip(columns_wrap.iter()); for ((width, max_width), wrap) in width_data { - if available_width_for_wrappable_columns > 0 && matches!(wrap, Wrap::Wrap) && width < max_width { + if available_width_for_wrappable_columns > 0 + && matches!(wrap, Wrap::Wrap) + && width < max_width + { if max_width > &mut width.saturating_add(additional_width_for_each) { // While the maximum width for this column will not be reached, we add all // the additional width @@ -144,7 +165,9 @@ impl Table { } fn get_max_column_width(&self) -> Vec { - let mut max_column_width: Vec = self.columns.iter() + let mut max_column_width: Vec = self + .columns + .iter() .map(|c| c.label.chars().count()) .collect(); @@ -167,20 +190,15 @@ impl Table { impl fmt::Display for Table { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - - let terminal_width = term_size::dimensions_stdout().map(|d| d.0) + let terminal_width = term_size::dimensions_stdout() + .map(|d| d.0) .unwrap_or(conf::DEFAULT_WIDTH); let column_width = self.get_column_width(terminal_width - self.columns.len()); - let labels : Vec<&String> = self.columns.iter().map(|c| &c.label).collect(); + let labels: Vec<&String> = self.columns.iter().map(|c| &c.label).collect(); - write_cells( - f, - &labels, - &column_width, - Some(Style::new().underline()), - )?; + write_cells(f, &labels, &column_width, Some(Style::new().underline()))?; writeln!(f)?; for row in &self.rows { @@ -195,11 +213,7 @@ impl fmt::Display for Table { } } -fn write_group( - f: &mut fmt::Formatter<'_>, - group: &Group, - column_width: &[usize], -) -> fmt::Result { +fn write_group(f: &mut fmt::Formatter<'_>, group: &Group, column_width: &[usize]) -> fmt::Result { let empty_string = "".to_string(); let title = group.title.as_ref().unwrap_or(&empty_string); @@ -225,8 +239,7 @@ fn write_cells + std::fmt::Display>( column_width: &[usize], style: Option