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}