From 9305a968a883e2f3a66138ed47347e85940e25bb Mon Sep 17 00:00:00 2001 From: Stephen Seo Date: Tue, 12 Jul 2022 17:30:15 +0900 Subject: [PATCH] Impl "--regex-cmd=,," The "--regex-cmd=..." allows executing processes and fetching regex'd strings from its stdout to use on swaybar. --- Cargo.lock | 33 +++++++++++++++++++++++ Cargo.toml | 1 + README.md | 7 ++++- src/args.rs | 24 +++++++++++++---- src/external.rs | 64 ++++++++++++++++++++++++++++++++++++++++++++ src/main.rs | 71 ++++++++++++++++++++++++++++++++++++++++++++----- 6 files changed, 188 insertions(+), 12 deletions(-) create mode 100644 src/external.rs diff --git a/Cargo.lock b/Cargo.lock index 7b025d5..a4a28c4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,6 +2,15 @@ # 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" @@ -33,6 +42,12 @@ version = "0.2.126" 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" @@ -70,6 +85,23 @@ dependencies = [ "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" @@ -112,6 +144,7 @@ name = "swaybar_info" version = "0.1.0" dependencies = [ "chrono", + "regex", "serde", "serde_json", ] diff --git a/Cargo.toml b/Cargo.toml index d5e8c8b..e7968ec 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -9,3 +9,4 @@ edition = "2021" serde_json = "1.0" serde = { version = "1.0", features = ["derive"] } chrono = "0.4" +regex = "1.5" diff --git a/README.md b/README.md index 48edae7..af96ddd 100644 --- a/README.md +++ b/README.md @@ -20,10 +20,15 @@ Put the following in your `~/.config/sway/config`: # want to monitor. You can omit --netdev=, 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=,," 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). diff --git a/src/args.rs b/src/args.rs index fa843ac..31a2955 100644 --- a/src/args.rs +++ b/src/args.rs @@ -2,8 +2,14 @@ use std::collections::HashMap; use std::io; use std::io::Write; -pub fn get_args() -> HashMap { +pub struct ArgsResult { + pub map: HashMap, + pub regex_cmds: Vec, +} + +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() { @@ -16,6 +22,9 @@ pub fn get_args() -> HashMap { } 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 { @@ -26,19 +35,24 @@ pub fn get_args() -> HashMap { } } - 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=\tCheck network traffic on specified device\n") + .write_all(b" --netdev=\t\tCheck network traffic on specified device\n") .ok(); stderr_handle - .write_all(b" --interval-sec=\tOutput at intervals of (default 5)\n") + .write_all(b" --interval-sec=\t\tOutput at intervals of (default 5)\n") + .ok(); + stderr_handle + .write_all( + b" --regex-cmd=,,\tUse an output of a command as a metric\n", + ) .ok(); } diff --git a/src/external.rs b/src/external.rs new file mode 100644 index 0000000..a8b4185 --- /dev/null +++ b/src/external.rs @@ -0,0 +1,64 @@ +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 for Error { + fn from(io_error: io::Error) -> Self { + Self::IO(io_error) + } +} + +impl From for Error { + fn from(utf8_error: std::string::FromUtf8Error) -> Self { + Self::FromUTF8(utf8_error) + } +} + +impl From 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 { + 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()) +} diff --git a/src/main.rs b/src/main.rs index c27d40f..6650e09 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,4 +1,5 @@ mod args; +mod external; mod proc; mod swaybar_object; @@ -7,21 +8,47 @@ use std::time::Duration; 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=,,"); + } + + 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 = 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 = args_map.get("interval-sec").unwrap().parse(); + if args_result.map.contains_key("interval-sec") { + let seconds: Result = 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); @@ -145,6 +172,38 @@ fn main() { } } + // 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();