i3status_ext/
lib.rs

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