From 282f653c750f6ab60de6b825018d100a2bd4f6bf Mon Sep 17 00:00:00 2001 From: Bel LaPointe <153096461+breel-render@users.noreply.github.com> Date: Wed, 15 May 2024 21:57:05 -0400 Subject: [PATCH 01/46] pttodoer --- pttodoer/Cargo.lock | 291 +++++++++++++++++++++++++++++++++++++++++++ pttodoer/Cargo.toml | 2 + pttodoer/src/main.rs | 80 +++++++++++- 3 files changed, 367 insertions(+), 6 deletions(-) diff --git a/pttodoer/Cargo.lock b/pttodoer/Cargo.lock index 41ab27c..cdf2ac2 100644 --- a/pttodoer/Cargo.lock +++ b/pttodoer/Cargo.lock @@ -2,6 +2,74 @@ # It is not intended for manual editing. version = 3 +[[package]] +name = "aho-corasick" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" +dependencies = [ + "memchr", +] + +[[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 = "autocfg" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c4b4d0bd25bd0b74681c0ad21497610ce1b7c91b1022cd21c80c6fbdd9476b0" + +[[package]] +name = "bumpalo" +version = "3.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c" + +[[package]] +name = "cc" +version = "1.0.97" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "099a5357d84c4c61eb35fc8eafa9a79a902c2f76911e5747ced4e032edd8d9b4" + +[[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.38" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a21f936df1771bf62b77f047b726c4625ff2e8aa607c01ec06e5a05bd8463401" +dependencies = [ + "android-tzdata", + "iana-time-zone", + "js-sys", + "num-traits", + "wasm-bindgen", + "windows-targets", +] + +[[package]] +name = "core-foundation-sys" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06ea2b9bc92be3c2baa9334a323ebca2d6f074ff852cd1d7b11064035cd3868f" + [[package]] name = "equivalent" version = "1.0.1" @@ -14,6 +82,29 @@ version = "0.14.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" +[[package]] +name = "iana-time-zone" +version = "0.1.60" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7ffbb5a1b541ea2561f8c41c087286cc091e21e556a4f09a8f6cbf17b69b141" +dependencies = [ + "android_system_properties", + "core-foundation-sys", + "iana-time-zone-haiku", + "js-sys", + "wasm-bindgen", + "windows-core", +] + +[[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 = "indexmap" version = "2.2.6" @@ -30,6 +121,48 @@ version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" +[[package]] +name = "js-sys" +version = "0.3.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29c15563dc2726973df627357ce0c9ddddbea194836909d655df6a75d2cf296d" +dependencies = [ + "wasm-bindgen", +] + +[[package]] +name = "libc" +version = "0.2.154" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae743338b92ff9146ce83992f766a31066a91a8c84a45e0e9f21e7cf6de6d346" + +[[package]] +name = "log" +version = "0.4.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90ed8c1e510134f979dbc4f070f87d4313098b704861a105fe34231c70a3901c" + +[[package]] +name = "memchr" +version = "2.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c8640c5d730cb13ebd907d8d04b52f55ac9a2eec55b440c8892f40d56c76c1d" + +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", +] + +[[package]] +name = "once_cell" +version = "1.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" + [[package]] name = "proc-macro2" version = "1.0.81" @@ -43,6 +176,8 @@ dependencies = [ name = "pttodoer" version = "0.1.0" dependencies = [ + "chrono", + "regex", "serde_yaml", ] @@ -55,6 +190,35 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "regex" +version = "1.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c117dbdfde9c8308975b6a18d71f3f385c89461f7b3fb054288ecf2a2058ba4c" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "86b83b8b9847f9bf95ef68afb0b8e6cdb80f498442f5179a29fad448fcc1eaea" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "adad44e29e4c806119491a7f06f03de4d1af22c3a680dd47f1e6e179439d1f56" + [[package]] name = "ryu" version = "1.0.18" @@ -116,3 +280,130 @@ name = "unsafe-libyaml" version = "0.2.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "673aac59facbab8a9007c7f6108d11f63b603f7cabff99fabf650fea5c32b861" + +[[package]] +name = "wasm-bindgen" +version = "0.2.92" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4be2531df63900aeb2bca0daaaddec08491ee64ceecbee5076636a3b026795a8" +dependencies = [ + "cfg-if", + "wasm-bindgen-macro", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.92" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "614d787b966d3989fa7bb98a654e369c762374fd3213d212cfc0251257e747da" +dependencies = [ + "bumpalo", + "log", + "once_cell", + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.92" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1f8823de937b71b9460c0c34e25f3da88250760bec0ebac694b49997550d726" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.92" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e94f17b526d0a461a191c78ea52bbce64071ed5c04c9ffe424dcb38f74171bb7" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.92" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af190c94f2773fdb3729c55b007a722abb5384da03bc0986df4c289bf5567e96" + +[[package]] +name = "windows-core" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-targets" +version = "0.52.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f0713a46559409d202e70e28227288446bf7841d3211583a4b53e3f6d96e7eb" +dependencies = [ + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_gnullvm", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7088eed71e8b8dda258ecc8bac5fb1153c5cffaf2578fc8ff5d61e23578d3263" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9985fd1504e250c615ca5f281c3f7a6da76213ebd5ccc9561496568a2752afb6" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88ba073cf16d5372720ec942a8ccbf61626074c6d4dd2e745299726ce8b89670" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87f4261229030a858f36b459e748ae97545d6f1ec60e5e0d6a3d32e0dc232ee9" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db3c2bf3d13d5b658be73463284eaf12830ac9a26a90c717b7f771dfe97487bf" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e4246f76bdeff09eb48875a0fd3e2af6aada79d409d33011886d3e1581517d9" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "852298e482cd67c356ddd9570386e2862b5673c85bd5f88df9ab6802b334c596" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bec47e5bfd1bff0eeaf6d8b485cc1074891a197ab4225d504cb7a1ab88b02bf0" diff --git a/pttodoer/Cargo.toml b/pttodoer/Cargo.toml index 4ce5496..00b3822 100644 --- a/pttodoer/Cargo.toml +++ b/pttodoer/Cargo.toml @@ -6,4 +6,6 @@ edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] +chrono = "0.4.38" +regex = "1.10.4" serde_yaml = "0.9.34" diff --git a/pttodoer/src/main.rs b/pttodoer/src/main.rs index 659b2d4..30623de 100644 --- a/pttodoer/src/main.rs +++ b/pttodoer/src/main.rs @@ -1,4 +1,7 @@ use serde_yaml; +use chrono::DateTime; +use chrono::naive::NaiveDateTime; +use regex::Regex; fn main() { println!("{:?}", Task::new()) @@ -12,20 +15,85 @@ struct Task(serde_yaml::Mapping); //pub sub: Vec, impl Task { + fn new() -> Task { + Task(serde_yaml::Mapping::new()) + } + fn is_due(&self) -> bool { assert!(false); false } } -/* -#[derive(Debug, Clone)] +#[derive(Debug)] struct When(String); impl When { - fn is_due(&self) -> bool { - assert!(false); - false + fn new(src: String) -> Result { + let duration = Regex::new(r"").unwrap(); + match duration.is_match(&src) { + _ => {}, + }; + Err("not impl: Duration".to_string()) + + //Err("not impl: TS".to_string()) + //Err("not impl: cron".to_string()) + } +} + +#[derive(Debug)] +struct TS(u64); + +impl TS { + fn new(src: String) -> Result { + match DateTime::parse_from_str( + &format!("{} +0000", src), + "%Y-%m-%dT%H:%MZ %z", + ) { + Ok(v) => { return Ok(TS(v.timestamp() as u64)) }, + _ => {}, + }; + match NaiveDateTime::parse_from_str( + &format!("{}:00", src), + "%Y-%m-%dT%H:%M", + ) { + Ok(v) => { return Ok(TS(v.timestamp() as u64)) }, + _ => {}, + }; + Err(format!("cannot parse date format from {}", src)) + } + + fn unix(&self) -> u64 { + self.0 + } + + fn to_string(&self) -> String { + DateTime::from_timestamp(self.0 as i64, 0) + .unwrap() + .format("%Y-%m-%dT%H:%MZ") + .to_string() + } +} + +#[cfg(test)] +mod test_ts { + use super::*; + + #[test] + fn parse() { + match TS::new("2024-05-01T00:00Z".to_string()) { + Ok(ts) => { + assert_eq!(1714521600, ts.unix()); + assert_eq!("2024-05-01T00:00Z", ts.to_string()); + }, + Err(err) => assert!(false, "failed to parse ts: {}", err), + }; + match TS::new("2024-05-01T00".to_string()) { + Ok(ts) => { + assert_eq!(1714521600, ts.unix()); + assert_eq!("2024-05-01T00:00Z", ts.to_string()); + }, + Err(err) => assert!(false, "failed to parse ts: {}", err), + }; } } -*/ From 233967e1770931d2e8c62514396b43e8a8bb5ad0 Mon Sep 17 00:00:00 2001 From: Bel LaPointe <153096461+breel-render@users.noreply.github.com> Date: Wed, 15 May 2024 22:24:21 -0400 Subject: [PATCH 02/46] okeydoke --- pttodoer/src/main.rs | 120 +++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 116 insertions(+), 4 deletions(-) diff --git a/pttodoer/src/main.rs b/pttodoer/src/main.rs index 30623de..d625f7f 100644 --- a/pttodoer/src/main.rs +++ b/pttodoer/src/main.rs @@ -30,14 +30,126 @@ struct When(String); impl When { fn new(src: String) -> Result { - let duration = Regex::new(r"").unwrap(); + match Self::new_duration(src.clone()) { + Some(x) => { return Ok(x); }, + None => {}, + }; + match Self::new_ts(src.clone()) { + Some(x) => { return Ok(x); }, + None => {}, + }; + match Self::new_cron(src.clone()) { + Some(x) => { return Ok(x); }, + None => {}, + }; + Err(format!("cannot parse when {}", src)) + } + + fn new_duration(src: String) -> Option { + match Duration::new(src.clone()) { + Ok(_) => Some(When{0: src.clone()}), + _ => None, + } + } + + fn new_ts(src: String) -> Option { + match TS::new(src.clone()) { + Ok(_) => Some(When{0: src.clone()}), + _ => None, + } + } + + fn new_cron(src: String) -> Option { + match Cron::new(src.clone()) { + Ok(_) => Some(When{0: src.clone()}), + _ => None, + } + } +} + +#[cfg(test)] +mod test_when { + use super::*; + + #[test] + fn parse() { + match When::new("1d2h3m".to_string()) { + Ok(when) => { + assert!(false, "not impl: {:?}", when); + }, + Err(err) => assert!(false, "failed to parse when: {}", err), + }; + } +} + +#[derive(Debug)] +struct Cron(String); + +impl Cron { + fn new(src: String) -> Result { + Err("not impl".to_string()) + } +} + +#[derive(Debug)] +struct Duration(u64); + +impl Duration { + fn new(src: String) -> Result { + let duration = Regex::new(r"([0-9]+d)?([0-9]+h)?([0-9]+m)?").unwrap(); match duration.is_match(&src) { + false => { return Err("ill formatted duration".to_string()); }, _ => {}, }; - Err("not impl: Duration".to_string()) + let caps = duration.captures(&src).unwrap(); + let mut sum: u64 = 0; + match caps.get(1) { + Some(d) => { sum += 60 * 60 * 24 * Self::to_n(d.as_str()); }, + _ => {}, + }; + match caps.get(2) { + Some(h) => { sum += 60 * 60 * Self::to_n(h.as_str()); }, + _ => {}, + }; + match caps.get(3) { + Some(m) => { sum += 60 * Self::to_n(m.as_str()); }, + _ => {}, + }; + Ok(Duration{0: sum}) + } - //Err("not impl: TS".to_string()) - //Err("not impl: cron".to_string()) + fn to_n(s: &str) -> u64 { + let s = s.to_string(); + let (s, _) = s.split_at(s.len()-1); + match s.parse::() { + Ok(n) => n, + _ => 0, + } + } +} + +#[cfg(test)] +mod test_duration { + use super::*; + + #[test] + fn parse() { + match Duration::new("1d2h3m".to_string()) { + Ok(d) => assert_eq!(60*60*24 + 60*60*2 + 60*3, d.0), + Err(err) => assert!(false, "failed to parse duration: {}", err), + }; + match Duration::new("1d".to_string()) { + Ok(d) => assert_eq!(60*60*24, d.0), + Err(err) => assert!(false, "failed to parse duration: {}", err), + }; + match Duration::new("2h".to_string()) { + Ok(d) => assert_eq!(60*60*2, d.0), + Err(err) => assert!(false, "failed to parse duration: {}", err), + }; + match Duration::new("3m".to_string()) { + Ok(d) => assert_eq!(60*3, d.0), + Err(err) => assert!(false, "failed to parse duration: {}", err), + }; } } From 66f533a2106065071feb704096f86dff48cd3295 Mon Sep 17 00:00:00 2001 From: Bel LaPointe <153096461+breel-render@users.noreply.github.com> Date: Wed, 15 May 2024 22:27:16 -0400 Subject: [PATCH 03/46] okeydoke --- pttodoer/src/main.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pttodoer/src/main.rs b/pttodoer/src/main.rs index d625f7f..915b913 100644 --- a/pttodoer/src/main.rs +++ b/pttodoer/src/main.rs @@ -75,7 +75,7 @@ mod test_when { fn parse() { match When::new("1d2h3m".to_string()) { Ok(when) => { - assert!(false, "not impl: {:?}", when); + assert_eq!(1714521600 + 60*3 + 60*60*2 + 60*60*24*1, when.next(TS::new("2024-05-01T00").unwrap())); }, Err(err) => assert!(false, "failed to parse when: {}", err), }; From 00813e39a91315597a3e8e53a77cfe9aeb8080db Mon Sep 17 00:00:00 2001 From: Bel LaPointe <153096461+breel-render@users.noreply.github.com> Date: Wed, 15 May 2024 22:53:34 -0400 Subject: [PATCH 04/46] close tho --- pttodoer/Cargo.lock | 10 +++++ pttodoer/Cargo.toml | 1 + pttodoer/src/main.rs | 91 +++++++++++++++++++++++++++++++++++++++++++- 3 files changed, 100 insertions(+), 2 deletions(-) diff --git a/pttodoer/Cargo.lock b/pttodoer/Cargo.lock index cdf2ac2..a4b158a 100644 --- a/pttodoer/Cargo.lock +++ b/pttodoer/Cargo.lock @@ -70,6 +70,15 @@ version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "06ea2b9bc92be3c2baa9334a323ebca2d6f074ff852cd1d7b11064035cd3868f" +[[package]] +name = "croner" +version = "2.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "516aad5374ea0ea75a0f0f4512fb4e7ad46c5eeff9971cb8ebc8fd74f1cd16c1" +dependencies = [ + "chrono", +] + [[package]] name = "equivalent" version = "1.0.1" @@ -177,6 +186,7 @@ name = "pttodoer" version = "0.1.0" dependencies = [ "chrono", + "croner", "regex", "serde_yaml", ] diff --git a/pttodoer/Cargo.toml b/pttodoer/Cargo.toml index 00b3822..053dd57 100644 --- a/pttodoer/Cargo.toml +++ b/pttodoer/Cargo.toml @@ -7,5 +7,6 @@ edition = "2021" [dependencies] chrono = "0.4.38" +croner = "2.0.4" regex = "1.10.4" serde_yaml = "0.9.34" diff --git a/pttodoer/src/main.rs b/pttodoer/src/main.rs index 915b913..ef30f21 100644 --- a/pttodoer/src/main.rs +++ b/pttodoer/src/main.rs @@ -2,6 +2,7 @@ use serde_yaml; use chrono::DateTime; use chrono::naive::NaiveDateTime; use regex::Regex; +use croner; fn main() { println!("{:?}", Task::new()) @@ -65,6 +66,29 @@ impl When { _ => None, } } + + fn next(&self, now: TS) -> TS { + match Duration::new(self.0.clone()) { + Ok(duration) => { + return TS::from_unix( + now.unix() + duration.0 + ); + }, + _ => {}, + }; + match TS::new(self.0.clone()) { + Ok(ts) => { return ts; }, + _ => {}, + }; + match Cron::new(self.0.clone()) { + Ok(x) => { + return x.next(now); + }, + _ => {}, + }; + assert!(false, "invalid when cooked"); + now + } } #[cfg(test)] @@ -75,7 +99,34 @@ mod test_when { fn parse() { match When::new("1d2h3m".to_string()) { Ok(when) => { - assert_eq!(1714521600 + 60*3 + 60*60*2 + 60*60*24*1, when.next(TS::new("2024-05-01T00").unwrap())); + assert_eq!( + 1714521600 + 60*3 + 60*60*2 + 60*60*24*1, + when.next( + TS::new("2024-05-01T00".to_string()) + .unwrap() + ).unix() + ); + }, + Err(err) => assert!(false, "failed to parse when: {}", err), + }; + match When::new("2024-05-01T00:00Z".to_string()) { + Ok(when) => { + assert_eq!( + 1714521600 , + when.next( + TS::new("2024-05-01T00".to_string()) + .unwrap() + ).unix() + ); + }, + Err(err) => assert!(false, "failed to parse when: {}", err), + }; + match When::new("0 1 * * *".to_string()) { + Ok(when) => { + assert_eq!( + 1714521600 + 60*60, + when.next(TS::from_unix(1714521600)).unix() + ); }, Err(err) => assert!(false, "failed to parse when: {}", err), }; @@ -87,10 +138,42 @@ struct Cron(String); impl Cron { fn new(src: String) -> Result { - Err("not impl".to_string()) + match croner::Cron::new(src.as_str()).parse() { + Ok(_) => Ok(Cron{0: src}), + Err(msg) => Err(format!("bad cron: {}", msg)), + } + } + + fn next(&self, now: TS) -> TS { + match croner::Cron::new(self.0.as_str()).parse() { + Ok(c) => match c.find_next_occurrence( + &DateTime::from_timestamp(now.unix() as i64, 0).unwrap(), + true, + ) { + Ok(dt) => { + return TS::from_unix(dt.timestamp() as u64); + }, + Err(_) => TS::from_unix(0), + }, + _ => TS::from_unix(0), + } } } +#[cfg(test)] +mod test_cron { + use super::*; + + #[test] + fn parse() { + match Cron::new("1 * * * *".to_string()) { + Ok(c) => assert_eq!(1714525200+60, c.next(TS::from_unix(1714525200)).unix()), + Err(err) => assert!(false, "failed to parse cron: {}", err), + }; + } +} + + #[derive(Debug)] struct Duration(u64); @@ -157,6 +240,10 @@ mod test_duration { struct TS(u64); impl TS { + fn from_unix(src: u64) -> TS { + TS{0: src} + } + fn new(src: String) -> Result { match DateTime::parse_from_str( &format!("{} +0000", src), From a627223e8a84f0bac8068cb347223963cbe3153d Mon Sep 17 00:00:00 2001 From: Bel LaPointe <153096461+breel-render@users.noreply.github.com> Date: Wed, 15 May 2024 22:54:37 -0400 Subject: [PATCH 05/46] oop --- pttodoer/src/main.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/pttodoer/src/main.rs b/pttodoer/src/main.rs index ef30f21..27f6afd 100644 --- a/pttodoer/src/main.rs +++ b/pttodoer/src/main.rs @@ -82,6 +82,7 @@ impl When { }; match Cron::new(self.0.clone()) { Ok(x) => { + eprintln!("cron::new({:?}) => {:?}", self.0.clone(), x.clone()); return x.next(now); }, _ => {}, From 4f4e7fd907a0f2c154f692d1d814b0f3b7fcf356 Mon Sep 17 00:00:00 2001 From: Bel LaPointe <153096461+breel-render@users.noreply.github.com> Date: Wed, 15 May 2024 22:56:40 -0400 Subject: [PATCH 06/46] oooo tests pass --- pttodoer/src/main.rs | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/pttodoer/src/main.rs b/pttodoer/src/main.rs index 27f6afd..8ffe24c 100644 --- a/pttodoer/src/main.rs +++ b/pttodoer/src/main.rs @@ -77,12 +77,13 @@ impl When { _ => {}, }; match TS::new(self.0.clone()) { - Ok(ts) => { return ts; }, + Ok(ts) => { + return ts; + }, _ => {}, }; match Cron::new(self.0.clone()) { Ok(x) => { - eprintln!("cron::new({:?}) => {:?}", self.0.clone(), x.clone()); return x.next(now); }, _ => {}, @@ -180,7 +181,10 @@ struct Duration(u64); impl Duration { fn new(src: String) -> Result { - let duration = Regex::new(r"([0-9]+d)?([0-9]+h)?([0-9]+m)?").unwrap(); + if src.len() == 0 { + return Err("no empty duration".to_string()); + } + let duration = Regex::new(r"^([0-9]+d)?([0-9]+h)?([0-9]+m)?$").unwrap(); match duration.is_match(&src) { false => { return Err("ill formatted duration".to_string()); }, _ => {}, From 4154e1aad87e4ed97f0ee1e89a6e6eb3b127a6d2 Mon Sep 17 00:00:00 2001 From: Bel LaPointe <153096461+breel-render@users.noreply.github.com> Date: Wed, 15 May 2024 23:05:34 -0400 Subject: [PATCH 07/46] Task has crud --- pttodoer/src/main.rs | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/pttodoer/src/main.rs b/pttodoer/src/main.rs index 8ffe24c..fb9af62 100644 --- a/pttodoer/src/main.rs +++ b/pttodoer/src/main.rs @@ -24,6 +24,40 @@ impl Task { assert!(false); false } + + fn get(&self, k: String) -> Option { + match self.0.get(k) { + Some(v) => Some( + serde_yaml::to_string(v) + .unwrap() + .trim() + .to_string() + ), + None => None, + } + } + + fn set(&mut self, k: String, v: String) { + self.0.insert( + serde_yaml::Value::String(k), + serde_yaml::Value::String(v) + ); + } +} + +#[cfg(test)] +mod test_task { + use super::*; + + #[test] + fn crud() { + let mut t = Task::new(); + t.set("k".to_string(), "v".to_string()); + assert_eq!( + t.get("k".to_string()), + Some("v".to_string()), + ); + } } #[derive(Debug)] From 931edec4a4465150e75f7d77f0e4f607f589bf84 Mon Sep 17 00:00:00 2001 From: Bel LaPointe <153096461+breel-render@users.noreply.github.com> Date: Wed, 15 May 2024 23:15:09 -0400 Subject: [PATCH 08/46] wip --- pttodoer/src/main.rs | 31 ++++++++++++++++++++++++++++--- 1 file changed, 28 insertions(+), 3 deletions(-) diff --git a/pttodoer/src/main.rs b/pttodoer/src/main.rs index fb9af62..0c29fb1 100644 --- a/pttodoer/src/main.rs +++ b/pttodoer/src/main.rs @@ -1,5 +1,5 @@ use serde_yaml; -use chrono::DateTime; +use chrono::{DateTime, Local}; use chrono::naive::NaiveDateTime; use regex::Regex; use croner; @@ -21,8 +21,29 @@ impl Task { } fn is_due(&self) -> bool { - assert!(false); - false + self.is_due_now(TS::now()) + } + + fn is_due_now(&self, now: TS) -> bool { + match self.get("schedule".to_string()) { + Some(v) => { + match When::new(v) { + Ok(when) => now.unix() <= when.next(self.ts()).unix(), + Err(_) => true, + } + }, + None => true, + } + } + + fn ts(&self) -> TS { + match self.get("ts".to_string()) { + Some(v) => match TS::new(v) { + Ok(ts) => ts, + Err(_) => TS::from_unix(0), + }, + None => TS::from_unix(0), + } } fn get(&self, k: String) -> Option { @@ -279,6 +300,10 @@ mod test_duration { struct TS(u64); impl TS { + fn now() -> TS { + Self::from_unix(Local::now().timestamp() as u64) + } + fn from_unix(src: u64) -> TS { TS{0: src} } From 2a04a030f770b0e0f77b765d1d17a292cbddb424 Mon Sep 17 00:00:00 2001 From: Bel LaPointe <153096461+breel-render@users.noreply.github.com> Date: Wed, 15 May 2024 23:16:26 -0400 Subject: [PATCH 09/46] functions good --- pttodoer/src/main.rs | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/pttodoer/src/main.rs b/pttodoer/src/main.rs index 0c29fb1..73213c0 100644 --- a/pttodoer/src/main.rs +++ b/pttodoer/src/main.rs @@ -25,17 +25,22 @@ impl Task { } fn is_due_now(&self, now: TS) -> bool { - match self.get("schedule".to_string()) { - Some(v) => { - match When::new(v) { - Ok(when) => now.unix() <= when.next(self.ts()).unix(), - Err(_) => true, - } - }, + match self.when() { + Some(when) => now.unix() <= when.next(self.ts()).unix(), None => true, } } + fn when(&self) -> Option { + match self.get("schedule".to_string()) { + Some(v) => match When::new(v) { + Ok(when) => Some(when), + Err(_) => None, + }, + None => None, + } + } + fn ts(&self) -> TS { match self.get("ts".to_string()) { Some(v) => match TS::new(v) { From 16392861893183d2394d27851a8e9499b0ab71a0 Mon Sep 17 00:00:00 2001 From: Bel LaPointe <153096461+breel-render@users.noreply.github.com> Date: Wed, 15 May 2024 23:27:03 -0400 Subject: [PATCH 10/46] so close tho --- pttodoer/src/main.rs | 36 ++++++++++++++++++++++++++++++------ 1 file changed, 30 insertions(+), 6 deletions(-) diff --git a/pttodoer/src/main.rs b/pttodoer/src/main.rs index 73213c0..8ed1ed8 100644 --- a/pttodoer/src/main.rs +++ b/pttodoer/src/main.rs @@ -26,7 +26,10 @@ impl Task { fn is_due_now(&self, now: TS) -> bool { match self.when() { - Some(when) => now.unix() <= when.next(self.ts()).unix(), + Some(when) => { + eprintln!("next: {}", when.next(self.ts()).to_string()); + now.unix() <= when.next(self.ts()).unix() + }, None => true, } } @@ -35,7 +38,10 @@ impl Task { match self.get("schedule".to_string()) { Some(v) => match When::new(v) { Ok(when) => Some(when), - Err(_) => None, + Err(msg) => { + eprintln!("Task.when(): {}", msg); + return None; + }, }, None => None, } @@ -55,9 +61,12 @@ impl Task { match self.0.get(k) { Some(v) => Some( serde_yaml::to_string(v) - .unwrap() - .trim() - .to_string() + .unwrap() + .to_string() + .trim() + .trim_start_matches('\'') + .trim_end_matches( '\'') + .to_string() ), None => None, } @@ -84,6 +93,17 @@ mod test_task { Some("v".to_string()), ); } + + #[test] + fn is_due() { + let mut t = Task::new(); + assert!(t.is_due()); + t.set("ts".to_string(), "61".to_string()); + t.set("schedule".to_string(), "* * * * *".to_string()); + assert!(!t.is_due_now(TS::from_unix(119))); + assert!(t.is_due_now(TS::from_unix(120))); + assert!(t.is_due_now(TS::from_unix(121))); + } } #[derive(Debug)] @@ -103,7 +123,7 @@ impl When { Some(x) => { return Ok(x); }, None => {}, }; - Err(format!("cannot parse when {}", src)) + Err(format!("cannot parse when: {}", src)) } fn new_duration(src: String) -> Option { @@ -228,6 +248,10 @@ mod test_cron { #[test] fn parse() { + match Cron::new("* * * * *".to_string()) { + Ok(c) => {} + Err(err) => assert!(false, "failed to parse cron: {}", err), + }; match Cron::new("1 * * * *".to_string()) { Ok(c) => assert_eq!(1714525200+60, c.next(TS::from_unix(1714525200)).unix()), Err(err) => assert!(false, "failed to parse cron: {}", err), From 3c132528e346b85667773b6cd79994ee573a3c99 Mon Sep 17 00:00:00 2001 From: Bel LaPointe <153096461+breel-render@users.noreply.github.com> Date: Wed, 15 May 2024 23:33:38 -0400 Subject: [PATCH 11/46] ok that was fun --- pttodoer/src/main.rs | 44 +++++++++++++++++++++++++++++++++++--------- 1 file changed, 35 insertions(+), 9 deletions(-) diff --git a/pttodoer/src/main.rs b/pttodoer/src/main.rs index 8ed1ed8..59f157a 100644 --- a/pttodoer/src/main.rs +++ b/pttodoer/src/main.rs @@ -11,7 +11,6 @@ fn main() { #[derive(Debug)] struct Task(serde_yaml::Mapping); //pub todo: String, - //pub when: Option, //pub detail: Option, //pub sub: Vec, @@ -27,8 +26,7 @@ impl Task { fn is_due_now(&self, now: TS) -> bool { match self.when() { Some(when) => { - eprintln!("next: {}", when.next(self.ts()).to_string()); - now.unix() <= when.next(self.ts()).unix() + now.unix() >= when.next(self.ts()).unix() }, None => true, } @@ -95,14 +93,42 @@ mod test_task { } #[test] - fn is_due() { + fn is_due_duration() { let mut t = Task::new(); assert!(t.is_due()); - t.set("ts".to_string(), "61".to_string()); - t.set("schedule".to_string(), "* * * * *".to_string()); - assert!(!t.is_due_now(TS::from_unix(119))); - assert!(t.is_due_now(TS::from_unix(120))); - assert!(t.is_due_now(TS::from_unix(121))); + let then = TS::new("2000-01-02T15:00Z".to_string()).unwrap(); + let now = TS::new("2000-01-02T16:00Z".to_string()).unwrap(); + t.set("ts".to_string(), then.to_string()); + t.set("schedule".to_string(), "1h".to_string()); + assert!(!t.is_due_now(TS::from_unix(now.unix()-1))); + assert!(t.is_due_now(TS::from_unix(now.unix()))); + assert!(t.is_due_now(TS::from_unix(now.unix()+1))); + } + + #[test] + fn is_due_schedule() { + let mut t = Task::new(); + assert!(t.is_due()); + let then = TS::new("2000-01-02T15:00Z".to_string()).unwrap(); + let now = TS::new("2000-01-02T16:00Z".to_string()).unwrap(); + t.set("ts".to_string(), then.to_string()); + t.set("schedule".to_string(), "2000-01-02T16:00Z".to_string()); + assert!(!t.is_due_now(TS::from_unix(now.unix()-1))); + assert!(t.is_due_now(TS::from_unix(now.unix()))); + assert!(t.is_due_now(TS::from_unix(now.unix()+1))); + } + + #[test] + fn is_due_cron() { + let mut t = Task::new(); + assert!(t.is_due()); + let then = TS::new("2000-01-02T15:00Z".to_string()).unwrap(); + let now = TS::new("2000-01-02T16:00Z".to_string()).unwrap(); + t.set("ts".to_string(), then.to_string()); + t.set("schedule".to_string(), "0 16 * * *".to_string()); + assert!(!t.is_due_now(TS::from_unix(now.unix()-1))); + assert!(t.is_due_now(TS::from_unix(now.unix()))); + assert!(t.is_due_now(TS::from_unix(now.unix()+1))); } } From 039c4dad0485b3fb2024c521aa382231ead16720 Mon Sep 17 00:00:00 2001 From: Bel LaPointe <153096461+breel-render@users.noreply.github.com> Date: Wed, 22 May 2024 14:04:38 -0400 Subject: [PATCH 12/46] okcool --- pttodoer/Cargo.lock | 9 ++-- pttodoer/Cargo.toml | 1 + pttodoer/src/main.rs | 101 ++++++++++++++++++++++++++++++------------- 3 files changed, 77 insertions(+), 34 deletions(-) diff --git a/pttodoer/Cargo.lock b/pttodoer/Cargo.lock index a4b158a..c0d7794 100644 --- a/pttodoer/Cargo.lock +++ b/pttodoer/Cargo.lock @@ -188,6 +188,7 @@ dependencies = [ "chrono", "croner", "regex", + "serde", "serde_yaml", ] @@ -237,18 +238,18 @@ checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" [[package]] name = "serde" -version = "1.0.200" +version = "1.0.202" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ddc6f9cc94d67c0e21aaf7eda3a010fd3af78ebf6e096aa6e2e13c79749cce4f" +checksum = "226b61a0d411b2ba5ff6d7f73a476ac4f8bb900373459cd00fab8512828ba395" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.200" +version = "1.0.202" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "856f046b9400cee3c8c94ed572ecdb752444c24528c035cd35882aad6f492bcb" +checksum = "6048858004bcff69094cd972ed40a32500f153bd3be9f716b2eed2e8217c4838" dependencies = [ "proc-macro2", "quote", diff --git a/pttodoer/Cargo.toml b/pttodoer/Cargo.toml index 053dd57..144d2c7 100644 --- a/pttodoer/Cargo.toml +++ b/pttodoer/Cargo.toml @@ -9,4 +9,5 @@ edition = "2021" chrono = "0.4.38" croner = "2.0.4" regex = "1.10.4" +serde = { version = "1.0.202", features = [ "serde_derive" ] } serde_yaml = "0.9.34" diff --git a/pttodoer/src/main.rs b/pttodoer/src/main.rs index 59f157a..d0a31ff 100644 --- a/pttodoer/src/main.rs +++ b/pttodoer/src/main.rs @@ -1,4 +1,5 @@ use serde_yaml; +use serde; use chrono::{DateTime, Local}; use chrono::naive::NaiveDateTime; use regex::Regex; @@ -8,22 +9,41 @@ fn main() { println!("{:?}", Task::new()) } -#[derive(Debug)] -struct Task(serde_yaml::Mapping); - //pub todo: String, - //pub detail: Option, - //pub sub: Vec, +#[derive(Debug, serde::Serialize, serde::Deserialize)] +pub struct Task(serde_yaml::Mapping); impl Task { - fn new() -> Task { + pub fn new() -> Task { Task(serde_yaml::Mapping::new()) } - fn is_due(&self) -> bool { - self.is_due_now(TS::now()) + pub fn from_value(v: serde_yaml::Value) -> Task { + let mut result = Task::new(); + match v.as_mapping() { + Some(m) => { result.0 = m.clone(); }, + None => { result.set_value("is".to_string(), v); }, + }; + result } - fn is_due_now(&self, now: TS) -> bool { + pub fn from_reader(r: impl std::io::Read) -> Result, String> { + let mut result = vec![]; + match serde_yaml::from_reader::<_, Vec>(r) { + Ok(v) => { + result.extend(v.iter().map(|x| { + Task::from_value(x.clone()) + })); + Ok(result) + }, + Err(msg) => Err(format!("failed to read: {}", msg)), + } + } + + pub fn is_due(&self) -> bool { + self.is_due_at(TS::now()) + } + + fn is_due_at(&self, now: TS) -> bool { match self.when() { Some(when) => { now.unix() >= when.next(self.ts()).unix() @@ -56,32 +76,53 @@ impl Task { } fn get(&self, k: String) -> Option { + match self.get_value(k) { + None => None, + Some(v) => match v.as_str() { + Some(s) => Some(s.to_string()), + None => {assert!(false, "got not a string"); None }, + }, + } + } + + fn get_value(&self, k: String) -> Option { match self.0.get(k) { - Some(v) => Some( - serde_yaml::to_string(v) - .unwrap() - .to_string() - .trim() - .trim_start_matches('\'') - .trim_end_matches( '\'') - .to_string() - ), + Some(v) => Some(v.clone()), None => None, } } fn set(&mut self, k: String, v: String) { + self.set_value(k, serde_yaml::Value::String(v)); + } + + fn set_value(&mut self, k: String, v: serde_yaml::Value) { self.0.insert( serde_yaml::Value::String(k), - serde_yaml::Value::String(v) + v ); } + } #[cfg(test)] mod test_task { use super::*; + #[test] + fn from_reader_testdata() { + let tasks = Task::from_reader( + std::fs::File::open("./src/testdata/mvp.yaml").expect("failed to open file") + ).expect("failed to read file"); + eprintln!("tasks from_reader ./src/testdata/mvp.yaml: {:?}", tasks); + assert_eq!(2, tasks.len()); + assert_eq!(1, tasks[0].0.len()); + assert!(tasks[0].get("is".to_string()).is_some()); + assert_eq!("x".to_string(), tasks[0].get("is".to_string()).unwrap()); + assert_eq!(1, tasks[1].0.len()); + assert_eq!("y and z".to_string(), tasks[1].get("is".to_string()).unwrap()); + } + #[test] fn crud() { let mut t = Task::new(); @@ -100,9 +141,9 @@ mod test_task { let now = TS::new("2000-01-02T16:00Z".to_string()).unwrap(); t.set("ts".to_string(), then.to_string()); t.set("schedule".to_string(), "1h".to_string()); - assert!(!t.is_due_now(TS::from_unix(now.unix()-1))); - assert!(t.is_due_now(TS::from_unix(now.unix()))); - assert!(t.is_due_now(TS::from_unix(now.unix()+1))); + assert!(!t.is_due_at(TS::from_unix(now.unix()-1))); + assert!(t.is_due_at(TS::from_unix(now.unix()))); + assert!(t.is_due_at(TS::from_unix(now.unix()+1))); } #[test] @@ -113,9 +154,9 @@ mod test_task { let now = TS::new("2000-01-02T16:00Z".to_string()).unwrap(); t.set("ts".to_string(), then.to_string()); t.set("schedule".to_string(), "2000-01-02T16:00Z".to_string()); - assert!(!t.is_due_now(TS::from_unix(now.unix()-1))); - assert!(t.is_due_now(TS::from_unix(now.unix()))); - assert!(t.is_due_now(TS::from_unix(now.unix()+1))); + assert!(!t.is_due_at(TS::from_unix(now.unix()-1))); + assert!(t.is_due_at(TS::from_unix(now.unix()))); + assert!(t.is_due_at(TS::from_unix(now.unix()+1))); } #[test] @@ -126,9 +167,9 @@ mod test_task { let now = TS::new("2000-01-02T16:00Z".to_string()).unwrap(); t.set("ts".to_string(), then.to_string()); t.set("schedule".to_string(), "0 16 * * *".to_string()); - assert!(!t.is_due_now(TS::from_unix(now.unix()-1))); - assert!(t.is_due_now(TS::from_unix(now.unix()))); - assert!(t.is_due_now(TS::from_unix(now.unix()+1))); + assert!(!t.is_due_at(TS::from_unix(now.unix()-1))); + assert!(t.is_due_at(TS::from_unix(now.unix()))); + assert!(t.is_due_at(TS::from_unix(now.unix()+1))); } } @@ -275,7 +316,7 @@ mod test_cron { #[test] fn parse() { match Cron::new("* * * * *".to_string()) { - Ok(c) => {} + Ok(_) => {} Err(err) => assert!(false, "failed to parse cron: {}", err), }; match Cron::new("1 * * * *".to_string()) { @@ -375,7 +416,7 @@ impl TS { &format!("{}:00", src), "%Y-%m-%dT%H:%M", ) { - Ok(v) => { return Ok(TS(v.timestamp() as u64)) }, + Ok(v) => { return Ok(TS(v.and_utc().timestamp() as u64)) }, _ => {}, }; Err(format!("cannot parse date format from {}", src)) From c5ed06b76c1e278e7133b334d97c7942793fd811 Mon Sep 17 00:00:00 2001 From: Bel LaPointe <153096461+breel-render@users.noreply.github.com> Date: Wed, 22 May 2024 14:14:14 -0400 Subject: [PATCH 13/46] okcool --- pttodoer/src/main.rs | 25 ++++++++++++++++++- .../src/testdata/mvp.uuid-789-012-abc.yaml | 2 +- 2 files changed, 25 insertions(+), 2 deletions(-) diff --git a/pttodoer/src/main.rs b/pttodoer/src/main.rs index d0a31ff..25462c2 100644 --- a/pttodoer/src/main.rs +++ b/pttodoer/src/main.rs @@ -17,6 +17,13 @@ impl Task { Task(serde_yaml::Mapping::new()) } + pub fn from_reader_value(r: impl std::io::Read) -> Result { + match serde_yaml::from_reader::<_, serde_yaml::Value>(r) { + Ok(v) => Ok(Task::from_value(v)), + Err(msg) => Err(format!("failed to read value: {}", msg)), + } + } + pub fn from_value(v: serde_yaml::Value) -> Task { let mut result = Task::new(); match v.as_mapping() { @@ -114,13 +121,29 @@ mod test_task { let tasks = Task::from_reader( std::fs::File::open("./src/testdata/mvp.yaml").expect("failed to open file") ).expect("failed to read file"); - eprintln!("tasks from_reader ./src/testdata/mvp.yaml: {:?}", tasks); assert_eq!(2, tasks.len()); assert_eq!(1, tasks[0].0.len()); assert!(tasks[0].get("is".to_string()).is_some()); assert_eq!("x".to_string(), tasks[0].get("is".to_string()).unwrap()); assert_eq!(1, tasks[1].0.len()); assert_eq!("y and z".to_string(), tasks[1].get("is".to_string()).unwrap()); + + let task = Task::from_reader_value( + std::fs::File::open("./src/testdata/mvp.uuid-123-456-xyz.yaml").expect("failed to open file") + ).expect("failed to read 123..."); + assert_eq!(1, task.0.len()); + assert!(task.get("is".to_string()).is_some()); + assert_eq!("plaintext".to_string(), task.get("is".to_string()).unwrap()); + + let task = Task::from_reader_value( + std::fs::File::open("./src/testdata/mvp.uuid-789-012-abc.yaml").expect("failed to open file") + ).expect("failed to read 789..."); + assert_eq!(3, task.0.len()); + assert!(task.get("is".to_string()).is_none()); + assert_eq!("todo here".to_string(), task.get("todo".to_string()).unwrap()); + assert_eq!("* * * * *".to_string(), task.get("schedule".to_string()).unwrap()); + assert_eq!("hello world\n".to_string(), task.get("details".to_string()).unwrap()); + assert!(task.is_due()); } #[test] diff --git a/pttodoer/src/testdata/mvp.uuid-789-012-abc.yaml b/pttodoer/src/testdata/mvp.uuid-789-012-abc.yaml index bd3f4fd..37ef90c 100644 --- a/pttodoer/src/testdata/mvp.uuid-789-012-abc.yaml +++ b/pttodoer/src/testdata/mvp.uuid-789-012-abc.yaml @@ -1,4 +1,4 @@ todo: todo here -schedule: * * * * * +schedule: '* * * * *' details: | hello world From 50e9c804730a9abff272afbc8ddbd2eb3d2b9df8 Mon Sep 17 00:00:00 2001 From: Bel LaPointe <153096461+breel-render@users.noreply.github.com> Date: Wed, 22 May 2024 14:30:05 -0400 Subject: [PATCH 14/46] ok got Tasks has due func --- pttodoer/src/main.rs | 123 ++++++++++++++---- .../testdata/tasks_due_scheduled_done.yaml | 10 ++ 2 files changed, 106 insertions(+), 27 deletions(-) create mode 100644 pttodoer/src/testdata/tasks_due_scheduled_done.yaml diff --git a/pttodoer/src/main.rs b/pttodoer/src/main.rs index 25462c2..3855a8b 100644 --- a/pttodoer/src/main.rs +++ b/pttodoer/src/main.rs @@ -10,6 +10,68 @@ fn main() { } #[derive(Debug, serde::Serialize, serde::Deserialize)] +pub struct Tasks(Vec); + +impl Tasks { + pub fn new() -> Tasks { + Tasks(vec![]) + } + + pub fn from_reader(r: impl std::io::Read) -> Result { + let mut result = Tasks::new(); + match serde_yaml::from_reader::<_, Vec>(r) { + Ok(v) => { + result.0.extend(v.iter().map(|x| { + Task::from_value(x.clone()) + })); + Ok(result) + }, + Err(msg) => Err(format!("failed to read: {}", msg)), + } + } + + pub fn len(&self) -> usize { + self.0.len() + } + + pub fn due(&self) -> Tasks { + Tasks(self.0.iter() + .filter(|x| x.is_due()) + .map(|x| x.clone()) + .collect() + ) + } +} + +#[cfg(test)] +mod test_tasks { + use super::*; + + #[test] + fn due() { + let tasks = Tasks::from_reader( + std::fs::File::open("./src/testdata/tasks_due_scheduled_done.yaml").expect("failed to open file") + ).expect("failed to read file"); + eprintln!("{:?}", tasks); + assert_eq!(2, tasks.due().len()); + } + + #[test] + fn from_reader() { + let tasks = Tasks::from_reader( + std::fs::File::open("./src/testdata/mvp.yaml").expect("failed to open file") + ).expect("failed to read file"); + assert_eq!(2, tasks.0.len()); + assert_eq!(1, tasks.0[0].0.len()); + assert!(tasks.0[0].get("is".to_string()).is_some()); + assert_eq!("x".to_string(), tasks.0[0].get("is".to_string()).unwrap()); + assert_eq!(1, tasks.0[1].0.len()); + assert_eq!("y and z".to_string(), tasks.0[1].get("is".to_string()).unwrap()); + } +} + + +#[derive(Debug, serde::Serialize, serde::Deserialize, Clone)] pub struct Task(serde_yaml::Mapping); impl Task { @@ -17,7 +79,7 @@ impl Task { Task(serde_yaml::Mapping::new()) } - pub fn from_reader_value(r: impl std::io::Read) -> Result { + pub fn from_reader(r: impl std::io::Read) -> Result { match serde_yaml::from_reader::<_, serde_yaml::Value>(r) { Ok(v) => Ok(Task::from_value(v)), Err(msg) => Err(format!("failed to read value: {}", msg)), @@ -33,21 +95,12 @@ impl Task { result } - pub fn from_reader(r: impl std::io::Read) -> Result, String> { - let mut result = vec![]; - match serde_yaml::from_reader::<_, Vec>(r) { - Ok(v) => { - result.extend(v.iter().map(|x| { - Task::from_value(x.clone()) - })); - Ok(result) - }, - Err(msg) => Err(format!("failed to read: {}", msg)), - } + pub fn is_done(&self) -> bool { + self.0.len() == 1 && self.get_value("done".to_string()).is_some() } pub fn is_due(&self) -> bool { - self.is_due_at(TS::now()) + self.is_due_at(TS::now()) && !self.is_done() } fn is_due_at(&self, now: TS) -> bool { @@ -87,7 +140,7 @@ impl Task { None => None, Some(v) => match v.as_str() { Some(s) => Some(s.to_string()), - None => {assert!(false, "got not a string"); None }, + None => None, }, } } @@ -117,25 +170,15 @@ mod test_task { use super::*; #[test] - fn from_reader_testdata() { - let tasks = Task::from_reader( - std::fs::File::open("./src/testdata/mvp.yaml").expect("failed to open file") - ).expect("failed to read file"); - assert_eq!(2, tasks.len()); - assert_eq!(1, tasks[0].0.len()); - assert!(tasks[0].get("is".to_string()).is_some()); - assert_eq!("x".to_string(), tasks[0].get("is".to_string()).unwrap()); - assert_eq!(1, tasks[1].0.len()); - assert_eq!("y and z".to_string(), tasks[1].get("is".to_string()).unwrap()); - - let task = Task::from_reader_value( + fn from_reader() { + let task = Task::from_reader( std::fs::File::open("./src/testdata/mvp.uuid-123-456-xyz.yaml").expect("failed to open file") ).expect("failed to read 123..."); assert_eq!(1, task.0.len()); assert!(task.get("is".to_string()).is_some()); assert_eq!("plaintext".to_string(), task.get("is".to_string()).unwrap()); - let task = Task::from_reader_value( + let task = Task::from_reader( std::fs::File::open("./src/testdata/mvp.uuid-789-012-abc.yaml").expect("failed to open file") ).expect("failed to read 789..."); assert_eq!(3, task.0.len()); @@ -146,6 +189,18 @@ mod test_task { assert!(task.is_due()); } + #[test] + fn is_done() { + let mut t = Task::new(); + t.set("done".to_string(), "anything".to_string()); + eprintln!("{:?}", t.0); + assert!(t.is_done()); + + t.set_value("done".to_string(), serde_yaml::Value::Null); + eprintln!("{:?}", t.0); + assert!(t.is_done()); + } + #[test] fn crud() { let mut t = Task::new(); @@ -442,6 +497,13 @@ impl TS { Ok(v) => { return Ok(TS(v.and_utc().timestamp() as u64)) }, _ => {}, }; + match NaiveDateTime::parse_from_str( + &format!("{}T00:00", src), + "%Y-%m-%dT%H:%M", + ) { + Ok(v) => { return Ok(TS(v.and_utc().timestamp() as u64)) }, + _ => {}, + }; Err(format!("cannot parse date format from {}", src)) } @@ -477,5 +539,12 @@ mod test_ts { }, Err(err) => assert!(false, "failed to parse ts: {}", err), }; + match TS::new("2024-05-01".to_string()) { + Ok(ts) => { + assert_eq!(1714521600, ts.unix()); + assert_eq!("2024-05-01T00:00Z", ts.to_string()); + }, + Err(err) => assert!(false, "failed to parse ts: {}", err), + }; } } diff --git a/pttodoer/src/testdata/tasks_due_scheduled_done.yaml b/pttodoer/src/testdata/tasks_due_scheduled_done.yaml new file mode 100644 index 0000000..8279173 --- /dev/null +++ b/pttodoer/src/testdata/tasks_due_scheduled_done.yaml @@ -0,0 +1,10 @@ +- 1do: due + schedule: 2006-04-05 +- 2do: scheduled + schedule: 2099-04-05 +- 3do: repeating scheduled + schedule: 0 0 1 1 * + ts: 2001-02-03T04:05:06Z +- done: + do: done + schedule: 2006-04-05 From c7b0fc8dba872a35e9241a945664f4db0ca2296f Mon Sep 17 00:00:00 2001 From: Bel LaPointe <153096461+breel-render@users.noreply.github.com> Date: Wed, 22 May 2024 14:33:28 -0400 Subject: [PATCH 15/46] less usable key --- pttodoer/src/main.rs | 6 +++--- pttodoer/src/testdata/tasks_due_scheduled_done.yaml | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/pttodoer/src/main.rs b/pttodoer/src/main.rs index 3855a8b..89399b2 100644 --- a/pttodoer/src/main.rs +++ b/pttodoer/src/main.rs @@ -96,7 +96,7 @@ impl Task { } pub fn is_done(&self) -> bool { - self.0.len() == 1 && self.get_value("done".to_string()).is_some() + self.get_value("_done".to_string()).is_some() } pub fn is_due(&self) -> bool { @@ -192,11 +192,11 @@ mod test_task { #[test] fn is_done() { let mut t = Task::new(); - t.set("done".to_string(), "anything".to_string()); + t.set("_done".to_string(), "anything".to_string()); eprintln!("{:?}", t.0); assert!(t.is_done()); - t.set_value("done".to_string(), serde_yaml::Value::Null); + t.set_value("_done".to_string(), serde_yaml::Value::Null); eprintln!("{:?}", t.0); assert!(t.is_done()); } diff --git a/pttodoer/src/testdata/tasks_due_scheduled_done.yaml b/pttodoer/src/testdata/tasks_due_scheduled_done.yaml index 8279173..3654126 100644 --- a/pttodoer/src/testdata/tasks_due_scheduled_done.yaml +++ b/pttodoer/src/testdata/tasks_due_scheduled_done.yaml @@ -5,6 +5,6 @@ - 3do: repeating scheduled schedule: 0 0 1 1 * ts: 2001-02-03T04:05:06Z -- done: +- _done: do: done schedule: 2006-04-05 From 74b02118bc06d7f8768e50bbe6245337180e5676 Mon Sep 17 00:00:00 2001 From: Bel LaPointe <153096461+breel-render@users.noreply.github.com> Date: Wed, 22 May 2024 15:21:11 -0400 Subject: [PATCH 16/46] parse go times with tz --- pttodoer/src/main.rs | 163 +++++++++++++++++++++++++++++- pttodoer/src/testdata/legacy.yaml | 17 ++++ 2 files changed, 175 insertions(+), 5 deletions(-) create mode 100644 pttodoer/src/testdata/legacy.yaml diff --git a/pttodoer/src/main.rs b/pttodoer/src/main.rs index 89399b2..4b6b309 100644 --- a/pttodoer/src/main.rs +++ b/pttodoer/src/main.rs @@ -18,15 +18,63 @@ impl Tasks { } pub fn from_reader(r: impl std::io::Read) -> Result { + let result = Tasks::_from_reader(r)?; + if !result.is_legacy() { + return Ok(result); + } + let mut v2 = Tasks::new(); + for k in vec!["todo", "scheduled", "done"] { + v2.0.extend( + result.0[0] + .get_value(k.to_string()) + .or(Some(serde_yaml::Value::Null)) + .unwrap() + .as_sequence() + .or(Some(&vec![])) + .unwrap() + .iter() + .map(|v| { + Task::from_value(match k { + "done" => { + let mut t = Task::new(); + t.set_value( + "_done".to_string(), + v.clone(), + ); + serde_yaml::Value::from(t.0) + }, + _ => v.clone(), + }) + }) + ); + } + Ok(v2) + } + + fn _from_reader(mut r: impl std::io::Read) -> Result { + let mut buff = String::new(); + match r.read_to_string(&mut buff) { + Err(msg) => { + return Err(format!("failed to read body: {}", msg)); + }, + _ => {} + }; + let mut result = Tasks::new(); - match serde_yaml::from_reader::<_, Vec>(r) { + match serde_yaml::from_str::>(&buff) { Ok(v) => { result.0.extend(v.iter().map(|x| { Task::from_value(x.clone()) })); Ok(result) }, - Err(msg) => Err(format!("failed to read: {}", msg)), + Err(msg) => match Task::from_str(buff) { + Ok(t) => { + result.0.push(t); + Ok(result) + }, + Err(_) => Err(format!("failed to parse yaml: {}", msg)), + }, } } @@ -41,6 +89,13 @@ impl Tasks { .collect() ) } + + fn is_legacy(&self) -> bool { + self.len() == 1 + && self.0[0].get_value("done".to_string()).is_some() + && self.0[0].get_value("scheduled".to_string()).is_some() + && self.0[0].get_value("todo".to_string()).is_some() + } } #[cfg(test)] @@ -56,6 +111,20 @@ mod test_tasks { assert_eq!(2, tasks.due().len()); } + #[test] + fn from_reader_legacy() { + let tasks = Tasks::from_reader( + std::fs::File::open("./src/testdata/legacy.yaml").expect("failed to open file") + ).expect("failed to read file"); + assert_eq!(8, tasks.len()); + assert_eq!(5, tasks.due().len()); + assert_eq!("a".to_string(), tasks.due().0[0].get("is".to_string()).expect("missing 0th is")); + assert_eq!("b".to_string(), tasks.due().0[1].get("todo".to_string()).expect("missing 1st todo")); + assert_eq!("c".to_string(), tasks.due().0[2].get("is".to_string()).expect("missing 2nd is")); + assert_eq!("d".to_string(), tasks.due().0[3].get("todo".to_string()).expect("missing 3rd todo")); + assert_eq!("e".to_string(), tasks.due().0[4].get("todo".to_string()).expect("missing 4th todo")); + } + #[test] fn from_reader() { let tasks = Tasks::from_reader( @@ -79,8 +148,16 @@ impl Task { Task(serde_yaml::Mapping::new()) } - pub fn from_reader(r: impl std::io::Read) -> Result { - match serde_yaml::from_reader::<_, serde_yaml::Value>(r) { + pub fn from_reader(mut r: impl std::io::Read) -> Result { + let mut buff = String::new(); + match r.read_to_string(&mut buff) { + Err(msg) => Err(format!("failed to read body: {}", msg)), + _ => Task::from_str(buff), + } + } + + pub fn from_str(s: String) -> Result { + match serde_yaml::from_str::(&s) { Ok(v) => Ok(Task::from_value(v)), Err(msg) => Err(format!("failed to read value: {}", msg)), } @@ -169,6 +246,17 @@ impl Task { mod test_task { use super::*; + #[test] + fn from_str() { + assert!(Task::from_str("{ invalid".to_string()).is_err()); + assert!(Task::from_str("1".to_string()).is_ok()); + assert!(Task::from_str("'1'".to_string()).is_ok()); + assert!(Task::from_str("null".to_string()).is_ok()); + assert!(Task::from_str("true".to_string()).is_ok()); + assert!(Task::from_str("[]".to_string()).is_ok()); + assert!(Task::from_str("{}".to_string()).is_ok()); + } + #[test] fn from_reader() { let task = Task::from_reader( @@ -483,6 +571,7 @@ impl TS { } fn new(src: String) -> Result { + // %Y-%m-%dT%H:%MZ match DateTime::parse_from_str( &format!("{} +0000", src), "%Y-%m-%dT%H:%MZ %z", @@ -490,6 +579,8 @@ impl TS { Ok(v) => { return Ok(TS(v.timestamp() as u64)) }, _ => {}, }; + + // %Y-%m-%dT%H match NaiveDateTime::parse_from_str( &format!("{}:00", src), "%Y-%m-%dT%H:%M", @@ -497,6 +588,8 @@ impl TS { Ok(v) => { return Ok(TS(v.and_utc().timestamp() as u64)) }, _ => {}, }; + + // %Y-%m-%d match NaiveDateTime::parse_from_str( &format!("{}T00:00", src), "%Y-%m-%dT%H:%M", @@ -504,7 +597,31 @@ impl TS { Ok(v) => { return Ok(TS(v.and_utc().timestamp() as u64)) }, _ => {}, }; - Err(format!("cannot parse date format from {}", src)) + + // Sun Dec 3 23:29:27 EST 2023 + match DateTime::parse_from_str( + &format!("{}", src) + .replace("PDT", "-0800") + .replace("MDT", "-0700") + .replace("EDT", "-0400") + .replace("PST", "-0700") + .replace("MST", "-0600") + .replace("EST", "-0500") + .replace(" 1 ", " 01 ") + .replace(" 2 ", " 02 ") + .replace(" 3 ", " 03 ") + .replace(" 4 ", " 04 ") + .replace(" 5 ", " 05 ") + .replace(" 6 ", " 06 ") + .replace(" 7 ", " 07 ") + .replace(" 8 ", " 08 ") + .replace(" 9 ", " 09 ") + .replace(" ", " "), + "%a %b %d %H:%M:%S %z %Y", + ) { + Ok(v) => Ok(TS(v.timestamp() as u64)), + Err(msg) => Err(format!("failed to parse legacy golang time: {}", msg)), + } } fn unix(&self) -> u64 { @@ -525,6 +642,42 @@ mod test_ts { #[test] fn parse() { + match TS::new("Tue Nov 7 07:33:11 PST 2023".to_string()) { + Ok(ts) => { + assert_eq!("2023-11-07T14:33Z", ts.to_string()); + }, + Err(err) => assert!(false, "failed to parse ts: {}", err), + }; + match TS::new("Sun Jun 4 05:24:32 PDT 2023".to_string()) { + Ok(ts) => { + assert_eq!("2023-06-04T13:24Z", ts.to_string()); + }, + Err(err) => assert!(false, "failed to parse ts: {}", err), + }; + match TS::new("Sat Nov 4 08:36:01 MDT 2023".to_string()) { + Ok(ts) => { + assert_eq!("2023-11-04T15:36Z", ts.to_string()); + }, + Err(err) => assert!(false, "failed to parse ts: {}", err), + }; + match TS::new("Sun Nov 5 22:27:17 MST 2023".to_string()) { + Ok(ts) => { + assert_eq!("2023-11-06T04:27Z", ts.to_string()); + }, + Err(err) => assert!(false, "failed to parse ts: {}", err), + }; + match TS::new("Thu Apr 18 16:17:41 EDT 2024".to_string()) { + Ok(ts) => { + assert_eq!("2024-04-18T20:17Z", ts.to_string()); + }, + Err(err) => assert!(false, "failed to parse ts: {}", err), + }; + match TS::new("Sun Dec 3 23:29:27 EST 2023".to_string()) { + Ok(ts) => { + assert_eq!("2023-12-04T04:29Z", ts.to_string()); + }, + Err(err) => assert!(false, "failed to parse ts: {}", err), + }; match TS::new("2024-05-01T00:00Z".to_string()) { Ok(ts) => { assert_eq!(1714521600, ts.unix()); diff --git a/pttodoer/src/testdata/legacy.yaml b/pttodoer/src/testdata/legacy.yaml new file mode 100644 index 0000000..cd2c153 --- /dev/null +++ b/pttodoer/src/testdata/legacy.yaml @@ -0,0 +1,17 @@ +todo: +- a +- todo: b + schedule: 2000-01-01 +scheduled: +- c +- todo: d + schedule: 2000-02-01 +- todo: e + ts: Sun Dec 3 23:29:27 EST 2023 + schedule: '0 0 1 1 *' +- todo: f + schedule: 2099-02-02 +done: +- g +- todo: h + ts: Sun Dec 3 23:29:27 EST 2023 From 7129723442b4104e9c326bba2eff234f99f3bc95 Mon Sep 17 00:00:00 2001 From: Bel LaPointe <153096461+breel-render@users.noreply.github.com> Date: Wed, 22 May 2024 15:34:04 -0400 Subject: [PATCH 17/46] accept flags but need to accept directories for multifile as buckets --- pttodoer/Cargo.lock | 129 +++++++++++++++++++++++++++++++++++++++++++ pttodoer/Cargo.toml | 1 + pttodoer/src/main.rs | 26 +++++++-- 3 files changed, 151 insertions(+), 5 deletions(-) diff --git a/pttodoer/Cargo.lock b/pttodoer/Cargo.lock index c0d7794..8b9016c 100644 --- a/pttodoer/Cargo.lock +++ b/pttodoer/Cargo.lock @@ -26,6 +26,55 @@ dependencies = [ "libc", ] +[[package]] +name = "anstream" +version = "0.6.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "418c75fa768af9c03be99d17643f93f79bbba589895012a80e3452a19ddda15b" +dependencies = [ + "anstyle", + "anstyle-parse", + "anstyle-query", + "anstyle-wincon", + "colorchoice", + "is_terminal_polyfill", + "utf8parse", +] + +[[package]] +name = "anstyle" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "038dfcf04a5feb68e9c60b21c9625a54c2c0616e79b72b0fd87075a056ae1d1b" + +[[package]] +name = "anstyle-parse" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c03a11a9034d92058ceb6ee011ce58af4a9bf61491aa7e1e59ecd24bd40d22d4" +dependencies = [ + "utf8parse", +] + +[[package]] +name = "anstyle-query" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a64c907d4e79225ac72e2a354c9ce84d50ebb4586dee56c82b3ee73004f537f5" +dependencies = [ + "windows-sys", +] + +[[package]] +name = "anstyle-wincon" +version = "3.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61a38449feb7068f52bb06c12759005cf459ee52bb4adc1d5a7c4322d716fb19" +dependencies = [ + "anstyle", + "windows-sys", +] + [[package]] name = "autocfg" version = "1.3.0" @@ -64,6 +113,52 @@ dependencies = [ "windows-targets", ] +[[package]] +name = "clap" +version = "4.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90bc066a67923782aa8515dbaea16946c5bcc5addbd668bb80af688e53e548a0" +dependencies = [ + "clap_builder", + "clap_derive", +] + +[[package]] +name = "clap_builder" +version = "4.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae129e2e766ae0ec03484e609954119f123cc1fe650337e155d03b022f24f7b4" +dependencies = [ + "anstream", + "anstyle", + "clap_lex", + "strsim", +] + +[[package]] +name = "clap_derive" +version = "4.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "528131438037fd55894f62d6e9f068b8f45ac57ffa77517819645d10aed04f64" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "clap_lex" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "98cc8fbded0c607b7ba9dd60cd98df59af97e84d24e49c8557331cfc26d301ce" + +[[package]] +name = "colorchoice" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b6a852b24ab71dffc585bcb46eaf7959d175cb865a7152e35b348d1b2960422" + [[package]] name = "core-foundation-sys" version = "0.8.6" @@ -91,6 +186,12 @@ version = "0.14.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + [[package]] name = "iana-time-zone" version = "0.1.60" @@ -124,6 +225,12 @@ dependencies = [ "hashbrown", ] +[[package]] +name = "is_terminal_polyfill" +version = "1.70.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8478577c03552c21db0e2724ffb8986a5ce7af88107e6be5d2ee6e158c12800" + [[package]] name = "itoa" version = "1.0.11" @@ -186,6 +293,7 @@ name = "pttodoer" version = "0.1.0" dependencies = [ "chrono", + "clap", "croner", "regex", "serde", @@ -269,6 +377,12 @@ dependencies = [ "unsafe-libyaml", ] +[[package]] +name = "strsim" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" + [[package]] name = "syn" version = "2.0.61" @@ -292,6 +406,12 @@ version = "0.2.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "673aac59facbab8a9007c7f6108d11f63b603f7cabff99fabf650fea5c32b861" +[[package]] +name = "utf8parse" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a" + [[package]] name = "wasm-bindgen" version = "0.2.92" @@ -355,6 +475,15 @@ dependencies = [ "windows-targets", ] +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets", +] + [[package]] name = "windows-targets" version = "0.52.5" diff --git a/pttodoer/Cargo.toml b/pttodoer/Cargo.toml index 144d2c7..1d86bc5 100644 --- a/pttodoer/Cargo.toml +++ b/pttodoer/Cargo.toml @@ -7,6 +7,7 @@ edition = "2021" [dependencies] chrono = "0.4.38" +clap = { version = "4.4.8", features = ["derive"] } croner = "2.0.4" regex = "1.10.4" serde = { version = "1.0.202", features = [ "serde_derive" ] } diff --git a/pttodoer/src/main.rs b/pttodoer/src/main.rs index 4b6b309..437ee1f 100644 --- a/pttodoer/src/main.rs +++ b/pttodoer/src/main.rs @@ -4,9 +4,28 @@ use chrono::{DateTime, Local}; use chrono::naive::NaiveDateTime; use regex::Regex; use croner; +use clap::Parser; fn main() { - println!("{:?}", Task::new()) + let flags = Flags::parse(); + println!("{}", flags.f()); +} + +#[derive(Debug, Parser)] +struct Flags { + #[arg(short = 'f', long = "file", default_value="$PTTODO_FILE")] + f: String, +} + +impl Flags { + fn f(&self) -> String { + if self.f.get(..1) == Some("$") { + return std::env::var( + self.f.get(1..).unwrap() + ).expect("not in env"); + } + self.f.clone() + } } #[derive(Debug, serde::Serialize, serde::Deserialize)] @@ -229,10 +248,6 @@ impl Task { } } - fn set(&mut self, k: String, v: String) { - self.set_value(k, serde_yaml::Value::String(v)); - } - fn set_value(&mut self, k: String, v: serde_yaml::Value) { self.0.insert( serde_yaml::Value::String(k), @@ -628,6 +643,7 @@ impl TS { self.0 } + #[allow(dead_code)] fn to_string(&self) -> String { DateTime::from_timestamp(self.0 as i64, 0) .unwrap() From e90cf0266448069ac410f976150dadaca55b41db Mon Sep 17 00:00:00 2001 From: Bel LaPointe <153096461+breel-render@users.noreply.github.com> Date: Thu, 23 May 2024 15:37:50 -0400 Subject: [PATCH 18/46] revive test func --- pttodoer/src/main.rs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/pttodoer/src/main.rs b/pttodoer/src/main.rs index 437ee1f..e1b5cd5 100644 --- a/pttodoer/src/main.rs +++ b/pttodoer/src/main.rs @@ -248,13 +248,19 @@ impl Task { } } + fn set(&mut self, k: String, v: String) { + self.0.insert( + serde_yaml::Value::String(k), + serde_yaml::Value::String(v), + ); + } + fn set_value(&mut self, k: String, v: serde_yaml::Value) { self.0.insert( serde_yaml::Value::String(k), v ); } - } #[cfg(test)] From c7163268d4ab80ff5f60138065fc8a969fc06b19 Mon Sep 17 00:00:00 2001 From: Bel LaPointe <153096461+breel-render@users.noreply.github.com> Date: Thu, 23 May 2024 15:45:01 -0400 Subject: [PATCH 19/46] stub --- pttodoer/src/main.rs | 33 +++++++++++++++++++++++---------- 1 file changed, 23 insertions(+), 10 deletions(-) diff --git a/pttodoer/src/main.rs b/pttodoer/src/main.rs index e1b5cd5..7b63e9d 100644 --- a/pttodoer/src/main.rs +++ b/pttodoer/src/main.rs @@ -7,24 +7,36 @@ use croner; use clap::Parser; fn main() { - let flags = Flags::parse(); - println!("{}", flags.f()); + let flags = Flags::new(); + println!("{:?}", flags); } #[derive(Debug, Parser)] struct Flags { #[arg(short = 'f', long = "file", default_value="$PTTODO_FILE")] - f: String, + file: String, + + #[arg(short = 'a', long = "add")] + add: Option, + + #[arg(short = 's', long = "add-schedule")] + add_schedule: Option, + + #[arg(short = 'e', long = "edit", default_value="false")] + edit: bool, } impl Flags { - fn f(&self) -> String { - if self.f.get(..1) == Some("$") { - return std::env::var( - self.f.get(1..).unwrap() - ).expect("not in env"); + fn new() -> Flags { + let mut result = Flags::parse(); + + if result.file.get(..1) == Some("$") { + result.file = std::env::var( + result.file.get(1..).unwrap() + ).expect(format!("'{}' unset", result.file).as_str()); } - self.f.clone() + + result } } @@ -248,6 +260,7 @@ impl Task { } } + #[allow(dead_code)] // used in test fn set(&mut self, k: String, v: String) { self.0.insert( serde_yaml::Value::String(k), @@ -649,7 +662,7 @@ impl TS { self.0 } - #[allow(dead_code)] + #[allow(dead_code)] // used in test fn to_string(&self) -> String { DateTime::from_timestamp(self.0 as i64, 0) .unwrap() From 7ad6dad214482b1e2bfc853a26dcec1f6fd76520 Mon Sep 17 00:00:00 2001 From: Bel LaPointe <153096461+breel-render@users.noreply.github.com> Date: Thu, 23 May 2024 16:11:35 -0400 Subject: [PATCH 20/46] gathering info on read --- pttodoer/src/main.rs | 96 ++++++++++++++++++- .../src/testdata/taskss.d/file.d/file.yaml | 1 + pttodoer/src/testdata/taskss.d/files.d/a.yaml | 1 + pttodoer/src/testdata/taskss.d/files.d/b.yaml | 1 + .../src/testdata/taskss.d/single_file.yaml | 4 + 5 files changed, 100 insertions(+), 3 deletions(-) create mode 100644 pttodoer/src/testdata/taskss.d/file.d/file.yaml create mode 100644 pttodoer/src/testdata/taskss.d/files.d/a.yaml create mode 100644 pttodoer/src/testdata/taskss.d/files.d/b.yaml create mode 100644 pttodoer/src/testdata/taskss.d/single_file.yaml diff --git a/pttodoer/src/main.rs b/pttodoer/src/main.rs index 7b63e9d..d00235d 100644 --- a/pttodoer/src/main.rs +++ b/pttodoer/src/main.rs @@ -40,6 +40,86 @@ impl Flags { } } +#[derive(Debug)] +pub struct DB(Vec); + +impl DB { + pub fn new(path: String) -> Result { + let metadata = match std::fs::metadata(path.clone()) { + Ok(v) => Ok(v), + Err(msg) => Err(format!("failed to load {}: {}", path, msg)), + }?; + let mut files = vec![]; + if metadata.is_file() { + files.push(path.clone()); + } else if metadata.is_dir() { + match std::fs::read_dir(path.clone()) { + Ok(paths) => { + files.extend(paths + .filter(|x| x.is_ok()) + .map(|x| x.unwrap()) + .filter(|x| x.metadata().unwrap().is_file()) + .map(|x| x.path().display().to_string()) + ); + Ok(()) + }, + Err(msg) => Err(format!("failed to read {}: {}", path.clone(), msg)), + }?; + } + + let mut result = vec![]; + for file in files { + let item = TasksAndMetadata::new(file)?; + result.push(item); + } + Ok(DB(result)) + } +} + +#[cfg(test)] +mod test_taskss { + use super::*; + + #[test] + fn read_dir_files() { + _ = DB::new("./src/testdata/taskss.d/files.d".to_string()).expect("failed to construct from dir of files"); + } + + #[test] + fn read_dir_file() { + _ = DB::new("./src/testdata/taskss.d/file.d".to_string()).expect("failed to construct from dir of a single file"); + } + + #[test] + fn read_single_file() { + _ = DB::new("./src/testdata/taskss.d/single_file.yaml".to_string()).expect("failed to construct from single file"); + } +} + +#[derive(Debug)] +pub struct TasksAndMetadata { + tasks: Tasks, + file: String, + version: TS, +} + +impl TasksAndMetadata { + pub fn new(file: String) -> Result { + let version = match std::fs::metadata(file.clone()) { + Ok(m) => Ok(TS::from_system_time(m.modified().unwrap())), + Err(msg) => Err(format!("couldnt get version from {}: {}", file, msg)), + }?; + match Tasks::from_file(file.clone()) { + Ok(tasks) => Ok(TasksAndMetadata{ + tasks: tasks, + file: file, + version: version, + }), + Err(msg) => Err(msg), + } + } +} + #[derive(Debug, serde::Serialize, serde::Deserialize)] pub struct Tasks(Vec); @@ -48,6 +128,14 @@ impl Tasks { Tasks(vec![]) } + pub fn from_file(path: String) -> Result { + let r = match std::fs::File::open(path.clone()) { + Ok(f) => Ok(f), + Err(msg) => Err(format!("could not open {}: {}", path, msg)), + }?; + Tasks::from_reader(r) + } + pub fn from_reader(r: impl std::io::Read) -> Result { let result = Tasks::_from_reader(r)?; if !result.is_legacy() { @@ -135,9 +223,7 @@ mod test_tasks { #[test] fn due() { - let tasks = Tasks::from_reader( - std::fs::File::open("./src/testdata/tasks_due_scheduled_done.yaml").expect("failed to open file") - ).expect("failed to read file"); + let tasks = Tasks::from_file("./src/testdata/tasks_due_scheduled_done.yaml".to_string()).expect("failed to open file"); eprintln!("{:?}", tasks); assert_eq!(2, tasks.due().len()); } @@ -600,6 +686,10 @@ impl TS { Self::from_unix(Local::now().timestamp() as u64) } + fn from_system_time(st: std::time::SystemTime) -> TS { + TS::from_unix(st.duration_since(std::time::SystemTime::UNIX_EPOCH).unwrap().as_secs()) + } + fn from_unix(src: u64) -> TS { TS{0: src} } diff --git a/pttodoer/src/testdata/taskss.d/file.d/file.yaml b/pttodoer/src/testdata/taskss.d/file.d/file.yaml new file mode 100644 index 0000000..d4c7c05 --- /dev/null +++ b/pttodoer/src/testdata/taskss.d/file.d/file.yaml @@ -0,0 +1 @@ +- file diff --git a/pttodoer/src/testdata/taskss.d/files.d/a.yaml b/pttodoer/src/testdata/taskss.d/files.d/a.yaml new file mode 100644 index 0000000..46ae6f5 --- /dev/null +++ b/pttodoer/src/testdata/taskss.d/files.d/a.yaml @@ -0,0 +1 @@ +- a diff --git a/pttodoer/src/testdata/taskss.d/files.d/b.yaml b/pttodoer/src/testdata/taskss.d/files.d/b.yaml new file mode 100644 index 0000000..7880ae6 --- /dev/null +++ b/pttodoer/src/testdata/taskss.d/files.d/b.yaml @@ -0,0 +1 @@ +- b diff --git a/pttodoer/src/testdata/taskss.d/single_file.yaml b/pttodoer/src/testdata/taskss.d/single_file.yaml new file mode 100644 index 0000000..47b487a --- /dev/null +++ b/pttodoer/src/testdata/taskss.d/single_file.yaml @@ -0,0 +1,4 @@ +- todo +- do: scheduled + schedule: 2099-01-01 +- _done: any From e81f221910a0c9ac4259a8c671b6f54d3ca466f9 Mon Sep 17 00:00:00 2001 From: Bel LaPointe <153096461+breel-render@users.noreply.github.com> Date: Thu, 23 May 2024 16:11:56 -0400 Subject: [PATCH 21/46] gathering info on read --- pttodoer/src/main.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pttodoer/src/main.rs b/pttodoer/src/main.rs index d00235d..a52adc3 100644 --- a/pttodoer/src/main.rs +++ b/pttodoer/src/main.rs @@ -92,7 +92,8 @@ mod test_taskss { #[test] fn read_single_file() { - _ = DB::new("./src/testdata/taskss.d/single_file.yaml".to_string()).expect("failed to construct from single file"); + let db = DB::new("./src/testdata/taskss.d/single_file.yaml".to_string()).expect("failed to construct from single file"); + assert_eq!(1, db.0.len()); } } From 8f8202282f0c7a5371a189eb61ae363730fe1e90 Mon Sep 17 00:00:00 2001 From: Bel LaPointe <153096461+breel-render@users.noreply.github.com> Date: Thu, 23 May 2024 16:18:06 -0400 Subject: [PATCH 22/46] parent level DB has multiple tasks that each have a file and file modified timestamp associated --- pttodoer/src/main.rs | 36 ++++++++++++++++++++++++++++++++++-- 1 file changed, 34 insertions(+), 2 deletions(-) diff --git a/pttodoer/src/main.rs b/pttodoer/src/main.rs index a52adc3..3d599c0 100644 --- a/pttodoer/src/main.rs +++ b/pttodoer/src/main.rs @@ -74,6 +74,22 @@ impl DB { } Ok(DB(result)) } + + pub fn incomplete(&self) -> Tasks { + let mut result = Tasks::new(); + for set in &self.0 { + result.0.extend(set.tasks.incomplete().0); + } + result + } + + pub fn due(&self) -> Tasks { + let mut result = Tasks::new(); + for set in &self.0 { + result.0.extend(set.tasks.due().0); + } + result + } } #[cfg(test)] @@ -82,18 +98,26 @@ mod test_taskss { #[test] fn read_dir_files() { - _ = DB::new("./src/testdata/taskss.d/files.d".to_string()).expect("failed to construct from dir of files"); + let db = DB::new("./src/testdata/taskss.d/files.d".to_string()).expect("failed to construct from dir of files"); + assert_eq!(2, db.0.len()); + assert_eq!(2, db.due().len()); + assert_eq!(2, db.incomplete().len()); } #[test] fn read_dir_file() { - _ = DB::new("./src/testdata/taskss.d/file.d".to_string()).expect("failed to construct from dir of a single file"); + let db = DB::new("./src/testdata/taskss.d/file.d".to_string()).expect("failed to construct from dir of a single file"); + assert_eq!(1, db.0.len()); + assert_eq!(1, db.due().len()); + assert_eq!(1, db.incomplete().len()); } #[test] fn read_single_file() { let db = DB::new("./src/testdata/taskss.d/single_file.yaml".to_string()).expect("failed to construct from single file"); assert_eq!(1, db.0.len()); + assert_eq!(1, db.due().len()); + assert_eq!(2, db.incomplete().len()); } } @@ -202,6 +226,14 @@ impl Tasks { self.0.len() } + pub fn incomplete(&self) -> Tasks { + Tasks(self.0.iter() + .filter(|x| !x.is_done()) + .map(|x| x.clone()) + .collect() + ) + } + pub fn due(&self) -> Tasks { Tasks(self.0.iter() .filter(|x| x.is_due()) From 4d8fd0597b8daa1145e4cf1254ebbf483ad5ca4f Mon Sep 17 00:00:00 2001 From: Bel LaPointe <153096461+breel-render@users.noreply.github.com> Date: Thu, 23 May 2024 16:36:05 -0400 Subject: [PATCH 23/46] okcool can read and even doesnt wrap so i like that some --- pttodoer/src/main.rs | 46 +++++++++++++++++++++++++++++++++++++------- 1 file changed, 39 insertions(+), 7 deletions(-) diff --git a/pttodoer/src/main.rs b/pttodoer/src/main.rs index 3d599c0..5fa68d2 100644 --- a/pttodoer/src/main.rs +++ b/pttodoer/src/main.rs @@ -1,4 +1,5 @@ use serde_yaml; +use serde::ser::{Serialize}; use serde; use chrono::{DateTime, Local}; use chrono::naive::NaiveDateTime; @@ -8,13 +9,14 @@ use clap::Parser; fn main() { let flags = Flags::new(); - println!("{:?}", flags); + let db = DB::new(flags.path).unwrap(); + println!("{}", db.due().to_string()); } #[derive(Debug, Parser)] struct Flags { - #[arg(short = 'f', long = "file", default_value="$PTTODO_FILE")] - file: String, + #[arg(short = 'f', long = "path", default_value="$PTTODO_FILE")] + path: String, #[arg(short = 'a', long = "add")] add: Option, @@ -30,10 +32,10 @@ impl Flags { fn new() -> Flags { let mut result = Flags::parse(); - if result.file.get(..1) == Some("$") { - result.file = std::env::var( - result.file.get(1..).unwrap() - ).expect(format!("'{}' unset", result.file).as_str()); + if result.path.get(..1) == Some("$") { + result.path = std::env::var( + result.path.get(1..).unwrap() + ).expect(format!("'{}' unset", result.path).as_str()); } result @@ -222,6 +224,32 @@ impl Tasks { } } + pub fn to_string(&self) -> String { + let mut buffer = Vec::new(); + let mut serializer = serde_yaml::Serializer::new(&mut buffer); + _ = serde_yaml::Value::Sequence( + self.0.iter() + .map(|x| { + let mut x = x.clone(); + if x.is_due() { + x.unset("ts".to_string()); + } + x + }) + .map(|x| { + let is = x.get_value("is".to_string()); + if x.0.len() == 1 && is.is_some() { + return is.unwrap(); + } + serde_yaml::Value::from(x.0.clone()) + }) + .map(|x| serde_yaml::Value::from(x.clone())) + .collect() + ).serialize(&mut serializer) + .expect("failed to serialize"); + String::from_utf8(buffer).expect("illegal utf8 characters found") + } + pub fn len(&self) -> usize { self.0.len() } @@ -379,6 +407,10 @@ impl Task { } } + fn unset(&mut self, k: String) { + self.0.remove(serde_yaml::Value::String(k)); + } + #[allow(dead_code)] // used in test fn set(&mut self, k: String, v: String) { self.0.insert( From b1d1c5d2b22aa3527da77096ab3e212b731cd007 Mon Sep 17 00:00:00 2001 From: Bel LaPointe <153096461+breel-render@users.noreply.github.com> Date: Thu, 23 May 2024 16:41:13 -0400 Subject: [PATCH 24/46] todo --- pttodoer/src/main.rs | 29 ++++++++++++++++++++++++++++- 1 file changed, 28 insertions(+), 1 deletion(-) diff --git a/pttodoer/src/main.rs b/pttodoer/src/main.rs index 5fa68d2..47007ff 100644 --- a/pttodoer/src/main.rs +++ b/pttodoer/src/main.rs @@ -9,7 +9,21 @@ use clap::Parser; fn main() { let flags = Flags::new(); - let db = DB::new(flags.path).unwrap(); + + let mut db = DB::new(flags.path).unwrap(); + + match flags.add { + Some(s) => { + let t = match flags.add_schedule { + Some(sch) => Task::from_string_schedule(s, sch), + None => Task::from_string(s), + }; + db.0[0].tasks.0.push(t); + // TODO save + }, + _ => {}, + }; + println!("{}", db.due().to_string()); } @@ -326,6 +340,19 @@ impl Task { Task(serde_yaml::Mapping::new()) } + pub fn from_string(s: String) -> Task { + let mut t = Task::new(); + t.set("is".to_string(), s); + t + } + + pub fn from_string_schedule(s: String, schedule: String) -> Task { + let mut t = Task::new(); + t.set("is".to_string(), s); + t.set("schedule".to_string(), schedule); + t + } + pub fn from_reader(mut r: impl std::io::Read) -> Result { let mut buff = String::new(); match r.read_to_string(&mut buff) { From 3cfaf1f1a0c2c2597c7ac81dee61d6c685443f26 Mon Sep 17 00:00:00 2001 From: Bel LaPointe <153096461+breel-render@users.noreply.github.com> Date: Fri, 14 Jun 2024 08:42:58 -0600 Subject: [PATCH 25/46] when adding w schedule then set ts to now --- pttodoer/src/main.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/pttodoer/src/main.rs b/pttodoer/src/main.rs index 47007ff..c06cd6a 100644 --- a/pttodoer/src/main.rs +++ b/pttodoer/src/main.rs @@ -350,6 +350,7 @@ impl Task { let mut t = Task::new(); t.set("is".to_string(), s); t.set("schedule".to_string(), schedule); + t.set("ts".to_string(), TS::now().to_string()); t } From e389f34bd68daf15e80d8d5a103d6174927505dc Mon Sep 17 00:00:00 2001 From: Bel LaPointe <153096461+breel-render@users.noreply.github.com> Date: Fri, 14 Jun 2024 09:14:07 -0600 Subject: [PATCH 26/46] impl save and dry run --- pttodoer/src/main.rs | 79 +++++++++++++++++++++++++--- pttodoer/src/testdata/.mvp.yaml.done | 2 +- 2 files changed, 73 insertions(+), 8 deletions(-) diff --git a/pttodoer/src/main.rs b/pttodoer/src/main.rs index c06cd6a..239da76 100644 --- a/pttodoer/src/main.rs +++ b/pttodoer/src/main.rs @@ -6,6 +6,7 @@ use chrono::naive::NaiveDateTime; use regex::Regex; use croner; use clap::Parser; +use std::io::Write; fn main() { let flags = Flags::new(); @@ -19,12 +20,11 @@ fn main() { None => Task::from_string(s), }; db.0[0].tasks.0.push(t); - // TODO save }, _ => {}, }; - println!("{}", db.due().to_string()); + db.save(flags.dry_run).expect("failed to save"); } #[derive(Debug, Parser)] @@ -40,6 +40,9 @@ struct Flags { #[arg(short = 'e', long = "edit", default_value="false")] edit: bool, + + #[arg(short = 'd', long = "dry-run", default_value="false")] + dry_run: bool, } impl Flags { @@ -106,6 +109,13 @@ impl DB { } result } + + pub fn save(&self, dry_run: bool) -> Result<(), String> { + for i in &self.0 { + i.save(dry_run)?; + } + Ok(()) + } } #[cfg(test)] @@ -146,10 +156,7 @@ pub struct TasksAndMetadata { impl TasksAndMetadata { pub fn new(file: String) -> Result { - let version = match std::fs::metadata(file.clone()) { - Ok(m) => Ok(TS::from_system_time(m.modified().unwrap())), - Err(msg) => Err(format!("couldnt get version from {}: {}", file, msg)), - }?; + let version = file_version(file.clone())?; match Tasks::from_file(file.clone()) { Ok(tasks) => Ok(TasksAndMetadata{ tasks: tasks, @@ -159,6 +166,60 @@ impl TasksAndMetadata { Err(msg) => Err(msg), } } + + pub fn save(&self, dry_run: bool) -> Result<(), String> { + let version = file_version(self.file.clone())?; + let mut file = self.file.clone(); + if version != self.version { + file = format!("{}.{}", &self.file, &self.version.to_string()); + } + + self.save_as(file.clone(), dry_run)?; + + match file == self.file { + true => Ok(()), + false => Err(format!("{} has been updated", self.file)), + } + } + + fn save_as(&self, file: String, dry_run: bool) -> Result<(), String> { + match dry_run { + true => { + match &file == &self.file { + true => eprintln!("# {}", &file), + false => eprintln!("# {} <- {}", &file, &self.file), + }; + eprintln!("{}", self.tasks.to_string()); + Ok(()) + }, + false => { + match std::fs::File::create(&file) { + Ok(mut f) => { + match f.write_all(self.tasks.to_string().as_bytes()) { + Ok(_) => Ok(()), + Err(msg) => Err(format!("failed to write {}: {}", file, msg)), + } + }, + Err(msg) => Err(format!("failed to create {}: {}", file, msg)), + } + }, + } + } + + //fn debug(&self) -> String { + // format!( + // "# {}\n{}", + // self.file, + // self.tasks.to_string(), + // ) + //} +} + +fn file_version(file: String) -> Result { + match std::fs::metadata(file.clone()) { + Ok(m) => Ok(TS::from_system_time(m.modified().unwrap())), + Err(msg) => Err(format!("couldnt get version from {}: {}", file, msg)), + } } #[derive(Debug, serde::Serialize, serde::Deserialize)] @@ -174,7 +235,10 @@ impl Tasks { Ok(f) => Ok(f), Err(msg) => Err(format!("could not open {}: {}", path, msg)), }?; - Tasks::from_reader(r) + match Tasks::from_reader(r) { + Ok(tasks) => Ok(tasks), + Err(msg) => Err(format!("failed to load from {}: {}", path, msg)), + } } pub fn from_reader(r: impl std::io::Read) -> Result { @@ -772,6 +836,7 @@ mod test_duration { } #[derive(Debug)] +#[derive(PartialEq)] struct TS(u64); impl TS { diff --git a/pttodoer/src/testdata/.mvp.yaml.done b/pttodoer/src/testdata/.mvp.yaml.done index f47c99f..a1fe40a 100644 --- a/pttodoer/src/testdata/.mvp.yaml.done +++ b/pttodoer/src/testdata/.mvp.yaml.done @@ -1,4 +1,4 @@ - todo: a b and c - schedule: * * * * * + schedule: '* * * * *' ts: 123 - d e f From 41cab740286912e483cba5c30f5c1f9d8e4cccec Mon Sep 17 00:00:00 2001 From: Bel LaPointe <153096461+breel-render@users.noreply.github.com> Date: Fri, 14 Jun 2024 09:24:05 -0600 Subject: [PATCH 27/46] eprint dont err on conflict --- pttodoer/src/main.rs | 24 +++++++++++------------- 1 file changed, 11 insertions(+), 13 deletions(-) diff --git a/pttodoer/src/main.rs b/pttodoer/src/main.rs index 239da76..d18a9e1 100644 --- a/pttodoer/src/main.rs +++ b/pttodoer/src/main.rs @@ -167,22 +167,14 @@ impl TasksAndMetadata { } } - pub fn save(&self, dry_run: bool) -> Result<(), String> { + fn save(&self, dry_run: bool) -> Result<(), String> { let version = file_version(self.file.clone())?; let mut file = self.file.clone(); if version != self.version { - file = format!("{}.{}", &self.file, &self.version.to_string()); + file = format!("{}.{}", &self.file, TS::now().to_string()); + eprintln!("saving conflicting {} as {}", &self.file, &file); } - self.save_as(file.clone(), dry_run)?; - - match file == self.file { - true => Ok(()), - false => Err(format!("{} has been updated", self.file)), - } - } - - fn save_as(&self, file: String, dry_run: bool) -> Result<(), String> { match dry_run { true => { match &file == &self.file { @@ -193,10 +185,16 @@ impl TasksAndMetadata { Ok(()) }, false => { - match std::fs::File::create(&file) { + let tmpf = format!("{}.{}", &file, TS::now().to_string()); + match std::fs::File::create(&tmpf) { Ok(mut f) => { match f.write_all(self.tasks.to_string().as_bytes()) { - Ok(_) => Ok(()), + Ok(_) => { + match std::fs::rename(&tmpf, &file) { + Ok(_) => Ok(()), + Err(msg) => Err(format!("failed to flush {}: {}", file, msg)), + } + }, Err(msg) => Err(format!("failed to write {}: {}", file, msg)), } }, From b949502869057f4fa2b8eb2a35d960794d74b37f Mon Sep 17 00:00:00 2001 From: Bel LaPointe <153096461+breel-render@users.noreply.github.com> Date: Fri, 14 Jun 2024 09:26:26 -0600 Subject: [PATCH 28/46] now it gets gross ew --- pttodoer/src/main.rs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/pttodoer/src/main.rs b/pttodoer/src/main.rs index d18a9e1..44e985c 100644 --- a/pttodoer/src/main.rs +++ b/pttodoer/src/main.rs @@ -24,6 +24,10 @@ fn main() { _ => {}, }; + if flags.edit { + db.edit().expect("failed to edit"); + } + db.save(flags.dry_run).expect("failed to save"); } @@ -110,6 +114,10 @@ impl DB { result } + pub fn edit(&self) -> Result<(), String> { + Err("not impl".to_string()) + } + pub fn save(&self, dry_run: bool) -> Result<(), String> { for i in &self.0 { i.save(dry_run)?; From aa9e032c319db320c1302b566508eea0a5b53ace Mon Sep 17 00:00:00 2001 From: Bel LaPointe <153096461+breel-render@users.noreply.github.com> Date: Fri, 14 Jun 2024 09:27:01 -0600 Subject: [PATCH 29/46] tempdir ty --- pttodoer/Cargo.lock | 85 +++++++++++++++++++++++++++++++++++++++++++++ pttodoer/Cargo.toml | 1 + 2 files changed, 86 insertions(+) diff --git a/pttodoer/Cargo.lock b/pttodoer/Cargo.lock index 8b9016c..840a0e3 100644 --- a/pttodoer/Cargo.lock +++ b/pttodoer/Cargo.lock @@ -180,6 +180,12 @@ version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" +[[package]] +name = "fuchsia-cprng" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a06f77d526c1a601b7c4cdd98f54b5eaabffc14d5f2f0296febdc7f357c6d3ba" + [[package]] name = "hashbrown" version = "0.14.5" @@ -298,6 +304,7 @@ dependencies = [ "regex", "serde", "serde_yaml", + "tempdir", ] [[package]] @@ -309,6 +316,43 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "rand" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "552840b97013b1a26992c11eac34bdd778e464601a4c2054b5f0bff7c6761293" +dependencies = [ + "fuchsia-cprng", + "libc", + "rand_core 0.3.1", + "rdrand", + "winapi", +] + +[[package]] +name = "rand_core" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a6fdeb83b075e8266dcc8762c22776f6877a63111121f5f8c7411e5be7eed4b" +dependencies = [ + "rand_core 0.4.2", +] + +[[package]] +name = "rand_core" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c33a3c44ca05fa6f1807d8e6743f3824e8509beca625669633be0acbdf509dc" + +[[package]] +name = "rdrand" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "678054eb77286b51581ba43620cc911abf02758c91f93f479767aed0f90458b2" +dependencies = [ + "rand_core 0.3.1", +] + [[package]] name = "regex" version = "1.10.4" @@ -338,6 +382,15 @@ version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "adad44e29e4c806119491a7f06f03de4d1af22c3a680dd47f1e6e179439d1f56" +[[package]] +name = "remove_dir_all" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3acd125665422973a33ac9d3dd2df85edad0f4ae9b00dafb1a05e43a9f5ef8e7" +dependencies = [ + "winapi", +] + [[package]] name = "ryu" version = "1.0.18" @@ -394,6 +447,16 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "tempdir" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "15f2b5fb00ccdf689e0149d1b1b3c03fead81c2b37735d812fa8bddbbf41b6d8" +dependencies = [ + "rand", + "remove_dir_all", +] + [[package]] name = "unicode-ident" version = "1.0.12" @@ -466,6 +529,28 @@ version = "0.2.92" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "af190c94f2773fdb3729c55b007a722abb5384da03bc0986df4c289bf5567e96" +[[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-core" version = "0.52.0" diff --git a/pttodoer/Cargo.toml b/pttodoer/Cargo.toml index 1d86bc5..d531fdb 100644 --- a/pttodoer/Cargo.toml +++ b/pttodoer/Cargo.toml @@ -12,3 +12,4 @@ croner = "2.0.4" regex = "1.10.4" serde = { version = "1.0.202", features = [ "serde_derive" ] } serde_yaml = "0.9.34" +tempdir = "0.3.7" From 7832d09fe894848ea4f6b4f9486f82067a41ebb9 Mon Sep 17 00:00:00 2001 From: Bel LaPointe <153096461+breel-render@users.noreply.github.com> Date: Fri, 14 Jun 2024 09:49:22 -0600 Subject: [PATCH 30/46] ok vim runs that was ez --- pttodoer/src/main.rs | 58 +++++++++++++++++++++++++++++--------------- 1 file changed, 38 insertions(+), 20 deletions(-) diff --git a/pttodoer/src/main.rs b/pttodoer/src/main.rs index 44e985c..18af025 100644 --- a/pttodoer/src/main.rs +++ b/pttodoer/src/main.rs @@ -7,6 +7,7 @@ use regex::Regex; use croner; use clap::Parser; use std::io::Write; +use tempdir::TempDir; fn main() { let flags = Flags::new(); @@ -115,6 +116,22 @@ impl DB { } pub fn edit(&self) -> Result<(), String> { + let d = TempDir::new(&TS::now().to_string()).expect("failed to create a temp dir"); + for set in &self.0 { + let base = set.file.split("/").last().unwrap().to_string(); + let f = d.path().join(base); + set.save_due_as(f.display().to_string())?; + } + + std::process::Command::new("/bin/sh") + .current_dir(d.path()) + .arg("-c") + .arg("vim -p ./*") + .spawn() + .expect("failed to start vim") + .wait() + .expect("failed to vim"); + Err("not impl".to_string()) } @@ -155,7 +172,7 @@ mod test_taskss { } } -#[derive(Debug)] +#[derive(Debug, Clone)] pub struct TasksAndMetadata { tasks: Tasks, file: String, @@ -175,7 +192,7 @@ impl TasksAndMetadata { } } - fn save(&self, dry_run: bool) -> Result<(), String> { + pub fn save(&self, dry_run: bool) -> Result<(), String> { let version = file_version(self.file.clone())?; let mut file = self.file.clone(); if version != self.version { @@ -192,23 +209,25 @@ impl TasksAndMetadata { eprintln!("{}", self.tasks.to_string()); Ok(()) }, - false => { - let tmpf = format!("{}.{}", &file, TS::now().to_string()); - match std::fs::File::create(&tmpf) { - Ok(mut f) => { - match f.write_all(self.tasks.to_string().as_bytes()) { - Ok(_) => { - match std::fs::rename(&tmpf, &file) { - Ok(_) => Ok(()), - Err(msg) => Err(format!("failed to flush {}: {}", file, msg)), - } - }, - Err(msg) => Err(format!("failed to write {}: {}", file, msg)), - } - }, - Err(msg) => Err(format!("failed to create {}: {}", file, msg)), + false => self.save_as(file), + } + } + + pub fn save_due_as(&self, file: String) -> Result<(), String> { + let mut cloned = self.clone(); + cloned.tasks = cloned.tasks.due(); + cloned.save_as(file) + } + + pub fn save_as(&self, file: String) -> Result<(), String> { + match std::fs::File::create(&file) { + Ok(mut f) => { + match f.write_all(self.tasks.to_string().as_bytes()) { + Ok(_) => Ok(()), + Err(msg) => Err(format!("failed to write {}: {}", file, msg)), } }, + Err(msg) => Err(format!("failed to create {}: {}", file, msg)), } } @@ -228,7 +247,7 @@ fn file_version(file: String) -> Result { } } -#[derive(Debug, serde::Serialize, serde::Deserialize)] +#[derive(Debug, serde::Serialize, serde::Deserialize, Clone)] pub struct Tasks(Vec); impl Tasks { @@ -841,8 +860,7 @@ mod test_duration { } } -#[derive(Debug)] -#[derive(PartialEq)] +#[derive(Debug, PartialEq, Clone)] struct TS(u64); impl TS { From 5a45cd14102a4a1d488099ab2e89dbdaa256ec64 Mon Sep 17 00:00:00 2001 From: Bel LaPointe <153096461+breel-render@users.noreply.github.com> Date: Fri, 14 Jun 2024 09:54:46 -0600 Subject: [PATCH 31/46] comment THE PLAN --- pttodoer/src/main.rs | 61 ++++++++++++++++++++++++++++++++++++-------- 1 file changed, 50 insertions(+), 11 deletions(-) diff --git a/pttodoer/src/main.rs b/pttodoer/src/main.rs index 18af025..97fc891 100644 --- a/pttodoer/src/main.rs +++ b/pttodoer/src/main.rs @@ -123,14 +123,35 @@ impl DB { set.save_due_as(f.display().to_string())?; } - std::process::Command::new("/bin/sh") - .current_dir(d.path()) - .arg("-c") - .arg("vim -p ./*") - .spawn() - .expect("failed to start vim") - .wait() - .expect("failed to vim"); + //std::process::Command::new("/bin/sh") + // .current_dir(d.path()) + // .arg("-c") + // .arg("vim -p ./*") + // .spawn() + // .expect("failed to start vim") + // .wait() + // .expect("failed to vim"); + + for set in &self.0 { + let base = set.file.split("/").last().unwrap().to_string(); + let f = d.path().join(base); + let edited = TasksAndMetadata::new(f.display().to_string()); + + let not_due = set.not_due(); + let was_due = set.due(); + + // for each in was_due + // if not in edited + // not_due.push + // due = [] + // for each in edited + // if not due + // not_due.push + // else + // due.push + // all = not_due + due + // save to self.0 + } Err("not impl".to_string()) } @@ -192,6 +213,18 @@ impl TasksAndMetadata { } } + pub fn due(&self) -> TasksAndMetadata { + let mut cloned = self.clone(); + cloned.tasks = cloned.tasks.due(); + cloned + } + + pub fn not_due(&self) -> TasksAndMetadata { + let mut cloned = self.clone(); + cloned.tasks = cloned.tasks.not_due(); + cloned + } + pub fn save(&self, dry_run: bool) -> Result<(), String> { let version = file_version(self.file.clone())?; let mut file = self.file.clone(); @@ -214,9 +247,7 @@ impl TasksAndMetadata { } pub fn save_due_as(&self, file: String) -> Result<(), String> { - let mut cloned = self.clone(); - cloned.tasks = cloned.tasks.due(); - cloned.save_as(file) + self.due().save_as(file) } pub fn save_as(&self, file: String) -> Result<(), String> { @@ -365,6 +396,14 @@ impl Tasks { ) } + pub fn not_due(&self) -> Tasks { + Tasks(self.0.iter() + .filter(|x| !x.is_due()) + .map(|x| x.clone()) + .collect() + ) + } + pub fn due(&self) -> Tasks { Tasks(self.0.iter() .filter(|x| x.is_due()) From 6eba05895f04a71c0aa6146427891e68ec05f076 Mon Sep 17 00:00:00 2001 From: Bel LaPointe <153096461+breel-render@users.noreply.github.com> Date: Fri, 14 Jun 2024 09:59:49 -0600 Subject: [PATCH 32/46] comment --- pttodoer/src/main.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pttodoer/src/main.rs b/pttodoer/src/main.rs index 97fc891..0d3029a 100644 --- a/pttodoer/src/main.rs +++ b/pttodoer/src/main.rs @@ -149,7 +149,7 @@ impl DB { // not_due.push // else // due.push - // all = not_due + due + // all = due + not_due // ordered // save to self.0 } From 50ea3258b7cb1ab3e5c5357c09e9c51671672c94 Mon Sep 17 00:00:00 2001 From: Bel LaPointe <153096461+breel-render@users.noreply.github.com> Date: Sun, 4 Aug 2024 11:42:09 -0600 Subject: [PATCH 33/46] edit implemented maybe --- pttodoer/src/main.rs | 86 ++++++++++++++++++++++++++++++-------------- 1 file changed, 59 insertions(+), 27 deletions(-) diff --git a/pttodoer/src/main.rs b/pttodoer/src/main.rs index 0d3029a..986e755 100644 --- a/pttodoer/src/main.rs +++ b/pttodoer/src/main.rs @@ -26,9 +26,8 @@ fn main() { }; if flags.edit { - db.edit().expect("failed to edit"); + db = db.edit().expect("failed to edit") } - db.save(flags.dry_run).expect("failed to save"); } @@ -115,7 +114,7 @@ impl DB { result } - pub fn edit(&self) -> Result<(), String> { + pub fn edit(&self) -> Result { let d = TempDir::new(&TS::now().to_string()).expect("failed to create a temp dir"); for set in &self.0 { let base = set.file.split("/").last().unwrap().to_string(); @@ -123,37 +122,62 @@ impl DB { set.save_due_as(f.display().to_string())?; } - //std::process::Command::new("/bin/sh") - // .current_dir(d.path()) - // .arg("-c") - // .arg("vim -p ./*") - // .spawn() - // .expect("failed to start vim") - // .wait() - // .expect("failed to vim"); + loop { + std::process::Command::new("/bin/sh") + .current_dir(d.path()) + .arg("-c") + .arg("vim -p ./*") + .spawn() + .expect("failed to start vim") + .wait() + .expect("failed to vim"); + let mut ok = true; + for set in &self.0 { + let base = set.file.split("/").last().unwrap().to_string(); + let f = d.path().join(base); + ok = ok && TasksAndMetadata::new(f.display().to_string()).is_ok(); + } + if ok { + break; + } + } + let mut result = vec![]; for set in &self.0 { let base = set.file.split("/").last().unwrap().to_string(); let f = d.path().join(base); - let edited = TasksAndMetadata::new(f.display().to_string()); - let not_due = set.not_due(); + let edited = TasksAndMetadata::new(f.display().to_string()).expect("failed to read edited tasks"); let was_due = set.due(); - // for each in was_due - // if not in edited - // not_due.push - // due = [] - // for each in edited - // if not due - // not_due.push - // else - // due.push - // all = due + not_due // ordered - // save to self.0 + let mut not_due = set.not_due(); + for task in &was_due.tasks.0 { + if !edited.tasks.0.contains(task) { + not_due.tasks.0.push(task.clone()); + } + } + + let mut now_due = vec![]; + for task in &edited.tasks.0 { + if task.is_due() { + now_due.push(task.clone()); + } else { + not_due.tasks.0.push(task.clone()); + } + } + + let mut new_tasks = Tasks::new(); + new_tasks.0.extend(now_due); + new_tasks.0.extend(not_due.tasks.0); + + result.push(TasksAndMetadata::new_with( + set.file.clone(), + set.version.clone(), + new_tasks, + )); } - Err("not impl".to_string()) + Ok(DB{0: result}) } pub fn save(&self, dry_run: bool) -> Result<(), String> { @@ -201,6 +225,14 @@ pub struct TasksAndMetadata { } impl TasksAndMetadata { + pub fn new_with(file: String, version: TS, tasks: Tasks) -> TasksAndMetadata { + TasksAndMetadata{ + file: file, + version: version, + tasks: tasks, + } + } + pub fn new(file: String) -> Result { let version = file_version(file.clone())?; match Tasks::from_file(file.clone()) { @@ -460,7 +492,7 @@ mod test_tasks { } -#[derive(Debug, serde::Serialize, serde::Deserialize, Clone)] +#[derive(Debug, serde::Serialize, serde::Deserialize, Clone, PartialEq)] pub struct Task(serde_yaml::Mapping); impl Task { @@ -900,7 +932,7 @@ mod test_duration { } #[derive(Debug, PartialEq, Clone)] -struct TS(u64); +pub struct TS(u64); impl TS { fn now() -> TS { From 2425f22b565f9b5397e69537388e312ba49af559 Mon Sep 17 00:00:00 2001 From: Bel LaPointe <153096461+breel-render@users.noreply.github.com> Date: Sun, 4 Aug 2024 11:44:22 -0600 Subject: [PATCH 34/46] grr edit dupes --- pttodoer/src/main.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pttodoer/src/main.rs b/pttodoer/src/main.rs index 986e755..e10b6a6 100644 --- a/pttodoer/src/main.rs +++ b/pttodoer/src/main.rs @@ -161,7 +161,7 @@ impl DB { for task in &edited.tasks.0 { if task.is_due() { now_due.push(task.clone()); - } else { + } else if !not_due.tasks.0.contains(task) { not_due.tasks.0.push(task.clone()); } } From e4834d8a11947c0d09f1ab600e14be6a8b76500f Mon Sep 17 00:00:00 2001 From: Bel LaPointe <153096461+breel-render@users.noreply.github.com> Date: Sun, 4 Aug 2024 13:35:29 -0600 Subject: [PATCH 35/46] gr --- pttodoer/src/main.rs | 43 ++++++++++++++++++++++--------- pttodoer/src/testdata/legacy.yaml | 18 +++++++------ 2 files changed, 41 insertions(+), 20 deletions(-) diff --git a/pttodoer/src/main.rs b/pttodoer/src/main.rs index e10b6a6..0dda5a6 100644 --- a/pttodoer/src/main.rs +++ b/pttodoer/src/main.rs @@ -148,27 +148,46 @@ impl DB { let f = d.path().join(base); let edited = TasksAndMetadata::new(f.display().to_string()).expect("failed to read edited tasks"); - let was_due = set.due(); - let mut not_due = set.not_due(); - for task in &was_due.tasks.0 { - if !edited.tasks.0.contains(task) { - not_due.tasks.0.push(task.clone()); + let was_due = set.due(); + let mut now_due = vec![]; + let mut not_due = set.not_due().tasks.0; + + for task in &edited.tasks.0 { + if task.is_due() { + eprintln!("now_due.push({:?})", task.clone()); + now_due.push(task.clone()); + } else if !not_due.contains(task) { + eprintln!("not_due.push({:?})", task.clone()); + not_due.push(task.clone()); } } - let mut now_due = vec![]; - for task in &edited.tasks.0 { - if task.is_due() { - now_due.push(task.clone()); - } else if !not_due.tasks.0.contains(task) { - not_due.tasks.0.push(task.clone()); + let mut already_due = vec![]; + let mut already_not_due = vec![]; + for task in &was_due.tasks.0 { + let mut without_ts = task.clone(); + without_ts.unset("ts".to_string()); + + let mut with_ts = task.clone(); + with_ts.set("ts".to_string(), TS::now().to_string()); + + if now_due.contains(&without_ts) { + } else if not_due.contains(task) { + } else if task.is_due() { + eprintln!("already_due.push({:?})", with_ts.clone()); + already_due.push(with_ts.clone()); + } else { + eprintln!("already_not_due.push({:?})", task.clone()); + already_not_due.push(task.clone()); } } let mut new_tasks = Tasks::new(); new_tasks.0.extend(now_due); - new_tasks.0.extend(not_due.tasks.0); + new_tasks.0.extend(already_due); + new_tasks.0.extend(not_due); + new_tasks.0.extend(already_not_due); result.push(TasksAndMetadata::new_with( set.file.clone(), diff --git a/pttodoer/src/testdata/legacy.yaml b/pttodoer/src/testdata/legacy.yaml index cd2c153..8bcd735 100644 --- a/pttodoer/src/testdata/legacy.yaml +++ b/pttodoer/src/testdata/legacy.yaml @@ -1,17 +1,19 @@ -todo: - a - todo: b schedule: 2000-01-01 -scheduled: - c - todo: d schedule: 2000-02-01 - todo: e - ts: Sun Dec 3 23:29:27 EST 2023 - schedule: '0 0 1 1 *' + schedule: 0 0 1 1 * +- todo: e3 + schedule: '* * * * *' +- todo: e2 + schedule: 0 0 1 1 * + ts: 2024-08-04T19:32Z - todo: f schedule: 2099-02-02 -done: -- g -- todo: h - ts: Sun Dec 3 23:29:27 EST 2023 +- _done: g +- _done: + todo: h + ts: Sun Dec 3 23:29:27 EST 2023 From 602939b25c6bacda8760b43a477f9191d3e75953 Mon Sep 17 00:00:00 2001 From: Bel LaPointe <153096461+breel-render@users.noreply.github.com> Date: Sun, 4 Aug 2024 13:36:08 -0600 Subject: [PATCH 37/46] revert testdata --- pttodoer/src/testdata/legacy.yaml | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/pttodoer/src/testdata/legacy.yaml b/pttodoer/src/testdata/legacy.yaml index 8bcd735..cd2c153 100644 --- a/pttodoer/src/testdata/legacy.yaml +++ b/pttodoer/src/testdata/legacy.yaml @@ -1,19 +1,17 @@ +todo: - a - todo: b schedule: 2000-01-01 +scheduled: - c - todo: d schedule: 2000-02-01 - todo: e - schedule: 0 0 1 1 * -- todo: e3 - schedule: '* * * * *' -- todo: e2 - schedule: 0 0 1 1 * - ts: 2024-08-04T19:32Z + ts: Sun Dec 3 23:29:27 EST 2023 + schedule: '0 0 1 1 *' - todo: f schedule: 2099-02-02 -- _done: g -- _done: - todo: h - ts: Sun Dec 3 23:29:27 EST 2023 +done: +- g +- todo: h + ts: Sun Dec 3 23:29:27 EST 2023 From 5c74ec0c1522733383c80870efead67dc82d4631 Mon Sep 17 00:00:00 2001 From: Bel LaPointe <153096461+breel-render@users.noreply.github.com> Date: Thu, 8 Aug 2024 12:53:15 -0700 Subject: [PATCH 38/46] todo --- pttodoer/src/main.rs | 92 +++++++++++++++++++++++++++++++++++--------- 1 file changed, 73 insertions(+), 19 deletions(-) diff --git a/pttodoer/src/main.rs b/pttodoer/src/main.rs index 0dda5a6..2925869 100644 --- a/pttodoer/src/main.rs +++ b/pttodoer/src/main.rs @@ -63,7 +63,7 @@ impl Flags { } } -#[derive(Debug)] +#[derive(Debug, PartialEq)] pub struct DB(Vec); impl DB { @@ -98,6 +98,10 @@ impl DB { Ok(DB(result)) } + fn new_empty() -> DB { + DB{0: vec![]} + } + pub fn incomplete(&self) -> Tasks { let mut result = Tasks::new(); for set in &self.0 { @@ -115,6 +119,19 @@ impl DB { } pub fn edit(&self) -> Result { + self._edit(|cwd: &String| { + std::process::Command::new("/bin/sh") + .current_dir(cwd) + .arg("-c") + .arg("vim -p ./*") + .spawn() + .expect("failed to start vim") + .wait() + .expect("failed to vim"); + }) + } + + fn _edit(&self, edit: impl Fn(&String)) -> Result { let d = TempDir::new(&TS::now().to_string()).expect("failed to create a temp dir"); for set in &self.0 { let base = set.file.split("/").last().unwrap().to_string(); @@ -123,19 +140,11 @@ impl DB { } loop { - std::process::Command::new("/bin/sh") - .current_dir(d.path()) - .arg("-c") - .arg("vim -p ./*") - .spawn() - .expect("failed to start vim") - .wait() - .expect("failed to vim"); + let cwd = d.path().display().to_string(); + edit(&cwd); let mut ok = true; - for set in &self.0 { - let base = set.file.split("/").last().unwrap().to_string(); - let f = d.path().join(base); - ok = ok && TasksAndMetadata::new(f.display().to_string()).is_ok(); + for f in std::fs::read_dir(&cwd).expect("failed to list edited files") { + ok = ok && TasksAndMetadata::new(f.expect("failed to list edited file").path().display().to_string()).is_ok(); } if ok { break; @@ -143,9 +152,10 @@ impl DB { } let mut result = vec![]; - for set in &self.0 { - let base = set.file.split("/").last().unwrap().to_string(); - let f = d.path().join(base); + for f in std::fs::read_dir(d.path()).expect("failed to list edited files") { + let f = f.expect("failed to list edited file"); + let base = f.path().display().to_string().split("/").last().unwrap().to_string(); + let set = match{ TODO find self.0.find(path.base(f)) OR init TasksAndMetadata{} } let edited = TasksAndMetadata::new(f.display().to_string()).expect("failed to read edited tasks"); @@ -208,9 +218,53 @@ impl DB { } #[cfg(test)] -mod test_taskss { +mod test_db { use super::*; + #[test] + fn edit_insert_empty() { + let db = new_test_db(0, 0); + let db_after = db._edit(|cwd| { + assert_eq!(0, std::fs::read_dir(cwd).expect("failed to ls cwd").collect::>().len()); + assert_ne!("", cwd); + + let mut f = std::fs::File::create(format!("{}/f", cwd)).expect("failed to open a file in cwd"); + f.write_all(b"f").expect("failed to write a file in cwd"); + + let mut g = std::fs::File::create(format!("{}/g", cwd)).expect("failed to open a file in cwd"); + g.write_all(b"g: h").expect("failed to write a file in cwd"); + }).expect("failed to not edit"); + assert_eq!(2, db.0.len()); + assert_eq!(1, db.0[0].tasks.len()); + assert_eq!(1, db.0[1].tasks.len()); + assert_ne!(db, db_after); + } + + #[test] + fn edit_noop_empty() { + let db = new_test_db(0, 0); + let db_after = db._edit(|_| {}).expect("failed to not edit"); + assert_eq!(0, db.0.len()); + assert_eq!(db, db_after); + } + + fn new_test_db(files: i32, tasks_per_file: i32) -> DB { + let mut db = DB::new_empty(); + + for i in 0..files { + let mut tasks = Tasks::new(); + for j in 0..tasks_per_file { + tasks.0.push(Task::from_string(format!("{{\"hello\": \"world[{}]\"}}", j))); + } + db.0.push(TasksAndMetadata::new_with( + format!("{}.yaml", i), + TS::now(), + tasks, + )); + } + db + } + #[test] fn read_dir_files() { let db = DB::new("./src/testdata/taskss.d/files.d".to_string()).expect("failed to construct from dir of files"); @@ -236,7 +290,7 @@ mod test_taskss { } } -#[derive(Debug, Clone)] +#[derive(Debug, Clone, PartialEq)] pub struct TasksAndMetadata { tasks: Tasks, file: String, @@ -329,7 +383,7 @@ fn file_version(file: String) -> Result { } } -#[derive(Debug, serde::Serialize, serde::Deserialize, Clone)] +#[derive(Debug, serde::Serialize, serde::Deserialize, Clone, PartialEq)] pub struct Tasks(Vec); impl Tasks { From 452b692c6acd310033e6d988cffb61b3b4e2aee3 Mon Sep 17 00:00:00 2001 From: Bel LaPointe <153096461+breel-render@users.noreply.github.com> Date: Thu, 8 Aug 2024 12:58:21 -0700 Subject: [PATCH 39/46] wipper --- pttodoer/src/main.rs | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/pttodoer/src/main.rs b/pttodoer/src/main.rs index 2925869..ec8d3c9 100644 --- a/pttodoer/src/main.rs +++ b/pttodoer/src/main.rs @@ -153,9 +153,15 @@ impl DB { let mut result = vec![]; for f in std::fs::read_dir(d.path()).expect("failed to list edited files") { - let f = f.expect("failed to list edited file"); - let base = f.path().display().to_string().split("/").last().unwrap().to_string(); - let set = match{ TODO find self.0.find(path.base(f)) OR init TasksAndMetadata{} } + let f = f.expect("failed to list edited file").path(); + let base = f.display().to_string().split("/").last().unwrap().to_string(); + let set = match self.0 + .iter() + .filter(|tasks_and_metadata| tasks_and_metadata.file.ends_with(format!("/{}", base))) + .nth(0) { + Some(set) => set.clone(), + None => TasksAndMetadata::new_with(base, TS::now(), Tasks::new()), + }; let edited = TasksAndMetadata::new(f.display().to_string()).expect("failed to read edited tasks"); From 52977ff0a73873635c091d3426c313f011b1aeea Mon Sep 17 00:00:00 2001 From: Bel LaPointe <153096461+breel-render@users.noreply.github.com> Date: Thu, 8 Aug 2024 13:14:26 -0700 Subject: [PATCH 40/46] insert reveal i cannot handle no src db --- pttodoer/src/main.rs | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/pttodoer/src/main.rs b/pttodoer/src/main.rs index ec8d3c9..a5ae6be 100644 --- a/pttodoer/src/main.rs +++ b/pttodoer/src/main.rs @@ -157,7 +157,7 @@ impl DB { let base = f.display().to_string().split("/").last().unwrap().to_string(); let set = match self.0 .iter() - .filter(|tasks_and_metadata| tasks_and_metadata.file.ends_with(format!("/{}", base))) + .filter(|tasks_and_metadata| tasks_and_metadata.file.ends_with(format!("/{}", base).as_str())) .nth(0) { Some(set) => set.clone(), None => TasksAndMetadata::new_with(base, TS::now(), Tasks::new()), @@ -238,12 +238,13 @@ mod test_db { f.write_all(b"f").expect("failed to write a file in cwd"); let mut g = std::fs::File::create(format!("{}/g", cwd)).expect("failed to open a file in cwd"); - g.write_all(b"g: h").expect("failed to write a file in cwd"); + g.write_all(b"- g: h\n- j: k").expect("failed to write a file in cwd"); }).expect("failed to not edit"); - assert_eq!(2, db.0.len()); - assert_eq!(1, db.0[0].tasks.len()); - assert_eq!(1, db.0[1].tasks.len()); assert_ne!(db, db_after); + + assert_eq!(2, db_after.0.len()); + assert_eq!(1, db_after.0[1].tasks.len()); + assert_eq!(2, db_after.0[0].tasks.len()); } #[test] From a5f5013ef6be765614466e50139c82c8fc955e0b Mon Sep 17 00:00:00 2001 From: Bel LaPointe <153096461+breel-render@users.noreply.github.com> Date: Thu, 8 Aug 2024 13:17:32 -0700 Subject: [PATCH 41/46] ew --- pttodoer/src/main.rs | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/pttodoer/src/main.rs b/pttodoer/src/main.rs index a5ae6be..3186372 100644 --- a/pttodoer/src/main.rs +++ b/pttodoer/src/main.rs @@ -160,7 +160,17 @@ impl DB { .filter(|tasks_and_metadata| tasks_and_metadata.file.ends_with(format!("/{}", base).as_str())) .nth(0) { Some(set) => set.clone(), - None => TasksAndMetadata::new_with(base, TS::now(), Tasks::new()), + None => TasksAndMetadata::new_with( + format!("{}/{}", + std::path::Path::new(self.0[0].file.as_str()) + .parent().expect("root?") + .display() + .to_string(), + base, + ), + TS::now(), + Tasks::new(), + ), }; let edited = TasksAndMetadata::new(f.display().to_string()).expect("failed to read edited tasks"); From 33d318edd45de8afde59b4e993d86f02abfdbc85 Mon Sep 17 00:00:00 2001 From: Bel LaPointe <153096461+breel-render@users.noreply.github.com> Date: Thu, 8 Aug 2024 13:25:38 -0700 Subject: [PATCH 42/46] o that swap wasnt too bd --- pttodoer/src/main.rs | 54 ++++++++++++++++++++++++-------------------- 1 file changed, 30 insertions(+), 24 deletions(-) diff --git a/pttodoer/src/main.rs b/pttodoer/src/main.rs index 3186372..37a0b07 100644 --- a/pttodoer/src/main.rs +++ b/pttodoer/src/main.rs @@ -20,7 +20,7 @@ fn main() { Some(sch) => Task::from_string_schedule(s, sch), None => Task::from_string(s), }; - db.0[0].tasks.0.push(t); + db.tasks_and_metadatas[0].tasks.0.push(t); }, _ => {}, }; @@ -64,7 +64,10 @@ impl Flags { } #[derive(Debug, PartialEq)] -pub struct DB(Vec); +pub struct DB { + tasks_and_metadatas: Vec, + cwd: String, +} impl DB { pub fn new(path: String) -> Result { @@ -95,16 +98,22 @@ impl DB { let item = TasksAndMetadata::new(file)?; result.push(item); } - Ok(DB(result)) + Ok(DB{ + tasks_and_metadatas: result, + cwd: match metadata.is_file() { + true => std::path::Path::new(&path).parent().expect("root?").display().to_string(), + _ => path.clone(), + }, + }) } - fn new_empty() -> DB { - DB{0: vec![]} + fn new_empty(cwd: String) -> DB { + DB{tasks_and_metadatas: vec![], cwd: cwd.clone()} } pub fn incomplete(&self) -> Tasks { let mut result = Tasks::new(); - for set in &self.0 { + for set in &self.tasks_and_metadatas { result.0.extend(set.tasks.incomplete().0); } result @@ -112,7 +121,7 @@ impl DB { pub fn due(&self) -> Tasks { let mut result = Tasks::new(); - for set in &self.0 { + for set in &self.tasks_and_metadatas { result.0.extend(set.tasks.due().0); } result @@ -133,7 +142,7 @@ impl DB { fn _edit(&self, edit: impl Fn(&String)) -> Result { let d = TempDir::new(&TS::now().to_string()).expect("failed to create a temp dir"); - for set in &self.0 { + for set in &self.tasks_and_metadatas { let base = set.file.split("/").last().unwrap().to_string(); let f = d.path().join(base); set.save_due_as(f.display().to_string())?; @@ -155,17 +164,14 @@ impl DB { for f in std::fs::read_dir(d.path()).expect("failed to list edited files") { let f = f.expect("failed to list edited file").path(); let base = f.display().to_string().split("/").last().unwrap().to_string(); - let set = match self.0 + let set = match self.tasks_and_metadatas .iter() .filter(|tasks_and_metadata| tasks_and_metadata.file.ends_with(format!("/{}", base).as_str())) .nth(0) { Some(set) => set.clone(), None => TasksAndMetadata::new_with( format!("{}/{}", - std::path::Path::new(self.0[0].file.as_str()) - .parent().expect("root?") - .display() - .to_string(), + self.cwd, base, ), TS::now(), @@ -222,11 +228,11 @@ impl DB { )); } - Ok(DB{0: result}) + Ok(DB{tasks_and_metadatas: result, cwd: self.cwd.clone()}) } pub fn save(&self, dry_run: bool) -> Result<(), String> { - for i in &self.0 { + for i in &self.tasks_and_metadatas { i.save(dry_run)?; } Ok(()) @@ -252,28 +258,28 @@ mod test_db { }).expect("failed to not edit"); assert_ne!(db, db_after); - assert_eq!(2, db_after.0.len()); - assert_eq!(1, db_after.0[1].tasks.len()); - assert_eq!(2, db_after.0[0].tasks.len()); + assert_eq!(2, db_after.tasks_and_metadatas.len()); + assert_eq!(1, db_after.tasks_and_metadatas[1].tasks.len()); + assert_eq!(2, db_after.tasks_and_metadatas[0].tasks.len()); } #[test] fn edit_noop_empty() { let db = new_test_db(0, 0); let db_after = db._edit(|_| {}).expect("failed to not edit"); - assert_eq!(0, db.0.len()); + assert_eq!(0, db.tasks_and_metadatas.len()); assert_eq!(db, db_after); } fn new_test_db(files: i32, tasks_per_file: i32) -> DB { - let mut db = DB::new_empty(); + let mut db = DB::new_empty("".to_string()); for i in 0..files { let mut tasks = Tasks::new(); for j in 0..tasks_per_file { tasks.0.push(Task::from_string(format!("{{\"hello\": \"world[{}]\"}}", j))); } - db.0.push(TasksAndMetadata::new_with( + db.tasks_and_metadatas.push(TasksAndMetadata::new_with( format!("{}.yaml", i), TS::now(), tasks, @@ -285,7 +291,7 @@ mod test_db { #[test] fn read_dir_files() { let db = DB::new("./src/testdata/taskss.d/files.d".to_string()).expect("failed to construct from dir of files"); - assert_eq!(2, db.0.len()); + assert_eq!(2, db.tasks_and_metadatas.len()); assert_eq!(2, db.due().len()); assert_eq!(2, db.incomplete().len()); } @@ -293,7 +299,7 @@ mod test_db { #[test] fn read_dir_file() { let db = DB::new("./src/testdata/taskss.d/file.d".to_string()).expect("failed to construct from dir of a single file"); - assert_eq!(1, db.0.len()); + assert_eq!(1, db.tasks_and_metadatas.len()); assert_eq!(1, db.due().len()); assert_eq!(1, db.incomplete().len()); } @@ -301,7 +307,7 @@ mod test_db { #[test] fn read_single_file() { let db = DB::new("./src/testdata/taskss.d/single_file.yaml".to_string()).expect("failed to construct from single file"); - assert_eq!(1, db.0.len()); + assert_eq!(1, db.tasks_and_metadatas.len()); assert_eq!(1, db.due().len()); assert_eq!(2, db.incomplete().len()); } From e05509f28fb03e7f1a86f7e2946bae09ffda612c Mon Sep 17 00:00:00 2001 From: Bel LaPointe <153096461+breel-render@users.noreply.github.com> Date: Fri, 9 Aug 2024 15:48:10 -0700 Subject: [PATCH 43/46] tests are good --- pttodoer/src/main.rs | 147 ++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 146 insertions(+), 1 deletion(-) diff --git a/pttodoer/src/main.rs b/pttodoer/src/main.rs index 37a0b07..29b15e5 100644 --- a/pttodoer/src/main.rs +++ b/pttodoer/src/main.rs @@ -243,6 +243,148 @@ impl DB { mod test_db { use super::*; + #[test] + fn edit_append_duplicate_keeps_both() { + use std::io::Seek; + + let db = new_test_db(1, 1); + let db_after = db._edit(|cwd| { + let files_in_dir = std::fs::read_dir(cwd).expect("failed to ls cwd").collect::>(); + assert_eq!(1, files_in_dir.len()); + let mut f = std::fs::OpenOptions::new() + .write(true) + .append(true) + .open(files_in_dir[0].as_ref().expect("failed to get file in dir").path()) + .expect("failed to open file for append"); + f.seek(std::io::SeekFrom::End(0)).expect("failed to seek to end"); + f.write_all(b"\n- f").expect("failed to append the file in cwd"); + f.write_all(b"\n- f").expect("failed to append the file in cwd"); + }).expect("failed to not edit"); + assert_ne!(db, db_after); + + assert_eq!(1, db_after.tasks_and_metadatas.len()); + assert_eq!(3, db_after.tasks_and_metadatas[0].tasks.len()); + assert_eq!(db.tasks_and_metadatas[0].tasks.0[0], db_after.tasks_and_metadatas[0].tasks.0[0]); + assert_eq!(Some("f".to_string()), db_after.tasks_and_metadatas[0].tasks.0[1].get("is".to_string())); + assert_eq!(Some("f".to_string()), db_after.tasks_and_metadatas[0].tasks.0[2].get("is".to_string())); + } + + #[test] + fn edit_schedule_future() { + let db = new_test_db(0, 0); + let db_after = db._edit(|cwd| { + let mut task = Task::new(); + task.set("k".to_string(), "v".to_string()); + task.set("schedule".to_string(), "2101-02-03".to_string()); + + let mut tasks = Tasks::new(); + tasks.0.push(task); + + let temp = TasksAndMetadata::new_with( + format!("{}/only", cwd), + TS::now(), + tasks, + ); + temp.save(false).expect("failed to save"); + }).expect("failed to not edit"); + assert_ne!(db, db_after); + + assert_eq!(1, db_after.tasks_and_metadatas.len()); + assert_eq!(1, db_after.tasks_and_metadatas[0].tasks.len()); + assert_eq!(0, db_after.tasks_and_metadatas[0].due().tasks.len()); + assert_eq!(1, db_after.tasks_and_metadatas[0].not_due().tasks.len()); + } + + #[test] + fn edit_schedule_past() { + let db = new_test_db(0, 0); + let db_after = db._edit(|cwd| { + let mut task = Task::new(); + task.set("k".to_string(), "v".to_string()); + task.set("schedule".to_string(), "2001-02-03".to_string()); + + let mut tasks = Tasks::new(); + tasks.0.push(task); + + let temp = TasksAndMetadata::new_with( + format!("{}/only", cwd), + TS::now(), + tasks, + ); + temp.save(false).expect("failed to save"); + }).expect("failed to not edit"); + assert_ne!(db, db_after); + + assert_eq!(1, db_after.tasks_and_metadatas.len()); + assert_eq!(1, db_after.tasks_and_metadatas[0].tasks.len()); + assert_eq!(1, db_after.tasks_and_metadatas[0].due().tasks.len()); + assert_eq!(0, db_after.tasks_and_metadatas[0].not_due().tasks.len()); + } + + + #[test] + fn edit_append_second_file() { + use std::io::Seek; + + let db = new_test_db(2, 1); + let db_after = db._edit(|cwd| { + let files_in_dir = std::fs::read_dir(cwd).expect("failed to ls cwd").collect::>(); + assert_eq!(2, files_in_dir.len()); + let mut f = std::fs::OpenOptions::new() + .write(true) + .append(true) + .open(files_in_dir[1].as_ref().expect("failed to get file in dir").path()) + .expect("failed to open file for append"); + f.seek(std::io::SeekFrom::End(0)).expect("failed to seek to end"); + f.write_all(b"\n- f").expect("failed to append the file in cwd"); + }).expect("failed to not edit"); + assert_ne!(db, db_after); + + assert_eq!(2, db_after.tasks_and_metadatas.len()); + assert_eq!(1, db_after.tasks_and_metadatas[0].tasks.len()); + assert_eq!(2, db_after.tasks_and_metadatas[1].tasks.len()); + assert_eq!(db.tasks_and_metadatas[1].tasks.0[0], db_after.tasks_and_metadatas[1].tasks.0[0]); + assert_eq!(Some("f".to_string()), db_after.tasks_and_metadatas[1].tasks.0[1].get("is".to_string())); + } + + #[test] + fn edit_append_current_file() { + use std::io::Seek; + + let db = new_test_db(1, 1); + let db_after = db._edit(|cwd| { + let files_in_dir = std::fs::read_dir(cwd).expect("failed to ls cwd").collect::>(); + assert_eq!(1, files_in_dir.len()); + let mut f = std::fs::OpenOptions::new() + .write(true) + .append(true) + .open(files_in_dir[0].as_ref().expect("failed to get file in dir").path()) + .expect("failed to open file for append"); + f.seek(std::io::SeekFrom::End(0)).expect("failed to seek to end"); + f.write_all(b"\n- f").expect("failed to append the file in cwd"); + }).expect("failed to not edit"); + assert_ne!(db, db_after); + + assert_eq!(1, db_after.tasks_and_metadatas.len()); + assert_eq!(2, db_after.tasks_and_metadatas[0].tasks.len()); + assert_eq!(db.tasks_and_metadatas[0].tasks.0[0], db_after.tasks_and_metadatas[0].tasks.0[0]); + assert_eq!(Some("f".to_string()), db_after.tasks_and_metadatas[0].tasks.0[1].get("is".to_string())); + } + + #[test] + fn edit_insert_new_second_file() { + let db = new_test_db(1, 1); + let db_after = db._edit(|cwd| { + let mut f = std::fs::File::create(format!("{}/new_second_file", cwd)).expect("failed to open a file in cwd"); + f.write_all(b"f").expect("failed to write a file in cwd"); + }).expect("failed to not edit"); + assert_ne!(db, db_after); + + assert_eq!(2, db_after.tasks_and_metadatas.len()); + assert_eq!(1, db_after.tasks_and_metadatas[1].tasks.len()); + assert_eq!(1, db_after.tasks_and_metadatas[0].tasks.len()); + } + #[test] fn edit_insert_empty() { let db = new_test_db(0, 0); @@ -354,7 +496,10 @@ impl TasksAndMetadata { } pub fn save(&self, dry_run: bool) -> Result<(), String> { - let version = file_version(self.file.clone())?; + let version = match file_version(self.file.clone()){ + Ok(v) => v, + Err(_) => self.version.clone(), + }; let mut file = self.file.clone(); if version != self.version { file = format!("{}.{}", &self.file, TS::now().to_string()); From b067d0fb8e07bd1fea4263c57318776a2b83bffa Mon Sep 17 00:00:00 2001 From: Bel LaPointe <153096461+breel-render@users.noreply.github.com> Date: Fri, 9 Aug 2024 17:07:25 -0600 Subject: [PATCH 44/46] failing test --- pttodoer/src/main.rs | 24 +++++++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/pttodoer/src/main.rs b/pttodoer/src/main.rs index 29b15e5..cf0210f 100644 --- a/pttodoer/src/main.rs +++ b/pttodoer/src/main.rs @@ -63,7 +63,7 @@ impl Flags { } } -#[derive(Debug, PartialEq)] +#[derive(Debug, PartialEq, Clone)] pub struct DB { tasks_and_metadatas: Vec, cwd: String, @@ -243,6 +243,28 @@ impl DB { mod test_db { use super::*; + #[test] + fn conflicting_save_fails() { + let d = TempDir::new(&TS::now().to_string()).expect("failed to create a temp dir"); + let d = d.path().display().to_string(); + + { + let p = format!("{}/f", &d); + eprintln!("p={}", p); + let mut f = std::fs::File::create(p).expect("failed to create a file in cwd"); + f.write_all(b"- x").expect("failed to create a file"); + } + + let mut db = DB::new(d.clone()).expect("failed to open tempd"); + assert_eq!(1, db.tasks_and_metadatas[0].tasks.len()); + db.tasks_and_metadatas[0].tasks.0.push(Task::new()); + let stale_db = db.clone(); + + db.save(false).expect("failed to save db with new task"); + assert_eq!(false, stale_db.save(false).is_ok()); + } + + #[test] fn edit_append_duplicate_keeps_both() { use std::io::Seek; From 301fb6046fd111a8d260b5e8ae7ff3b14dfc63f1 Mon Sep 17 00:00:00 2001 From: Bel LaPointe <153096461+breel-render@users.noreply.github.com> Date: Fri, 9 Aug 2024 17:22:08 -0600 Subject: [PATCH 45/46] tests yay --- pttodoer/src/main.rs | 26 +++++++++++++++++--------- 1 file changed, 17 insertions(+), 9 deletions(-) diff --git a/pttodoer/src/main.rs b/pttodoer/src/main.rs index cf0210f..08ae15c 100644 --- a/pttodoer/src/main.rs +++ b/pttodoer/src/main.rs @@ -244,24 +244,32 @@ mod test_db { use super::*; #[test] - fn conflicting_save_fails() { + fn conflicting_save_handled() { + use std::ops::Add; + let d = TempDir::new(&TS::now().to_string()).expect("failed to create a temp dir"); let d = d.path().display().to_string(); - + let p = format!("{}/f", &d); { - let p = format!("{}/f", &d); - eprintln!("p={}", p); - let mut f = std::fs::File::create(p).expect("failed to create a file in cwd"); + let mut f = std::fs::File::create(p.clone()).expect("failed to create a file in cwd"); f.write_all(b"- x").expect("failed to create a file"); } let mut db = DB::new(d.clone()).expect("failed to open tempd"); assert_eq!(1, db.tasks_and_metadatas[0].tasks.len()); - db.tasks_and_metadatas[0].tasks.0.push(Task::new()); - let stale_db = db.clone(); - db.save(false).expect("failed to save db with new task"); - assert_eq!(false, stale_db.save(false).is_ok()); + std::fs::File::open(p) + .expect("failed to open file to change modtime") + .set_modified(std::time::SystemTime::now().add(std::time::Duration::new(10, 0))) + .expect("failed to change modtime"); + + db.tasks_and_metadatas[0].tasks.0.push(Task::new()); + db.save(false).expect("save as conflicting update shouldve saved as a different file"); + + let db = DB::new(d.clone()).expect("failed to open tempd again"); + assert_eq!(2, db.tasks_and_metadatas.len()); + assert_eq!(1, db.tasks_and_metadatas[0].tasks.len()); + assert_eq!(2, db.tasks_and_metadatas[1].tasks.len()); } From 7344c660ed773e080b7c9df0fa963fc12ab848c4 Mon Sep 17 00:00:00 2001 From: Bel LaPointe <153096461+breel-render@users.noreply.github.com> Date: Sat, 10 Aug 2024 09:30:06 -0600 Subject: [PATCH 46/46] todos as failing tests --- pttodoer/src/main.rs | 24 ++++++++++++++++++++++-- 1 file changed, 22 insertions(+), 2 deletions(-) diff --git a/pttodoer/src/main.rs b/pttodoer/src/main.rs index 08ae15c..5e0e4cf 100644 --- a/pttodoer/src/main.rs +++ b/pttodoer/src/main.rs @@ -14,7 +14,7 @@ fn main() { let mut db = DB::new(flags.path).unwrap(); - match flags.add { + match flags.add.clone() { Some(s) => { let t = match flags.add_schedule { Some(sch) => Task::from_string_schedule(s, sch), @@ -28,7 +28,14 @@ fn main() { if flags.edit { db = db.edit().expect("failed to edit") } - db.save(flags.dry_run).expect("failed to save"); + + if flags.edit || flags.add.is_some() { + db.save(flags.dry_run).expect("failed to save"); + } + + if flags.list { + println!("{}", db.due().to_string()); + } } #[derive(Debug, Parser)] @@ -47,6 +54,9 @@ struct Flags { #[arg(short = 'd', long = "dry-run", default_value="false")] dry_run: bool, + + #[arg(short = 'l', long = "list", default_value="true")] + list: bool, } impl Flags { @@ -243,6 +253,16 @@ impl DB { mod test_db { use super::*; + #[test] + fn resolved_scheduled_recurring() { + assert!(false, "not impl") + } + + #[test] + fn resolved_scheduled_non_recurring() { + assert!(false, "not impl") + } + #[test] fn conflicting_save_handled() { use std::ops::Add;