From dfb2fee6ea405f9e604509fedca36242091a9d05 Mon Sep 17 00:00:00 2001 From: Martin Bley Date: Fri, 11 Sep 2026 18:32:46 +0200 Subject: [PATCH] Claude optimisations --- Cargo.lock | 33 ++++++- Cargo.toml | 2 +- src/main.rs | 256 ++++++++++++++++++++++++++-------------------------- 3 files changed, 158 insertions(+), 133 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index d28a563..dcdc646 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1,5 +1,7 @@ # This file is automatically @generated by Cargo. # It is not intended for manual editing. +version = 4 + [[package]] name = "ansi_term" version = "0.11.0" @@ -83,15 +85,15 @@ name = "i3status-custom-pim" version = "0.1.0" dependencies = [ "chrono", - "json", + "serde_json", "structopt", ] [[package]] -name = "json" -version = "0.12.4" +name = "itoa" +version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "078e285eafdfb6c4b434e0d31e8cfcb5115b651496faca5749b88fafd4f23bfd" +checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" [[package]] name = "lazy_static" @@ -166,6 +168,29 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "ryu" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9774ba4a74de5f7b1c1451ed6cd5285a32eddb5cccb8cc655a4e50009e06477f" + +[[package]] +name = "serde" +version = "1.0.185" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be9b6f69f1dfd54c3b568ffa45c310d6973a5e5148fd40cf515acaf38cf5bc31" + +[[package]] +name = "serde_json" +version = "1.0.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb0652c533506ad7a2e353cce269330d6afd8bdfb6d75e0ace5b35aacbd7b9e9" +dependencies = [ + "itoa", + "ryu", + "serde", +] + [[package]] name = "strsim" version = "0.8.0" diff --git a/Cargo.toml b/Cargo.toml index eecfd87..607a950 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,4 +10,4 @@ edition = "2018" [dependencies] structopt = "0.3" chrono = "0.4" -json = "0.12.4" +serde_json = "1.0" diff --git a/src/main.rs b/src/main.rs index f2be1b9..76d977e 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,6 +1,6 @@ use chrono::{Local, NaiveDateTime, NaiveTime}; +use serde_json::json; use std::process; -use std::process::Command; use structopt::StructOpt; #[derive(Debug, StructOpt)] @@ -42,150 +42,150 @@ enum Status { Critical, } -fn get_status(events: Vec, w: i64, c: i64) -> Result { - let now = Local::now(); - let mut event_remaining: i64 = 365 * 24 * 60; - let mut state = Status::Idle; - - for e in events.iter() { - let diff = *e - now.naive_local(); - if (diff.num_minutes() < event_remaining) && (diff.num_minutes() >= 0) { - event_remaining = diff.num_minutes() - } - - if event_remaining >= 0 { - if event_remaining <= w { - state = Status::Warning; - } - if event_remaining <= c { - state = Status::Critical; - } +impl Status { + fn as_str(&self) -> &str { + match self { + Status::Idle => "Idle", + Status::Warning => "Warning", + Status::Critical => "Critical", } } - Ok(state) +} + +fn get_status(events: Vec, warn: i64, crit: i64) -> Status { + let now = Local::now().naive_local(); + + let nearest = events + .into_iter() + .filter_map(|e| { + let diff = e.signed_duration_since(now).num_minutes(); + if diff >= 0 { + Some(diff) + } else { + None + } + }) + .min() + .unwrap_or(365 * 24 * 60); + + if nearest <= crit { + Status::Critical + } else if nearest <= warn { + Status::Warning + } else { + Status::Idle + } +} + +fn run_command(cmd: &str, args: &[&str]) -> Result { + std::process::Command::new(cmd) + .args(args) + .output() + .map_err(|e| format!("Error running {}: {}", cmd, e)) + .and_then(|output| { + String::from_utf8(output.stdout) + .map_err(|e| format!("Converting output failed: {}", e)) + }) +} + +fn output_json(icon: &str, state: &Status, count: usize) { + println!( + "{}", + json!({ + "icon": icon, + "state": state.as_str(), + "text": count.to_string() + }) + ); } fn main() { let args = Cli::from_args(); - if (!args.khal && !args.todo) || (args.khal && args.todo) { - eprintln!("Please provide either khal or todo flag."); + let mode_count = (args.khal as i32) + (args.todo as i32); + if mode_count != 1 { + eprintln!("Please provide either --khal or --todo flag (not both, not none)."); Cli::clap().print_help().unwrap(); println!(); process::exit(1); } + if args.khal { + handle_khal(&args); + } else { + handle_todo(&args); + } +} + +fn handle_khal(args: &Cli) { let now = Local::now().time(); let from = now.format("%H:%M").to_string(); - if args.khal { - let cmd = Command::new("khal") - .arg("list") - .arg("-df") - .arg("{name}") - .arg("-f") - .arg("{start-time}") - .arg("--notstarted") - .arg(&from) - .arg("23:59") - .output(); - - let stdout = match cmd { - Ok(o) => o.stdout, - Err(e) => { - eprintln!("Error running khal: {}", e); - process::exit(1); - } - }; - - let output = match String::from_utf8(stdout) { - Ok(o) => o, - Err(e) => { - eprintln!("Converting output failed: {}", e); - process::exit(1); - } - }; - - let mut out_iter = output.lines(); - - let dayline = match out_iter.nth(0) { - Some(d) => d, - None => { "None" } - }; - - let today = Local::today(); - let mut events: Vec = Vec::new(); - if dayline.trim() == "Today" { - for e in out_iter { - let event_start = match NaiveTime::parse_from_str(e, "%H:%M") { - Ok(s) => s, - Err(_f) => { - continue; - } - }; - events.push(today.and_time(event_start).unwrap().naive_local()); - } + let output = match run_command( + "khal", + &["list", "-df", "{name}", "-f", "{start-time}", "--notstarted", &from, "23:59"], + ) { + Ok(o) => o, + Err(e) => { + eprintln!("{}", e); + process::exit(1); } - let count = events.len(); - let state = match get_status(events, args.threshold_warn, args.threshold_crit) { - Ok(s) => s, - Err(e) => { - eprintln!("{}", e); - process::exit(1); - } - }; + }; - println!( - "{{ \"icon\": \"{}\", \"state\": \"{:?}\", \"text\": \"{}\" }}", - args.icon, state, count - ); - } else if args.todo { - let cmd = Command::new("todo").arg("--porcelain").output(); + let mut lines = output.lines(); + let is_today = matches!(lines.next(), Some("Today")); - let stdout = match cmd { - Ok(o) => o.stdout, - Err(e) => { - eprintln!("Error running todo: {}", e); - process::exit(1); - } - }; - - let output = match String::from_utf8(stdout) { - Ok(o) => o, - Err(e) => { - eprintln!("Converting output failed: {}", e); - process::exit(1); - } - }; - - let parsed = match json::parse(&output) { - Ok(p) => p, - Err(e) => { - eprintln!("parsing JSON failed: {}", e); - process::exit(1); - } - }; - - let count = parsed.len(); - //println!("{:#?}", count); - - let mut tasks: Vec = Vec::new(); - for due in parsed.members() { - tasks.push(NaiveDateTime::from_timestamp( - due["due"].as_i64().unwrap(), - 0, - )); - } - let state = match get_status(tasks, args.threshold_warn, args.threshold_crit) { - Ok(s) => s, - Err(e) => { - eprintln!("{}", e); - process::exit(1); - } - }; - println!( - "{{ \"icon\": \"{}\", \"state\": \"{:?}\", \"text\": \"{}\" }}", - args.icon, state, count - ); + if !is_today { + output_json(&args.icon, &Status::Idle, 0); + return; } + + let today = Local::today(); + let events: Vec = lines + .filter_map(|line| { + NaiveTime::parse_from_str(line.trim(), "%H:%M") + .ok() + .and_then(|time| today.and_time(time)) + .map(|dt| dt.naive_local()) + }) + .collect(); + + let count = events.len(); + let state = get_status(events, args.threshold_warn, args.threshold_crit); + + output_json(&args.icon, &state, count); } + +fn handle_todo(args: &Cli) { + let output = match run_command("todo", &["--porcelain"]) { + Ok(o) => o, + Err(e) => { + eprintln!("{}", e); + process::exit(1); + } + }; + + let parsed = match serde_json::from_str::>(&output) { + Ok(p) => p, + Err(e) => { + eprintln!("Parsing JSON failed: {}", e); + process::exit(1); + } + }; + + let count = parsed.len(); + + let tasks: Vec = parsed + .iter() + .filter_map(|due| { + due.get("due") + .and_then(|v| v.as_i64()) + .map(|ts| NaiveDateTime::from_timestamp(ts, 0)) + }) + .collect(); + + let state = get_status(tasks, args.threshold_warn, args.threshold_crit); + + output_json(&args.icon, &state, count); +} +