use std::{io::Write, path::Path};
use chrono_humanize::{Accuracy, HumanTime, Tense};
use tabled::{Table, Tabled, settings::Style};
use crate::{prelude::TimeTracker, task::CompletedTask};
pub struct Report {
time_tracker: TimeTracker,
}
#[derive(Tabled)]
struct TableRow<'a> {
name: &'a str,
time: String,
percentage: String,
}
impl Report {
fn title(&self, depth: usize) -> String {
format!("{} Time Report for {}\n\n", "#".repeat(depth + 1), self.time_tracker.name())
}
fn description(&self) -> String {
let total_time = self.time_tracker.total_time();
format!(
"The total time spent on all tasks was {}.\n",
HumanTime::from(total_time).to_text_en(Accuracy::Rough, Tense::Present),
)
}
#[allow(clippy::cast_precision_loss)]
fn slowest_task_description(&self) -> Option<String> {
self.time_tracker.slowest_task().map(|task| {
let total_time = self.time_tracker.total_time();
format!(
"The slowest task was `{}` which took {} ({:.2}% of all time).",
task.name(),
HumanTime::from(task.time()).to_text_en(Accuracy::Precise, Tense::Present),
task.precise_percentage_over(total_time),
)
})
}
#[must_use]
pub fn slowest_task(&self) -> Option<&CompletedTask> {
self.time_tracker.slowest_task()
}
fn sub_reports(&self) -> impl Iterator<Item = Report> + '_ {
self.time_tracker.sub_trackers().iter().cloned().map(|time_tracker| Self { time_tracker })
}
#[allow(clippy::cast_precision_loss)]
fn text(&self, depth: usize) -> String {
let total_time = self.time_tracker.total_time();
let rows = self.time_tracker.tasks().map(|task| {
TableRow {
name: task.name(),
time: HumanTime::from(task.time()).to_text_en(Accuracy::Precise, Tense::Present),
percentage: format!("{:.2}%", task.precise_percentage_over(total_time)),
}
});
let mut table = Table::new(rows);
table.with(Style::markdown());
let mut report = String::new();
report.push_str(&self.title(depth));
report.push_str(&self.description());
if let Some(description) = self.slowest_task_description() {
report.push_str(&description);
}
report.push_str("\n\n");
report.push_str(&table.to_string());
for sub_report in self.sub_reports() {
report.push_str("\n\n");
report.push_str(&sub_report.text((depth + 1).min(6)));
}
report
}
pub fn write<S: AsRef<Path> + ?Sized>(&self, report_path: &S) -> std::io::Result<()> {
let mut file = std::fs::File::create(report_path)?;
writeln!(file, "{}", self.text(0))?;
Ok(())
}
}
impl From<TimeTracker> for Report {
fn from(time_tracker: TimeTracker) -> Self {
Self { time_tracker }
}
}