Compare commits
13
Commits
57142c6645
...
v0.9.1
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4bc7ff2fde | ||
|
|
dfb2fee6ea | ||
|
|
4d6ada502d | ||
|
|
c624a111dc | ||
|
|
da5d80a1c8 | ||
|
|
e35a1eeda1 | ||
|
|
dc18aaaf03 | ||
|
|
6b6919fed2 | ||
|
|
085973963a | ||
|
|
6664709189 | ||
|
|
11cbf9687e | ||
|
|
466d2807e6 | ||
|
|
b2c4547842 |
@@ -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
@@ -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
@@ -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"
|
||||||
|
|||||||
@@ -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)
|

|
||||||
|
|
||||||
|
`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
@@ -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);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
Reference in New Issue
Block a user