Add filesize precision

This commit is contained in:
Ian Manske 2024-11-19 19:20:00 -08:00
parent fa017d32a7
commit 2c74c679b5
8 changed files with 86 additions and 43 deletions

View File

@ -141,7 +141,7 @@ fn local_into_string(value: Value, separator: &str, config: &Config) -> String {
Value::Bool { val, .. } => val.to_string(),
Value::Int { val, .. } => val.to_string(),
Value::Float { val, .. } => val.to_string(),
Value::Filesize { val, .. } => config.filesize.unit.display(val).to_string(),
Value::Filesize { val, .. } => config.filesize.display(val).to_string(),
Value::Duration { val, .. } => format_duration(val),
Value::Date { val, .. } => {
format!("{} ({})", val.to_rfc2822(), HumanTime::from(val))

View File

@ -46,12 +46,7 @@ impl Command for SubCommand {
Value::Filesize { val, .. } => {
usize::try_from(val).map_err(|_| ShellError::InvalidValue {
valid: "a non-negative int or filesize".into(),
actual: engine_state
.get_config()
.filesize
.unit
.display(val)
.to_string(),
actual: engine_state.get_config().filesize.display(val).to_string(),
span: length_val.span(),
})
}

View File

@ -83,12 +83,7 @@ fn chars(
Value::Filesize { val, .. } => {
usize::try_from(val).map_err(|_| ShellError::InvalidValue {
valid: "a non-negative int or filesize".into(),
actual: engine_state
.get_config()
.filesize
.unit
.display(val)
.to_string(),
actual: engine_state.get_config().filesize.display(val).to_string(),
span: length_val.span(),
})
}

View File

@ -1,5 +1,5 @@
use super::{config_update_string_enum, prelude::*};
use crate::{self as nu_protocol, DisplayFilesize, Filesize, FilesizeUnit};
use crate::{DisplayFilesize, Filesize, FilesizeUnit};
#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub enum FilesizeFormatUnit {
@ -8,17 +8,6 @@ pub enum FilesizeFormatUnit {
Unit(FilesizeUnit),
}
impl FilesizeFormatUnit {
pub fn display(&self, filesize: Filesize) -> DisplayFilesize {
let unit = match self {
Self::Decimal => filesize.largest_decimal_unit(),
Self::Binary => filesize.largest_binary_unit(),
Self::Unit(unit) => *unit,
};
filesize.display(unit)
}
}
impl From<FilesizeUnit> for FilesizeFormatUnit {
fn from(unit: FilesizeUnit) -> Self {
Self::Unit(unit)
@ -54,15 +43,28 @@ impl IntoValue for FilesizeFormatUnit {
}
}
#[derive(Clone, Copy, Debug, IntoValue, PartialEq, Eq, Serialize, Deserialize)]
#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub struct FilesizeConfig {
pub unit: FilesizeFormatUnit,
pub precision: u64,
}
impl FilesizeConfig {
pub fn display(&self, filesize: Filesize) -> DisplayFilesize {
let unit = match self.unit {
FilesizeFormatUnit::Decimal => filesize.largest_decimal_unit(),
FilesizeFormatUnit::Binary => filesize.largest_binary_unit(),
FilesizeFormatUnit::Unit(unit) => unit,
};
filesize.display(unit).precision(self.precision as usize)
}
}
impl Default for FilesizeConfig {
fn default() -> Self {
Self {
unit: FilesizeFormatUnit::Decimal,
precision: 1,
}
}
}
@ -83,6 +85,7 @@ impl UpdateFromValue for FilesizeConfig {
let path = &mut path.push(col);
match col.as_str() {
"unit" => config_update_string_enum(&mut self.unit, val, path, errors),
"precision" => self.precision.update(value, path, errors),
"format" | "metric" => {
// TODO: remove after next release
errors.deprecated_option(path, "set $env.config.filesize.unit", val.span())
@ -92,3 +95,13 @@ impl UpdateFromValue for FilesizeConfig {
}
}
}
impl IntoValue for FilesizeConfig {
fn into_value(self, span: Span) -> Value {
record! {
"unit" => self.unit.into_value(span),
"precision" => (self.precision as i64).into_value(span),
}
.into_value(span)
}
}

View File

@ -91,6 +91,20 @@ impl UpdateFromValue for i64 {
}
}
impl UpdateFromValue for u64 {
fn update(&mut self, value: &Value, path: &mut ConfigPath, errors: &mut ConfigErrors) {
if let Ok(val) = value.as_int() {
if let Ok(val) = val.try_into() {
*self = val;
} else {
errors.invalid_value(path, "a non-negative integer", value);
}
} else {
errors.type_mismatch(path, Type::Int, value);
}
}
}
impl UpdateFromValue for usize {
fn update(&mut self, value: &Value, path: &mut ConfigPath, errors: &mut ConfigErrors) {
if let Ok(val) = value.as_int() {

View File

@ -162,6 +162,7 @@ impl Filesize {
DisplayFilesize {
filesize: *self,
unit,
precision: None,
}
}
}
@ -529,11 +530,24 @@ impl fmt::Display for FilesizeUnit {
pub struct DisplayFilesize {
filesize: Filesize,
unit: FilesizeUnit,
precision: Option<usize>,
}
impl DisplayFilesize {
pub fn precision(mut self, precision: usize) -> Self {
self.precision = Some(precision);
self
}
}
impl fmt::Display for DisplayFilesize {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let Self { filesize, unit } = *self;
let Self {
filesize,
unit,
precision,
} = *self;
let precision = precision.or(f.precision());
match unit {
FilesizeUnit::B => write!(f, "{} B", filesize.0),
FilesizeUnit::KiB
@ -542,8 +556,13 @@ impl fmt::Display for DisplayFilesize {
| FilesizeUnit::TiB
| FilesizeUnit::PiB
| FilesizeUnit::EiB => {
let val = filesize.0 as f64 / unit.as_bytes() as f64;
// This won't give exact results for large filesizes and/or units.
write!(f, "{} {unit}", filesize.0 as f64 / unit.as_bytes() as f64)
if let Some(precision) = precision {
write!(f, "{val:.precision$} {unit}")
} else {
write!(f, "{val} {unit}")
}
}
FilesizeUnit::KB
| FilesizeUnit::GB
@ -554,31 +573,37 @@ impl fmt::Display for DisplayFilesize {
// Format an exact, possibly fractional, string representation of `filesize`.
let bytes = unit.as_bytes() as i64;
let whole = filesize.0 / bytes;
let mut fract = (filesize.0 % bytes).unsigned_abs();
if fract == 0 || f.precision() == Some(0) {
let fract = (filesize.0 % bytes).unsigned_abs();
if fract == 0 || precision == Some(0) {
write!(f, "{whole} {unit}")
} else {
// fract <= bytes by nature of `%` and bytes <= EB = 10 ^ 18
// So, the longest string for the fractional portion can be 18 characters.
let buf = &mut [b'0'; 18];
for d in buf.iter_mut().rev() {
*d += (fract % 10) as u8;
fract /= 10;
if fract == 0 {
let stop = precision.unwrap_or(buf.len());
let mut fract = fract;
let mut power = bytes.unsigned_abs() / 10;
let mut i = 0;
loop {
let q = fract / power;
let r = fract % power;
debug_assert!(q < 10);
buf[i] += q as u8;
i += 1;
if r == 0 || i >= stop {
break;
}
fract = r;
power /= 10;
}
let power = bytes.ilog10() as usize;
debug_assert_eq!(bytes, 10_i64.pow(power as u32), "an exact power of 10");
// Safety: all the characters in `buf` are valid UTF-8.
let fract =
unsafe { std::str::from_utf8_unchecked(&buf[(buf.len() - power)..]) };
let fract = unsafe { std::str::from_utf8_unchecked(&buf[..i]) };
match f.precision() {
Some(p) if p <= power => write!(f, "{whole}.{} {unit}", &fract[..p]),
Some(p) => write!(f, "{whole}.{fract:0<p$} {unit}"),
None => write!(f, "{whole}.{} {unit}", fract.trim_end_matches('0')),
if let Some(p) = precision {
write!(f, "{whole}.{fract:0<p$} {unit}")
} else {
write!(f, "{whole}.{fract} {unit}")
}
}
}

View File

@ -837,7 +837,7 @@ impl Value {
Value::Bool { val, .. } => val.to_string(),
Value::Int { val, .. } => val.to_string(),
Value::Float { val, .. } => val.to_string(),
Value::Filesize { val, .. } => config.filesize.unit.display(*val).to_string(),
Value::Filesize { val, .. } => config.filesize.display(*val).to_string(),
Value::Duration { val, .. } => format_duration(*val),
Value::Date { val, .. } => match &config.datetime_format.normal {
Some(format) => self.format_datetime(val, format),

View File

@ -231,6 +231,7 @@ $env.config = {
# You can also set this to a particular unit to use that unit to display all file sizes.
# The available units are: B, kB, KiB, MB, MiB, GB, GiB, TB, TiB, PB, PiB, EB, or EiB.
unit: decimal
precision: 1 # the number of digits to display after the decimal point for file sizes
}
cursor_shape: {