Impl "--regex-cmd=<cmd>,<args...>,<regex>"

The "--regex-cmd=..." allows executing processes and fetching regex'd strings
from its stdout to use on swaybar.
This commit is contained in:
Stephen Seo 2022-07-12 17:30:15 +09:00
parent 90dee55e7a
commit 9305a968a8
6 changed files with 188 additions and 12 deletions

33
Cargo.lock generated
View file

@ -2,6 +2,15 @@
# It is not intended for manual editing. # It is not intended for manual editing.
version = 3 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]] [[package]]
name = "autocfg" name = "autocfg"
version = "1.1.0" version = "1.1.0"
@ -33,6 +42,12 @@ version = "0.2.126"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "349d5a591cd28b49e1d1037471617a32ddcda5731b99419008085f72d5a53836" checksum = "349d5a591cd28b49e1d1037471617a32ddcda5731b99419008085f72d5a53836"
[[package]]
name = "memchr"
version = "2.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d"
[[package]] [[package]]
name = "num-integer" name = "num-integer"
version = "0.1.45" version = "0.1.45"
@ -70,6 +85,23 @@ dependencies = [
"proc-macro2", "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]] [[package]]
name = "ryu" name = "ryu"
version = "1.0.10" version = "1.0.10"
@ -112,6 +144,7 @@ name = "swaybar_info"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"chrono", "chrono",
"regex",
"serde", "serde",
"serde_json", "serde_json",
] ]

View file

@ -9,3 +9,4 @@ edition = "2021"
serde_json = "1.0" serde_json = "1.0"
serde = { version = "1.0", features = ["derive"] } serde = { version = "1.0", features = ["derive"] }
chrono = "0.4" chrono = "0.4"
regex = "1.5"

View file

@ -20,10 +20,15 @@ Put the following in your `~/.config/sway/config`:
# want to monitor. You can omit --netdev=<device>, but that will also # want to monitor. You can omit --netdev=<device>, but that will also
# cause the program to omit network traffic stats. # cause the program to omit network traffic stats.
status_command $HOME/.config/sway/swaybar_info --netdev=enp7s0 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 ## Dependencies
Uses [`serde_json`](https://crates.io/crates/serde_json), Uses [`serde_json`](https://crates.io/crates/serde_json),
[`serde`](https://crates.io/crates/serde), [`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).

View file

@ -2,8 +2,14 @@ use std::collections::HashMap;
use std::io; use std::io;
use std::io::Write; 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 map = HashMap::new();
let mut regex_cmds = Vec::new();
let mut first = true; let mut first = true;
for arg in std::env::args() { for arg in std::env::args() {
@ -16,6 +22,9 @@ pub fn get_args() -> HashMap<String, String> {
} else if arg.starts_with("--interval-sec=") { } else if arg.starts_with("--interval-sec=") {
let (_, back) = arg.split_at(15); let (_, back) = arg.split_at(15);
map.insert("interval-sec".into(), back.into()); 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" { } else if arg == "--help" || arg == "-h" {
map.insert("help".into(), "".into()); map.insert("help".into(), "".into());
} else { } else {
@ -26,19 +35,24 @@ pub fn get_args() -> HashMap<String, String> {
} }
} }
map ArgsResult { map, regex_cmds }
} }
pub fn print_usage() { pub fn print_usage() {
let mut stderr_handle = io::stderr().lock(); let mut stderr_handle = io::stderr().lock();
stderr_handle.write_all(b"Usage:\n").ok(); stderr_handle.write_all(b"Usage:\n").ok();
stderr_handle stderr_handle
.write_all(b" -h | --help\t\t\tPrints help\n") .write_all(b" -h | --help\t\t\t\tPrints help\n")
.ok(); .ok();
stderr_handle stderr_handle
.write_all(b" --netdev=<device_name>\tCheck network traffic on specified device\n") .write_all(b" --netdev=<device_name>\t\tCheck network traffic on specified device\n")
.ok(); .ok();
stderr_handle stderr_handle
.write_all(b" --interval-sec=<seconds>\tOutput at intervals of <seconds> (default 5)\n") .write_all(b" --interval-sec=<seconds>\t\tOutput at intervals of <seconds> (default 5)\n")
.ok();
stderr_handle
.write_all(
b" --regex-cmd=<cmd>,<args...>,<regex>\tUse an output of a command as a metric\n",
)
.ok(); .ok();
} }

64
src/external.rs Normal file
View file

@ -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<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())
}

View file

@ -1,4 +1,5 @@
mod args; mod args;
mod external;
mod proc; mod proc;
mod swaybar_object; mod swaybar_object;
@ -7,21 +8,47 @@ use std::time::Duration;
use swaybar_object::*; use swaybar_object::*;
fn main() { fn main() {
let args_map = args::get_args(); let args_result = args::get_args();
if args_map.contains_key("help") { if args_result.map.contains_key("help") {
args::print_usage(); args::print_usage();
return; 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 net_obj: Option<proc::NetInfo> = None;
let mut interval: Duration = Duration::from_secs(5); 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( 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") { if args_result.map.contains_key("interval-sec") {
let seconds: Result<i64, _> = args_map.get("interval-sec").unwrap().parse(); let seconds: Result<i64, _> = args_result.map.get("interval-sec").unwrap().parse();
if let Ok(seconds_value) = seconds { if let Ok(seconds_value) = seconds {
if seconds_value > 0 { if seconds_value > 0 {
interval = Duration::from_secs(seconds_value as u64); 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 // loadavg
{ {
let loadavg_result = proc::get_loadavg(); let loadavg_result = proc::get_loadavg();