From 44616be8f696fc2e499eaab4753c721a143df0a3 Mon Sep 17 00:00:00 2001 From: Jan Bergen Date: Wed, 4 Feb 2026 17:30:46 +0100 Subject: [PATCH] add proof of concept for patterns --- autotodo/Cargo.toml | 2 +- autotodo/data/notes.md | 6 +- autotodo/data/patterns.ron | 10 ++- autotodo/src/main.rs | 162 +++++++++++++++++++++++-------------- 4 files changed, 118 insertions(+), 62 deletions(-) diff --git a/autotodo/Cargo.toml b/autotodo/Cargo.toml index 0fe12c3..a0916dd 100644 --- a/autotodo/Cargo.toml +++ b/autotodo/Cargo.toml @@ -7,5 +7,5 @@ edition = "2024" chrono = { version = "0.4.43", features = ["serde"] } clap = { version = "4.5.54", features = ["derive"] } env = "1.0.1" -ron = "0.12.0" +ron = { version = "0.12.0" } serde = { version = "1.0.228", features = ["derive"] } diff --git a/autotodo/data/notes.md b/autotodo/data/notes.md index e628906..6b79362 100644 --- a/autotodo/data/notes.md +++ b/autotodo/data/notes.md @@ -16,8 +16,12 @@ - [ ] Test 2 - [ ] Test 3 -# 2026-02-03 +# 2026-02-04 +- [ ] Rechnung Gertrudenhof +- [ ] Rechnung an lustige Leute +- [ ] Cornelius hauen +- [ ] Rechnung STG - [ ] Test 2 - [ ] Test 3 diff --git a/autotodo/data/patterns.ron b/autotodo/data/patterns.ron index ec262df..41a2984 100644 --- a/autotodo/data/patterns.ron +++ b/autotodo/data/patterns.ron @@ -3,9 +3,17 @@ name: "Rechnung Gertrudenhof", pattern: Monthly(dom: 1), ), + ( + name: "Rechnung an lustige Leute", + pattern: Monthly(dom: 4), + ), + ( + name: "Cornelius hauen", + pattern: Daily, + ), ( name: "Rechnung STG", - pattern: MonthlyOpt(dom: 1, months: [February, May, August, November]), + pattern: MonthlyOpt(dom: 1, months: ["February", "May", "August", "November"]), ), ( name: "Kalender durchlesen", diff --git a/autotodo/src/main.rs b/autotodo/src/main.rs index 84bffb0..b830ca2 100644 --- a/autotodo/src/main.rs +++ b/autotodo/src/main.rs @@ -1,12 +1,14 @@ -use chrono::{self, Local, NaiveDate}; +use chrono::{self, Datelike, Duration, Local, NaiveDate}; use clap::Parser; use std::{ collections::HashSet, fmt::Write as _, fs::OpenOptions, io::{BufRead, BufReader, Read, Seek, Write as _}, - path::{Path, PathBuf}, + path::{PathBuf}, }; +use ron; +use serde::{Serialize, Deserialize}; #[derive(Parser, Debug)] #[command(version, about, long_about = None)] @@ -15,75 +17,79 @@ struct Args { patterns: Option, } -// #[derive(Serialize, Deserialize, Debug)] -// enum Pattern { -// Yearly { -// doy: u32, -// }, -// MonthlyOpt { -// dom: u32, -// months: Vec, -// }, -// Monthly { -// dom: u32, -// }, -// Weekly { -// dow: u32, -// }, -// Daily, -// } +#[derive(Serialize, Deserialize, Debug)] +enum Pattern { + Yearly { + doy: u32, + }, + MonthlyOpt { + dom: u32, + months: Vec, + }, + Monthly { + dom: u32, + }, + Weekly { + dow: u32, + }, + Daily, +} -// #[derive(Serialize, Deserialize, Debug)] -// struct Position { -// name: String, -// pattern: Pattern, -// } +#[derive(Serialize, Deserialize, Debug)] +struct Position { + name: String, + pattern: Pattern, +} + +fn is_todo(bytes: &[u8]) -> bool { + return bytes == b"- [ ]" // done + || bytes == b"- [r]" // tried unsucessfully, have to retry + || bytes == b"- [p]" // Made progress, but not done +} fn is_done(bytes: &[u8]) -> bool { return bytes == b"- [x]" // done || bytes == b"- [n]" // no || bytes == b"- [i]" // into issue || bytes == b"- [d]" // someone else did it / took "assignment" (spiritual or actually in Gitea) - || bytes == b"- [r]" // tried unsucessfully, have to retry || bytes == b"- [m]" // moved to a later, specified time || bytes == b"- [c]" // i will come back to this when i feel like it, but no need to track it now - || bytes == b"- [p]" // Made progress, but not done || bytes == b"- [q]"; // deprecated / morphed into another todo } -fn update_notes(notes_path: &Path) { - let mut file = OpenOptions::new() +fn update_notes(notes_path: &PathBuf, patterns_path: Option) { + let mut notes_file = OpenOptions::new() .read(true) .write(true) .open(notes_path) .expect("file should exist"); - let mut bufreader = BufReader::new(&file); - let mut linebuf = String::new(); - let mut all_lines = String::new(); + let mut notes_bufreader = BufReader::new(¬es_file); + let mut str_buf1 = String::new(); + let mut str_buf2 = String::new(); let mut latest_date = NaiveDate::MIN; let mut lines_to_not_use: Vec = Vec::new(); + // Find todo-type lines let mut n_lines = 0; - while let Ok(n) = bufreader.read_line(&mut linebuf) { + while let Ok(n) = notes_bufreader.read_line(&mut str_buf1) { if n == 0 { break; } - if linebuf == "\n" { - linebuf.clear(); + if str_buf1 == "\n" { + str_buf1.clear(); continue; } - let trimmed_str = linebuf.trim(); + let trimmed_str = str_buf1.trim(); let trimmed_bytes = trimmed_str.as_bytes(); - dbg!(&linebuf, trimmed_str, trimmed_bytes); let Some(prefix) = trimmed_bytes.get(0..5) else { continue; }; - if prefix == b"- [ ]" || is_done(prefix) { - all_lines.push_str(&linebuf); + if is_todo(prefix) || is_done(prefix) { + str_buf2.push_str(&str_buf1); n_lines += 1; } else if trimmed_bytes.get(0) == Some(&b'#') { if let Some(header_cont) = trimmed_str.get(1..) @@ -94,11 +100,11 @@ fn update_notes(notes_path: &Path) { } } } - linebuf.clear(); + str_buf1.clear(); } let mut todos_hashset: HashSet<&str> = HashSet::new(); - let split_lines = all_lines.rsplit("\n"); + let split_lines = str_buf2.rsplit("\n"); for (rev_idx, line) in split_lines.enumerate() { let org_idx = n_lines - rev_idx; let trimmed_str = line.trim(); @@ -115,43 +121,81 @@ fn update_notes(notes_path: &Path) { } // line break as needed - bufreader.seek(std::io::SeekFrom::End(-2)).unwrap_or_else(|e| panic!("file is shorter than two characters. you dont need a todo program for that amount of todos, enjoy your life. Error is: {e}")); + notes_bufreader.seek(std::io::SeekFrom::End(-2)).unwrap_or_else(|e| panic!("file is shorter than two characters. you dont need a todo program for that amount of todos, enjoy your life. Error is: {e}")); let mut last_two_bytes = [0u8, 0u8]; - bufreader.read_exact(&mut last_two_bytes).unwrap_or_else(|e| panic!("file is shorter than two characters. you dont need a todo program for that amount of todos, enjoy your life. Error is: {e}")); + notes_bufreader.read_exact(&mut last_two_bytes).unwrap_or_else(|e| panic!("file is shorter than two characters. you dont need a todo program for that amount of todos, enjoy your life. Error is: {e}")); if last_two_bytes != *b"\n\n" { - write!(linebuf, "\n").unwrap_or_else(|e| panic!("{e}")); + write!(str_buf1, "\n").unwrap_or_else(|e| panic!("{e}")); } // Header line let today = Local::now().date_naive(); if latest_date < today { - writeln!(linebuf, "# {}\n", today).unwrap_or_else(|e| panic!("{e}")); + writeln!(str_buf1, "# {}\n", today).unwrap_or_else(|e| panic!("{e}")); } else { println!("Come back tomorrow!"); // TODO Handle this return; } - let mut cur = lines_to_not_use.len() - 1; - for (idx, line) in all_lines.split("\n").enumerate() { - if lines_to_not_use.get(cur).unwrap() == &idx { - cur = cur.saturating_sub(1); - } else { - writeln!(linebuf, "{line}").unwrap_or_else(|e| panic!("{e}")); + // Add patternized todos + if let Some(patterns) = patterns_path { + let patterns_file = OpenOptions::new() + .read(true) + .open(patterns) + .expect("file should exist"); + let notes_bufreader = BufReader::new(&patterns_file); + let positions: Vec = ron::de::from_reader(notes_bufreader).expect("File should be of correct ron syntax"); + for p in &positions { + match p.pattern { + Pattern::Yearly { doy } => {} + Pattern::Monthly { dom } => { + let mut date_idx = latest_date.clone(); + while date_idx <= today { + if date_idx.day() == dom { + writeln!(str_buf1, "- [ ] {}", &p.name).unwrap_or_else(|e| panic!("{e}")); + break; + } + date_idx += Duration::days(1); + } + } + Pattern::MonthlyOpt { dom, ref months } => { + let mut date_idx = latest_date.clone(); + while date_idx <= today { + if date_idx.day() == dom && months.contains(&chrono::Month::try_from(u8::try_from(date_idx.month()).unwrap()).unwrap()) { + writeln!(str_buf1, "- [ ] {}", &p.name).unwrap_or_else(|e| panic!("{e}")); + break; + } + date_idx += Duration::days(1); + } + } + Pattern::Weekly { dow } => {} + Pattern::Daily => { + writeln!(str_buf1, "- [ ] {}", &p.name).unwrap_or_else(|e| panic!("{e}")); + } + } } } - file.write_all(linebuf.as_bytes()).unwrap_or_else(|e| panic!("Maaan look out for these files. Error is: {e}")); -} + // Add todos from prev days + let mut cur = lines_to_not_use.len() - 1; + for (idx, line) in str_buf2.split("\n").enumerate() { + if lines_to_not_use.get(cur).unwrap() == &idx { + cur = cur.saturating_sub(1); + } else { + writeln!(str_buf1, "{line}").unwrap_or_else(|e| panic!("{e}")); + } + } -// fn parse_patterns(notes_path: &Path, patterns_path: &Path) { -// // Put all patternized todos into today -// } + str_buf2.clear(); + + notes_file.write_all(str_buf1.as_bytes()).unwrap_or_else(|e| panic!("Maaan look out for these files. Error is: {e}")); +} fn main() { let args = Args::parse(); - update_notes(&args.notes); - // if let Some(patterns_path) = args.patterns { - // parse_patterns(&args.notes, &patterns_path); - // } + update_notes(&args.notes, args.patterns); } + + +// TODO für Patterns alle Tage zwischen heute und letztem Mal ausführen scannen