Implement Slack flavored markdown reporting for report tasks
This commit is contained in:
parent
2c25aabc6a
commit
11fbaa582e
8 changed files with 715 additions and 2 deletions
388
Cargo.lock
generated
Normal file
388
Cargo.lock
generated
Normal file
|
|
@ -0,0 +1,388 @@
|
||||||
|
# This file is automatically @generated by Cargo.
|
||||||
|
# It is not intended for manual editing.
|
||||||
|
version = 3
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "android-tzdata"
|
||||||
|
version = "0.1.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "android_system_properties"
|
||||||
|
version = "0.1.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311"
|
||||||
|
dependencies = [
|
||||||
|
"libc",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "anyhow"
|
||||||
|
version = "1.0.72"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "3b13c32d80ecc7ab747b80c3784bce54ee8a7a0cc4fbda9bf4cda2cf6fe90854"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "autocfg"
|
||||||
|
version = "1.1.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "bumpalo"
|
||||||
|
version = "3.13.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a3e2c3daef883ecc1b5d58c15adae93470a91d425f3532ba1695849656af3fc1"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "cc"
|
||||||
|
version = "1.0.79"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "50d30906286121d95be3d479533b458f87493b30a4b5f79a607db8f5d11aa91f"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "cfg-if"
|
||||||
|
version = "1.0.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "chrono"
|
||||||
|
version = "0.4.26"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ec837a71355b28f6556dbd569b37b3f363091c0bd4b2e735674521b4c5fd9bc5"
|
||||||
|
dependencies = [
|
||||||
|
"android-tzdata",
|
||||||
|
"iana-time-zone",
|
||||||
|
"js-sys",
|
||||||
|
"num-traits",
|
||||||
|
"serde",
|
||||||
|
"time",
|
||||||
|
"wasm-bindgen",
|
||||||
|
"winapi",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "core-foundation-sys"
|
||||||
|
version = "0.8.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e496a50fda8aacccc86d7529e2c1e0892dbd0f898a6b5645b5561b89c3210efa"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "day-reporter"
|
||||||
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"anyhow",
|
||||||
|
"chrono",
|
||||||
|
"osascript",
|
||||||
|
"serde",
|
||||||
|
"serde_json",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "iana-time-zone"
|
||||||
|
version = "0.1.57"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "2fad5b825842d2b38bd206f3e81d6957625fd7f0a361e345c30e01a0ae2dd613"
|
||||||
|
dependencies = [
|
||||||
|
"android_system_properties",
|
||||||
|
"core-foundation-sys",
|
||||||
|
"iana-time-zone-haiku",
|
||||||
|
"js-sys",
|
||||||
|
"wasm-bindgen",
|
||||||
|
"windows",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "iana-time-zone-haiku"
|
||||||
|
version = "0.1.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f"
|
||||||
|
dependencies = [
|
||||||
|
"cc",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "itoa"
|
||||||
|
version = "1.0.9"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "af150ab688ff2122fcef229be89cb50dd66af9e01a4ff320cc137eecc9bacc38"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "js-sys"
|
||||||
|
version = "0.3.64"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "c5f195fe497f702db0f318b07fdd68edb16955aed830df8363d837542f8f935a"
|
||||||
|
dependencies = [
|
||||||
|
"wasm-bindgen",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "libc"
|
||||||
|
version = "0.2.147"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b4668fb0ea861c1df094127ac5f1da3409a82116a4ba74fca2e58ef927159bb3"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "log"
|
||||||
|
version = "0.4.19"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b06a4cde4c0f271a446782e3eff8de789548ce57dbc8eca9292c27f4a42004b4"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "num-traits"
|
||||||
|
version = "0.2.15"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd"
|
||||||
|
dependencies = [
|
||||||
|
"autocfg",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "once_cell"
|
||||||
|
version = "1.18.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "osascript"
|
||||||
|
version = "0.3.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "38731fa859ef679f1aec66ca9562165926b442f298467f76f5990f431efe87dc"
|
||||||
|
dependencies = [
|
||||||
|
"serde",
|
||||||
|
"serde_derive",
|
||||||
|
"serde_json",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "proc-macro2"
|
||||||
|
version = "1.0.66"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "18fb31db3f9bddb2ea821cde30a9f70117e3f119938b5ee630b7403aa6e2ead9"
|
||||||
|
dependencies = [
|
||||||
|
"unicode-ident",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "quote"
|
||||||
|
version = "1.0.31"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "5fe8a65d69dd0808184ebb5f836ab526bb259db23c657efa38711b1072ee47f0"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "ryu"
|
||||||
|
version = "1.0.15"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "1ad4cc8da4ef723ed60bced201181d83791ad433213d8c24efffda1eec85d741"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "serde"
|
||||||
|
version = "1.0.173"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e91f70896d6720bc714a4a57d22fc91f1db634680e65c8efe13323f1fa38d53f"
|
||||||
|
dependencies = [
|
||||||
|
"serde_derive",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "serde_derive"
|
||||||
|
version = "1.0.173"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a6250dde8342e0232232be9ca3db7aa40aceb5a3e5dd9bddbc00d99a007cde49"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "serde_json"
|
||||||
|
version = "1.0.103"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d03b412469450d4404fe8499a268edd7f8b79fecb074b0d812ad64ca21f4031b"
|
||||||
|
dependencies = [
|
||||||
|
"itoa",
|
||||||
|
"ryu",
|
||||||
|
"serde",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "syn"
|
||||||
|
version = "2.0.26"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "45c3457aacde3c65315de5031ec191ce46604304d2446e803d71ade03308d970"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"unicode-ident",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "time"
|
||||||
|
version = "0.1.45"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "1b797afad3f312d1c66a56d11d0316f916356d11bd158fbc6ca6389ff6bf805a"
|
||||||
|
dependencies = [
|
||||||
|
"libc",
|
||||||
|
"wasi",
|
||||||
|
"winapi",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "unicode-ident"
|
||||||
|
version = "1.0.11"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "301abaae475aa91687eb82514b328ab47a211a533026cb25fc3e519b86adfc3c"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "wasi"
|
||||||
|
version = "0.10.0+wasi-snapshot-preview1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "1a143597ca7c7793eff794def352d41792a93c481eb1042423ff7ff72ba2c31f"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "wasm-bindgen"
|
||||||
|
version = "0.2.87"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "7706a72ab36d8cb1f80ffbf0e071533974a60d0a308d01a5d0375bf60499a342"
|
||||||
|
dependencies = [
|
||||||
|
"cfg-if",
|
||||||
|
"wasm-bindgen-macro",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "wasm-bindgen-backend"
|
||||||
|
version = "0.2.87"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "5ef2b6d3c510e9625e5fe6f509ab07d66a760f0885d858736483c32ed7809abd"
|
||||||
|
dependencies = [
|
||||||
|
"bumpalo",
|
||||||
|
"log",
|
||||||
|
"once_cell",
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn",
|
||||||
|
"wasm-bindgen-shared",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "wasm-bindgen-macro"
|
||||||
|
version = "0.2.87"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "dee495e55982a3bd48105a7b947fd2a9b4a8ae3010041b9e0faab3f9cd028f1d"
|
||||||
|
dependencies = [
|
||||||
|
"quote",
|
||||||
|
"wasm-bindgen-macro-support",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "wasm-bindgen-macro-support"
|
||||||
|
version = "0.2.87"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "54681b18a46765f095758388f2d0cf16eb8d4169b639ab575a8f5693af210c7b"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn",
|
||||||
|
"wasm-bindgen-backend",
|
||||||
|
"wasm-bindgen-shared",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "wasm-bindgen-shared"
|
||||||
|
version = "0.2.87"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ca6ad05a4870b2bf5fe995117d3728437bd27d7cd5f06f13c17443ef369775a1"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "winapi"
|
||||||
|
version = "0.3.9"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
|
||||||
|
dependencies = [
|
||||||
|
"winapi-i686-pc-windows-gnu",
|
||||||
|
"winapi-x86_64-pc-windows-gnu",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "winapi-i686-pc-windows-gnu"
|
||||||
|
version = "0.4.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "winapi-x86_64-pc-windows-gnu"
|
||||||
|
version = "0.4.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows"
|
||||||
|
version = "0.48.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e686886bc078bc1b0b600cac0147aadb815089b6e4da64016cbd754b6342700f"
|
||||||
|
dependencies = [
|
||||||
|
"windows-targets",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows-targets"
|
||||||
|
version = "0.48.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "05d4b17490f70499f20b9e791dcf6a299785ce8af4d709018206dc5b4953e95f"
|
||||||
|
dependencies = [
|
||||||
|
"windows_aarch64_gnullvm",
|
||||||
|
"windows_aarch64_msvc",
|
||||||
|
"windows_i686_gnu",
|
||||||
|
"windows_i686_msvc",
|
||||||
|
"windows_x86_64_gnu",
|
||||||
|
"windows_x86_64_gnullvm",
|
||||||
|
"windows_x86_64_msvc",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows_aarch64_gnullvm"
|
||||||
|
version = "0.48.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "91ae572e1b79dba883e0d315474df7305d12f569b400fcf90581b06062f7e1bc"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows_aarch64_msvc"
|
||||||
|
version = "0.48.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b2ef27e0d7bdfcfc7b868b317c1d32c641a6fe4629c171b8928c7b08d98d7cf3"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows_i686_gnu"
|
||||||
|
version = "0.48.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "622a1962a7db830d6fd0a69683c80a18fda201879f0f447f065a3b7467daa241"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows_i686_msvc"
|
||||||
|
version = "0.48.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "4542c6e364ce21bf45d69fdd2a8e455fa38d316158cfd43b3ac1c5b1b19f8e00"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows_x86_64_gnu"
|
||||||
|
version = "0.48.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ca2b8a661f7628cbd23440e50b05d705db3686f894fc9580820623656af974b1"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows_x86_64_gnullvm"
|
||||||
|
version = "0.48.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "7896dbc1f41e08872e9d5e8f8baa8fdd2677f29468c4e156210174edc7f7b953"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows_x86_64_msvc"
|
||||||
|
version = "0.48.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "1a515f5799fe4961cb532f983ce2b23082366b898e52ffbce459c86f67c8378a"
|
||||||
|
|
@ -6,3 +6,8 @@ edition = "2021"
|
||||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
|
anyhow = "1.0.72"
|
||||||
|
chrono = { version = "0.4.26", features = ["serde"] }
|
||||||
|
osascript = "0.3.0"
|
||||||
|
serde = { version = "1.0.173", features = ["derive"] }
|
||||||
|
serde_json = "1.0.103"
|
||||||
|
|
|
||||||
16
src/main.rs
16
src/main.rs
|
|
@ -1,3 +1,15 @@
|
||||||
fn main() {
|
mod things;
|
||||||
println!("Hello, world!");
|
mod reporter;
|
||||||
|
|
||||||
|
use reporter::{MarkdownReporter, Reporter};
|
||||||
|
|
||||||
|
use things::task::Task;
|
||||||
|
use anyhow::Result;
|
||||||
|
|
||||||
|
fn main() -> Result<()> {
|
||||||
|
let today = Task::today()?;
|
||||||
|
let reported: Vec<Task> = today.into_iter().filter(|task| task.has_tag("Report")).collect();
|
||||||
|
println!("{}", MarkdownReporter.report(reported));
|
||||||
|
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
|
||||||
183
src/reporter.rs
Normal file
183
src/reporter.rs
Normal file
|
|
@ -0,0 +1,183 @@
|
||||||
|
use crate::things::task::Task;
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct ProjectTree {
|
||||||
|
id: String,
|
||||||
|
title: String,
|
||||||
|
tasks: Vec<Task>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct AreaTree {
|
||||||
|
id: String,
|
||||||
|
title: String,
|
||||||
|
projects: Vec<ProjectTree>,
|
||||||
|
hanging_tasks: Vec<Task>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct ThingsTree {
|
||||||
|
areas: Vec<AreaTree>,
|
||||||
|
hanging_tasks: Vec<Task>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ProjectTree {
|
||||||
|
/// Add the task to this project if it belongs here, otherwise pass it back out.
|
||||||
|
pub fn try_take_task(&mut self, mut task: Task) -> Option<Task> {
|
||||||
|
if let Some(proj) = task.project {
|
||||||
|
if proj.id == self.id {
|
||||||
|
task.project = Some(proj);
|
||||||
|
self.tasks.push(task);
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
task.project = Some(proj);
|
||||||
|
return Some(task);
|
||||||
|
}
|
||||||
|
return Some(task);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AreaTree {
|
||||||
|
pub fn add_new_project_and_task(&mut self, mut task: Task) {
|
||||||
|
if let Some(proj) = task.project {
|
||||||
|
let id = proj.id.clone();
|
||||||
|
let title = proj.title.clone();
|
||||||
|
task.project = Some(proj);
|
||||||
|
self.projects.push(ProjectTree {
|
||||||
|
id,
|
||||||
|
title,
|
||||||
|
tasks: vec![task],
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
self.hanging_tasks.push(task);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub fn try_take_task(&mut self, mut task: Task) -> Option<Task> {
|
||||||
|
if let Some(area) = task.area {
|
||||||
|
if area.id == self.id {
|
||||||
|
task.area = Some(area);
|
||||||
|
let mut maybe_task = Some(task);
|
||||||
|
for proj in self.projects.iter_mut() {
|
||||||
|
if let Some(t) = maybe_task {
|
||||||
|
maybe_task = proj.try_take_task(t);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Here, the task belongs in this area but there was no project for it.
|
||||||
|
maybe_task.map(|t| self.add_new_project_and_task(t));
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
task.area = Some(area);
|
||||||
|
return Some(task);
|
||||||
|
}
|
||||||
|
return Some(task);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ThingsTree {
|
||||||
|
pub fn new() -> ThingsTree {
|
||||||
|
ThingsTree { areas: vec![], hanging_tasks: vec![] }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn add_new_area_and_task(&mut self, mut task: Task) {
|
||||||
|
if let Some(area) = task.area {
|
||||||
|
let id = area.id.clone();
|
||||||
|
let title = area.title.clone();
|
||||||
|
task.area = Some(area);
|
||||||
|
let mut area_tree = AreaTree {
|
||||||
|
id,
|
||||||
|
title,
|
||||||
|
projects: vec![],
|
||||||
|
hanging_tasks: vec![],
|
||||||
|
};
|
||||||
|
let took = area_tree.try_take_task(task);
|
||||||
|
if took.is_some() {
|
||||||
|
panic!("Area should have matched the task because it was created with the task");
|
||||||
|
}
|
||||||
|
self.areas.push(area_tree);
|
||||||
|
} else {
|
||||||
|
self.hanging_tasks.push(task);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn try_take_task(&mut self, task: Task) {
|
||||||
|
let mut maybe_task = Some(task);
|
||||||
|
for area in self.areas.iter_mut() {
|
||||||
|
if let Some(t) = maybe_task {
|
||||||
|
maybe_task = area.try_take_task(t);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
maybe_task.map(|t| self.add_new_area_and_task(t));
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn from_tasks(tasks: Vec<Task>) -> ThingsTree {
|
||||||
|
let mut tree = ThingsTree::new();
|
||||||
|
for task in tasks.into_iter() {
|
||||||
|
tree.try_take_task(task);
|
||||||
|
}
|
||||||
|
return tree;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait Reporter {
|
||||||
|
fn report_task(&mut self, task: &Task, depth: usize) -> String;
|
||||||
|
fn report_project(&mut self, project: &ProjectTree, depth: usize) -> String;
|
||||||
|
fn report_single_area(&mut self, area: &AreaTree) -> String;
|
||||||
|
fn report_multiple_areas(&mut self, areas: &Vec<AreaTree>) -> String;
|
||||||
|
fn report(&mut self, tasks: Vec<Task>) -> String {
|
||||||
|
let tree = ThingsTree::from_tasks(tasks);
|
||||||
|
let untracked_tasks = tree.hanging_tasks
|
||||||
|
.iter()
|
||||||
|
.map(|t| self.report_task(t, 0))
|
||||||
|
.collect::<Vec<String>>()
|
||||||
|
.join("\n");
|
||||||
|
let area_tasks: String = match tree.areas.len() {
|
||||||
|
0 => "".to_string(),
|
||||||
|
1 => self.report_single_area(&tree.areas[0]),
|
||||||
|
_ => self.report_multiple_areas(&tree.areas),
|
||||||
|
};
|
||||||
|
|
||||||
|
let separator = if area_tasks == "" || untracked_tasks == "" {
|
||||||
|
""
|
||||||
|
} else {
|
||||||
|
"\n\n"
|
||||||
|
};
|
||||||
|
|
||||||
|
format!("{}{}{}", area_tasks, separator, untracked_tasks)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct MarkdownReporter;
|
||||||
|
|
||||||
|
impl Reporter for MarkdownReporter {
|
||||||
|
fn report_task(&mut self, task: &Task, depth: usize) -> String {
|
||||||
|
format!("{}- {}", String::from(" ").repeat(depth), task.title)
|
||||||
|
}
|
||||||
|
fn report_project(&mut self, project: &ProjectTree, depth: usize) -> String {
|
||||||
|
let tasks = project.tasks.iter().map(|t| self.report_task(t, depth + 4)).collect::<Vec<String>>().join("\n");
|
||||||
|
format!("{}{}\n{}", String::from(" ").repeat(depth), project.title, tasks)
|
||||||
|
}
|
||||||
|
fn report_single_area(&mut self, area: &AreaTree) -> String {
|
||||||
|
let project_tasks = area.projects.iter().map(|p| self.report_project(p, 0)).collect::<Vec<String>>().join("\n");
|
||||||
|
let untracked_tasks = area.hanging_tasks.iter().map(|t| self.report_task(t, 0)).collect::<Vec<String>>().join("\n");
|
||||||
|
let separator = if project_tasks == "" || untracked_tasks == "" {
|
||||||
|
""
|
||||||
|
} else {
|
||||||
|
"\n\n"
|
||||||
|
};
|
||||||
|
format!("{}{}{}", project_tasks, separator, untracked_tasks)
|
||||||
|
}
|
||||||
|
fn report_multiple_areas(&mut self, areas: &Vec<AreaTree>) -> String {
|
||||||
|
match areas.len() {
|
||||||
|
0 => "".to_string(),
|
||||||
|
1 => self.report_single_area(&areas[0]),
|
||||||
|
_ => {
|
||||||
|
areas.iter().map(|area| {
|
||||||
|
let single = self.report_single_area(area);
|
||||||
|
format!("*{}*\n{}", area.title, single)
|
||||||
|
})
|
||||||
|
.collect::<Vec<String>>()
|
||||||
|
.join("\n\n")
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
36
src/things/logbook.js
Normal file
36
src/things/logbook.js
Normal file
|
|
@ -0,0 +1,36 @@
|
||||||
|
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);
|
||||||
|
|
||||||
|
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() },
|
||||||
|
area: area && { id: area.id(), title: area.name() },
|
||||||
|
tags: [...tags, ...todo.tagNames().split(', ')].filter(t => t),
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
return JSON.stringify(objs, undefined, 2);
|
||||||
1
src/things/mod.rs
Normal file
1
src/things/mod.rs
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
pub mod task;
|
||||||
64
src/things/task.rs
Normal file
64
src/things/task.rs
Normal file
|
|
@ -0,0 +1,64 @@
|
||||||
|
use std::str::from_utf8;
|
||||||
|
use serde::Deserialize;
|
||||||
|
use anyhow::Result;
|
||||||
|
use osascript;
|
||||||
|
use serde_json;
|
||||||
|
use chrono::{DateTime, Utc};
|
||||||
|
|
||||||
|
#[derive(Deserialize, Debug)]
|
||||||
|
pub enum Status {
|
||||||
|
#[serde(rename = "completed")]
|
||||||
|
Completed,
|
||||||
|
#[serde(rename = "incomplete")]
|
||||||
|
Incomplete,
|
||||||
|
#[serde(rename = "open")]
|
||||||
|
Open,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize, Debug)]
|
||||||
|
pub struct Project {
|
||||||
|
pub id: String,
|
||||||
|
pub title: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize, Debug)]
|
||||||
|
pub struct Area {
|
||||||
|
pub id: String,
|
||||||
|
pub title: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize, Debug)]
|
||||||
|
pub struct Task {
|
||||||
|
pub id: String,
|
||||||
|
pub title: String,
|
||||||
|
pub notes: Option<String>,
|
||||||
|
pub status: Status,
|
||||||
|
pub tags: Vec<String>,
|
||||||
|
pub completion_date: Option<DateTime<Utc>>,
|
||||||
|
pub project: Option<Project>,
|
||||||
|
pub area: Option<Area>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Task {
|
||||||
|
/// A Helper for loading Tasks from json returned by an osascript
|
||||||
|
fn from_script(script_bytes: &[u8]) -> Result<Vec<Task>> {
|
||||||
|
let script = osascript::JavaScript::new(from_utf8(script_bytes)?);
|
||||||
|
let raw_json: String = script.execute()?;
|
||||||
|
let tasks: Vec<Task> = serde_json::from_str(&raw_json)?;
|
||||||
|
Ok(tasks)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get all tasks in the today list from Things
|
||||||
|
pub fn today() -> Result<Vec<Task>> {
|
||||||
|
Task::from_script(include_bytes!("today.js"))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get all tasks in the logbook list from Things
|
||||||
|
pub fn logbook() -> Result<Vec<Task>> {
|
||||||
|
Task::from_script(include_bytes!("logbook.js"))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn has_tag(&self, tag: &str) -> bool {
|
||||||
|
self.tags.contains(&String::from(tag))
|
||||||
|
}
|
||||||
|
}
|
||||||
24
src/things/today.js
Normal file
24
src/things/today.js
Normal file
|
|
@ -0,0 +1,24 @@
|
||||||
|
var things = Application("Things");
|
||||||
|
var today = things.lists.byName("Today").toDos();
|
||||||
|
var objs = [];
|
||||||
|
|
||||||
|
today.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() },
|
||||||
|
area: area && { id: area.id(), title: area.name() },
|
||||||
|
tags: [...tags, ...todo.tagNames().split(', ')].filter(t => t),
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
return JSON.stringify(objs, undefined, 2);
|
||||||
Reference in a new issue