i3status_ext/lib.rs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129
extern crate serde_json;
use regex::Regex;
use serde::{Deserialize, Serialize};
pub mod io;
use io::*;
#[cfg(test)]
mod tests;
/// i3status json entry in a struct like described
/// [here](https://i3wm.org/docs/i3status.html#_external_scripts_programs_with_i3status)
#[derive(Serialize, Deserialize, Debug)]
struct I3StatusItem {
name: String,
instance: Option<String>,
markup: String,
color: Option<String>,
full_text: String,
}
// Internal
fn begin_io<IO : Io>(io: &mut IO) -> std::io::Result<()> {
// read first two lines, check and ignore them
let line = io.read_line()?;
io.write_line(&line)?;
assert!(line == "{\"version\":1}\n");
let line = io.read_line()?;
io.write_line(&line)?;
assert!(line == "[\n");
return Ok(());
}
/// Call this function once at program start so that `i3status_ext` can pass-through the header
/// from `stdin` which comes initially from i3status.
pub fn begin() -> std::io::Result<StdIo> {
let mut io = StdIo::new();
begin_io(&mut io)?;
return Ok(io);
}
/// dummiy version of `begin()`
pub fn begin_dummy() -> std::io::Result<StdIo> {
return Ok(StdIo::new());
}
/// Call this function once at program start so that `i3status_ext` can pass-through the header
/// from given String (used for tests).
pub fn begin_str<'a>( input : &'a String) -> std::io::Result<StringInStdOut<'a>> {
let mut io = StringInStdOut::from_string(&input);
begin_io(&mut io)?;
return Ok(io);
}
/// Insert new an item into *i3status*'s json string at given position.
/// Call this within a loop continuously to add your custom item into the json data from *i3status*.
/// #### Parameters
/// - `io`: input and output channels behind `Io` trait
/// - `name`: name of the *i3status* item (could be anything)
/// - `position`: insert item at this position (from left to right)
/// - `reverse`: reverse `position` to count from right to left.
/// - `what`: text to insert
pub fn update<IO: Io>(
io: &mut IO,
name: &str,
position: usize,
reverse: bool,
what: &str,
) -> std::io::Result<()> {
// read one line from stdin
let mut line = io.read_line()?;
// check if begin() was called
assert!(line != "{\"version\":1}");
assert!(line != "[");
// handle prefix comma
if line.chars().next().unwrap() == ',' {
line.remove(0);
io.write_line(",")?;
}
// read all incoming entries
match serde_json::from_str(&line) {
Ok(i) => {
let mut items: Vec<I3StatusItem> = i;
// insert this one
let w: I3StatusItem = I3StatusItem {
full_text: what.to_string(),
markup: "none".to_string(),
name: name.to_string(),
instance: None,
color: None,
};
// insert at given position
if reverse {
items.insert(items.len() - 1 - position, w);
} else {
items.insert(position, w);
}
// format output back up json string
io.write_line(&format_json(format!("{:?}", items)))?;
}
_ => io.write_line(&line)?,
}
Ok(())
}
/// preprocess output so that i3bar will eat it
fn format_json(line: String) -> String {
// FIXIT: all the following replacements are needed because I just can not deal
// with serde_json the right way :/ PLEASE HELP!
let line = line
// remove all the 'Item' names
// thought about using '#[serde(rename = "name")]' but could not make it work
.replace("I3StatusItem", "")
// remove optional values which are 'None'
// tried '#[serde(skip_serializing_if = "Option::is_none")]' but did not work.
.replace(", color: None", "")
.replace(", instance: None", "")
// add quotations arround json names. can you setup serge_json doing that?
.replace(", full_text:", ",\"full_text\":")
.replace(", instance:", ",\"instance\":")
.replace(", color:", ",\"color\":")
.replace(", markup:", ",\"markup\":")
.replace("{ name:", "{\"name\":");
// remove the 'Some()' envelop from all optional values
let re = Regex::new(r"Some\((?P<v>[^\)]*)\)").unwrap();
re.replace_all(&line, "$v").to_owned().to_string()
}