Compare commits

..
13 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
theamma 4d6ada502d WIth new khal version output changed
Rust / build (push) Has been cancelled
Fixed empty output bug for khal
2024-12-12 22:02:04 +01:00
Martin Bley c624a111dc Updated README 2022-01-02 18:12:13 +01:00
amma da5d80a1c8 bugfix, when khal event is a birthday 2020-11-28 12:18:50 +01:00
amma e35a1eeda1 Changes email adrress of author 2020-11-26 13:05:48 +01:00
amma dc18aaaf03 wrote documentation 2020-11-26 12:59:13 +01:00
amma 6b6919fed2 bugfix on enum for status 2020-11-25 22:59:41 +01:00
amma 085973963a fixed formating 2020-11-25 22:46:39 +01:00
amma 6664709189 bugfix for calculation of remaining time until due time reached 2020-11-25 22:40:28 +01:00
amma 11cbf9687e Merge branch 'master' of github.com:theamma/i3status-custom-pim 2020-11-25 22:12:49 +01:00
amma 466d2807e6 impemented todo 2020-11-25 22:10:35 +01:00
theamma b2c4547842 Create rust.yml 2020-11-20 14:13:17 +01:00
6 changed files with 250 additions and 139 deletions
+22
View File
@@ -0,0 +1,22 @@
name: Rust
on:
push:
branches: [ master ]
pull_request:
branches: [ master ]
env:
CARGO_TERM_COLOR: always
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Build
run: cargo build --verbose
- name: Run tests
run: cargo test --verbose
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"
+3 -3
View File
@@ -1,8 +1,8 @@
[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 <martin@mb-oss.de>"] authors = ["Martin Bley <dev@mb-oss.de>"]
edition = "2018" edition = "2018"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
@@ -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"
+64 -1
View File
@@ -1,3 +1,66 @@
# i3status-custom-pim # i3status-custom-pim
Generates JSON output for use with _custom_ block of [i3status-rust](https://github.com/greshake/i3status-rust) ![example bar](img/example_bar.png)
`i3status-custom-pim` is a tool to output JSON content for users of the [pimutils](https://github.com/pimutils)
- [khal](https://github.com/pimutils/khal) and
- [todoman](https://github.com/pimutils/todoman).
The output can be used for the custom block of [i3status-rust](https://github.com/greshake/i3status-rust) to display events or due dates in the i3 status bar.
## Requirements
In order to build a binary, you need the Rust language and the package manager `cargo`. And of course you need the pimutils [khal](https://github.com/pimutils/khal) and/or
[todoman](https://github.com/pimutils/todoman). Please refer to the documentation on the project pages in order to install and configure these tools.
## Install
There are no packaged versions yet, so you have to compile `i3status-cutom-pim` yourself.
```
$ cargo install --git https://github.com/theamma/i3status-custom-pim i3status-custom-pim
```
By default, this will install the binary to `~/.cargo/bin/i3status-custom-pim`. Please make sure, this is in your $PATH or copy the binary to an appropriate location (like `/usr/local/bin`).
You may also compile manually
```
$ git clone https://github.com/theamma/i3status-custom-pim
$ cd i3status-custom-pim && cargo build --release
```
## Configuration
To add the blocks, add this to your i3status-rs configuration
```
[[block]]
block = "custom"
command = ''' i3status-custom-pim -k '''
json = true
[[block]]
block = "custom"
command = ''' i3status-custom-pim -t -i tasks -w 20160 -c 7200'''
json = true
```
Thresholds for *warning* or *critical* state can be given via commandline switches. In *warning* state the widget will change the background colour to orange, in *critical* state to red.
Please note: A due date has to be set for each task, otherwise the block will run into an error. This bug is addressed in issue #2.
Please run `i3status-custom-pim --help` for available options.
```
i3status-custom-pim 0.1.0
Martin Bley <dev@mb-oss.de>
Generates JSON output for use with custom block of i3status-rust.
USAGE:
i3status-custom-pim [FLAGS] [OPTIONS]
FLAGS:
-h, --help Prints help information
-k, --khal output khal events
-t, --todo output todos
-V, --version Prints version information
OPTIONS:
-i, --icon <icon> the icon used (only names valid in i3status-rust) [default: calendar]
-c <threshold-crit> threshold for critical status in minutes [default: 15]
-w <threshold-warn> threshold for warning status in minutes [default: 60]
```
Binary file not shown.

After

Width:  |  Height:  |  Size: 4.0 KiB

+105 -104
View File
@@ -1,6 +1,6 @@
use chrono::{Local, 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)]
@@ -38,153 +38,154 @@ struct Cli {
#[derive(Debug)] #[derive(Debug)]
enum Status { enum Status {
Idle, Idle,
Warn, Warning,
Crit, Critical,
} }
fn get_status(events: Vec<String>, w: i64, c: i64) -> Result<Status, String> { impl Status {
let now = Local::now().time(); fn as_str(&self) -> &str {
let mut event_remaining: i64 = 24 * 60; match self {
let mut state = Status::Idle; Status::Idle => "Idle",
Status::Warning => "Warning",
for e in events.iter() { Status::Critical => "Critical",
let e_start = match NaiveTime::parse_from_str(e, "%H:%M") { }
Ok(s) => s,
Err(f) => {
let message = format!("error parsing time value: {}", f);
return Err(message);
} }
};
let diff = e_start - now;
if (diff.num_minutes() < event_remaining) && (diff.num_minutes() >= 0) {
event_remaining = diff.num_minutes()
} }
if event_remaining >= 0 { fn get_status(events: Vec<NaiveDateTime>, warn: i64, crit: i64) -> Status {
if event_remaining <= w { let now = Local::now().naive_local();
state = Status::Warn;
let nearest = events
.into_iter()
.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::Crit; .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<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))
})
} }
Ok(state)
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 {
Cli::clap().print_help(); eprintln!("Please provide either --khal or --todo flag (not both, not none).");
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 => {
eprintln!("Output seems empty. Exiting");
process::exit(1);
}
};
let mut events: Vec<String> = Vec::new();
if dayline.trim() == "Today" {
for e in out_iter {
events.push(e.to_string());
}
}
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 mut 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<String> = Vec::new(); let tasks: Vec<NaiveDateTime> = parsed
for due in parsed.members() { .iter()
tasks.push(due.as_str().unwrap().to_string()); .filter_map(|due| {
} due.get("due")
/* .and_then(|v| v.as_i64())
for i in parsed { .map(|ts| NaiveDateTime::from_timestamp(ts, 0))
i.foo(); })
} .collect();
*/
let state = get_status(tasks, args.threshold_warn, args.threshold_crit);
output_json(&args.icon, &state, count);
}
}
}