Compare commits

..
2 Commits
Author SHA1 Message Date
Martin Bley 4bc7ff2fde New versions string
Rust / build (push) Canceled after 0s
2026-09-11 18:34:07 +02:00
Martin Bley dfb2fee6ea Claude optimisations
Rust / build (push) Canceled after 0s
2026-09-11 18:32:46 +02:00
3 changed files with 159 additions and 134 deletions
Generated
+29 -4
View File
@@ -1,5 +1,7 @@
# This file is automatically @generated by Cargo. # This file is automatically @generated by Cargo.
# It is not intended for manual editing. # It is not intended for manual editing.
version = 4
[[package]] [[package]]
name = "ansi_term" name = "ansi_term"
version = "0.11.0" version = "0.11.0"
@@ -83,15 +85,15 @@ name = "i3status-custom-pim"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"chrono", "chrono",
"json", "serde_json",
"structopt", "structopt",
] ]
[[package]] [[package]]
name = "json" name = "itoa"
version = "0.12.4" version = "1.0.18"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "078e285eafdfb6c4b434e0d31e8cfcb5115b651496faca5749b88fafd4f23bfd" checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682"
[[package]] [[package]]
name = "lazy_static" name = "lazy_static"
@@ -166,6 +168,29 @@ dependencies = [
"proc-macro2", "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]] [[package]]
name = "strsim" name = "strsim"
version = "0.8.0" version = "0.8.0"
+2 -2
View File
@@ -1,7 +1,7 @@
[package] [package]
name = "i3status-custom-pim" name = "i3status-custom-pim"
description = "Generates JSON output for use with custom block of i3status-rust." description = "Generates JSON output for use with custom block of i3status-rust."
version = "0.1.0" version = "0.9.1"
authors = ["Martin Bley <dev@mb-oss.de>"] authors = ["Martin Bley <dev@mb-oss.de>"]
edition = "2018" edition = "2018"
@@ -10,4 +10,4 @@ edition = "2018"
[dependencies] [dependencies]
structopt = "0.3" structopt = "0.3"
chrono = "0.4" chrono = "0.4"
json = "0.12.4" serde_json = "1.0"
+106 -106
View File
@@ -1,6 +1,6 @@
use chrono::{Local, NaiveDateTime, NaiveTime}; use chrono::{Local, NaiveDateTime, NaiveTime};
use serde_json::json;
use std::process; use std::process;
use std::process::Command;
use structopt::StructOpt; use structopt::StructOpt;
#[derive(Debug, StructOpt)] #[derive(Debug, StructOpt)]
@@ -42,150 +42,150 @@ enum Status {
Critical, Critical,
} }
fn get_status(events: Vec<NaiveDateTime>, w: i64, c: i64) -> Result<Status, String> { impl Status {
let now = Local::now(); fn as_str(&self) -> &str {
let mut event_remaining: i64 = 365 * 24 * 60; match self {
let mut state = Status::Idle; Status::Idle => "Idle",
Status::Warning => "Warning",
Status::Critical => "Critical",
}
}
}
for e in events.iter() { fn get_status(events: Vec<NaiveDateTime>, warn: i64, crit: i64) -> Status {
let diff = *e - now.naive_local(); let now = Local::now().naive_local();
if (diff.num_minutes() < event_remaining) && (diff.num_minutes() >= 0) {
event_remaining = diff.num_minutes()
}
if event_remaining >= 0 { let nearest = events
if event_remaining <= w { .into_iter()
state = Status::Warning; .filter_map(|e| {
let diff = e.signed_duration_since(now).num_minutes();
if diff >= 0 {
Some(diff)
} else {
None
} }
if event_remaining <= c { })
state = Status::Critical; .min()
.unwrap_or(365 * 24 * 60);
if nearest <= crit {
Status::Critical
} else if nearest <= warn {
Status::Warning
} else {
Status::Idle
} }
} }
}
Ok(state) fn run_command(cmd: &str, args: &[&str]) -> Result<String, String> {
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() { fn main() {
let args = Cli::from_args(); let args = Cli::from_args();
if (!args.khal && !args.todo) || (args.khal && args.todo) { let mode_count = (args.khal as i32) + (args.todo as i32);
eprintln!("Please provide either khal or todo flag."); if mode_count != 1 {
eprintln!("Please provide either --khal or --todo flag (not both, not none).");
Cli::clap().print_help().unwrap(); Cli::clap().print_help().unwrap();
println!(); println!();
process::exit(1); process::exit(1);
} }
if args.khal {
handle_khal(&args);
} else {
handle_todo(&args);
}
}
fn handle_khal(args: &Cli) {
let now = Local::now().time(); let now = Local::now().time();
let from = now.format("%H:%M").to_string(); let from = now.format("%H:%M").to_string();
if args.khal { let output = match run_command(
let cmd = Command::new("khal") "khal",
.arg("list") &["list", "-df", "{name}", "-f", "{start-time}", "--notstarted", &from, "23:59"],
.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, 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<NaiveDateTime> = 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 count = events.len();
let state = match get_status(events, args.threshold_warn, args.threshold_crit) {
Ok(s) => s,
Err(e) => { Err(e) => {
eprintln!("{}", e); eprintln!("{}", e);
process::exit(1); process::exit(1);
} }
}; };
println!( let mut lines = output.lines();
"{{ \"icon\": \"{}\", \"state\": \"{:?}\", \"text\": \"{}\" }}", let is_today = matches!(lines.next(), Some("Today"));
args.icon, state, count
);
} else if args.todo {
let cmd = Command::new("todo").arg("--porcelain").output();
let stdout = match cmd { if !is_today {
Ok(o) => o.stdout, output_json(&args.icon, &Status::Idle, 0);
Err(e) => { return;
eprintln!("Error running todo: {}", e);
process::exit(1);
} }
};
let output = match String::from_utf8(stdout) { let today = Local::today();
let events: Vec<NaiveDateTime> = 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, Ok(o) => o,
Err(e) => { Err(e) => {
eprintln!("Converting output failed: {}", e); eprintln!("{}", e);
process::exit(1); process::exit(1);
} }
}; };
let parsed = match json::parse(&output) { let parsed = match serde_json::from_str::<Vec<serde_json::Value>>(&output) {
Ok(p) => p, Ok(p) => p,
Err(e) => { Err(e) => {
eprintln!("parsing JSON failed: {}", e); eprintln!("Parsing JSON failed: {}", e);
process::exit(1); process::exit(1);
} }
}; };
let count = parsed.len(); let count = parsed.len();
//println!("{:#?}", count);
let mut tasks: Vec<NaiveDateTime> = Vec::new(); let tasks: Vec<NaiveDateTime> = parsed
for due in parsed.members() { .iter()
tasks.push(NaiveDateTime::from_timestamp( .filter_map(|due| {
due["due"].as_i64().unwrap(), due.get("due")
0, .and_then(|v| v.as_i64())
)); .map(|ts| NaiveDateTime::from_timestamp(ts, 0))
} })
let state = match get_status(tasks, args.threshold_warn, args.threshold_crit) { .collect();
Ok(s) => s,
Err(e) => { let state = get_status(tasks, args.threshold_warn, args.threshold_crit);
eprintln!("{}", e);
process::exit(1); output_json(&args.icon, &state, count);
}
};
println!(
"{{ \"icon\": \"{}\", \"state\": \"{:?}\", \"text\": \"{}\" }}",
args.icon, state, count
);
}
} }