#[cfg(feature = "clap")]
use clap::Parser;
use getset::{Getters, MutGetters, Setters};
use pace_time::{
flags::{DateFlags, TimeFlags},
time_frame::PaceTimeFrame,
time_zone::PaceTimeZoneKind,
};
use serde_derive::Serialize;
use std::path::PathBuf;
use tracing::debug;
use typed_builder::TypedBuilder;
use crate::{
config::PaceConfig,
domain::{activity::ActivityKind, filter::FilterOptions, reflection::ReflectionsFormatKind},
error::{PaceResult, TemplatingErrorKind, UserMessage},
service::{activity_store::ActivityStore, activity_tracker::ActivityTracker},
storage::get_storage_from_config,
template::{PaceReflectionTemplate, TEMPLATES},
};
#[derive(Debug, Getters)]
#[getset(get = "pub")]
#[cfg_attr(feature = "clap", derive(Parser))]
#[cfg_attr(
feature = "clap", clap(group = clap::ArgGroup::new("tz").multiple(false).required(false)))]
pub struct ReflectCommandOptions {
#[cfg_attr(
feature = "clap",
clap(short, long, value_name = "Activity Kind", visible_alias = "kind")
)]
activity_kind: Option<ActivityKind>,
#[cfg_attr(
feature = "clap",
clap(short, long, value_name = "Category", visible_alias = "cat")
)]
category: Option<String>,
#[cfg_attr(
feature = "clap",
clap(short = 'i', long, value_name = "Case Sensitive")
)]
case_sensitive: bool,
#[cfg_attr(
feature = "clap",
clap(short, long, value_name = "Output Format", visible_alias = "format",)
)]
output_format: Option<ReflectionsFormatKind>,
#[cfg_attr(
feature = "clap",
clap(short, long, value_name = "Template File", visible_alias = "tpl")
)]
template_file: Option<PathBuf>,
#[cfg_attr(
feature = "clap",
clap(short, long, value_name = "Export File", visible_alias = "export")
)]
export_file: Option<PathBuf>,
#[cfg_attr(
feature = "clap",
clap(
rename_all = "kebab-case",
value_name = "Time Flags",
next_help_heading = "Flags for specifying time periods"
)
)]
time_flags: Option<TimeFlags>,
#[cfg_attr(
feature = "clap",
clap(
flatten,
next_help_heading = "Date flags for specifying custom date ranges or specific dates"
)
)]
date_flags: Option<DateFlags>,
#[cfg_attr(
feature = "clap",
clap(flatten, next_help_heading = "Expensive flags for detailed insights")
)]
expensive_flags: ExpensiveFlags,
}
impl ReflectCommandOptions {
#[tracing::instrument(skip(self))]
pub fn handle_reflect(&self, config: &PaceConfig) -> PaceResult<UserMessage> {
let Self {
export_file,
time_flags,
date_flags,
template_file,
output_format,
.. } = self;
let time_frame = PaceTimeFrame::try_from((
time_flags.as_ref(),
date_flags.as_ref(),
PaceTimeZoneKind::NotSet,
PaceTimeZoneKind::NotSet,
))?;
let activity_store = ActivityStore::with_storage(get_storage_from_config(config)?)?;
let activity_tracker = ActivityTracker::with_activity_store(activity_store);
debug!("Displaying reflection for time frame: {}", time_frame);
let Some(reflection) =
activity_tracker.generate_reflection(FilterOptions::from(self), time_frame)?
else {
return Ok(UserMessage::new(
"No activities found for the specified time frame",
));
};
match output_format {
Some(ReflectionsFormatKind::Console) | None => {
return Ok(UserMessage::new(reflection.to_string()));
}
Some(ReflectionsFormatKind::Json) => {
let json = serde_json::to_string_pretty(&reflection)?;
debug!("Reflection: {}", json);
if let Some(export_file) = export_file {
std::fs::write(export_file, json)?;
return Ok(UserMessage::new(format!(
"Reflection generated: {}",
export_file.display()
)));
}
return Ok(UserMessage::new(json));
}
Some(ReflectionsFormatKind::Template) => {
let context = PaceReflectionTemplate::from(reflection).into_context();
let templated = if template_file.is_none() {
TEMPLATES
.render("base.html", &context)
.map_err(TemplatingErrorKind::RenderingToTemplateFailed)?
} else {
let Some(user_tpl) = template_file.as_ref() else {
return Err(TemplatingErrorKind::TemplateFileNotSpecified.into());
};
let user_tpl = std::fs::read_to_string(user_tpl)
.map_err(TemplatingErrorKind::FailedToReadTemplateFile)?;
tera::Tera::one_off(&user_tpl, &context, true)
.map_err(TemplatingErrorKind::RenderingToTemplateFailed)?
};
debug!("Reflection: {}", templated);
if let Some(export_file) = export_file {
std::fs::write(export_file, templated)?;
return Ok(UserMessage::new(format!(
"Reflection generated: {}",
export_file.display()
)));
}
return Ok(UserMessage::new(templated));
}
Some(ReflectionsFormatKind::Csv) => unimplemented!("CSV format not yet supported"),
}
}
}
#[derive(
Debug, TypedBuilder, Serialize, Getters, Setters, MutGetters, Clone, Eq, PartialEq, Default,
)]
#[cfg_attr(feature = "clap", derive(Parser))]
pub struct ExpensiveFlags {
#[cfg_attr(feature = "clap", clap(long))]
detailed: bool,
#[cfg_attr(feature = "clap", clap(long))]
comparative: bool,
#[cfg_attr(feature = "clap", clap(long))]
recommendations: bool,
}