diff --git a/CHANGELOG.md b/CHANGELOG.md index d94afd65..b6b96ad6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -36,6 +36,7 @@ All notable changes to eww will be listed here, starting at changes since versio - Add `log` function calls to simplexpr (By: topongo) - Add `:lines` and `:wrap-mode` properties to label widget (By: vaporii) - Add `value-pos` to scale widget (By: ipsvn) +- Add `byteshumanreadable` function calls to simplexpr (By: bkueng) ## [0.6.0] (21.04.2024) diff --git a/crates/simplexpr/src/eval.rs b/crates/simplexpr/src/eval.rs index 8ee0d843..59975085 100644 --- a/crates/simplexpr/src/eval.rs +++ b/crates/simplexpr/src/eval.rs @@ -63,6 +63,9 @@ pub enum EvalError { #[error("{1}")] Spanned(Span, Box), + + #[error("Invalid argument: {0}")] + InvalidArgument(String), } static_assertions::assert_impl_all!(EvalError: Send, Sync); @@ -508,6 +511,23 @@ fn call_expr_function(name: &str, args: Vec) -> Result Err(EvalError::WrongArgCount(name.to_string())), }, + "byteshumanreadable" => match args.as_slice() { + [size, digits] => { + let size = size.as_f64()?; + let digits = if size > 0.0 { digits.as_i32()? } else { 0 }; + Ok(DynVal::from(bytes_human_readable(size, digits, true))) + } + [size, digits, unit] => { + let unit = unit.as_string()?; + if unit != "binary" && unit != "decimal" { + return Err(EvalError::InvalidArgument(format!("expected 'binary' or 'decimal', given '{}'", unit))); + } + let size = size.as_f64()?; + let digits = if size > 0.0 { digits.as_i32()? } else { 0 }; + Ok(DynVal::from(bytes_human_readable(size, digits, unit == "binary"))) + } + _ => Err(EvalError::WrongArgCount(name.to_string())), + }, _ => Err(EvalError::UnknownFunction(name.to_string())), } @@ -548,6 +568,31 @@ fn run_jaq_function(json: serde_json::Value, code: String, args: &str) -> Result .map_err(|e| EvalError::JaqError(e.to_string())) } +fn bytes_human_readable(size: f64, digits: i32, unit_is_binary: bool) -> String { + let mut size = size; + let mut suffix = "B"; + let base = if unit_is_binary { 1024.0 } else { 1000.0 }; + while size >= base { + match suffix { + "B" => { + suffix = "KB"; + } + "KB" => { + suffix = "MB"; + } + "MB" => { + suffix = "GB"; + } + "GB" => { + suffix = "TB"; + } + _ => break, + } + size /= base; + } + format!("{:.1$} ", size, digits as usize) + suffix +} + #[cfg(test)] mod tests { use crate::dynval::DynVal; @@ -610,5 +655,9 @@ mod tests { jq_empty_arg(r#"jq("[ \"foo\" ]", ".[0]", "")"#) => Ok(DynVal::from(r#""foo""#)), jq_invalid_arg(r#"jq("[ \"foo\" ]", ".[0]", "hello")"#) => Ok(DynVal::from(r#""foo""#)), jq_no_arg(r#"jq("[ \"foo\" ]", ".[0]")"#) => Ok(DynVal::from(r#""foo""#)), + byteshumanreadable1(r#"byteshumanreadable(2048,3)"#) => Ok(DynVal::from("2.000 KB")), + byteshumanreadable2(r#"byteshumanreadable(10000000000,3)"#) => Ok(DynVal::from("9.313 GB")), + byteshumanreadable3(r#"byteshumanreadable(2048,3,'decimal')"#) => Ok(DynVal::from("2.048 KB")), + byteshumanreadable4(r#"byteshumanreadable(10000000000,2,'decimal')"#) => Ok(DynVal::from("10.00 GB")), } } diff --git a/docs/src/expression_language.md b/docs/src/expression_language.md index 106776ae..8e267876 100644 --- a/docs/src/expression_language.md +++ b/docs/src/expression_language.md @@ -68,3 +68,6 @@ Supported currently are the following features: Same as other `formattime`, but does not accept timezone. Instead, it uses system's local timezone. Check [chrono's documentation](https://docs.rs/chrono/latest/chrono/format/strftime/index.html) for more information about format string. + - `byteshumanreadable(size, decimal_digits, unit)`: Convert and round a given value in bytes to a human-readable format. + `unit` is optional, and can be either `'binary'` (default) or `'decimal'` to use a power of 1024 or 1000 respectively. + E.g. `byteshumanreadable(2048, 2)` returns `"2.00 KB"`.