From e6ffc866c4d694b8ce1edd11bd81870e530bea9b Mon Sep 17 00:00:00 2001 From: Campbell Alden Date: Mon, 24 Jul 2023 12:02:40 +0900 Subject: [PATCH 01/15] Sanitize name tags --- Cargo.lock | 2 +- Cargo.toml | 2 +- src/main.rs | 15 +++++++++------ src/names.rs | 39 +++++++++++++++++++++++++++++++++++++++ 4 files changed, 50 insertions(+), 8 deletions(-) create mode 100644 src/names.rs diff --git a/Cargo.lock b/Cargo.lock index bca000e..a4a1536 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -173,7 +173,7 @@ checksum = "e496a50fda8aacccc86d7529e2c1e0892dbd0f898a6b5645b5561b89c3210efa" [[package]] name = "day-reporter" -version = "0.2.0" +version = "1.0.0" dependencies = [ "anyhow", "chrono", diff --git a/Cargo.toml b/Cargo.toml index bfd4001..4eb7199 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "day-reporter" -version = "0.2.0" +version = "1.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 b72c3f5..0523aef 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,12 +1,14 @@ mod things; mod reporter; mod emoji; +mod names; use reporter::{MarkdownReporter, Reporter, Resolution, ReportOptions}; use things::task::{Task, Status}; use anyhow::Result; use clap::{Parser, ValueEnum}; +use names::sanitize_names; #[derive(ValueEnum, Copy, Clone, Eq, PartialEq)] enum Modes { @@ -20,19 +22,19 @@ enum Modes { } impl Modes { - fn format_tasks(&self, tasks: Vec, tags: Vec) -> String { + fn format_tasks(&self, tasks: Vec, tags: &Vec) -> String { match self { Modes::Morning => { let task_report = MarkdownReporter.report(tasks, &ReportOptions { resolution: Resolution::FullTask, - tags, + tags: tags.to_vec(), }); format!("{}\n\n{}", emoji::pick(3).join(" "), task_report) }, Modes::Signoff => { let task_report = MarkdownReporter.report(tasks, &ReportOptions { resolution: Resolution::FullTask, - tags, + tags: tags.to_vec(), }); format!("Stopping now\n\n{}", task_report) }, @@ -45,7 +47,7 @@ impl Modes { }).collect::>(); let task_report = MarkdownReporter.report(further_filtered, &ReportOptions { resolution: Resolution::Project, - tags, + tags: tags.to_vec(), }); format!("*Cycle Report*\n\n{}", task_report) }, @@ -82,8 +84,9 @@ fn main() -> Result<()> { let reported: Vec = tasks.into_iter().filter(|task| { args.tags.iter().all(|tag| task.has_tag(tag)) }).collect(); - let report = args.mode.format_tasks(reported, args.tags); - println!("{report}"); + let report = args.mode.format_tasks(reported, &args.tags); + let sanitized = sanitize_names(&report, &args.tags); + println!("{sanitized}"); Ok(()) } diff --git a/src/names.rs b/src/names.rs new file mode 100644 index 0000000..9fab02d --- /dev/null +++ b/src/names.rs @@ -0,0 +1,39 @@ +fn sanitize(src: &str) -> String { + // NOTE: The values on the right are _NOT_ the characters they appear to be. + src.replace("a", "а") + .replace("e", "e") + .replace("i", "і") + .replace("o", "о") + .replace("u", "ս") +} + +/// Replace vowel characters with look alikes to avoid Slack mention logic +/// # Args +/// - `src`: The source text to modify +/// - `names`: The set of names to sanitize +fn sanitize_strings<'a>(src: &'a str, names: &Vec) -> String { + let sanitization_strings = names.iter() + .map(|name| (name, sanitize(name))) + .collect::>(); + let mut dest = src.to_string(); + for (name, replacement) in sanitization_strings { + dest = dest.replace(name, &replacement); + } + return dest; +} + +fn extract_names_from_tags(tags: &Vec) -> Vec { + tags + .iter() + .filter(|t| t.starts_with("@")) + .map(|t| { + let formatted_name = t.strip_prefix("@").expect(&format!("{t} should have started with @")); + String::from(formatted_name) + }) + .collect() +} + +pub fn sanitize_names(src: &str, tags: &Vec) -> String { + let name_tags = extract_names_from_tags(tags); + sanitize_strings(src, &name_tags) +} From d232763850dcb046b63d359373a508b43f3797a4 Mon Sep 17 00:00:00 2001 From: Campbell Alden Date: Mon, 24 Jul 2023 13:56:56 +0900 Subject: [PATCH 02/15] Fix name sanitization to be based on specific tags instead of on input tag filtering --- Cargo.lock | 2 +- Cargo.toml | 2 +- src/main.rs | 17 ++++++++++++----- src/reporter.rs | 19 +++++++++++++++++-- src/things/logbook.js | 8 +++++++- src/things/logbook_cycle.js | 8 +++++++- src/things/task.rs | 1 + src/things/today.js | 8 +++++++- 8 files changed, 53 insertions(+), 12 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index a4a1536..5b42a12 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -173,7 +173,7 @@ checksum = "e496a50fda8aacccc86d7529e2c1e0892dbd0f898a6b5645b5561b89c3210efa" [[package]] name = "day-reporter" -version = "1.0.0" +version = "1.1.0" dependencies = [ "anyhow", "chrono", diff --git a/Cargo.toml b/Cargo.toml index 4eb7199..9eaa0d2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "day-reporter" -version = "1.0.0" +version = "1.1.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 0523aef..529d9d4 100644 --- a/src/main.rs +++ b/src/main.rs @@ -8,7 +8,6 @@ use reporter::{MarkdownReporter, Reporter, Resolution, ReportOptions}; use things::task::{Task, Status}; use anyhow::Result; use clap::{Parser, ValueEnum}; -use names::sanitize_names; #[derive(ValueEnum, Copy, Clone, Eq, PartialEq)] enum Modes { @@ -22,12 +21,13 @@ enum Modes { } impl Modes { - fn format_tasks(&self, tasks: Vec, tags: &Vec) -> String { + fn format_tasks(&self, tasks: Vec, tags: &Vec, sanitize_names: bool) -> String { match self { Modes::Morning => { let task_report = MarkdownReporter.report(tasks, &ReportOptions { resolution: Resolution::FullTask, tags: tags.to_vec(), + sanitize_names, }); format!("{}\n\n{}", emoji::pick(3).join(" "), task_report) }, @@ -35,6 +35,7 @@ impl Modes { let task_report = MarkdownReporter.report(tasks, &ReportOptions { resolution: Resolution::FullTask, tags: tags.to_vec(), + sanitize_names, }); format!("Stopping now\n\n{}", task_report) }, @@ -48,6 +49,7 @@ impl Modes { let task_report = MarkdownReporter.report(further_filtered, &ReportOptions { resolution: Resolution::Project, tags: tags.to_vec(), + sanitize_names, }); format!("*Cycle Report*\n\n{}", task_report) }, @@ -72,6 +74,12 @@ struct CliArgs { #[arg(short, long, default_value_t = Modes::default())] #[clap(value_enum)] mode: Modes, + + /// By default, any @ style tags will be sanitized in the output to avoid @-mentions in + /// slack. This is done by replacing vowel characters with look unicode lookalikes. If this + /// flag is set then the names will be passed through unsanitized + #[arg(long, default_value_t = false)] + no_sanitize: bool, } fn main() -> Result<()> { @@ -84,9 +92,8 @@ fn main() -> Result<()> { let reported: Vec = tasks.into_iter().filter(|task| { args.tags.iter().all(|tag| task.has_tag(tag)) }).collect(); - let report = args.mode.format_tasks(reported, &args.tags); - let sanitized = sanitize_names(&report, &args.tags); - println!("{sanitized}"); + let report = args.mode.format_tasks(reported, &args.tags, !args.no_sanitize); + println!("{report}"); Ok(()) } diff --git a/src/reporter.rs b/src/reporter.rs index 0cc72f3..8a26526 100644 --- a/src/reporter.rs +++ b/src/reporter.rs @@ -1,4 +1,5 @@ use crate::things::task::Task; +use crate::names::sanitize_names; /// Given a notes field and a list of possible tags for sections, return the content of triple tick /// blocks containing those tags @@ -36,6 +37,7 @@ pub struct ProjectTree { id: String, title: String, notes: Option, + tags: Vec, tasks: Vec, } @@ -72,6 +74,7 @@ impl AreaTree { id: project.id.clone(), title: project.title.clone(), notes: project.notes.clone(), + tags: project.tags.clone(), tasks: vec![task], }); } @@ -103,6 +106,7 @@ impl ThingsTree { id: project.id.clone(), title: project.title.clone(), notes: project.notes.clone(), + tags: project.tags.clone(), tasks: vec![task], }); } @@ -130,6 +134,7 @@ pub enum Resolution { pub struct ReportOptions { pub resolution: Resolution, pub tags: Vec, + pub sanitize_names: bool, } pub trait Reporter { @@ -171,7 +176,11 @@ impl Reporter for MarkdownReporter { .map(|l| format!("\n{}- {}", String::from(" ").repeat(depth + 4), l)) .collect::>() .join(""); - format!("\n{}- {}{}", String::from(" ").repeat(depth), task.title, relevant_notes) + let mut output = format!("\n{}- {}{}", String::from(" ").repeat(depth), task.title, relevant_notes); + if options.sanitize_names { + output = sanitize_names(&output, &task.tags); + } + return output; } fn report_project(&mut self, project: &ProjectTree, depth: usize, options: &ReportOptions) -> String { let resolution = &options.resolution; @@ -182,7 +191,7 @@ impl Reporter for MarkdownReporter { .map(|l| format!("\n{}- {}", String::from(" ").repeat(depth + 4), l)) .collect::>() .join(""); - match resolution { + let mut output = match resolution { Resolution::FullTask => { let tasks = project.tasks .iter() @@ -194,7 +203,13 @@ impl Reporter for MarkdownReporter { Resolution::Project => { format!("{}- {}{}", String::from(" ").repeat(depth), project.title, relevant_notes) } + }; + + if options.sanitize_names { + output = sanitize_names(&output, &project.tags); } + + return output; } fn report_single_area(&mut self, area: &AreaTree, options: &ReportOptions) -> String { let project_reports = area.projects diff --git a/src/things/logbook.js b/src/things/logbook.js index 7124fcd..dd3439e 100644 --- a/src/things/logbook.js +++ b/src/things/logbook.js @@ -27,7 +27,13 @@ logbook.filter(task => { notes: todo.notes() || null, status: todo.status(), completion_date: todo.completionDate(), - project: proj && { id: proj.id(), title: proj.name(), status: proj.status(), notes: proj.notes() }, + project: proj && { + id: proj.id(), + title: proj.name(), + status: proj.status(), + notes: proj.notes(), + tags: proj.tagNames().split(', '), + }, area: area && { id: area.id(), title: area.name() }, tags: [...tags, ...todo.tagNames().split(', ')].filter(t => t), }); diff --git a/src/things/logbook_cycle.js b/src/things/logbook_cycle.js index b902324..292a7f5 100644 --- a/src/things/logbook_cycle.js +++ b/src/things/logbook_cycle.js @@ -27,7 +27,13 @@ logbook.filter(task => { notes: todo.notes() || null, status: todo.status(), completion_date: todo.completionDate(), - project: proj && { id: proj.id(), title: proj.name(), status: proj.status(), notes: proj.notes() }, + project: proj && { + id: proj.id(), + title: proj.name(), + status: proj.status(), + notes: proj.notes(), + tags: proj.tagNames().split(', '), + }, area: area && { id: area.id(), title: area.name() }, tags: [...tags, ...todo.tagNames().split(', ')].filter(t => t), }); diff --git a/src/things/task.rs b/src/things/task.rs index 3e8e15b..a8bc62d 100644 --- a/src/things/task.rs +++ b/src/things/task.rs @@ -19,6 +19,7 @@ pub enum Status { pub struct Project { pub id: String, pub title: String, + pub tags: Vec, pub notes: Option, pub status: Status, } diff --git a/src/things/today.js b/src/things/today.js index 8d57d48..6b5cf85 100644 --- a/src/things/today.js +++ b/src/things/today.js @@ -15,7 +15,13 @@ today.forEach(todo => { notes: todo.notes() || null, status: todo.status(), completion_date: todo.completionDate(), - project: proj && { id: proj.id(), title: proj.name(), status: proj.status(), notes: proj.notes() }, + project: proj && { + id: proj.id(), + title: proj.name(), + status: proj.status(), + notes: proj.notes(), + tags: proj.tagNames().split(', '), + }, area: area && { id: area.id(), title: area.name() }, tags: [...tags, ...todo.tagNames().split(', ')].filter(t => t), }); From b7cedea8ebc4eb9405ec90477c62370aec6e27ed Mon Sep 17 00:00:00 2001 From: Campbell Alden Date: Mon, 24 Jul 2023 14:44:22 +0900 Subject: [PATCH 03/15] Update README --- README.md | 57 ++++++++++++++++++++++--------------------------------- 1 file changed, 23 insertions(+), 34 deletions(-) diff --git a/README.md b/README.md index 1f7c357..8611e8f 100644 --- a/README.md +++ b/README.md @@ -1,41 +1,30 @@ # things-3-report -A small script to generate bulleted reports from my Things 3 tasks +A small program written in rust that digests my Things tasks into a format for Slack reporting. -## Usage -The environment should be provided by `nix-shell`. Alternatively you can install the requirements directly with -```bash -$ pip install -r requirements.txt -``` -To run the script, pass in the tag or list of tags to filter on. Projects and Tasks lacking one of the specified tags will be excluded from the generated report. I have a `Report` tag which I use for this. If a project is tagged with the tag, all tasks are considered tagged even if not explicitly done so. +Tasks are pulled out of Things using AppleScript over a JavaScript OSA Bridge. Tasks are then filtered +and formatted based on the selected format schema. -### Specific functionality -I have also included a few pieces of additional functionality: -- The project is headed with a set of 3 randomly selected emojis from `emojis.txt` -- If you have a person's name in the task or project title, you can add an `@` tag to the task or project and the script will automatically replace the vowels in their name with unicode that looks similar. This allows you to post the list on slack without generating mentions. -- Adding a code block that begins with `report` will cause the script to dump the contents into the end of the generated message. This can be used to add notes to the generated output. -- Be default the script draws from today's current list of tasks. If you specify `--signoff` it will instead draw on completed tasks from your logbook. (This is useful for an end of the day message). +## Basic Task format +A task is returned as a markdown list element containing its title and optionally a list of information parsed from +related tag sections in the notes portion of the task. -## Example Usage: -Given a set of tasks tagged `Report` -```txt -Project named "Some Project" tagged as Report: - containing two tasks (not necessarily tagged) "Task 1" and "Task 2" +Tag specific additional bullets are included using a triple backtick (like GitHub code block markdown). Then instead of +specifying the coding style as you would (for example `typescript`) you specify the relevant tag: `MyTag`. Running the +program with a given `--tags` argument will cause related tag blocks to be included as sub-bullets. -A top level task called some task with a notes filed containing -```report -(#9999)[https://example.com] -`` ` <-- No space there but it's hard to escape these ticks -``` +## Name Sanitization +Name tags found on projects and tasks are automatically sanitized by replacing vowel characters with lookalikes. This +behavior can be disabled with the `--no-sanitize` argument. -``` -$ python dump.py Report -``` -will generate: +## Project Formatting +Projects make up a bullet level above tasks. Any tasks in the project will be nested under the project title following +and project specific matched tag block comments in the project notes. -```txt -:emoji: :emoji: :emoji: - - Some Project - - Task 1 - - Task 2 - - Some Task (#9999)(https://example.com) -``` +## Area Formatting +Areas collect up projects, but instead of indenting them further, Area's are returned as section titles above the +generated output. If only one area contains all of the reported tasks, then the area name is omitted entirely. + +## Cycle Message Formatting +The `--mode cycle` argument will output slightly different content. All tasks are collected at a project level, and +only the project name is given. Cycle messages look back in time at the last 6 weeks and therefore have too many values +to meaningfully output at full resolution. From 30b22de2b925ce7caf54bf59a13b24215a78b712 Mon Sep 17 00:00:00 2001 From: Campbell Alden Date: Mon, 24 Jul 2023 20:00:23 +0900 Subject: [PATCH 04/15] Improve CLI arguments and help text --- Cargo.lock | 2 +- Cargo.toml | 2 +- src/main.rs | 46 +++++++++++++++++++++++----------------------- 3 files changed, 25 insertions(+), 25 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 5b42a12..4040e03 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -173,7 +173,7 @@ checksum = "e496a50fda8aacccc86d7529e2c1e0892dbd0f898a6b5645b5561b89c3210efa" [[package]] name = "day-reporter" -version = "1.1.0" +version = "1.2.0" dependencies = [ "anyhow", "chrono", diff --git a/Cargo.toml b/Cargo.toml index 9eaa0d2..b3ce27c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "day-reporter" -version = "1.1.0" +version = "1.2.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 529d9d4..99e04d4 100644 --- a/src/main.rs +++ b/src/main.rs @@ -10,20 +10,19 @@ use anyhow::Result; use clap::{Parser, ValueEnum}; #[derive(ValueEnum, Copy, Clone, Eq, PartialEq)] -enum Modes { - /// Generate a report containing projected work for the day and a morning message +enum ReportTypes { + /// report projected work for the day and a morning message Morning, - /// Generate a report that is intended to be used for sharing what major tasks were completed - /// in the last cycle. + /// report what major tasks were completed in the last cycle. Cycle, - /// Generate a report for what was actually done today and a signoff message + /// report what was actually done today and a signoff message Signoff, } -impl Modes { +impl ReportTypes { fn format_tasks(&self, tasks: Vec, tags: &Vec, sanitize_names: bool) -> String { match self { - Modes::Morning => { + ReportTypes::Morning => { let task_report = MarkdownReporter.report(tasks, &ReportOptions { resolution: Resolution::FullTask, tags: tags.to_vec(), @@ -31,7 +30,7 @@ impl Modes { }); format!("{}\n\n{}", emoji::pick(3).join(" "), task_report) }, - Modes::Signoff => { + ReportTypes::Signoff => { let task_report = MarkdownReporter.report(tasks, &ReportOptions { resolution: Resolution::FullTask, tags: tags.to_vec(), @@ -39,7 +38,7 @@ impl Modes { }); format!("Stopping now\n\n{}", task_report) }, - Modes::Cycle => { + ReportTypes::Cycle => { let further_filtered = tasks.into_iter().filter(|t| { if let Some(p) = &t.project { return p.status == Status::Completed; @@ -57,42 +56,43 @@ impl Modes { } } -impl Default for Modes { - fn default() -> Modes { - Modes::Morning +impl Default for ReportTypes { + fn default() -> ReportTypes { + ReportTypes::Morning } } +/// A program that generates Slack flavor markdown reports from Things 3 todo list items. #[derive(Parser)] #[command(author, version, about, long_about = None)] struct CliArgs { - /// A list of tags to filter requests on + /// A list of tags to filter todos by. Only todo list items with every tag will be reported #[arg(short, long)] tags: Vec, - /// Control what type of report to generate - #[arg(short, long, default_value_t = Modes::default())] + /// Select the type of report to generate + #[arg(short, long, default_value_t = ReportTypes::default())] #[clap(value_enum)] - mode: Modes, + report: ReportTypes, /// By default, any @ style tags will be sanitized in the output to avoid @-mentions in - /// slack. This is done by replacing vowel characters with look unicode lookalikes. If this - /// flag is set then the names will be passed through unsanitized + /// 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, } fn main() -> Result<()> { let args = CliArgs::parse(); - let tasks = match args.mode { - Modes::Morning => Task::today(), - Modes::Signoff => Task::logbook_today(), - Modes::Cycle => Task::logbook_this_cycle(), + let tasks = match args.report { + ReportTypes::Morning => Task::today(), + ReportTypes::Signoff => Task::logbook_today(), + ReportTypes::Cycle => Task::logbook_this_cycle(), }?; let reported: Vec = tasks.into_iter().filter(|task| { args.tags.iter().all(|tag| task.has_tag(tag)) }).collect(); - let report = args.mode.format_tasks(reported, &args.tags, !args.no_sanitize); + let report = args.report.format_tasks(reported, &args.tags, !args.no_sanitize); println!("{report}"); Ok(()) From a9658a9d94d0519235ab5b13122053571108a26e Mon Sep 17 00:00:00 2001 From: Campbell Alden Date: Mon, 24 Jul 2023 22:50:04 +0900 Subject: [PATCH 05/15] v1.2.1 --- Cargo.lock | 2 +- Cargo.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 4040e03..8b8ee35 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -173,7 +173,7 @@ checksum = "e496a50fda8aacccc86d7529e2c1e0892dbd0f898a6b5645b5561b89c3210efa" [[package]] name = "day-reporter" -version = "1.2.0" +version = "1.2.1" dependencies = [ "anyhow", "chrono", diff --git a/Cargo.toml b/Cargo.toml index b3ce27c..51908d8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "day-reporter" -version = "1.2.0" +version = "1.2.1" edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html From 23eaf0c5d84005478274a5b1d3e9f470e308c6af Mon Sep 17 00:00:00 2001 From: Campbell Alden Date: Fri, 4 Aug 2023 10:42:22 +0900 Subject: [PATCH 06/15] Add new emojis --- Cargo.lock | 2 +- Cargo.toml | 2 +- src/emoji/emoji_list.txt | 21 +++++++++++++++++++-- 3 files changed, 21 insertions(+), 4 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 8b8ee35..183e185 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -173,7 +173,7 @@ checksum = "e496a50fda8aacccc86d7529e2c1e0892dbd0f898a6b5645b5561b89c3210efa" [[package]] name = "day-reporter" -version = "1.2.1" +version = "1.2.2" dependencies = [ "anyhow", "chrono", diff --git a/Cargo.toml b/Cargo.toml index 51908d8..7c9c1a6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "day-reporter" -version = "1.2.1" +version = "1.2.2" edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html diff --git a/src/emoji/emoji_list.txt b/src/emoji/emoji_list.txt index 9dcb383..16e0f68 100644 --- a/src/emoji/emoji_list.txt +++ b/src/emoji/emoji_list.txt @@ -1,15 +1,22 @@ :audioowl: :ayaya: :blobcat_cookie: +:blobcat_munch: :boat-cat: :bouquet_owl: :cat-confused: :cat-cook: +:cat-dance: :cat-on-the-laptop: +:cat-peek: :cat-roll: +:cat-roomba-exceptionally-fast: +:cat-roomba: :cat-shook: :cat-skype: :cat_blush: +:cat_bobble: +:cat_clap: :cat_type: :catcat: :catdance: @@ -31,22 +38,27 @@ :dancing_dog: :deadowl: :dnowl: +:dog_cool: +:doge-dance-kfc: :doge: :dogjam: :eikaiwaowl: -:eve-owl: :eve-owl-evil: +:eve-owl: :fakeowl: +:french-bulldog_massage: :gatocat: :grumpycat: :gull_scream: :heart-eyes-dog: :hungry_cat: :i_regret_nothing: +:insomnia-owl: :investigate-owl: :jakethedog1: :jakethedog2: :jenkinsowl: +:long_cat: :look-owl: :loopyowl: :love_letter_owl: @@ -67,8 +79,8 @@ :meow_buzz: :meow_camera: :meow_code: -:meow_coffee: :meow_coffee2: +:meow_coffee: :meow_comfy: :meow_comfy_coffee: :meow_comfydonut: @@ -126,9 +138,12 @@ :meowth: :mild-surprise-owl: :nerd-cat: +:nerdcat: :octocat1: :octocat2: :octocat3: +:ok-owl: +:omg-owl: :owl-travel: :owl_celebration: :owl_christmas_stocking: @@ -149,7 +164,9 @@ :realowl_back: :realowl_guruguru: :realowl_side: +:rodger-owl: :sakura_owl: +:seems-good-owl: :shrodingers-cat: :steampunk-owl: :stopowl: From 9fa5818fde26d268f9fa7fe7c1ea46cc16fb935c Mon Sep 17 00:00:00 2001 From: Campbell Alden Date: Wed, 10 Jan 2024 10:23:09 +0900 Subject: [PATCH 07/15] Remove redundant clone() --- src/reporter.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/reporter.rs b/src/reporter.rs index 8a26526..b63ad43 100644 --- a/src/reporter.rs +++ b/src/reporter.rs @@ -59,8 +59,8 @@ pub struct ThingsTree { impl AreaTree { fn new(id: &str, title: &str) -> AreaTree { AreaTree { - id: id.clone().to_string(), - title: title.clone().to_string(), + id: id.to_string(), + title: title.to_string(), projects: vec![], hanging_tasks: vec![], } From 52b869cca5abc62a5370b850aae4e2649c6f7c99 Mon Sep 17 00:00:00 2001 From: Campbell Alden Date: Wed, 10 Jan 2024 10:23:26 +0900 Subject: [PATCH 08/15] Sort tasks by completion date (newer later) --- src/main.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/main.rs b/src/main.rs index 99e04d4..914723b 100644 --- a/src/main.rs +++ b/src/main.rs @@ -89,9 +89,12 @@ fn main() -> Result<()> { ReportTypes::Signoff => Task::logbook_today(), ReportTypes::Cycle => Task::logbook_this_cycle(), }?; - let reported: Vec = tasks.into_iter().filter(|task| { + let mut reported: Vec = tasks.into_iter().filter(|task| { args.tags.iter().all(|tag| task.has_tag(tag)) }).collect(); + 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}"); From f1ac5bb7186d70190cbe5024c6e7103185855a37 Mon Sep 17 00:00:00 2001 From: Campbell Alden Date: Wed, 28 Feb 2024 09:24:54 +0900 Subject: [PATCH 09/15] BUG FIX: Report relevant notes after a project title when there are notes on a project --- Cargo.lock | 2 +- Cargo.toml | 2 +- src/reporter.rs | 6 +++--- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 183e185..606110d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -173,7 +173,7 @@ checksum = "e496a50fda8aacccc86d7529e2c1e0892dbd0f898a6b5645b5561b89c3210efa" [[package]] name = "day-reporter" -version = "1.2.2" +version = "1.3.0" dependencies = [ "anyhow", "chrono", diff --git a/Cargo.toml b/Cargo.toml index 7c9c1a6..c182791 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "day-reporter" -version = "1.2.2" +version = "1.3.0" edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html diff --git a/src/reporter.rs b/src/reporter.rs index b63ad43..d108d41 100644 --- a/src/reporter.rs +++ b/src/reporter.rs @@ -180,7 +180,7 @@ impl Reporter for MarkdownReporter { if options.sanitize_names { output = sanitize_names(&output, &task.tags); } - return output; + output } fn report_project(&mut self, project: &ProjectTree, depth: usize, options: &ReportOptions) -> String { let resolution = &options.resolution; @@ -198,7 +198,7 @@ impl Reporter for MarkdownReporter { .map(|t| self.report_task(t, depth + 4, options)) .collect::>() .join(""); - format!("{}{}{}{}", String::from(" ").repeat(depth), relevant_notes, project.title, tasks) + format!("{}{}{}{}", String::from(" ").repeat(depth), project.title, relevant_notes, tasks) }, Resolution::Project => { format!("{}- {}{}", String::from(" ").repeat(depth), project.title, relevant_notes) @@ -209,7 +209,7 @@ impl Reporter for MarkdownReporter { output = sanitize_names(&output, &project.tags); } - return output; + output } fn report_single_area(&mut self, area: &AreaTree, options: &ReportOptions) -> String { let project_reports = area.projects From 29d1d5a2ef433e8cfd312731542bf346b36e71b3 Mon Sep 17 00:00:00 2001 From: Campbell Alden Date: Wed, 13 Mar 2024 18:17:50 +0900 Subject: [PATCH 10/15] Handle canceled tasks --- Cargo.lock | 2 +- Cargo.toml | 2 +- src/reporter.rs | 10 ++++++++-- src/things/task.rs | 2 ++ 4 files changed, 12 insertions(+), 4 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 606110d..63ae8c7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -173,7 +173,7 @@ checksum = "e496a50fda8aacccc86d7529e2c1e0892dbd0f898a6b5645b5561b89c3210efa" [[package]] name = "day-reporter" -version = "1.3.0" +version = "1.3.1" dependencies = [ "anyhow", "chrono", diff --git a/Cargo.toml b/Cargo.toml index c182791..f0472e7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "day-reporter" -version = "1.3.0" +version = "1.3.1" edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html diff --git a/src/reporter.rs b/src/reporter.rs index d108d41..14a1a65 100644 --- a/src/reporter.rs +++ b/src/reporter.rs @@ -1,4 +1,4 @@ -use crate::things::task::Task; +use crate::things::task::{Task, Status}; use crate::names::sanitize_names; /// Given a notes field and a list of possible tags for sections, return the content of triple tick @@ -176,10 +176,16 @@ impl Reporter for MarkdownReporter { .map(|l| format!("\n{}- {}", String::from(" ").repeat(depth + 4), l)) .collect::>() .join(""); - let mut output = format!("\n{}- {}{}", String::from(" ").repeat(depth), task.title, relevant_notes); + let title = if task.status == Status::Canceled { + format!("~{}~", task.title) + } else { + task.title.to_string() + }; + let mut output = format!("\n{}- {}{}", String::from(" ").repeat(depth), title, relevant_notes); if options.sanitize_names { output = sanitize_names(&output, &task.tags); } + output } fn report_project(&mut self, project: &ProjectTree, depth: usize, options: &ReportOptions) -> String { diff --git a/src/things/task.rs b/src/things/task.rs index a8bc62d..61a2f20 100644 --- a/src/things/task.rs +++ b/src/things/task.rs @@ -13,6 +13,8 @@ pub enum Status { Incomplete, #[serde(rename = "open")] Open, + #[serde(rename = "canceled")] + Canceled, } #[derive(Deserialize, Debug)] From c6942468b20bd746fee3d2b8afdd3001372f77bc Mon Sep 17 00:00:00 2001 From: Campbell Alden Date: Wed, 8 May 2024 11:00:26 +0900 Subject: [PATCH 11/15] Allow filtering reported tasks such that certain tags are omitted --- Cargo.lock | 2 +- Cargo.toml | 2 +- src/main.rs | 8 +++++++- 3 files changed, 9 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 63ae8c7..208356e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -173,7 +173,7 @@ checksum = "e496a50fda8aacccc86d7529e2c1e0892dbd0f898a6b5645b5561b89c3210efa" [[package]] name = "day-reporter" -version = "1.3.1" +version = "1.4.0" dependencies = [ "anyhow", "chrono", diff --git a/Cargo.toml b/Cargo.toml index f0472e7..44e805d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "day-reporter" -version = "1.3.1" +version = "1.4.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 914723b..1af7bf4 100644 --- a/src/main.rs +++ b/src/main.rs @@ -70,6 +70,11 @@ struct CliArgs { #[arg(short, long)] tags: Vec, + /// A list of tags to specifically omit from the results. Only todo list items WITHOUT these + /// tags will be included + #[arg(short, long)] + omit: Vec, + /// Select the type of report to generate #[arg(short, long, default_value_t = ReportTypes::default())] #[clap(value_enum)] @@ -90,7 +95,8 @@ fn main() -> Result<()> { ReportTypes::Cycle => Task::logbook_this_cycle(), }?; let mut reported: Vec = tasks.into_iter().filter(|task| { - args.tags.iter().all(|tag| task.has_tag(tag)) + // Filter down to tasks with all selected tags and without any of the omitted tags + args.tags.iter().all(|tag| task.has_tag(tag)) && !args.omit.iter().any(|tag| task.has_tag(tag)) }).collect(); reported.sort_by(|a, b| { a.completion_date.cmp(&b.completion_date) From 154793ccbed13ac20c0941746451c42718dfaff8 Mon Sep 17 00:00:00 2001 From: Campbell Alden Date: Mon, 23 Dec 2024 18:09:10 +0900 Subject: [PATCH 12/15] Speedup the signoff report by short circuiting when the first task not on today is found --- Cargo.toml | 2 +- src/things/logbook.js | 52 ++++++++++++++++++++++--------------------- 2 files changed, 28 insertions(+), 26 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 44e805d..c5c19b9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "day-reporter" -version = "1.4.0" +version = "1.4.1" edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html diff --git a/src/things/logbook.js b/src/things/logbook.js index dd3439e..326abac 100644 --- a/src/things/logbook.js +++ b/src/things/logbook.js @@ -12,31 +12,33 @@ to.setHours(23); to.setMinutes(59); to.setSeconds(59); -logbook.filter(task => { - return task.completionDate() >= from && task.completionDate() < to; -}).forEach(todo => { - var proj = todo.project(); - var tags = []; - if (proj) { - tags.push(...proj.tagNames().split(', ')); +for (const todo of logbook) { + if(todo.completionDate() >= from && todo.completionDate() < to) { + var proj = todo.project(); + var tags = []; + if (proj) { + tags.push(...proj.tagNames().split(', ')); + } + var area = todo.area() || proj && proj.area(); + objs.push({ + id: todo.id(), + title: todo.name(), + notes: todo.notes() || null, + status: todo.status(), + completion_date: todo.completionDate(), + project: proj && { + id: proj.id(), + title: proj.name(), + status: proj.status(), + notes: proj.notes(), + tags: proj.tagNames().split(', '), + }, + area: area && { id: area.id(), title: area.name() }, + tags: [...tags, ...todo.tagNames().split(', ')].filter(t => t), + }); + } else { + break; } - var area = todo.area() || proj && proj.area(); - objs.push({ - id: todo.id(), - title: todo.name(), - notes: todo.notes() || null, - status: todo.status(), - completion_date: todo.completionDate(), - project: proj && { - id: proj.id(), - title: proj.name(), - status: proj.status(), - notes: proj.notes(), - tags: proj.tagNames().split(', '), - }, - area: area && { id: area.id(), title: area.name() }, - tags: [...tags, ...todo.tagNames().split(', ')].filter(t => t), - }); -}); +} return JSON.stringify(objs, undefined, 2); From 9d62d4d555dc92be7c6f72f09bcfb43436d7ca60 Mon Sep 17 00:00:00 2001 From: Campbell Alden Date: Tue, 24 Dec 2024 11:56:52 +0900 Subject: [PATCH 13/15] Update the day reporter to use lists instead of report types --- Cargo.lock | 2 +- src/main.rs | 50 +++++++++++++------------------------ src/things/logbook_cycle.js | 42 ------------------------------- src/things/task.rs | 6 +---- 4 files changed, 19 insertions(+), 81 deletions(-) delete mode 100644 src/things/logbook_cycle.js diff --git a/Cargo.lock b/Cargo.lock index 208356e..c73a052 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -173,7 +173,7 @@ checksum = "e496a50fda8aacccc86d7529e2c1e0892dbd0f898a6b5645b5561b89c3210efa" [[package]] name = "day-reporter" -version = "1.4.0" +version = "1.4.1" dependencies = [ "anyhow", "chrono", diff --git a/src/main.rs b/src/main.rs index 1af7bf4..ea0a082 100644 --- a/src/main.rs +++ b/src/main.rs @@ -5,32 +5,31 @@ mod names; use reporter::{MarkdownReporter, Reporter, Resolution, ReportOptions}; -use things::task::{Task, Status}; +use things::task::Task; use anyhow::Result; use clap::{Parser, ValueEnum}; #[derive(ValueEnum, Copy, Clone, Eq, PartialEq)] -enum ReportTypes { - /// report projected work for the day and a morning message - Morning, - /// report what major tasks were completed in the last cycle. - Cycle, - /// report what was actually done today and a signoff message - Signoff, +enum ListType { + /// Generate a report from the Things today list + Today, + /// Generate a report from the Things logbook + Logbook, } -impl ReportTypes { +impl ListType { fn format_tasks(&self, tasks: Vec, tags: &Vec, sanitize_names: bool) -> String { match self { - ReportTypes::Morning => { + ListType::Today => { let task_report = MarkdownReporter.report(tasks, &ReportOptions { resolution: Resolution::FullTask, 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) }, - ReportTypes::Signoff => { + ListType::Logbook => { let task_report = MarkdownReporter.report(tasks, &ReportOptions { resolution: Resolution::FullTask, tags: tags.to_vec(), @@ -38,27 +37,13 @@ impl ReportTypes { }); format!("Stopping now\n\n{}", task_report) }, - ReportTypes::Cycle => { - let further_filtered = tasks.into_iter().filter(|t| { - if let Some(p) = &t.project { - return p.status == Status::Completed; - } - return false; - }).collect::>(); - let task_report = MarkdownReporter.report(further_filtered, &ReportOptions { - resolution: Resolution::Project, - tags: tags.to_vec(), - sanitize_names, - }); - format!("*Cycle Report*\n\n{}", task_report) - }, } } } -impl Default for ReportTypes { - fn default() -> ReportTypes { - ReportTypes::Morning +impl Default for ListType { + fn default() -> ListType { + ListType::Today } } @@ -76,9 +61,9 @@ struct CliArgs { omit: Vec, /// Select the type of report to generate - #[arg(short, long, default_value_t = ReportTypes::default())] + #[arg(short, long, default_value_t = ListType::default())] #[clap(value_enum)] - report: ReportTypes, + report: 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 @@ -90,9 +75,8 @@ struct CliArgs { fn main() -> Result<()> { let args = CliArgs::parse(); let tasks = match args.report { - ReportTypes::Morning => Task::today(), - ReportTypes::Signoff => Task::logbook_today(), - ReportTypes::Cycle => Task::logbook_this_cycle(), + ListType::Today => Task::today(), + ListType::Logbook => Task::logbook(), }?; let mut reported: Vec = tasks.into_iter().filter(|task| { // Filter down to tasks with all selected tags and without any of the omitted tags diff --git a/src/things/logbook_cycle.js b/src/things/logbook_cycle.js deleted file mode 100644 index 292a7f5..0000000 --- a/src/things/logbook_cycle.js +++ /dev/null @@ -1,42 +0,0 @@ -var things = Application("Things"); -var logbook = things.lists.byName("Logbook").toDos(); -var objs = []; - -// From 6 weeks ago -var from = new Date(new Date().getTime() - (6 * 7 * 24 * 60 * 60 * 1000)); -from.setHours(0); -from.setMinutes(0); -from.setSeconds(0); -var to = new Date(); -to.setHours(23); -to.setMinutes(59); -to.setSeconds(59); - -logbook.filter(task => { - return task.completionDate() >= from && task.completionDate() < to; -}).forEach(todo => { - var proj = todo.project(); - var tags = []; - if (proj) { - tags.push(...proj.tagNames().split(', ')); - } - var area = todo.area() || proj && proj.area(); - objs.push({ - id: todo.id(), - title: todo.name(), - notes: todo.notes() || null, - status: todo.status(), - completion_date: todo.completionDate(), - project: proj && { - id: proj.id(), - title: proj.name(), - status: proj.status(), - notes: proj.notes(), - tags: proj.tagNames().split(', '), - }, - area: area && { id: area.id(), title: area.name() }, - tags: [...tags, ...todo.tagNames().split(', ')].filter(t => t), - }); -}); - -return JSON.stringify(objs, undefined, 2); diff --git a/src/things/task.rs b/src/things/task.rs index 61a2f20..0d69863 100644 --- a/src/things/task.rs +++ b/src/things/task.rs @@ -59,14 +59,10 @@ impl Task { } /// Get all tasks in the logbook list from Things - pub fn logbook_today() -> Result> { + pub fn logbook() -> Result> { Task::from_script(include_bytes!("logbook.js")) } - pub fn logbook_this_cycle() -> Result> { - Task::from_script(include_bytes!("logbook_cycle.js")) - } - pub fn has_tag(&self, tag: &str) -> bool { self.tags.contains(&String::from(tag)) } From fb711c28104120d83c46a40b98657bcb4dd550d8 Mon Sep 17 00:00:00 2001 From: Campbell Alden Date: Tue, 24 Dec 2024 12:28:07 +0900 Subject: [PATCH 14/15] Update the day reporter to support more arbitrary reporting from things lists and between arbitrary dates --- Cargo.lock | 2 +- Cargo.toml | 2 +- src/main.rs | 56 ++++++++++++++++++++++++++++++------------- src/reporter.rs | 9 +++++++ src/things/logbook.js | 11 ++------- src/things/task.rs | 22 +++++++++++------ 6 files changed, 68 insertions(+), 34 deletions(-) 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 { From ecd732dbbd4179b5753996f7cbc04dd1a88d0acb Mon Sep 17 00:00:00 2001 From: Campbell Alden Date: Fri, 27 Dec 2024 09:48:28 +0900 Subject: [PATCH 15/15] Update the readme since v2 broke a lot of options --- README.md | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 8611e8f..660386e 100644 --- a/README.md +++ b/README.md @@ -4,6 +4,9 @@ A small program written in rust that digests my Things tasks into a format for S Tasks are pulled out of Things using AppleScript over a JavaScript OSA Bridge. Tasks are then filtered and formatted based on the selected format schema. +## Options +See `day-reporter -h` for a current list of options + ## Basic Task format A task is returned as a markdown list element containing its title and optionally a list of information parsed from related tag sections in the notes portion of the task. @@ -12,6 +15,16 @@ Tag specific additional bullets are included using a triple backtick (like GitHu specifying the coding style as you would (for example `typescript`) you specify the relevant tag: `MyTag`. Running the program with a given `--tags` argument will cause related tag blocks to be included as sub-bullets. +### Example + +
+```MyTag
+Some notes
+```
+
+ +will result in "Some notes" being included with the task + ## Name Sanitization Name tags found on projects and tasks are automatically sanitized by replacing vowel characters with lookalikes. This behavior can be disabled with the `--no-sanitize` argument. @@ -23,8 +36,3 @@ and project specific matched tag block comments in the project notes. ## Area Formatting Areas collect up projects, but instead of indenting them further, Area's are returned as section titles above the generated output. If only one area contains all of the reported tasks, then the area name is omitted entirely. - -## Cycle Message Formatting -The `--mode cycle` argument will output slightly different content. All tasks are collected at a project level, and -only the project name is given. Cycle messages look back in time at the last 6 weeks and therefore have too many values -to meaningfully output at full resolution.