]> git.seodisparate.com - swaybar_info/commitdiff
Impl "--regex-cmd=<cmd>,<args...>,<regex>"
authorStephen Seo <seo.disparate@gmail.com>
Tue, 12 Jul 2022 08:30:15 +0000 (17:30 +0900)
committerStephen Seo <seo.disparate@gmail.com>
Tue, 12 Jul 2022 08:30:15 +0000 (17:30 +0900)
The "--regex-cmd=..." allows executing processes and fetching regex'd strings
from its stdout to use on swaybar.

Cargo.lock
Cargo.toml
README.md
src/args.rs
src/external.rs [new file with mode: 0644]
src/main.rs

index 7b025d55bc074128d183d91106f23928f2c6849f..a4a28c448cd5bc7fb652463be3550642d401af57 100644 (file)
@@ -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",
 ]
index d5e8c8b521b23aa468c2e08024da10e7f410a088..e7968ec64a6ad2e435d5c12901ad42510e2cc049 100644 (file)
@@ -9,3 +9,4 @@ edition = "2021"
 serde_json = "1.0"
 serde = { version = "1.0", features = ["derive"] }
 chrono = "0.4"
+regex = "1.5"
index 48edae7fa0634c39d5b8e06c77bd9edbafec6423..af96ddd6fb1528abeef15f3b387c7f78963c02c3 100644 (file)
--- 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=<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).
index fa843ac5060d851b45598b4b40ac7efaa5168bfe..31a29552d1d6ab6f18130d67fd69a98e7ac39add 100644 (file)
@@ -2,8 +2,14 @@ use std::collections::HashMap;
 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() {
@@ -16,6 +22,9 @@ pub fn get_args() -> HashMap<String, String> {
         } 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<String, String> {
         }
     }
 
-    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();
 }
diff --git a/src/external.rs b/src/external.rs
new file mode 100644 (file)
index 0000000..a8b4185
--- /dev/null
@@ -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())
+}
index c27d40f6fc85704194332611adb521b4f2522fb6..6650e09a7e954652fbfd66998bd257a813bb12fc 100644 (file)
@@ -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=<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);
@@ -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();