Skip to content

Commit

Permalink
Merge pull request #47 from PicoJr/dev
Browse files Browse the repository at this point in the history
Prepare release 2.1.0
  • Loading branch information
PicoJr authored Dec 8, 2020
2 parents d9dc0ec + 04e280c commit d301240
Show file tree
Hide file tree
Showing 9 changed files with 154 additions and 19 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,10 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [2.1.0](https://crates.io/crates/rtw/2.1.0) Dec 8, 2020

* Add `--report` option to summary command.

## [2.0.1](https://crates.io/crates/rtw/2.0.1) Nov 3, 2020

* Fix CLI output for Windows 10 cf [#43](https://github.com/PicoJr/rtw/pull/43) thanks [ythri](https://github.com/ythri)
Expand Down
2 changes: 1 addition & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "rtw"
version = "2.0.1"
version = "2.1.0"
authors = ["PicoJr <[email protected]>"]
edition = "2018"
repository = "https://github.com/PicoJr/rtw"
Expand Down
17 changes: 17 additions & 0 deletions commands.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
* [Display finished activities summary for last week](#display-finished-activities-summary-for-last-week)
* [Display finished activities summary for range](#display-finished-activities-summary-for-range)
* [Display finished activities id](#display-finished-activities-id)
* [Display a report (sum same activities)](#display-a-report-sum-same-activities)
* [Display a timeline](#display-a-timeline)
* [For the day](#for-the-day)
* [For the week](#for-the-week)
Expand Down Expand Up @@ -208,6 +209,22 @@ Example output:

> id 0 = last finished activity
### Display a report (sum same activities)

Example:
```
rtw track 8 - 9 foo
rtw track 9 - 10 foo
rtw track 10 - 11 bar
rtw summary --report
```

Example output:
```
foo 02:00:00 (2 segments)
bar 01:00:00 (1 segments)
```

## Display a timeline

### For the day
Expand Down
18 changes: 15 additions & 3 deletions src/cli_helper.rs
Original file line number Diff line number Diff line change
Expand Up @@ -217,6 +217,12 @@ pub fn get_app() -> App<'static, 'static> {
.short("d")
.long("description")
.help("display activities descriptions"),
)
.arg(
Arg::with_name("report")
.short("r")
.long("report")
.help("sum up activities with same tag together"),
),
)
.subcommand(
Expand Down Expand Up @@ -363,8 +369,9 @@ pub fn parse_cancel_args(cancel_m: &ArgMatches) -> anyhow::Result<Option<Activit
pub fn parse_summary_args(
summary_m: &ArgMatches,
clock: &dyn Clock,
) -> anyhow::Result<((DateTimeW, DateTimeW), bool, bool)> {
) -> anyhow::Result<((DateTimeW, DateTimeW), bool, bool, bool)> {
let display_id = summary_m.is_present("id");
let report = summary_m.is_present("report");
let display_description = summary_m.is_present("description");
let values_arg = summary_m.values_of("tokens");
if let Some(values) = values_arg {
Expand All @@ -374,7 +381,12 @@ pub fn parse_summary_args(
Ok((range_start, range_end)) => {
let range_start = clock.date_time(range_start);
let range_end = clock.date_time(range_end);
Ok(((range_start, range_end), display_id, display_description))
Ok((
(range_start, range_end),
display_id,
display_description,
report,
))
}
Err(e) => Err(anyhow::anyhow!(e)),
};
Expand All @@ -390,7 +402,7 @@ pub fn parse_summary_args(
clock.today_range()
}
};
Ok((range, display_id, display_description))
Ok((range, display_id, display_description, report))
}

pub fn parse_timeline_args(
Expand Down
51 changes: 47 additions & 4 deletions src/rtw_cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,15 @@ use crate::rtw_config::RTWConfig;
use crate::rtw_core::activity::{Activity, OngoingActivity};
use crate::rtw_core::clock::Clock;
use crate::rtw_core::datetimew::DateTimeW;
use crate::rtw_core::durationw::DurationW;
use crate::rtw_core::service::ActivityService;
use crate::rtw_core::storage::Storage;
use crate::rtw_core::ActivityId;
use crate::rtw_core::{Description, Tags};
use crate::service::Service;
use crate::timeline::render_days;
use clap::ArgMatches;
use itertools::Itertools;

type ActivityWithId = (ActivityId, Activity);

Expand All @@ -24,7 +26,7 @@ pub enum RTWAction {
Start(DateTimeW, Tags, Option<Description>),
Track((DateTimeW, DateTimeW), Tags, Option<Description>),
Stop(DateTimeW, Option<ActivityId>),
Summary((DateTimeW, DateTimeW), bool, bool),
Summary((DateTimeW, DateTimeW), bool, bool, bool),
DumpICal((DateTimeW, DateTimeW)),
Continue,
Delete(ActivityId),
Expand All @@ -48,6 +50,27 @@ enum OptionalOrAmbiguousOrNotFound {
NotFound(ActivityId),
}

fn merge_same_tags(activities: &[ActivityWithId]) -> Vec<(ActivityId, Activity, DurationW, usize)> {
let uniques: Vec<ActivityWithId> = activities
.iter()
.cloned()
.unique_by(|(_i, activity)| activity.get_title())
.collect();
uniques
.iter()
.cloned()
.map(|(i, activity)| {
let same_tag = activities
.iter()
.filter(|(_i, other)| activity.get_title() == other.get_title());
let durations: Vec<DurationW> = same_tag.map(|(_i, a)| a.get_duration()).collect();
let segments = durations.len();
let duration = durations.into_iter().sum();
(i, activity, duration, segments)
})
.collect()
}

fn get_ongoing_activity<S: Storage>(
id_maybe: Option<ActivityId>,
service: &Service<S>,
Expand Down Expand Up @@ -90,12 +113,13 @@ where
Ok(RTWAction::Stop(abs_stop_time, stopped_id_maybe))
}
("summary", Some(sub_m)) => {
let ((range_start, range_end), display_id, display_description) =
let ((range_start, range_end), display_id, display_description, report) =
cli_helper::parse_summary_args(sub_m, clock)?;
Ok(RTWAction::Summary(
(range_start, range_end),
display_id,
display_description,
report,
))
}
("timeline", Some(sub_m)) => {
Expand Down Expand Up @@ -128,7 +152,7 @@ where
Ok(RTWAction::Cancel(cancelled_id_maybe))
}
("dump", Some(sub_m)) => {
let ((range_start, range_end), _display_id, _description) =
let ((range_start, range_end), _display_id, _description, _report) =
cli_helper::parse_summary_args(sub_m, clock)?;
Ok(RTWAction::DumpICal((range_start, range_end)))
}
Expand Down Expand Up @@ -191,7 +215,7 @@ where
}
}
}
RTWAction::Summary((range_start, range_end), display_id, display_description) => {
RTWAction::Summary((range_start, range_end), display_id, display_description, report) => {
let activities = service.get_finished_activities()?;
let activities: Vec<(ActivityId, Activity)> = activities
.iter()
Expand All @@ -207,6 +231,25 @@ where
.unwrap_or_default();
if activities.is_empty() {
println!("No filtered data found.");
} else if report {
let activities_report = merge_same_tags(activities.as_slice());
for (_id, finished, duration, segments) in activities_report {
let singular_or_plural = if segments <= 1 {
String::from("segment")
} else {
// segments > 1
String::from("segments")
};
let output = format!(
"{:width$} {} ({} {})",
finished.get_title(),
duration,
segments,
singular_or_plural,
width = longest_title
);
println!("{}", output)
}
} else {
for (id, finished) in activities {
let output = format!(
Expand Down
22 changes: 22 additions & 0 deletions src/rtw_core/durationw.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
use chrono::Duration;
use std::fmt;
use std::fmt::{Error, Formatter};
use std::iter::Sum;
use std::ops::Add;

/// Newtype on `chrono::Duration`
pub struct DurationW(chrono::Duration);
Expand All @@ -24,6 +26,12 @@ impl DurationW {
}
}

impl Default for DurationW {
fn default() -> Self {
DurationW::new(Duration::seconds(0))
}
}

impl From<Duration> for DurationW {
fn from(d: Duration) -> Self {
DurationW(d)
Expand All @@ -35,3 +43,17 @@ impl Into<Duration> for DurationW {
self.0
}
}

impl Add<DurationW> for DurationW {
type Output = DurationW;

fn add(self, rhs: DurationW) -> Self::Output {
DurationW::new(self.0 + rhs.0)
}
}

impl Sum for DurationW {
fn sum<I: Iterator<Item = DurationW>>(iter: I) -> Self {
iter.fold(DurationW::default(), Add::add)
}
}
20 changes: 10 additions & 10 deletions src/timeline.rs
Original file line number Diff line number Diff line change
Expand Up @@ -199,9 +199,9 @@ pub(crate) fn render_days(activities: &[Interval], colors: &[RGB]) -> anyhow::Re
.collect();
let day_month = day_activities
.first()
.and_then(|(_, a)| {
.map(|(_, a)| {
let start_time: DateTime<Local> = a.get_start_time().into();
Some(start_time.format("%d/%m").to_string())
start_time.format("%d/%m").to_string()
})
.unwrap_or_else(|| "??/??".to_string());
let total: DurationW = DurationW::from(day_total(day_activities.as_slice()));
Expand All @@ -213,22 +213,22 @@ pub(crate) fn render_days(activities: &[Interval], colors: &[RGB]) -> anyhow::Re
.with_length(available_length)
.with_boundaries((min_second, max_second))
.render()
.or_else(|e| match e {
TBLError::NoBoundaries => Err(anyhow!("failed to create timeline")),
TBLError::Intersection(left, right) => Err(anyhow!(
.map_err(|e| match e {
TBLError::NoBoundaries => anyhow!("failed to create timeline"),
TBLError::Intersection(left, right) => anyhow!(
"failed to create timeline: some activities are overlapping: {:?} intersects {:?}", left, right
)),
),
})?;
let legend = Renderer::new(day_activities.as_slice(), &bounds, &legend)
.with_renderer(&render)
.with_length(available_length)
.with_boundaries((min_second, max_second))
.render()
.or_else(|e| match e {
TBLError::NoBoundaries => Err(anyhow!("failed to create timeline")),
TBLError::Intersection(left, right) => Err(anyhow!(
.map_err(|e| match e {
TBLError::NoBoundaries => anyhow!("failed to create timeline"),
TBLError::Intersection(left, right) => anyhow!(
"failed to create timeline: some activities are overlapping: {:?} intersects {:?}", left, right
)),
),
})?;
let timeline = legend.iter().zip(data.iter());
for (i, (legend_timelines, data_timelines)) in timeline.enumerate() {
Expand Down
37 changes: 37 additions & 0 deletions tests/integration_test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,43 @@ mod tests {
.success();
}

#[test]
fn summary_something_with_report() {
let test_dir = tempdir().expect("could not create temp directory");
let test_dir_path = test_dir.path().to_str().unwrap();
let mut cmd = Command::cargo_bin("rtw").unwrap();
cmd.arg("-d")
.arg(test_dir_path)
.arg("track")
.arg("09:00")
.arg("-")
.arg("10:00")
.arg("foo")
.assert()
.success();
let mut cmd = Command::cargo_bin("rtw").unwrap();
cmd.arg("-d")
.arg(test_dir_path)
.arg("track")
.arg("10:00")
.arg("-")
.arg("11:00")
.arg("foo")
.assert()
.success();
let mut cmd = Command::cargo_bin("rtw").unwrap();
cmd.arg("-d")
.arg(test_dir_path)
.arg("summary")
.arg("--report")
.arg("08:00")
.arg("-")
.arg("12:00")
.assert()
.success()
.stdout(predicates::str::contains("foo 02:00:00 (2 segments)\n"));
}

#[test]
fn dump_ical_nothing() {
let test_dir = tempdir().expect("could not create temp directory");
Expand Down

0 comments on commit d301240

Please sign in to comment.