mirror of
https://github.com/nikolassv/bartib
synced 2024-12-11 04:32:27 +00:00
Merge branch 'dev/list-last-tasks-with-index'
This commit is contained in:
commit
e1f06a0270
6 changed files with 227 additions and 114 deletions
78
README.md
78
README.md
|
@ -24,7 +24,9 @@ Print help information:
|
|||
bartib -h
|
||||
```
|
||||
|
||||
### Start a new activity
|
||||
### Tracking activities
|
||||
|
||||
#### Start a new activity
|
||||
|
||||
Start a new activity with a short description and an associated project:
|
||||
|
||||
|
@ -40,7 +42,7 @@ The `-t/--time` option specifies at which time of the current day the new activi
|
|||
bartib start -p "The name of the associated project" -d "A description of the activity" -t 13:45
|
||||
```
|
||||
|
||||
### Stop a running activity
|
||||
#### Stop a running activity
|
||||
|
||||
Stops the currently running activity:
|
||||
|
||||
|
@ -50,17 +52,47 @@ bartib stop
|
|||
|
||||
The `-t/--time` option specifies at which time of the current day the activities stop.
|
||||
|
||||
### Continue the last activity
|
||||
#### Continue a recent activity
|
||||
|
||||
Instead of typing a project and a description for each new activity, it is possible to continue a recent activity. The `last` subcommand prints a list of recently
|
||||
used projects and descriptions. Per default it prints 10 lines. The `-n/--number` option may be used to print a longer list.
|
||||
|
||||
```
|
||||
bartib continue [-p "Another project"] [-d "Another description"]
|
||||
bartib last [-n 25]
|
||||
```
|
||||
|
||||
This continues the last activity. If an activity is currently tracked, bartib stops and restarts this activity. The associated project and description may be overwritten by setting a `-p / --project` or `-d / --description` option.
|
||||
In the list each activity will be asigned an index. This index can be used with the `continue` subcommand to restart the selected activitiy. The description
|
||||
and or the project of this activity can be overwritten for the new activity with the help of the `-d/--description` respectively the `-p/--project` argument.
|
||||
The `-t/--time` option may be used to specify at which time of the current day the activity should be restarted. If an activity is currently tracked, bartib
|
||||
stops it at the time the new activity starts.
|
||||
|
||||
The `-t/--time` option specifies at which time of the current day the activity (re-)starts.
|
||||
```
|
||||
bartib continue [3] [-p "Another project"] [-d "Another description"] [-t 8:45]
|
||||
```
|
||||
|
||||
### List activities
|
||||
The default value for the index parameter is `0` which always points to the most recently tracked activity. Therefore `bartib continue` without any parameters
|
||||
or options may be used to continue the most recently tracked activity.
|
||||
|
||||
### Reporting and listing activities
|
||||
|
||||
#### Create a report
|
||||
|
||||
This will create a report of how much time has been spent on which projects and activities:
|
||||
|
||||
```
|
||||
bartib report
|
||||
```
|
||||
|
||||
The `report` subcommand accepts most of the arguments that the `list` subcommand accepts:
|
||||
|
||||
```
|
||||
bartib report --today
|
||||
bartib report --yesterday
|
||||
bartib report --from 2021-09-01 --to 2021-09-05
|
||||
bartib report --date 2021-09-03
|
||||
```
|
||||
|
||||
#### List activities
|
||||
|
||||
All activities:
|
||||
|
||||
|
@ -93,30 +125,10 @@ bartib list --today
|
|||
bartib list --yesterday
|
||||
```
|
||||
|
||||
### List all currently running activities
|
||||
### Miscellaneous commands
|
||||
|
||||
```
|
||||
bartib current
|
||||
```
|
||||
|
||||
### Create a report
|
||||
|
||||
This will create a report of how much time has been spent on which projects and activities:
|
||||
|
||||
```
|
||||
bartib report
|
||||
```
|
||||
|
||||
The `report` subcommand accepts most of the arguments that the `list` subcommand accepts:
|
||||
|
||||
```
|
||||
bartib report --today
|
||||
bartib report --yesterday
|
||||
bartib report --from 2021-09-01 --to 2021-09-05
|
||||
bartib report --date 2021-09-03
|
||||
```
|
||||
|
||||
### Edit activities
|
||||
#### Edit activities
|
||||
|
||||
To change tracked activities, just open the file with your activities log in any text editor. To facilitate this, bartib offers the `edit` subcommand:
|
||||
|
||||
|
@ -130,13 +142,15 @@ This will open your log in the editor you have defined in your `EDITOR` environm
|
|||
bartib edit -e vim
|
||||
```
|
||||
|
||||
### Show last activity
|
||||
|
||||
#### List all currently running activities
|
||||
|
||||
```
|
||||
bartib last
|
||||
bartib current
|
||||
```
|
||||
|
||||
### List all projects
|
||||
|
||||
#### List all projects
|
||||
|
||||
This command lists all projects for which an activity has ever been logged:
|
||||
|
||||
|
|
|
@ -29,12 +29,12 @@ pub fn list(file_name: &str, filter: getter::ActivityFilter, do_group_activities
|
|||
|
||||
if do_group_activities {
|
||||
list::list_activities_grouped_by_date(
|
||||
&filtered_activities[first_element..filtered_activities.len()],
|
||||
&filtered_activities[first_element..],
|
||||
);
|
||||
} else {
|
||||
let with_start_dates = !filter.date.is_some();
|
||||
list::list_activities(
|
||||
&filtered_activities[first_element..filtered_activities.len()],
|
||||
&filtered_activities[first_element..],
|
||||
with_start_dates,
|
||||
);
|
||||
}
|
||||
|
@ -61,16 +61,13 @@ pub fn list_projects(file_name: &str) -> Result<()> {
|
|||
}
|
||||
|
||||
// return last finished activity
|
||||
pub fn display_last_activity(file_name: &str) -> Result<()> {
|
||||
pub fn list_last_activities(file_name: &str, number: usize) -> Result<()> {
|
||||
let file_content = bartib_file::get_file_content(file_name)?;
|
||||
|
||||
let last_activity = getter::get_last_activity_by_end(&file_content);
|
||||
let descriptions_and_projects : Vec<(&String, &String)> = getter::get_descriptions_and_projects(&file_content);
|
||||
let first_element = descriptions_and_projects.len().saturating_sub(number);
|
||||
|
||||
if let Some(activity) = last_activity {
|
||||
list::display_single_activity(&activity);
|
||||
} else {
|
||||
println!("No activity has been finished yet.")
|
||||
}
|
||||
list::list_descriptions_and_projects(&descriptions_and_projects[first_element..]);
|
||||
|
||||
Ok(())
|
||||
}
|
|
@ -67,24 +67,33 @@ pub fn continue_last_activity(
|
|||
project_name: Option<&str>,
|
||||
activity_description: Option<&str>,
|
||||
time: Option<NaiveDateTime>,
|
||||
number: usize
|
||||
) -> Result<()> {
|
||||
let mut file_content = bartib_file::get_file_content(file_name)?;
|
||||
|
||||
let optional_last_activity =
|
||||
getter::get_last_activity_by_start(&file_content).or(getter::get_last_activity_by_end(&file_content));
|
||||
let descriptions_and_projects : Vec<(&String, &String)> = getter::get_descriptions_and_projects(&file_content);
|
||||
|
||||
if let Some(last_activity) = optional_last_activity {
|
||||
if descriptions_and_projects.is_empty() {
|
||||
bail!("No activity has been started before.")
|
||||
}
|
||||
|
||||
if number > descriptions_and_projects.len() {
|
||||
bail!(format!("Less than {} distinct activities have been logged yet", number));
|
||||
}
|
||||
|
||||
let i = descriptions_and_projects.len().saturating_sub(number).saturating_sub(1);
|
||||
let optional_description_and_project = descriptions_and_projects.get(i);
|
||||
|
||||
if let Some((description, project)) = optional_description_and_project {
|
||||
let new_activity = activity::Activity::start(
|
||||
project_name.unwrap_or(&last_activity.project).to_string(),
|
||||
activity_description
|
||||
.unwrap_or(&last_activity.description)
|
||||
.to_string(),
|
||||
project_name.unwrap_or(project).to_string(),
|
||||
activity_description.unwrap_or(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.")
|
||||
bail!(format!("Less than {} distinct activities have been logged yet", number));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,7 +1,9 @@
|
|||
use std::collections::HashSet;
|
||||
use chrono::{naive, NaiveDate};
|
||||
|
||||
use crate::data::activity;
|
||||
use crate::data::bartib_file;
|
||||
use crate::data::activity::Activity;
|
||||
|
||||
pub struct ActivityFilter {
|
||||
pub number_of_activities: Option<usize>,
|
||||
|
@ -10,6 +12,38 @@ pub struct ActivityFilter {
|
|||
pub date: Option<NaiveDate>,
|
||||
}
|
||||
|
||||
pub fn get_descriptions_and_projects(file_content: &[bartib_file::Line]) -> Vec<(&String, &String)> {
|
||||
let mut activities : Vec<&activity::Activity> = get_activities(file_content).collect();
|
||||
get_descriptions_and_projects_from_activities(&mut activities)
|
||||
}
|
||||
|
||||
fn get_descriptions_and_projects_from_activities<'a>(activities: &mut [&'a Activity]) -> Vec<(&'a String, &'a String)> {
|
||||
activities.sort_by_key(|activity| activity.start);
|
||||
|
||||
/* each activity should be placed in the list in the descending order of when it had been
|
||||
started last. To achieve this we reverse the order of the activities before we extract the
|
||||
set of descriptions and activities. Afterwards we also reverse the list of descriptions and
|
||||
activities.
|
||||
|
||||
e.g. if tasks have been started in this order: a, b, c, a, c the list of descriptions and
|
||||
activities should have this order: b, a, c
|
||||
*/
|
||||
activities.reverse();
|
||||
|
||||
let mut known_descriptions_and_projects: HashSet<(&String, &String)> = HashSet::new();
|
||||
let mut descriptions_and_projects: Vec<(&String, &String)> = Vec::new();
|
||||
|
||||
for description_and_project in activities.iter().map(|a| (&a.description, &a.project)) {
|
||||
if !known_descriptions_and_projects.contains(&description_and_project) {
|
||||
known_descriptions_and_projects.insert(description_and_project);
|
||||
descriptions_and_projects.push(description_and_project);
|
||||
}
|
||||
}
|
||||
|
||||
descriptions_and_projects.reverse();
|
||||
descriptions_and_projects
|
||||
}
|
||||
|
||||
pub fn get_running_activities(file_content: &[bartib_file::Line]) -> Vec<&activity::Activity> {
|
||||
get_activities(file_content)
|
||||
.filter(|activity| !activity.is_stopped())
|
||||
|
@ -43,7 +77,7 @@ pub fn filter_activities<'a>(
|
|||
})
|
||||
}
|
||||
|
||||
pub fn get_last_activity_by_end(file_content: &Vec<bartib_file::Line>) -> Option<&activity::Activity> {
|
||||
pub fn get_last_activity_by_end(file_content: &[bartib_file::Line]) -> 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)))
|
||||
|
@ -52,3 +86,36 @@ pub fn get_last_activity_by_end(file_content: &Vec<bartib_file::Line>) -> Option
|
|||
pub fn get_last_activity_by_start(file_content: &Vec<bartib_file::Line>) -> Option<&activity::Activity> {
|
||||
get_activities(&file_content).max_by_key(|activity| activity.start)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn get_descriptions_and_projects_test_simple() {
|
||||
let a1 = activity::Activity::start("p1".to_string(), "d1".to_string(), None);
|
||||
let a2 = activity::Activity::start("p1".to_string(), "d1".to_string(), None);
|
||||
let a3 = activity::Activity::start("p2".to_string(), "d1".to_string(), None);
|
||||
let mut activities = vec![&a1, &a2, &a3];
|
||||
|
||||
let descriptions_and_projects = get_descriptions_and_projects_from_activities(&mut activities);
|
||||
|
||||
assert_eq!(descriptions_and_projects.len(), 2);
|
||||
assert_eq!(*descriptions_and_projects.get(0).unwrap(), (&"d1".to_string(), &"p1".to_string()));
|
||||
assert_eq!(*descriptions_and_projects.get(1).unwrap(), (&"d1".to_string(), &"p2".to_string()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn get_descriptions_and_projects_test_restarted_activitiy() {
|
||||
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);
|
||||
let mut activities = vec![&a1, &a2, &a3];
|
||||
|
||||
let descriptions_and_projects = get_descriptions_and_projects_from_activities(&mut activities);
|
||||
|
||||
assert_eq!(descriptions_and_projects.len(), 2);
|
||||
assert_eq!(*descriptions_and_projects.get(0).unwrap(), (&"d1".to_string(), &"p2".to_string()));
|
||||
assert_eq!(*descriptions_and_projects.get(1).unwrap(), (&"d1".to_string(), &"p1".to_string()));
|
||||
}
|
||||
}
|
||||
|
|
121
src/main.rs
121
src/main.rs
|
@ -53,6 +53,18 @@ fn main() -> Result<()> {
|
|||
.conflicts_with_all(&["from_date", "to_date", "date", "today"])
|
||||
.takes_value(false);
|
||||
|
||||
let arg_description = Arg::with_name("description")
|
||||
.short("d")
|
||||
.value_name("DESCRIPTION")
|
||||
.help("the description of the new activity")
|
||||
.takes_value(true);
|
||||
|
||||
let arg_project = Arg::with_name("project")
|
||||
.short("p")
|
||||
.value_name("PROJECT")
|
||||
.help("the project to which the new activity belongs")
|
||||
.takes_value(true);
|
||||
|
||||
let matches = App::new("bartib")
|
||||
.version("0.1")
|
||||
.author("Nikolas Schmidt-Voigt <nikolas.schmidt-voigt@posteo.de>")
|
||||
|
@ -70,40 +82,22 @@ fn main() -> Result<()> {
|
|||
.subcommand(
|
||||
SubCommand::with_name("start")
|
||||
.about("starts a new activity")
|
||||
.arg(
|
||||
Arg::with_name("project")
|
||||
.short("p")
|
||||
.value_name("PROJECT")
|
||||
.help("the project to which the new activity belong")
|
||||
.required(true)
|
||||
.takes_value(true),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("description")
|
||||
.short("d")
|
||||
.value_name("DESCRIPTION")
|
||||
.help("a description of the new activity")
|
||||
.required(true)
|
||||
.takes_value(true),
|
||||
)
|
||||
.arg(&arg_description)
|
||||
.arg(&arg_project)
|
||||
.arg(&arg_time),
|
||||
)
|
||||
.subcommand(
|
||||
SubCommand::with_name("continue")
|
||||
.about("continues the last activity")
|
||||
.about("continues a previous activity")
|
||||
.arg(&arg_description)
|
||||
.arg(&arg_project)
|
||||
.arg(
|
||||
Arg::with_name("project")
|
||||
.short("p")
|
||||
.value_name("PROJECT")
|
||||
.help("the project to which the new activity belong")
|
||||
.takes_value(true),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("description")
|
||||
.short("d")
|
||||
.value_name("DESCRIPTION")
|
||||
.help("a description of the new activity")
|
||||
.takes_value(true),
|
||||
Arg::with_name("number")
|
||||
.value_name("NUMBER")
|
||||
.help("the number of the activity to continue (see subcommand `last`)")
|
||||
.required(false)
|
||||
.takes_value(true)
|
||||
.default_value("0")
|
||||
)
|
||||
.arg(&arg_time),
|
||||
)
|
||||
|
@ -147,7 +141,20 @@ fn main() -> Result<()> {
|
|||
.arg(&arg_today)
|
||||
.arg(&arg_yesterday),
|
||||
)
|
||||
.subcommand(SubCommand::with_name("last").about("displays last finished acitivity"))
|
||||
.subcommand(
|
||||
SubCommand::with_name("last")
|
||||
.about("displays the descriptions and projects of recent activities")
|
||||
.arg(
|
||||
Arg::with_name("number")
|
||||
.short("n")
|
||||
.long("number")
|
||||
.value_name("NUMBER")
|
||||
.help("maximum number of lines to display")
|
||||
.required(false)
|
||||
.takes_value(true)
|
||||
.default_value("10")
|
||||
)
|
||||
)
|
||||
.subcommand(SubCommand::with_name("projects").about("list all projects"))
|
||||
.subcommand(
|
||||
SubCommand::with_name("edit")
|
||||
|
@ -184,8 +191,12 @@ fn run_subcommand(matches: &ArgMatches, file_name: &str) -> Result<()> {
|
|||
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::today().naive_local().and_time(t));
|
||||
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)
|
||||
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")
|
||||
|
@ -195,25 +206,6 @@ fn run_subcommand(matches: &ArgMatches, file_name: &str) -> Result<()> {
|
|||
}
|
||||
("current", Some(_)) => bartib::controller::list::list_running(file_name),
|
||||
("list", Some(sub_m)) => {
|
||||
let mut filter = bartib::data::getter::ActivityFilter {
|
||||
number_of_activities: None,
|
||||
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"),
|
||||
};
|
||||
|
||||
if sub_m.is_present("today") {
|
||||
filter.date = Some(Local::now().naive_local().date());
|
||||
}
|
||||
|
||||
if sub_m.is_present("yesterday") {
|
||||
filter.date = Some(Local::now().naive_local().date() - Duration::days(1));
|
||||
}
|
||||
|
||||
let do_group_activities = !sub_m.is_present("no_grouping") && !filter.date.is_some();
|
||||
bartib::controller::list::list(file_name, filter, do_group_activities)
|
||||
}
|
||||
("report", Some(sub_m)) => {
|
||||
let mut filter = bartib::data::getter::ActivityFilter {
|
||||
number_of_activities: get_number_argument_or_ignore(
|
||||
sub_m.value_of("number"),
|
||||
|
@ -232,10 +224,35 @@ fn run_subcommand(matches: &ArgMatches, file_name: &str) -> Result<()> {
|
|||
filter.date = Some(Local::now().naive_local().date() - Duration::days(1));
|
||||
}
|
||||
|
||||
let do_group_activities = !sub_m.is_present("no_grouping") && !filter.date.is_some();
|
||||
bartib::controller::list::list(file_name, filter, do_group_activities)
|
||||
}
|
||||
("report", Some(sub_m)) => {
|
||||
let mut filter = bartib::data::getter::ActivityFilter {
|
||||
number_of_activities: None,
|
||||
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"),
|
||||
};
|
||||
|
||||
if sub_m.is_present("today") {
|
||||
filter.date = Some(Local::now().naive_local().date());
|
||||
}
|
||||
|
||||
if sub_m.is_present("yesterday") {
|
||||
filter.date = Some(Local::now().naive_local().date() - Duration::days(1));
|
||||
}
|
||||
|
||||
bartib::controller::report::show_report(file_name, filter)
|
||||
}
|
||||
("projects", Some(_)) => bartib::controller::list::list_projects(file_name),
|
||||
("last", Some(_)) => bartib::controller::list::display_last_activity(file_name),
|
||||
("projects", Some(_)) => bartib::controller::list::list_projects(file_name),
|
||||
("last", Some(sub_m)) => {
|
||||
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)) => {
|
||||
let optional_editor_command = sub_m.value_of("editor");
|
||||
bartib::controller::manipulation::start_editor(file_name, optional_editor_command)
|
||||
|
|
|
@ -91,20 +91,29 @@ pub fn list_running_activities(running_activities: &[&activity::Activity]) {
|
|||
}
|
||||
}
|
||||
|
||||
// displays a single activity
|
||||
pub fn display_single_activity(activity: &activity::Activity) {
|
||||
println!("Begin: {}", activity.start.format(conf::FORMAT_DATETIME));
|
||||
// display a list of projects and descriptions with index number
|
||||
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![
|
||||
" # ".to_string(),
|
||||
"Description".to_string(),
|
||||
"Project".to_string()
|
||||
]);
|
||||
|
||||
if let Some(end) = activity.end {
|
||||
println!("End: {}", end.format(conf::FORMAT_DATETIME));
|
||||
println!(
|
||||
"Duration: {}",
|
||||
format_util::format_duration(&activity.get_duration())
|
||||
);
|
||||
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()])
|
||||
);
|
||||
}
|
||||
|
||||
println!("\n{}", descriptions_and_projects_table);
|
||||
}
|
||||
|
||||
println!("Project: {}", activity.project);
|
||||
println!("Description: {}", activity.description);
|
||||
}
|
||||
|
||||
// create a row for a activity
|
||||
|
|
Loading…
Reference in a new issue