# It is not intended for manual editing.
version = 3
+[[package]]
+name = "aho-corasick"
+version = "0.7.18"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1e37cfd5e7657ada45f742d6e99ca5788580b5c529dc78faf11ece6dc702656f"
+dependencies = [
+ "memchr",
+]
+
[[package]]
name = "autocfg"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "349d5a591cd28b49e1d1037471617a32ddcda5731b99419008085f72d5a53836"
+[[package]]
+name = "memchr"
+version = "2.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d"
+
[[package]]
name = "num-integer"
version = "0.1.45"
"proc-macro2",
]
+[[package]]
+name = "regex"
+version = "1.6.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4c4eb3267174b8c6c2f654116623910a0fef09c4753f8dd83db29c48a0df988b"
+dependencies = [
+ "aho-corasick",
+ "memchr",
+ "regex-syntax",
+]
+
+[[package]]
+name = "regex-syntax"
+version = "0.6.27"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a3f87b73ce11b1619a3c6332f45341e0047173771e8b8b73f87bfeefb7b56244"
+
[[package]]
name = "ryu"
version = "1.0.10"
version = "0.1.0"
dependencies = [
"chrono",
+ "regex",
"serde",
"serde_json",
]
serde_json = "1.0"
serde = { version = "1.0", features = ["derive"] }
chrono = "0.4"
+regex = "1.5"
# want to monitor. You can omit --netdev=<device>, but that will also
# cause the program to omit network traffic stats.
status_command $HOME/.config/sway/swaybar_info --netdev=enp7s0
+
+ # One can use the "--regex-cmd=<cmd>,<args...>,<regex>" option like so:
+ status_command $HOME/.config/sway/swaybar_info --regex-cmd="acpi,-b,[0-9]+%.*"
+ # This example gets battery info into the bar.
}
## Dependencies
Uses [`serde_json`](https://crates.io/crates/serde_json),
[`serde`](https://crates.io/crates/serde),
-and [`chrono`](https://crates.io/crates/chrono).
+[`chrono`](https://crates.io/crates/chrono),
+and [`regex`](https://crates.io/crates/regex).
use std::io;
use std::io::Write;
-pub fn get_args() -> HashMap<String, String> {
+pub struct ArgsResult {
+ pub map: HashMap<String, String>,
+ pub regex_cmds: Vec<String>,
+}
+
+pub fn get_args() -> ArgsResult {
let mut map = HashMap::new();
+ let mut regex_cmds = Vec::new();
let mut first = true;
for arg in std::env::args() {
} else if arg.starts_with("--interval-sec=") {
let (_, back) = arg.split_at(15);
map.insert("interval-sec".into(), back.into());
+ } else if arg.starts_with("--regex-cmd=") {
+ let (_, back) = arg.split_at(12);
+ regex_cmds.push(back.to_owned());
} else if arg == "--help" || arg == "-h" {
map.insert("help".into(), "".into());
} else {
}
}
- map
+ ArgsResult { map, regex_cmds }
}
pub fn print_usage() {
let mut stderr_handle = io::stderr().lock();
stderr_handle.write_all(b"Usage:\n").ok();
stderr_handle
- .write_all(b" -h | --help\t\t\tPrints help\n")
+ .write_all(b" -h | --help\t\t\t\tPrints help\n")
+ .ok();
+ stderr_handle
+ .write_all(b" --netdev=<device_name>\t\tCheck network traffic on specified device\n")
.ok();
stderr_handle
- .write_all(b" --netdev=<device_name>\tCheck network traffic on specified device\n")
+ .write_all(b" --interval-sec=<seconds>\t\tOutput at intervals of <seconds> (default 5)\n")
.ok();
stderr_handle
- .write_all(b" --interval-sec=<seconds>\tOutput at intervals of <seconds> (default 5)\n")
+ .write_all(
+ b" --regex-cmd=<cmd>,<args...>,<regex>\tUse an output of a command as a metric\n",
+ )
.ok();
}
--- /dev/null
+use regex::Regex;
+use std::io;
+use std::process::Command;
+
+#[derive(Debug)]
+pub enum Error {
+ IO(io::Error),
+ FromUTF8(std::string::FromUtf8Error),
+ Generic(String),
+}
+
+impl From<io::Error> for Error {
+ fn from(io_error: io::Error) -> Self {
+ Self::IO(io_error)
+ }
+}
+
+impl From<std::string::FromUtf8Error> for Error {
+ fn from(utf8_error: std::string::FromUtf8Error) -> Self {
+ Self::FromUTF8(utf8_error)
+ }
+}
+
+impl From<String> for Error {
+ fn from(string: String) -> Self {
+ Self::Generic(string)
+ }
+}
+
+impl std::fmt::Display for Error {
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+ match self {
+ Error::IO(e) => e.fmt(f),
+ Error::FromUTF8(e) => e.fmt(f),
+ Error::Generic(s) => f.write_str(s),
+ }
+ }
+}
+
+impl std::error::Error for Error {
+ fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
+ match self {
+ Error::IO(e) => e.source(),
+ Error::FromUTF8(e) => e.source(),
+ Error::Generic(_) => None,
+ }
+ }
+}
+
+pub fn get_cmd_output(cmd: &str, args: &[&str], regex: &Regex) -> Result<String, Error> {
+ let mut cmd_builder = Command::new(cmd);
+ for arg in args {
+ cmd_builder.arg(arg);
+ }
+ let output = cmd_builder.output()?;
+ let stdout_output: String = String::from_utf8(output.stdout)?;
+ let regex_captures = regex
+ .captures(&stdout_output)
+ .ok_or_else(|| Error::from("Regex returned empty matches".to_owned()))?;
+ let regex_match = regex_captures
+ .get(0)
+ .ok_or_else(|| Error::from("Failed to get regex match".to_owned()))?;
+ Ok(regex_match.as_str().to_owned())
+}
mod args;
+mod external;
mod proc;
mod swaybar_object;
use swaybar_object::*;
fn main() {
- let args_map = args::get_args();
- if args_map.contains_key("help") {
+ let args_result = args::get_args();
+ if args_result.map.contains_key("help") {
args::print_usage();
return;
}
+ let mut cmds: Vec<(&str, Vec<&str>, regex::Regex)> = Vec::new();
+ for regex_cmd in &args_result.regex_cmds {
+ let mut split_strs = regex_cmd.split_terminator(',');
+ let cmd: &str = split_strs.next().expect("Should have cmd in option");
+ let mut args: Vec<&str> = Vec::new();
+ let mut next: Option<&str>;
+ loop {
+ next = split_strs.next();
+ if let Some(str) = next {
+ args.push(str);
+ } else {
+ break;
+ }
+ }
+ if args.is_empty() {
+ panic!("Missing regex for --regex-cmd=<cmd>,<args...>,<regex>");
+ }
+
+ let regex_str: &str = args[args.len() - 1];
+ args.pop();
+
+ let regex = regex::Regex::new(regex_str).expect("Should be able to compile regex");
+
+ cmds.push((cmd, args, regex));
+ }
+
let mut net_obj: Option<proc::NetInfo> = None;
let mut interval: Duration = Duration::from_secs(5);
- if args_map.contains_key("netdev") {
+ if args_result.map.contains_key("netdev") {
net_obj = Some(proc::NetInfo::new(
- args_map.get("netdev").unwrap().to_owned(),
+ args_result.map.get("netdev").unwrap().to_owned(),
));
}
- if args_map.contains_key("interval-sec") {
- let seconds: Result<i64, _> = args_map.get("interval-sec").unwrap().parse();
+ if args_result.map.contains_key("interval-sec") {
+ let seconds: Result<i64, _> = args_result.map.get("interval-sec").unwrap().parse();
if let Ok(seconds_value) = seconds {
if seconds_value > 0 {
interval = Duration::from_secs(seconds_value as u64);
}
}
+ // regex_cmds
+ {
+ for (idx, (cmd, args, regex)) in cmds.iter().enumerate() {
+ let cmd_result = external::get_cmd_output(cmd, args, regex);
+ if let Ok(cmd_string) = cmd_result {
+ if is_empty {
+ let cmd_obj =
+ SwaybarObject::from_string(format!("regex_cmd_{}", idx), cmd_string);
+ array.push_object(cmd_obj);
+ } else if let Some(cmd_obj) =
+ array.get_by_name_mut(&format!("regex_cmd_{}", idx))
+ {
+ cmd_obj.update_as_generic(cmd_string, None);
+ }
+ } else if let Err(e) = cmd_result {
+ let mut stderr_handle = io::stderr().lock();
+ stderr_handle.write_all(format!("{}\n", e).as_bytes()).ok();
+ if is_empty {
+ let cmd_obj = SwaybarObject::from_error_string(
+ format!("regex_cmd_{}", idx),
+ "REGEX_CMD ERROR".into(),
+ );
+ array.push_object(cmd_obj);
+ } else if let Some(cmd_obj) =
+ array.get_by_name_mut(&format!("regex_cmd_{}", idx))
+ {
+ cmd_obj.update_as_error("REGEX_CMD ERROR".into());
+ }
+ }
+ }
+ }
+
// loadavg
{
let loadavg_result = proc::get_loadavg();