diff --git a/Cargo.lock b/Cargo.lock index c73a052..bac445a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -173,7 +173,7 @@ checksum = "e496a50fda8aacccc86d7529e2c1e0892dbd0f898a6b5645b5561b89c3210efa" [[package]] name = "day-reporter" -version = "1.4.1" +version = "2.0.0" dependencies = [ "anyhow", "chrono", diff --git a/Cargo.toml b/Cargo.toml index c5c19b9..549bcd1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "day-reporter" -version = "1.4.1" +version = "2.0.0" edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html diff --git a/src/main.rs b/src/main.rs index ea0a082..20bfa04 100644 --- a/src/main.rs +++ b/src/main.rs @@ -18,24 +18,21 @@ enum ListType { } impl ListType { - fn format_tasks(&self, tasks: Vec, tags: &Vec, sanitize_names: bool) -> String { + fn format_tasks(&self, tasks: Vec, tags: &Vec, sanitize_names: bool, resolution: Resolution) -> String { match self { ListType::Today => { - let task_report = MarkdownReporter.report(tasks, &ReportOptions { - resolution: Resolution::FullTask, + MarkdownReporter.report(tasks, &ReportOptions { + resolution, tags: tags.to_vec(), sanitize_names, - }); - // TODO: Use a cli flag to determine if the emoji should be included. - format!("{}\n\n{}", emoji::pick(3).join(" "), task_report) + }) }, ListType::Logbook => { - let task_report = MarkdownReporter.report(tasks, &ReportOptions { - resolution: Resolution::FullTask, + MarkdownReporter.report(tasks, &ReportOptions { + resolution, tags: tags.to_vec(), sanitize_names, - }); - format!("Stopping now\n\n{}", task_report) + }) }, } } @@ -63,20 +60,47 @@ struct CliArgs { /// Select the type of report to generate #[arg(short, long, default_value_t = ListType::default())] #[clap(value_enum)] - report: ListType, + list: ListType, /// By default, any @ style tags will be sanitized in the output to avoid @-mentions in /// Slack. This is done by replacing vowel characters with unicode lookalikes. If this /// flag is set then the names will be passed through unsanitized. #[arg(long, default_value_t = false)] no_sanitize: bool, + + /// An ISO date string for when to filter tasks from. + /// Defaults to midnight this morning if unset. Not used for the today list + #[arg(long)] + from: Option, + + /// An ISO date string for when to filter tasks until. + /// Defaults to 1 second before midnight tonight if unset. Not used for the today list + #[arg(long)] + to: Option, + + /// An optional message to include at the beginning of the report. If omitted, 3 random emojis + /// will be included instead + #[arg(short, long)] + message: Option, + + /// Choose a resolution for the report. This will determine how much detail is included in the + /// output. Default is "FullTask" + #[arg(short, long, default_value_t = Resolution::default())] + #[clap(value_enum)] + resolution: Resolution, } fn main() -> Result<()> { let args = CliArgs::parse(); - let tasks = match args.report { - ListType::Today => Task::today(), - ListType::Logbook => Task::logbook(), + let now = chrono::Local::now(); + let from = args.from.unwrap_or_else(|| now.date().and_hms(0, 0, 0).to_rfc3339()); + let to = args.to.unwrap_or_else(|| (now + chrono::Duration::days(1)).date().and_hms(0, 0, 0).to_rfc3339()); + + let message = args.message.unwrap_or_else(|| emoji::pick(3).join(" ")); + + let tasks = match args.list { + ListType::Today => Task::today(&from, &to), + ListType::Logbook => Task::logbook(&from, &to), }?; let mut reported: Vec = tasks.into_iter().filter(|task| { // Filter down to tasks with all selected tags and without any of the omitted tags @@ -85,8 +109,8 @@ fn main() -> Result<()> { reported.sort_by(|a, b| { a.completion_date.cmp(&b.completion_date) }); - let report = args.report.format_tasks(reported, &args.tags, !args.no_sanitize); - println!("{report}"); + let report = args.list.format_tasks(reported, &args.tags, !args.no_sanitize, args.resolution); + println!("{message}\n\n{report}"); Ok(()) } diff --git a/src/reporter.rs b/src/reporter.rs index 14a1a65..d7af026 100644 --- a/src/reporter.rs +++ b/src/reporter.rs @@ -1,3 +1,5 @@ +use clap::ValueEnum; + use crate::things::task::{Task, Status}; use crate::names::sanitize_names; @@ -126,11 +128,18 @@ impl ThingsTree { } } +#[derive(ValueEnum, Copy, Clone, Eq, PartialEq)] pub enum Resolution { FullTask, Project, } +impl Default for Resolution { + fn default() -> Resolution { + Resolution::FullTask + } +} + pub struct ReportOptions { pub resolution: Resolution, pub tags: Vec, diff --git a/src/things/logbook.js b/src/things/logbook.js index 326abac..e0828a3 100644 --- a/src/things/logbook.js +++ b/src/things/logbook.js @@ -2,15 +2,8 @@ var things = Application("Things"); var logbook = things.lists.byName("Logbook").toDos(); var objs = []; -var from = new Date(); -from.setHours(0); -from.setMinutes(0); -from.setSeconds(0); -var to = new Date(); -to.setTime(from.getTime()); -to.setHours(23); -to.setMinutes(59); -to.setSeconds(59); +var from = new Date($params.from); +var to = new Date($params.to); for (const todo of logbook) { if(todo.completionDate() >= from && todo.completionDate() < to) { diff --git a/src/things/task.rs b/src/things/task.rs index 0d69863..2d9c944 100644 --- a/src/things/task.rs +++ b/src/things/task.rs @@ -1,5 +1,5 @@ use std::str::from_utf8; -use serde::Deserialize; +use serde::{Deserialize, Serialize}; use anyhow::Result; use osascript; use serde_json; @@ -44,23 +44,31 @@ pub struct Task { pub area: Option, } +#[derive(Serialize, Debug)] +pub struct TaskParams { + // ISO string for the start date + from: String, + // ISO string for the end date + to: String, +} + impl Task { /// A Helper for loading Tasks from json returned by an osascript - fn from_script(script_bytes: &[u8]) -> Result> { + fn from_script(script_bytes: &[u8], params: TaskParams) -> Result> { let script = osascript::JavaScript::new(from_utf8(script_bytes)?); - let raw_json: String = script.execute()?; + let raw_json: String = script.execute_with_params(params)?; let tasks: Vec = serde_json::from_str(&raw_json)?; Ok(tasks) } /// Get all tasks in the today list from Things - pub fn today() -> Result> { - Task::from_script(include_bytes!("today.js")) + pub fn today(from: &String, to: &String) -> Result> { + Task::from_script(include_bytes!("today.js"), TaskParams { from: from.to_string(), to: to.to_string() }) } /// Get all tasks in the logbook list from Things - pub fn logbook() -> Result> { - Task::from_script(include_bytes!("logbook.js")) + pub fn logbook(from: &String, to: &String) -> Result> { + Task::from_script(include_bytes!("logbook.js"), TaskParams { from: from.to_string(), to: to.to_string() }) } pub fn has_tag(&self, tag: &str) -> bool {