fixed formatting to be dynamic

This commit is contained in:
2026-03-16 19:48:49 +01:00
parent 9fa765fdff
commit 98e45150b3
15 changed files with 181 additions and 63 deletions

13
Cargo.lock generated
View File

@@ -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"

View File

@@ -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"

View File

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

View File

@@ -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")
};

View File

@@ -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,

View File

@@ -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,

View File

@@ -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,

View File

@@ -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"

View File

@@ -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,

View File

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

View File

@@ -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"

View File

@@ -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")

View File

@@ -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,

View File

@@ -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,

View File

@@ -30,3 +30,65 @@ pub fn show_menu(prompt: &str, items: &[String], menu_cmd: &str) -> Result<Strin
Ok(selected)
}
use regex::Regex;
use std::sync::LazyLock;
pub enum TokenValue<'a> {
Float(f64),
Int(i64),
String(&'a str),
}
pub fn format_template(template: &str, values: &[(&str, TokenValue)]) -> String {
static RE: LazyLock<Regex> = LazyLock::new(|| {
Regex::new(r"\{([a-zA-Z0-9_]+)(?::([<>\^])?(\d+)?(?:\.(\d+))?)?\}").unwrap()
});
RE.replace_all(template, |caps: &regex::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::<usize>().unwrap_or(0)).unwrap_or(0);
let precision = caps.get(4).map(|m| m.as_str().parse::<usize>().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<usize>) -> String {
match (align, precision) {
("<", Some(p)) => format!("{:<width$.p$}", f, width=width, p=p),
("^", Some(p)) => format!("{:^width$.p$}", f, width=width, p=p),
(">", Some(p)) => format!("{:>width$.p$}", f, width=width, p=p),
("<", None) => format!("{:<width$}", f, width=width),
("^", None) => 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!("{:<width$}", i, width=width),
"^" => 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!("{:<width$}", s, width=width),
"^" => format!("{:^width$}", s, width=width),
">" => format!("{:>width$}", s, width=width),
_ => format!("{}", s),
}
}