From 98e45150b32ff0bedefa9fa82df5d021d2bc145e Mon Sep 17 00:00:00 2001 From: Nils Pukropp Date: Mon, 16 Mar 2026 19:48:49 +0100 Subject: [PATCH] fixed formatting to be dynamic --- Cargo.lock | 13 +++++++++ Cargo.toml | 1 + example.config.toml | 3 ++ src/modules/audio.rs | 23 ++++++++++++---- src/modules/bt.rs | 6 +++- src/modules/btrfs.rs | 13 +++++---- src/modules/buds.rs | 13 ++++++--- src/modules/cpu.rs | 13 +++++---- src/modules/disk.rs | 15 ++++++---- src/modules/gpu.rs | 19 +++++++------ src/modules/memory.rs | 13 +++++---- src/modules/network.rs | 20 +++++++------- src/modules/power.rs | 12 +++++--- src/modules/sys.rs | 18 ++++++------ src/utils.rs | 62 ++++++++++++++++++++++++++++++++++++++++++ 15 files changed, 181 insertions(+), 63 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index a3f5329..e0fd601 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -148,6 +148,7 @@ dependencies = [ "anyhow", "clap", "fs4", + "regex", "serde", "serde_json", "sysinfo", @@ -312,6 +313,18 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "regex" +version = "1.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e10754a14b9137dd7b1e3e5b0493cc9171fdd105e0ab477f51b72e7f3ac0e276" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + [[package]] name = "regex-automata" version = "0.4.14" diff --git a/Cargo.toml b/Cargo.toml index 6303db7..ff39b82 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -7,6 +7,7 @@ edition = "2024" anyhow = "1.0.102" clap = { version = "4.6.0", features = ["derive"] } fs4 = "0.13.1" +regex = "1.10" serde = { version = "1.0.228", features = ["derive"] } serde_json = "1.0.149" sysinfo = "0.38.4" diff --git a/example.config.toml b/example.config.toml index c920dd9..74b6616 100644 --- a/example.config.toml +++ b/example.config.toml @@ -1,6 +1,9 @@ # fluxo-rs example configuration # place this at ~/.config/fluxo/config.toml +# Note: All tokens support standard alignment, padding, and precision specifiers dynamically. +# For example, you can change {rx:>5.2} to {rx:<8.1} or {used} to {used:^4.0} directly here. + [general] # command used for interactive menus (e.g., bluetooth device selection) # tokens: {prompt} diff --git a/src/modules/audio.rs b/src/modules/audio.rs index 86c2f6d..639e053 100644 --- a/src/modules/audio.rs +++ b/src/modules/audio.rs @@ -2,6 +2,7 @@ use crate::config::Config; use crate::modules::WaybarModule; use crate::output::WaybarOutput; use crate::state::SharedState; +use crate::utils::{format_template, TokenValue}; use anyhow::{Result, anyhow}; use std::process::Command; @@ -58,7 +59,14 @@ impl AudioModule { let (text, class) = if muted { let icon = if target_type == "sink" { "" } else { "" }; let format_str = if target_type == "sink" { &config.audio.format_sink_muted } else { &config.audio.format_source_muted }; - (format_str.replace("{name}", &name).replace("{icon}", icon), "muted") + let t = format_template( + format_str, + &[ + ("name", TokenValue::String(&name)), + ("icon", TokenValue::String(icon)), + ] + ); + (t, "muted") } else { let icon = if target_type == "sink" { if display_vol <= 30 { "" } @@ -68,11 +76,14 @@ impl AudioModule { "" }; let format_str = if target_type == "sink" { &config.audio.format_sink_unmuted } else { &config.audio.format_source_unmuted }; - let t = format_str - .replace("{name}", &name) - .replace("{icon}", icon) - .replace("{volume:>3}", &format!("{:>3}", display_vol)) - .replace("{volume}", &format!("{}", display_vol)); + let t = format_template( + format_str, + &[ + ("name", TokenValue::String(&name)), + ("icon", TokenValue::String(icon)), + ("volume", TokenValue::Int(display_vol as i64)), + ] + ); (t, "unmuted") }; diff --git a/src/modules/bt.rs b/src/modules/bt.rs index 9e66bb5..bcbe733 100644 --- a/src/modules/bt.rs +++ b/src/modules/bt.rs @@ -2,6 +2,7 @@ use crate::config::Config; use crate::modules::WaybarModule; use crate::output::WaybarOutput; use crate::state::SharedState; +use crate::utils::{format_template, TokenValue}; use anyhow::Result; use std::process::Command; @@ -63,7 +64,10 @@ impl WaybarModule for BtModule { battery.map(|b| format!("{}%", b)).unwrap_or_else(|| "N/A".to_string()) ); - let text = config.bt.format_connected.replace("{alias}", &alias); + let text = format_template( + &config.bt.format_connected, + &[("alias", TokenValue::String(&alias))] + ); Ok(WaybarOutput { text, diff --git a/src/modules/btrfs.rs b/src/modules/btrfs.rs index 3496605..0c36414 100644 --- a/src/modules/btrfs.rs +++ b/src/modules/btrfs.rs @@ -2,6 +2,7 @@ use crate::config::Config; use crate::modules::WaybarModule; use crate::output::WaybarOutput; use crate::state::SharedState; +use crate::utils::{format_template, TokenValue}; use anyhow::Result; use sysinfo::Disks; @@ -43,11 +44,13 @@ impl WaybarModule for BtrfsModule { "normal" }; - let text = config.pool.format - .replace("{used:>4.0}", &format!("{:>4.0}", used_gb)) - .replace("{total:>4.0}", &format!("{:>4.0}", size_gb)) - .replace("{used}", &format!("{:.0}", used_gb)) - .replace("{total}", &format!("{:.0}", size_gb)); + let text = format_template( + &config.pool.format, + &[ + ("used", TokenValue::Float(used_gb)), + ("total", TokenValue::Float(size_gb)), + ] + ); Ok(WaybarOutput { text, diff --git a/src/modules/buds.rs b/src/modules/buds.rs index 4466448..118c281 100644 --- a/src/modules/buds.rs +++ b/src/modules/buds.rs @@ -2,6 +2,7 @@ use crate::config::Config; use crate::modules::WaybarModule; use crate::output::WaybarOutput; use crate::state::SharedState; +use crate::utils::{format_template, TokenValue}; use anyhow::Result; use std::process::Command; @@ -101,10 +102,14 @@ impl WaybarModule for BudsModule { _ => ("?", "anc-unknown"), }; - let text = config.buds.format - .replace("{left}", &left_display) - .replace("{right}", &right_display) - .replace("{anc}", anc_icon); + let text = format_template( + &config.buds.format, + &[ + ("left", TokenValue::String(&left_display)), + ("right", TokenValue::String(&right_display)), + ("anc", TokenValue::String(anc_icon)), + ] + ); Ok(WaybarOutput { text, diff --git a/src/modules/cpu.rs b/src/modules/cpu.rs index b079b3e..8f9c481 100644 --- a/src/modules/cpu.rs +++ b/src/modules/cpu.rs @@ -2,6 +2,7 @@ use crate::config::Config; use crate::modules::WaybarModule; use crate::output::WaybarOutput; use crate::state::SharedState; +use crate::utils::{format_template, TokenValue}; use anyhow::Result; pub struct CpuModule; @@ -20,11 +21,13 @@ impl WaybarModule for CpuModule { } }; - let text = config.cpu.format - .replace("{usage:>4.1}", &format!("{:>4.1}", usage)) - .replace("{temp:>4.1}", &format!("{:>4.1}", temp)) - .replace("{usage}", &format!("{:.1}", usage)) - .replace("{temp}", &format!("{:.1}", temp)); + let text = format_template( + &config.cpu.format, + &[ + ("usage", TokenValue::Float(usage)), + ("temp", TokenValue::Float(temp)), + ] + ); let class = if usage > 95.0 { "max" diff --git a/src/modules/disk.rs b/src/modules/disk.rs index f28a545..e440992 100644 --- a/src/modules/disk.rs +++ b/src/modules/disk.rs @@ -2,6 +2,7 @@ use crate::config::Config; use crate::modules::WaybarModule; use crate::output::WaybarOutput; use crate::state::SharedState; +use crate::utils::{format_template, TokenValue}; use anyhow::Result; use sysinfo::Disks; @@ -32,12 +33,14 @@ impl WaybarModule for DiskModule { "normal" }; - let text = config.disk.format - .replace("{mount}", mountpoint) - .replace("{used:>5.1}", &format!("{:>5.1}", used_gb)) - .replace("{total:>5.1}", &format!("{:>5.1}", total_gb)) - .replace("{used}", &format!("{:.1}", used_gb)) - .replace("{total}", &format!("{:.1}", total_gb)); + let text = format_template( + &config.disk.format, + &[ + ("mount", TokenValue::String(mountpoint)), + ("used", TokenValue::Float(used_gb)), + ("total", TokenValue::Float(total_gb)), + ] + ); return Ok(WaybarOutput { text, diff --git a/src/modules/gpu.rs b/src/modules/gpu.rs index 5b5d67a..4052da0 100644 --- a/src/modules/gpu.rs +++ b/src/modules/gpu.rs @@ -2,6 +2,7 @@ use crate::config::Config; use crate::modules::WaybarModule; use crate::output::WaybarOutput; use crate::state::SharedState; +use crate::utils::{format_template, TokenValue}; use anyhow::Result; pub struct GpuModule; @@ -47,15 +48,15 @@ impl WaybarModule for GpuModule { _ => &config.gpu.format_amd, }; - let text = format_str - .replace("{usage:>3.0}", &format!("{:>3.0}", usage)) - .replace("{vram_used:>4.1}", &format!("{:>4.1}", vram_used)) - .replace("{vram_total:>4.1}", &format!("{:>4.1}", vram_total)) - .replace("{temp:>4.1}", &format!("{:>4.1}", temp)) - .replace("{usage}", &format!("{:.0}", usage)) - .replace("{vram_used}", &format!("{:.1}", vram_used)) - .replace("{vram_total}", &format!("{:.1}", vram_total)) - .replace("{temp}", &format!("{:.1}", temp)); + let text = format_template( + format_str, + &[ + ("usage", TokenValue::Float(usage)), + ("vram_used", TokenValue::Float(vram_used)), + ("vram_total", TokenValue::Float(vram_total)), + ("temp", TokenValue::Float(temp)), + ] + ); let tooltip = if vendor == "Intel" { format!("Model: {}\nApprox Usage: {:.0}%", model, usage) diff --git a/src/modules/memory.rs b/src/modules/memory.rs index 42c587a..cb40e23 100644 --- a/src/modules/memory.rs +++ b/src/modules/memory.rs @@ -2,6 +2,7 @@ use crate::config::Config; use crate::modules::WaybarModule; use crate::output::WaybarOutput; use crate::state::SharedState; +use crate::utils::{format_template, TokenValue}; use anyhow::Result; pub struct MemoryModule; @@ -21,11 +22,13 @@ impl WaybarModule for MemoryModule { let ratio = if total_gb > 0.0 { (used_gb / total_gb) * 100.0 } else { 0.0 }; - let text = config.memory.format - .replace("{used:>5.2}", &format!("{:>5.2}", used_gb)) - .replace("{total:>5.2}", &format!("{:>5.2}", total_gb)) - .replace("{used}", &format!("{:.2}", used_gb)) - .replace("{total}", &format!("{:.2}", total_gb)); + let text = format_template( + &config.memory.format, + &[ + ("used", TokenValue::Float(used_gb)), + ("total", TokenValue::Float(total_gb)), + ] + ); let class = if ratio > 95.0 { "max" diff --git a/src/modules/network.rs b/src/modules/network.rs index c0ccb2e..75f78e5 100644 --- a/src/modules/network.rs +++ b/src/modules/network.rs @@ -2,10 +2,10 @@ use crate::config::Config; use crate::modules::WaybarModule; use crate::output::WaybarOutput; use crate::state::SharedState; +use crate::utils::{format_template, TokenValue}; use anyhow::Result; use std::fs; use std::time::{SystemTime, UNIX_EPOCH}; -use tracing::{debug, warn}; pub struct NetworkModule; @@ -101,15 +101,15 @@ impl WaybarModule for NetworkModule { } }; - let mut output_text = config - .network - .format - .replace("{interface}", &interface) - .replace("{ip}", &ip) - .replace("{rx:>5.2}", &format!("{:>5.2}", rx_mbps)) - .replace("{tx:>5.2}", &format!("{:>5.2}", tx_mbps)) - .replace("{rx}", &format!("{:.2}", rx_mbps)) - .replace("{tx}", &format!("{:.2}", tx_mbps)); + let mut output_text = format_template( + &config.network.format, + &[ + ("interface", TokenValue::String(&interface)), + ("ip", TokenValue::String(&ip)), + ("rx", TokenValue::Float(rx_mbps)), + ("tx", TokenValue::Float(tx_mbps)), + ] + ); if interface.starts_with("tun") || interface.starts_with("wg") diff --git a/src/modules/power.rs b/src/modules/power.rs index 146a098..952f2ac 100644 --- a/src/modules/power.rs +++ b/src/modules/power.rs @@ -2,6 +2,7 @@ use crate::config::Config; use crate::modules::WaybarModule; use crate::output::WaybarOutput; use crate::state::SharedState; +use crate::utils::{format_template, TokenValue}; use anyhow::Result; use std::fs; @@ -89,10 +90,13 @@ impl WaybarModule for PowerModule { ) }; - let text = config.power.format - .replace("{percentage:>3}", &format!("{:>3}", percentage)) - .replace("{percentage}", &format!("{}", percentage)) - .replace("{icon}", icon); + let text = format_template( + &config.power.format, + &[ + ("percentage", TokenValue::Int(percentage as i64)), + ("icon", TokenValue::String(icon)), + ] + ); Ok(WaybarOutput { text, diff --git a/src/modules/sys.rs b/src/modules/sys.rs index f2aa89a..f30fa95 100644 --- a/src/modules/sys.rs +++ b/src/modules/sys.rs @@ -2,6 +2,7 @@ use crate::config::Config; use crate::modules::WaybarModule; use crate::output::WaybarOutput; use crate::state::SharedState; +use crate::utils::{format_template, TokenValue}; use anyhow::Result; pub struct SysModule; @@ -30,14 +31,15 @@ impl WaybarModule for SysModule { format!("{}m", minutes) }; - let text = config.sys.format - .replace("{uptime}", &uptime_str) - .replace("{load1:>4.2}", &format!("{:>4.2}", load1)) - .replace("{load5:>4.2}", &format!("{:>4.2}", load5)) - .replace("{load15:>4.2}", &format!("{:>4.2}", load15)) - .replace("{load1}", &format!("{:.2}", load1)) - .replace("{load5}", &format!("{:.2}", load5)) - .replace("{load15}", &format!("{:.2}", load15)); + let text = format_template( + &config.sys.format, + &[ + ("uptime", TokenValue::String(&uptime_str)), + ("load1", TokenValue::Float(load1)), + ("load5", TokenValue::Float(load5)), + ("load15", TokenValue::Float(load15)), + ] + ); Ok(WaybarOutput { text, diff --git a/src/utils.rs b/src/utils.rs index ab5cf7a..9d73a55 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -30,3 +30,65 @@ pub fn show_menu(prompt: &str, items: &[String], menu_cmd: &str) -> Result { + Float(f64), + Int(i64), + String(&'a str), +} + +pub fn format_template(template: &str, values: &[(&str, TokenValue)]) -> String { + static RE: LazyLock = LazyLock::new(|| { + Regex::new(r"\{([a-zA-Z0-9_]+)(?::([<>\^])?(\d+)?(?:\.(\d+))?)?\}").unwrap() + }); + + RE.replace_all(template, |caps: ®ex::Captures| { + let name = &caps[1]; + if let Some((_, val)) = values.iter().find(|(k, _)| *k == name) { + let align = caps.get(2).map(|m| m.as_str()).unwrap_or(">"); + let width = caps.get(3).map(|m| m.as_str().parse::().unwrap_or(0)).unwrap_or(0); + let precision = caps.get(4).map(|m| m.as_str().parse::().unwrap_or(0)); + + match val { + TokenValue::Float(f) => format_float(*f, align, width, precision), + TokenValue::Int(i) => format_int(*i, align, width), + TokenValue::String(s) => format_str(s, align, width), + } + } else { + caps[0].to_string() + } + }).into_owned() +} + +fn format_float(f: f64, align: &str, width: usize, precision: Option) -> String { + match (align, precision) { + ("<", Some(p)) => format!("{: format!("{:^width$.p$}", f, width=width, p=p), + (">", Some(p)) => format!("{:>width$.p$}", f, width=width, p=p), + ("<", None) => format!("{: format!("{:^width$}", f, width=width), + (">", None) => format!("{:>width$}", f, width=width), + _ => format!("{}", f), + } +} + +fn format_int(i: i64, align: &str, width: usize) -> String { + match align { + "<" => format!("{: format!("{:^width$}", i, width=width), + ">" => format!("{:>width$}", i, width=width), + _ => format!("{}", i), + } +} + +fn format_str(s: &str, align: &str, width: usize) -> String { + match align { + "<" => format!("{: format!("{:^width$}", s, width=width), + ">" => format!("{:>width$}", s, width=width), + _ => format!("{}", s), + } +}