Files
i3status-custom-pim/src/main.rs
theamma 4d6ada502d
Some checks failed
Rust / build (push) Has been cancelled
WIth new khal version output changed
Fixed empty output bug for khal
2024-12-12 22:02:04 +01:00

192 lines
5.0 KiB
Rust

use chrono::{Local, NaiveDateTime, NaiveTime};
use std::process;
use std::process::Command;
use structopt::StructOpt;
#[derive(Debug, StructOpt)]
#[structopt(author, about)]
struct Cli {
#[structopt(short, long, help = "output khal events")]
khal: bool,
#[structopt(short, long, help = "output todos")]
todo: bool,
#[structopt(
short = "w",
default_value = "60",
help = "threshold for warning status in minutes"
)]
threshold_warn: i64,
#[structopt(
short = "c",
default_value = "15",
help = "threshold for critical status in minutes"
)]
threshold_crit: i64,
#[structopt(
short,
long,
default_value = "calendar",
help = "the icon used (only names valid in i3status-rust)"
)]
icon: String,
}
#[derive(Debug)]
enum Status {
Idle,
Warning,
Critical,
}
fn get_status(events: Vec<NaiveDateTime>, w: i64, c: i64) -> Result<Status, String> {
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;
}
}
}
Ok(state)
}
fn main() {
let args = Cli::from_args();
if (!args.khal && !args.todo) || (args.khal && args.todo) {
eprintln!("Please provide either khal or todo flag.");
Cli::clap().print_help().unwrap();
println!();
process::exit(1);
}
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<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) => {
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 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<NaiveDateTime> = 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
);
}
}