Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add byteshumanreadable(bytes, num_digits) to expressions #1277

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand Down
49 changes: 49 additions & 0 deletions crates/simplexpr/src/eval.rs
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,9 @@ pub enum EvalError {

#[error("{1}")]
Spanned(Span, Box<EvalError>),

#[error("Invalid argument: {0}")]
InvalidArgument(String),
}

static_assertions::assert_impl_all!(EvalError: Send, Sync);
Expand Down Expand Up @@ -508,6 +511,23 @@ fn call_expr_function(name: &str, args: Vec<DynVal>) -> Result<DynVal, EvalError
}
_ => 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())),
}
Expand Down Expand Up @@ -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";
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The suffixes should also be adjusted accordingly.

Also, it might be better to not do this using a loop (just thinking in terms of performance). I would use an ordered array of suffixes and just index into that.

}
"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;
Expand Down Expand Up @@ -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")),
}
}
3 changes: 3 additions & 0 deletions docs/src/expression_language.md
Original file line number Diff line number Diff line change
Expand Up @@ -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"`.