mirror of
https://github.com/nushell/nushell.git
synced 2025-03-13 15:08:43 +01:00
Respect system locale when formatting file sizes via config (#15271)
# Description Commands and other pieces of code using `$env.config.format.filesize` to format filesizes now respect the system locale when formatting the numeric portion of a file size. # User-Facing Changes - System locale is respected when using `$env.config.format.filesize` to format file sizes. - Formatting a file size with a binary unit is now exact for large file sizes and units. - The output of `to text` is no longer dependent on the config.
This commit is contained in:
parent
4fe7865ad0
commit
d97b2e3c60
@ -1,8 +1,6 @@
|
|||||||
use chrono_humanize::HumanTime;
|
use chrono_humanize::HumanTime;
|
||||||
use nu_engine::command_prelude::*;
|
use nu_engine::command_prelude::*;
|
||||||
use nu_protocol::{
|
use nu_protocol::{format_duration, shell_error::io::IoError, ByteStream, PipelineMetadata};
|
||||||
format_duration, shell_error::io::IoError, ByteStream, Config, PipelineMetadata,
|
|
||||||
};
|
|
||||||
use std::io::Write;
|
use std::io::Write;
|
||||||
|
|
||||||
const LINE_ENDING: &str = if cfg!(target_os = "windows") {
|
const LINE_ENDING: &str = if cfg!(target_os = "windows") {
|
||||||
@ -50,7 +48,6 @@ impl Command for ToText {
|
|||||||
let no_newline = call.has_flag(engine_state, stack, "no-newline")?;
|
let no_newline = call.has_flag(engine_state, stack, "no-newline")?;
|
||||||
let serialize_types = call.has_flag(engine_state, stack, "serialize")?;
|
let serialize_types = call.has_flag(engine_state, stack, "serialize")?;
|
||||||
let input = input.try_expand_range()?;
|
let input = input.try_expand_range()?;
|
||||||
let config = stack.get_config(engine_state);
|
|
||||||
|
|
||||||
match input {
|
match input {
|
||||||
PipelineData::Empty => Ok(Value::string(String::new(), head)
|
PipelineData::Empty => Ok(Value::string(String::new(), head)
|
||||||
@ -62,8 +59,7 @@ impl Command for ToText {
|
|||||||
Value::Record { val, .. } => !val.is_empty(),
|
Value::Record { val, .. } => !val.is_empty(),
|
||||||
_ => false,
|
_ => false,
|
||||||
};
|
};
|
||||||
let mut str =
|
let mut str = local_into_string(engine_state, value, LINE_ENDING, serialize_types);
|
||||||
local_into_string(engine_state, value, LINE_ENDING, &config, serialize_types);
|
|
||||||
if add_trailing {
|
if add_trailing {
|
||||||
str.push_str(LINE_ENDING);
|
str.push_str(LINE_ENDING);
|
||||||
}
|
}
|
||||||
@ -98,7 +94,6 @@ impl Command for ToText {
|
|||||||
&engine_state_clone,
|
&engine_state_clone,
|
||||||
val,
|
val,
|
||||||
LINE_ENDING,
|
LINE_ENDING,
|
||||||
&config,
|
|
||||||
serialize_types,
|
serialize_types,
|
||||||
);
|
);
|
||||||
write!(buf, "{str}").map_err(&from_io_error)?;
|
write!(buf, "{str}").map_err(&from_io_error)?;
|
||||||
@ -113,7 +108,6 @@ impl Command for ToText {
|
|||||||
&engine_state_clone,
|
&engine_state_clone,
|
||||||
val,
|
val,
|
||||||
LINE_ENDING,
|
LINE_ENDING,
|
||||||
&config,
|
|
||||||
serialize_types,
|
serialize_types,
|
||||||
);
|
);
|
||||||
str.push_str(LINE_ENDING);
|
str.push_str(LINE_ENDING);
|
||||||
@ -163,7 +157,6 @@ fn local_into_string(
|
|||||||
engine_state: &EngineState,
|
engine_state: &EngineState,
|
||||||
value: Value,
|
value: Value,
|
||||||
separator: &str,
|
separator: &str,
|
||||||
config: &Config,
|
|
||||||
serialize_types: bool,
|
serialize_types: bool,
|
||||||
) -> String {
|
) -> String {
|
||||||
let span = value.span();
|
let span = value.span();
|
||||||
@ -171,7 +164,7 @@ fn local_into_string(
|
|||||||
Value::Bool { val, .. } => val.to_string(),
|
Value::Bool { val, .. } => val.to_string(),
|
||||||
Value::Int { val, .. } => val.to_string(),
|
Value::Int { val, .. } => val.to_string(),
|
||||||
Value::Float { val, .. } => val.to_string(),
|
Value::Float { val, .. } => val.to_string(),
|
||||||
Value::Filesize { val, .. } => config.filesize.display(val).to_string(),
|
Value::Filesize { val, .. } => val.to_string(),
|
||||||
Value::Duration { val, .. } => format_duration(val),
|
Value::Duration { val, .. } => format_duration(val),
|
||||||
Value::Date { val, .. } => {
|
Value::Date { val, .. } => {
|
||||||
format!("{} ({})", val.to_rfc2822(), HumanTime::from(val))
|
format!("{} ({})", val.to_rfc2822(), HumanTime::from(val))
|
||||||
@ -181,7 +174,7 @@ fn local_into_string(
|
|||||||
Value::Glob { val, .. } => val,
|
Value::Glob { val, .. } => val,
|
||||||
Value::List { vals: val, .. } => val
|
Value::List { vals: val, .. } => val
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|x| local_into_string(engine_state, x, ", ", config, serialize_types))
|
.map(|x| local_into_string(engine_state, x, ", ", serialize_types))
|
||||||
.collect::<Vec<_>>()
|
.collect::<Vec<_>>()
|
||||||
.join(separator),
|
.join(separator),
|
||||||
Value::Record { val, .. } => val
|
Value::Record { val, .. } => val
|
||||||
@ -191,7 +184,7 @@ fn local_into_string(
|
|||||||
format!(
|
format!(
|
||||||
"{}: {}",
|
"{}: {}",
|
||||||
x,
|
x,
|
||||||
local_into_string(engine_state, y, ", ", config, serialize_types)
|
local_into_string(engine_state, y, ", ", serialize_types)
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
.collect::<Vec<_>>()
|
.collect::<Vec<_>>()
|
||||||
@ -221,7 +214,7 @@ fn local_into_string(
|
|||||||
// that critical here
|
// that critical here
|
||||||
Value::Custom { val, .. } => val
|
Value::Custom { val, .. } => val
|
||||||
.to_base_value(span)
|
.to_base_value(span)
|
||||||
.map(|val| local_into_string(engine_state, val, separator, config, serialize_types))
|
.map(|val| local_into_string(engine_state, val, separator, serialize_types))
|
||||||
.unwrap_or_else(|_| format!("<{}>", val.type_name())),
|
.unwrap_or_else(|_| format!("<{}>", val.type_name())),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -46,7 +46,7 @@ impl Command for SubCommand {
|
|||||||
Value::Filesize { val, .. } => {
|
Value::Filesize { val, .. } => {
|
||||||
usize::try_from(val).map_err(|_| ShellError::InvalidValue {
|
usize::try_from(val).map_err(|_| ShellError::InvalidValue {
|
||||||
valid: "a non-negative int or filesize".into(),
|
valid: "a non-negative int or filesize".into(),
|
||||||
actual: engine_state.get_config().filesize.display(val).to_string(),
|
actual: engine_state.get_config().filesize.format(val).to_string(),
|
||||||
span: length_val.span(),
|
span: length_val.span(),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -83,7 +83,7 @@ fn chars(
|
|||||||
Value::Filesize { val, .. } => {
|
Value::Filesize { val, .. } => {
|
||||||
usize::try_from(val).map_err(|_| ShellError::InvalidValue {
|
usize::try_from(val).map_err(|_| ShellError::InvalidValue {
|
||||||
valid: "a non-negative int or filesize".into(),
|
valid: "a non-negative int or filesize".into(),
|
||||||
actual: engine_state.get_config().filesize.display(val).to_string(),
|
actual: engine_state.get_config().filesize.format(val).to_string(),
|
||||||
span: length_val.span(),
|
span: length_val.span(),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -1,9 +1,9 @@
|
|||||||
use nu_cmd_base::input_handler::{operate, CmdArgument};
|
use nu_cmd_base::input_handler::{operate, CmdArgument};
|
||||||
use nu_engine::command_prelude::*;
|
use nu_engine::command_prelude::*;
|
||||||
use nu_protocol::{engine::StateWorkingSet, FilesizeUnit};
|
use nu_protocol::{engine::StateWorkingSet, FilesizeFormatter, FilesizeUnit};
|
||||||
|
|
||||||
struct Arguments {
|
struct Arguments {
|
||||||
format: FilesizeUnit,
|
unit: FilesizeUnit,
|
||||||
cell_paths: Option<Vec<CellPath>>,
|
cell_paths: Option<Vec<CellPath>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -61,10 +61,10 @@ impl Command for FormatFilesize {
|
|||||||
call: &Call,
|
call: &Call,
|
||||||
input: PipelineData,
|
input: PipelineData,
|
||||||
) -> Result<PipelineData, ShellError> {
|
) -> Result<PipelineData, ShellError> {
|
||||||
let format = parse_filesize_unit(call.req::<Spanned<String>>(engine_state, stack, 0)?)?;
|
let unit = parse_filesize_unit(call.req::<Spanned<String>>(engine_state, stack, 0)?)?;
|
||||||
let cell_paths: Vec<CellPath> = call.rest(engine_state, stack, 1)?;
|
let cell_paths: Vec<CellPath> = call.rest(engine_state, stack, 1)?;
|
||||||
let cell_paths = (!cell_paths.is_empty()).then_some(cell_paths);
|
let cell_paths = (!cell_paths.is_empty()).then_some(cell_paths);
|
||||||
let arg = Arguments { format, cell_paths };
|
let arg = Arguments { unit, cell_paths };
|
||||||
operate(
|
operate(
|
||||||
format_value_impl,
|
format_value_impl,
|
||||||
arg,
|
arg,
|
||||||
@ -80,10 +80,10 @@ impl Command for FormatFilesize {
|
|||||||
call: &Call,
|
call: &Call,
|
||||||
input: PipelineData,
|
input: PipelineData,
|
||||||
) -> Result<PipelineData, ShellError> {
|
) -> Result<PipelineData, ShellError> {
|
||||||
let format = parse_filesize_unit(call.req_const::<Spanned<String>>(working_set, 0)?)?;
|
let unit = parse_filesize_unit(call.req_const::<Spanned<String>>(working_set, 0)?)?;
|
||||||
let cell_paths: Vec<CellPath> = call.rest_const(working_set, 1)?;
|
let cell_paths: Vec<CellPath> = call.rest_const(working_set, 1)?;
|
||||||
let cell_paths = (!cell_paths.is_empty()).then_some(cell_paths);
|
let cell_paths = (!cell_paths.is_empty()).then_some(cell_paths);
|
||||||
let arg = Arguments { format, cell_paths };
|
let arg = Arguments { unit, cell_paths };
|
||||||
operate(
|
operate(
|
||||||
format_value_impl,
|
format_value_impl,
|
||||||
arg,
|
arg,
|
||||||
@ -127,7 +127,11 @@ fn parse_filesize_unit(format: Spanned<String>) -> Result<FilesizeUnit, ShellErr
|
|||||||
fn format_value_impl(val: &Value, arg: &Arguments, span: Span) -> Value {
|
fn format_value_impl(val: &Value, arg: &Arguments, span: Span) -> Value {
|
||||||
let value_span = val.span();
|
let value_span = val.span();
|
||||||
match val {
|
match val {
|
||||||
Value::Filesize { val, .. } => Value::string(val.display(arg.format).to_string(), span),
|
Value::Filesize { val, .. } => FilesizeFormatter::new()
|
||||||
|
.unit(arg.unit)
|
||||||
|
.format(*val)
|
||||||
|
.to_string()
|
||||||
|
.into_value(span),
|
||||||
Value::Error { .. } => val.clone(),
|
Value::Error { .. } => val.clone(),
|
||||||
_ => Value::error(
|
_ => Value::error(
|
||||||
ShellError::OnlySupportsThisInputType {
|
ShellError::OnlySupportsThisInputType {
|
||||||
|
@ -1,74 +1,47 @@
|
|||||||
use super::{config_update_string_enum, prelude::*};
|
use super::prelude::*;
|
||||||
use crate::{DisplayFilesize, Filesize, FilesizeUnit};
|
use crate::{Filesize, FilesizeFormatter, FilesizeUnitFormat, FormattedFilesize};
|
||||||
|
use nu_utils::get_system_locale;
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
|
impl IntoValue for FilesizeUnitFormat {
|
||||||
pub enum FilesizeFormatUnit {
|
|
||||||
Metric,
|
|
||||||
Binary,
|
|
||||||
Unit(FilesizeUnit),
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<FilesizeUnit> for FilesizeFormatUnit {
|
|
||||||
fn from(unit: FilesizeUnit) -> Self {
|
|
||||||
Self::Unit(unit)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl FromStr for FilesizeFormatUnit {
|
|
||||||
type Err = &'static str;
|
|
||||||
|
|
||||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
|
||||||
match s {
|
|
||||||
"metric" => Ok(Self::Metric),
|
|
||||||
"binary" => Ok(Self::Binary),
|
|
||||||
_ => {
|
|
||||||
if let Ok(unit) = s.parse() {
|
|
||||||
Ok(Self::Unit(unit))
|
|
||||||
} else {
|
|
||||||
Err("'metric', 'binary', 'B', 'kB', 'MB', 'GB', 'TB', 'PB', 'EB', 'KiB', 'MiB', 'GiB', 'TiB', 'PiB', or 'EiB'")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl IntoValue for FilesizeFormatUnit {
|
|
||||||
fn into_value(self, span: Span) -> Value {
|
fn into_value(self, span: Span) -> Value {
|
||||||
match self {
|
self.as_str().into_value(span)
|
||||||
FilesizeFormatUnit::Metric => "metric",
|
|
||||||
FilesizeFormatUnit::Binary => "binary",
|
|
||||||
FilesizeFormatUnit::Unit(unit) => unit.as_str(),
|
|
||||||
}
|
|
||||||
.into_value(span)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
|
#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
|
||||||
pub struct FilesizeConfig {
|
pub struct FilesizeConfig {
|
||||||
pub unit: FilesizeFormatUnit,
|
pub unit: FilesizeUnitFormat,
|
||||||
pub precision: Option<usize>,
|
pub precision: Option<usize>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl FilesizeConfig {
|
impl FilesizeConfig {
|
||||||
pub fn display(&self, filesize: Filesize) -> DisplayFilesize {
|
pub fn formatter(&self) -> FilesizeFormatter {
|
||||||
let unit = match self.unit {
|
FilesizeFormatter::new()
|
||||||
FilesizeFormatUnit::Metric => filesize.largest_metric_unit(),
|
.unit(self.unit)
|
||||||
FilesizeFormatUnit::Binary => filesize.largest_binary_unit(),
|
.precision(self.precision)
|
||||||
FilesizeFormatUnit::Unit(unit) => unit,
|
.locale(get_system_locale()) // TODO: cache this somewhere or pass in as argument
|
||||||
};
|
}
|
||||||
filesize.display(unit).precision(self.precision)
|
|
||||||
|
pub fn format(&self, filesize: Filesize) -> FormattedFilesize {
|
||||||
|
self.formatter().format(filesize)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for FilesizeConfig {
|
impl Default for FilesizeConfig {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self {
|
Self {
|
||||||
unit: FilesizeFormatUnit::Metric,
|
unit: FilesizeUnitFormat::Metric,
|
||||||
precision: Some(1),
|
precision: Some(1),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl From<FilesizeConfig> for FilesizeFormatter {
|
||||||
|
fn from(config: FilesizeConfig) -> Self {
|
||||||
|
config.formatter()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl UpdateFromValue for FilesizeConfig {
|
impl UpdateFromValue for FilesizeConfig {
|
||||||
fn update<'a>(
|
fn update<'a>(
|
||||||
&mut self,
|
&mut self,
|
||||||
@ -84,17 +57,22 @@ impl UpdateFromValue for FilesizeConfig {
|
|||||||
for (col, val) in record.iter() {
|
for (col, val) in record.iter() {
|
||||||
let path = &mut path.push(col);
|
let path = &mut path.push(col);
|
||||||
match col.as_str() {
|
match col.as_str() {
|
||||||
"unit" => config_update_string_enum(&mut self.unit, val, path, errors),
|
"unit" => {
|
||||||
|
if let Ok(str) = val.as_str() {
|
||||||
|
match str.parse() {
|
||||||
|
Ok(unit) => self.unit = unit,
|
||||||
|
Err(_) => errors.invalid_value(path, "'metric', 'binary', 'B', 'kB', 'MB', 'GB', 'TB', 'PB', 'EB', 'KiB', 'MiB', 'GiB', 'TiB', 'PiB', or 'EiB'", val),
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
errors.type_mismatch(path, Type::String, val)
|
||||||
|
}
|
||||||
|
}
|
||||||
"precision" => match *val {
|
"precision" => match *val {
|
||||||
Value::Nothing { .. } => self.precision = None,
|
Value::Nothing { .. } => self.precision = None,
|
||||||
Value::Int { val, .. } if val >= 0 => self.precision = Some(val as usize),
|
Value::Int { val, .. } if val >= 0 => self.precision = Some(val as usize),
|
||||||
Value::Int { .. } => errors.invalid_value(path, "a non-negative integer", val),
|
Value::Int { .. } => errors.invalid_value(path, "a non-negative integer", val),
|
||||||
_ => errors.type_mismatch(path, Type::custom("int or nothing"), val),
|
_ => errors.type_mismatch(path, Type::custom("int or nothing"), val),
|
||||||
},
|
},
|
||||||
"format" | "metric" => {
|
|
||||||
// TODO: remove after next release
|
|
||||||
errors.deprecated_option(path, "set $env.config.filesize.unit", val.span())
|
|
||||||
}
|
|
||||||
_ => errors.unknown_option(path, val),
|
_ => errors.unknown_option(path, val),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,9 @@
|
|||||||
use crate::{FromValue, IntoValue, ShellError, Span, Type, Value};
|
use crate::{FromValue, IntoValue, ShellError, Span, Type, Value};
|
||||||
|
use num_format::{Locale, WriteFormatted};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use std::{
|
use std::{
|
||||||
fmt,
|
char,
|
||||||
|
fmt::{self, Write},
|
||||||
iter::Sum,
|
iter::Sum,
|
||||||
ops::{Add, Mul, Neg, Sub},
|
ops::{Add, Mul, Neg, Sub},
|
||||||
str::FromStr,
|
str::FromStr,
|
||||||
@ -137,34 +139,6 @@ impl Filesize {
|
|||||||
EIB.. => FilesizeUnit::EiB,
|
EIB.. => FilesizeUnit::EiB,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns a struct that can be used to display a [`Filesize`] scaled to the given
|
|
||||||
/// [`FilesizeUnit`].
|
|
||||||
///
|
|
||||||
/// You can use [`largest_binary_unit`](Filesize::largest_binary_unit) or
|
|
||||||
/// [`largest_metric_unit`](Filesize::largest_metric_unit) to automatically determine a
|
|
||||||
/// [`FilesizeUnit`] of appropriate scale for a specific [`Filesize`].
|
|
||||||
///
|
|
||||||
/// The default [`Display`](fmt::Display) implementation for [`Filesize`] is
|
|
||||||
/// `self.display(self.largest_metric_unit())`.
|
|
||||||
///
|
|
||||||
/// # Examples
|
|
||||||
/// ```
|
|
||||||
/// # use nu_protocol::{Filesize, FilesizeUnit};
|
|
||||||
/// let filesize = Filesize::from_unit(4, FilesizeUnit::KiB).unwrap();
|
|
||||||
///
|
|
||||||
/// assert_eq!(filesize.display(FilesizeUnit::B).to_string(), "4096 B");
|
|
||||||
/// assert_eq!(filesize.display(FilesizeUnit::KiB).to_string(), "4 KiB");
|
|
||||||
/// assert_eq!(filesize.display(filesize.largest_binary_unit()).to_string(), "4 KiB");
|
|
||||||
/// assert_eq!(filesize.display(filesize.largest_metric_unit()).to_string(), "4.096 kB");
|
|
||||||
/// ```
|
|
||||||
pub fn display(&self, unit: FilesizeUnit) -> DisplayFilesize {
|
|
||||||
DisplayFilesize {
|
|
||||||
filesize: *self,
|
|
||||||
unit,
|
|
||||||
precision: None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<i64> for Filesize {
|
impl From<i64> for Filesize {
|
||||||
@ -359,7 +333,7 @@ impl Sum<Filesize> for Option<Filesize> {
|
|||||||
|
|
||||||
impl fmt::Display for Filesize {
|
impl fmt::Display for Filesize {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
write!(f, "{}", self.display(self.largest_metric_unit()))
|
write!(f, "{}", FilesizeFormatter::new().format(*self))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -368,9 +342,8 @@ impl fmt::Display for Filesize {
|
|||||||
/// This type contains both units with metric (SI) prefixes which are powers of 10 (e.g., kB = 1000 bytes)
|
/// This type contains both units with metric (SI) prefixes which are powers of 10 (e.g., kB = 1000 bytes)
|
||||||
/// and units with binary prefixes which are powers of 2 (e.g., KiB = 1024 bytes).
|
/// and units with binary prefixes which are powers of 2 (e.g., KiB = 1024 bytes).
|
||||||
///
|
///
|
||||||
/// The number of bytes in a [`FilesizeUnit`] can be obtained using
|
/// The number of bytes in a [`FilesizeUnit`] can be obtained using [`as_bytes`](Self::as_bytes).
|
||||||
/// [`as_bytes`](FilesizeUnit::as_bytes).
|
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
|
||||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
|
|
||||||
pub enum FilesizeUnit {
|
pub enum FilesizeUnit {
|
||||||
/// One byte
|
/// One byte
|
||||||
B,
|
B,
|
||||||
@ -432,12 +405,16 @@ impl FilesizeUnit {
|
|||||||
/// The symbol is exactly the same as the enum case name in Rust code except for
|
/// The symbol is exactly the same as the enum case name in Rust code except for
|
||||||
/// [`FilesizeUnit::KB`] which is `kB`.
|
/// [`FilesizeUnit::KB`] which is `kB`.
|
||||||
///
|
///
|
||||||
|
/// The returned string is the same exact string needed for a successful call to
|
||||||
|
/// [`parse`](str::parse) for a [`FilesizeUnit`].
|
||||||
|
///
|
||||||
/// # Examples
|
/// # Examples
|
||||||
/// ```
|
/// ```
|
||||||
/// # use nu_protocol::FilesizeUnit;
|
/// # use nu_protocol::FilesizeUnit;
|
||||||
/// assert_eq!(FilesizeUnit::B.as_str(), "B");
|
/// assert_eq!(FilesizeUnit::B.as_str(), "B");
|
||||||
/// assert_eq!(FilesizeUnit::KB.as_str(), "kB");
|
/// assert_eq!(FilesizeUnit::KB.as_str(), "kB");
|
||||||
/// assert_eq!(FilesizeUnit::KiB.as_str(), "KiB");
|
/// assert_eq!(FilesizeUnit::KiB.as_str(), "KiB");
|
||||||
|
/// assert_eq!(FilesizeUnit::KB.as_str().parse(), Ok(FilesizeUnit::KB));
|
||||||
/// ```
|
/// ```
|
||||||
pub const fn as_str(&self) -> &'static str {
|
pub const fn as_str(&self) -> &'static str {
|
||||||
match self {
|
match self {
|
||||||
@ -484,6 +461,12 @@ impl From<FilesizeUnit> for Filesize {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl fmt::Display for FilesizeUnit {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
f.write_str(self.as_str())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// The error returned when failing to parse a [`FilesizeUnit`].
|
/// The error returned when failing to parse a [`FilesizeUnit`].
|
||||||
///
|
///
|
||||||
/// This occurs when the string being parsed does not exactly match the name of one of the
|
/// This occurs when the string being parsed does not exactly match the name of one of the
|
||||||
@ -520,93 +503,344 @@ impl FromStr for FilesizeUnit {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl fmt::Display for FilesizeUnit {
|
/// The different file size unit display formats for a [`FilesizeFormatter`].
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
///
|
||||||
self.as_str().fmt(f)
|
/// To see more information about each possible format, see the documentation for each of the enum
|
||||||
|
/// cases of [`FilesizeUnitFormat`].
|
||||||
|
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
|
||||||
|
pub enum FilesizeUnitFormat {
|
||||||
|
/// [`Metric`](Self::Metric) will make a [`FilesizeFormatter`] use the
|
||||||
|
/// [`largest_metric_unit`](Filesize::largest_metric_unit) of a [`Filesize`] when formatting it.
|
||||||
|
Metric,
|
||||||
|
/// [`Binary`](Self::Binary) will make a [`FilesizeFormatter`] use the
|
||||||
|
/// [`largest_binary_unit`](Filesize::largest_binary_unit) of a [`Filesize`] when formatting it.
|
||||||
|
Binary,
|
||||||
|
/// [`FilesizeUnitFormat::Unit`] will make a [`FilesizeFormatter`] use the provided
|
||||||
|
/// [`FilesizeUnit`] when formatting all [`Filesize`]s.
|
||||||
|
Unit(FilesizeUnit),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FilesizeUnitFormat {
|
||||||
|
/// Returns a string representation of a [`FilesizeUnitFormat`].
|
||||||
|
///
|
||||||
|
/// The returned string is the same exact string needed for a successful call to
|
||||||
|
/// [`parse`](str::parse) for a [`FilesizeUnitFormat`].
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
/// ```
|
||||||
|
/// # use nu_protocol::{FilesizeUnit, FilesizeUnitFormat};
|
||||||
|
/// assert_eq!(FilesizeUnitFormat::Metric.as_str(), "metric");
|
||||||
|
/// assert_eq!(FilesizeUnitFormat::Binary.as_str(), "binary");
|
||||||
|
/// assert_eq!(FilesizeUnitFormat::Unit(FilesizeUnit::KB).as_str(), "kB");
|
||||||
|
/// assert_eq!(FilesizeUnitFormat::Metric.as_str().parse(), Ok(FilesizeUnitFormat::Metric));
|
||||||
|
/// ```
|
||||||
|
pub const fn as_str(&self) -> &'static str {
|
||||||
|
match self {
|
||||||
|
Self::Metric => "metric",
|
||||||
|
Self::Binary => "binary",
|
||||||
|
Self::Unit(unit) => unit.as_str(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns `true` for [`DisplayFilesizeUnit::Metric`] or if the underlying [`FilesizeUnit`]
|
||||||
|
/// is metric according to [`FilesizeUnit::is_metric`].
|
||||||
|
///
|
||||||
|
/// Note that this returns `true` for [`FilesizeUnit::B`] as well.
|
||||||
|
pub const fn is_metric(&self) -> bool {
|
||||||
|
match self {
|
||||||
|
Self::Metric => true,
|
||||||
|
Self::Binary => false,
|
||||||
|
Self::Unit(unit) => unit.is_metric(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns `true` for [`DisplayFilesizeUnit::Binary`] or if the underlying [`FilesizeUnit`]
|
||||||
|
/// is binary according to [`FilesizeUnit::is_binary`].
|
||||||
|
///
|
||||||
|
/// Note that this returns `true` for [`FilesizeUnit::B`] as well.
|
||||||
|
pub const fn is_binary(&self) -> bool {
|
||||||
|
match self {
|
||||||
|
Self::Metric => false,
|
||||||
|
Self::Binary => true,
|
||||||
|
Self::Unit(unit) => unit.is_binary(),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
impl From<FilesizeUnit> for FilesizeUnitFormat {
|
||||||
pub struct DisplayFilesize {
|
fn from(unit: FilesizeUnit) -> Self {
|
||||||
filesize: Filesize,
|
Self::Unit(unit)
|
||||||
unit: FilesizeUnit,
|
}
|
||||||
precision: Option<usize>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl DisplayFilesize {
|
impl fmt::Display for FilesizeUnitFormat {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
f.write_str(self.as_str())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The error returned when failing to parse a [`DisplayFilesizeUnit`].
|
||||||
|
///
|
||||||
|
/// This occurs when the string being parsed does not exactly match any of:
|
||||||
|
/// - `metric`
|
||||||
|
/// - `binary`
|
||||||
|
/// - The name of any of the enum cases in [`FilesizeUnit`]. The exception is [`FilesizeUnit::KB`] which must be `kB`.
|
||||||
|
#[derive(Debug, Copy, Clone, PartialEq, Eq, Error)]
|
||||||
|
pub struct ParseFilesizeUnitFormatError(());
|
||||||
|
|
||||||
|
impl fmt::Display for ParseFilesizeUnitFormatError {
|
||||||
|
fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
write!(fmt, "invalid file size unit format")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FromStr for FilesizeUnitFormat {
|
||||||
|
type Err = ParseFilesizeUnitFormatError;
|
||||||
|
|
||||||
|
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||||
|
Ok(match s {
|
||||||
|
"metric" => Self::Metric,
|
||||||
|
"binary" => Self::Binary,
|
||||||
|
s => Self::Unit(s.parse().map_err(|_| ParseFilesizeUnitFormatError(()))?),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A configurable formatter for [`Filesize`]s.
|
||||||
|
///
|
||||||
|
/// [`FilesizeFormatter`] is a builder struct that you can modify via the following methods:
|
||||||
|
/// - [`unit`](Self::unit)
|
||||||
|
/// - [`precision`](Self::precision)
|
||||||
|
/// - [`locale`](Self::locale)
|
||||||
|
///
|
||||||
|
/// For more information, see the documentation for each of those methods.
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
/// ```
|
||||||
|
/// # use nu_protocol::{Filesize, FilesizeFormatter, FilesizeUnit};
|
||||||
|
/// # use num_format::Locale;
|
||||||
|
/// let filesize = Filesize::from_unit(4, FilesizeUnit::KiB).unwrap();
|
||||||
|
/// let formatter = FilesizeFormatter::new();
|
||||||
|
///
|
||||||
|
/// assert_eq!(formatter.unit(FilesizeUnit::B).format(filesize).to_string(), "4096 B");
|
||||||
|
/// assert_eq!(formatter.unit(FilesizeUnit::KiB).format(filesize).to_string(), "4 KiB");
|
||||||
|
/// assert_eq!(formatter.precision(2).format(filesize).to_string(), "4.09 kB");
|
||||||
|
/// assert_eq!(
|
||||||
|
/// formatter
|
||||||
|
/// .unit(FilesizeUnit::B)
|
||||||
|
/// .locale(Locale::en)
|
||||||
|
/// .format(filesize)
|
||||||
|
/// .to_string(),
|
||||||
|
/// "4,096 B",
|
||||||
|
/// );
|
||||||
|
/// ```
|
||||||
|
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
|
||||||
|
pub struct FilesizeFormatter {
|
||||||
|
unit: FilesizeUnitFormat,
|
||||||
|
precision: Option<usize>,
|
||||||
|
locale: Locale,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FilesizeFormatter {
|
||||||
|
/// Create a new, default [`FilesizeFormatter`].
|
||||||
|
///
|
||||||
|
/// The default formatter has:
|
||||||
|
/// - a [`unit`](Self::unit) of [`FilesizeUnitFormat::Metric`].
|
||||||
|
/// - a [`precision`](Self::precision) of `None`.
|
||||||
|
/// - a [`locale`](Self::locale) of [`Locale::en_US_POSIX`]
|
||||||
|
/// (a very plain format with no thousands separators).
|
||||||
|
pub fn new() -> Self {
|
||||||
|
FilesizeFormatter {
|
||||||
|
unit: FilesizeUnitFormat::Metric,
|
||||||
|
precision: None,
|
||||||
|
locale: Locale::en_US_POSIX,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Set the [`FilesizeUnitFormat`] used by the formatter.
|
||||||
|
///
|
||||||
|
/// A [`FilesizeUnit`] or a [`FilesizeUnitFormat`] can be provided to this method.
|
||||||
|
/// [`FilesizeUnitFormat::Metric`] and [`FilesizeUnitFormat::Binary`] will use a unit of an
|
||||||
|
/// appropriate scale for each [`Filesize`], whereas providing a [`FilesizeUnit`] will use that
|
||||||
|
/// unit to format all [`Filesize`]s.
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
/// ```
|
||||||
|
/// # use nu_protocol::{Filesize, FilesizeFormatter, FilesizeUnit, FilesizeUnitFormat};
|
||||||
|
/// let formatter = FilesizeFormatter::new().precision(1);
|
||||||
|
///
|
||||||
|
/// let filesize = Filesize::from_unit(4, FilesizeUnit::KiB).unwrap();
|
||||||
|
/// assert_eq!(formatter.unit(FilesizeUnit::B).format(filesize).to_string(), "4096 B");
|
||||||
|
/// assert_eq!(formatter.unit(FilesizeUnitFormat::Binary).format(filesize).to_string(), "4.0 KiB");
|
||||||
|
///
|
||||||
|
/// let filesize = Filesize::from_unit(4, FilesizeUnit::MiB).unwrap();
|
||||||
|
/// assert_eq!(formatter.unit(FilesizeUnitFormat::Metric).format(filesize).to_string(), "4.1 MB");
|
||||||
|
/// assert_eq!(formatter.unit(FilesizeUnitFormat::Binary).format(filesize).to_string(), "4.0 MiB");
|
||||||
|
/// ```
|
||||||
|
pub fn unit(mut self, unit: impl Into<FilesizeUnitFormat>) -> Self {
|
||||||
|
self.unit = unit.into();
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Set the number of digits to display after the decimal place.
|
||||||
|
///
|
||||||
|
/// Note that digits after the decimal place will never be shown if:
|
||||||
|
/// - [`unit`](Self::unit) is [`FilesizeUnit::B`],
|
||||||
|
/// - [`unit`](Self::unit) is [`FilesizeUnitFormat::Metric`] and the number of bytes
|
||||||
|
/// is less than [`FilesizeUnit::KB`]
|
||||||
|
/// - [`unit`](Self::unit) is [`FilesizeUnitFormat::Binary`] and the number of bytes
|
||||||
|
/// is less than [`FilesizeUnit::KiB`].
|
||||||
|
///
|
||||||
|
/// Additionally, the precision specified in the format string
|
||||||
|
/// (i.e., [`std::fmt::Formatter::precision`]) will take precedence if is specified.
|
||||||
|
/// If the format string precision and the [`FilesizeFormatter`]'s precision are both `None`,
|
||||||
|
/// then all digits after the decimal place, if any, are shown.
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
/// ```
|
||||||
|
/// # use nu_protocol::{Filesize, FilesizeFormatter, FilesizeUnit, FilesizeUnitFormat};
|
||||||
|
/// let filesize = Filesize::from_unit(4, FilesizeUnit::KiB).unwrap();
|
||||||
|
/// let formatter = FilesizeFormatter::new();
|
||||||
|
///
|
||||||
|
/// assert_eq!(formatter.precision(2).format(filesize).to_string(), "4.09 kB");
|
||||||
|
/// assert_eq!(formatter.precision(0).format(filesize).to_string(), "4 kB");
|
||||||
|
/// assert_eq!(formatter.precision(None).format(filesize).to_string(), "4.096 kB");
|
||||||
|
/// assert_eq!(
|
||||||
|
/// formatter
|
||||||
|
/// .precision(None)
|
||||||
|
/// .unit(FilesizeUnit::KiB)
|
||||||
|
/// .format(filesize)
|
||||||
|
/// .to_string(),
|
||||||
|
/// "4 KiB",
|
||||||
|
/// );
|
||||||
|
/// assert_eq!(
|
||||||
|
/// formatter
|
||||||
|
/// .unit(FilesizeUnit::B)
|
||||||
|
/// .precision(2)
|
||||||
|
/// .format(filesize)
|
||||||
|
/// .to_string(),
|
||||||
|
/// "4096 B",
|
||||||
|
/// );
|
||||||
|
/// assert_eq!(format!("{:.2}", formatter.precision(0).format(filesize)), "4.09 kB");
|
||||||
|
/// ```
|
||||||
pub fn precision(mut self, precision: impl Into<Option<usize>>) -> Self {
|
pub fn precision(mut self, precision: impl Into<Option<usize>>) -> Self {
|
||||||
self.precision = precision.into();
|
self.precision = precision.into();
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Set the [`Locale`] to use when formatting the numeric portion of a [`Filesize`].
|
||||||
|
///
|
||||||
|
/// The [`Locale`] determines the decimal place character, minus sign character,
|
||||||
|
/// digit grouping method, and digit separator character.
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
/// ```
|
||||||
|
/// # use nu_protocol::{Filesize, FilesizeFormatter, FilesizeUnit, FilesizeUnitFormat};
|
||||||
|
/// # use num_format::Locale;
|
||||||
|
/// let filesize = Filesize::from_unit(-4, FilesizeUnit::MiB).unwrap();
|
||||||
|
/// let formatter = FilesizeFormatter::new().unit(FilesizeUnit::KB).precision(1);
|
||||||
|
///
|
||||||
|
/// assert_eq!(formatter.format(filesize).to_string(), "-4194.3 kB");
|
||||||
|
/// assert_eq!(formatter.locale(Locale::en).format(filesize).to_string(), "-4,194.3 kB");
|
||||||
|
/// assert_eq!(formatter.locale(Locale::rm).format(filesize).to_string(), "\u{2212}4’194.3 kB");
|
||||||
|
/// let filesize = Filesize::from_unit(-4, FilesizeUnit::GiB).unwrap();
|
||||||
|
/// assert_eq!(formatter.locale(Locale::ta).format(filesize).to_string(), "-42,94,967.2 kB");
|
||||||
|
/// ```
|
||||||
|
pub fn locale(mut self, locale: Locale) -> Self {
|
||||||
|
self.locale = locale;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Format a [`Filesize`] into a [`FormattedFilesize`] which implements [`fmt::Display`].
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
/// ```
|
||||||
|
/// # use nu_protocol::{Filesize, FilesizeFormatter, FilesizeUnit};
|
||||||
|
/// let filesize = Filesize::from_unit(4, FilesizeUnit::KB).unwrap();
|
||||||
|
/// let formatter = FilesizeFormatter::new();
|
||||||
|
///
|
||||||
|
/// assert_eq!(format!("{}", formatter.format(filesize)), "4 kB");
|
||||||
|
/// assert_eq!(formatter.format(filesize).to_string(), "4 kB");
|
||||||
|
/// ```
|
||||||
|
pub fn format(&self, filesize: Filesize) -> FormattedFilesize {
|
||||||
|
FormattedFilesize {
|
||||||
|
format: *self,
|
||||||
|
filesize,
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl fmt::Display for DisplayFilesize {
|
impl Default for FilesizeFormatter {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
fn default() -> Self {
|
||||||
let Self {
|
Self::new()
|
||||||
filesize: Filesize(filesize),
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The resulting struct from calling [`FilesizeFormatter::format`] on a [`Filesize`].
|
||||||
|
///
|
||||||
|
/// The only purpose of this struct is to implement [`fmt::Display`].
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct FormattedFilesize {
|
||||||
|
format: FilesizeFormatter,
|
||||||
|
filesize: Filesize,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Display for FormattedFilesize {
|
||||||
|
fn fmt(&self, mut f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
let Self { filesize, format } = *self;
|
||||||
|
let FilesizeFormatter {
|
||||||
unit,
|
unit,
|
||||||
precision,
|
precision,
|
||||||
} = *self;
|
locale,
|
||||||
let precision = precision.or(f.precision());
|
} = format;
|
||||||
match unit {
|
let unit = match unit {
|
||||||
FilesizeUnit::B => write!(f, "{filesize} B"),
|
FilesizeUnitFormat::Metric => filesize.largest_metric_unit(),
|
||||||
FilesizeUnit::KiB
|
FilesizeUnitFormat::Binary => filesize.largest_binary_unit(),
|
||||||
| FilesizeUnit::MiB
|
FilesizeUnitFormat::Unit(unit) => unit,
|
||||||
| FilesizeUnit::GiB
|
};
|
||||||
| FilesizeUnit::TiB
|
let Filesize(filesize) = filesize;
|
||||||
| FilesizeUnit::PiB
|
let precision = f.precision().or(precision);
|
||||||
| FilesizeUnit::EiB => {
|
|
||||||
// This won't give exact results for large filesizes and/or units.
|
let bytes = unit.as_bytes() as i64;
|
||||||
let val = filesize as f64 / unit.as_bytes() as f64;
|
let whole = filesize / bytes;
|
||||||
if let Some(precision) = precision {
|
let fract = (filesize % bytes).unsigned_abs();
|
||||||
write!(f, "{val:.precision$} {unit}")
|
|
||||||
} else {
|
f.write_formatted(&whole, &locale)
|
||||||
write!(f, "{val} {unit}")
|
.map_err(|_| std::fmt::Error)?;
|
||||||
|
|
||||||
|
if unit != FilesizeUnit::B && precision != Some(0) && !(precision.is_none() && fract == 0) {
|
||||||
|
f.write_str(locale.decimal())?;
|
||||||
|
|
||||||
|
let bytes = unit.as_bytes();
|
||||||
|
let mut fract = fract * 10;
|
||||||
|
let mut i = 0;
|
||||||
|
loop {
|
||||||
|
let q = fract / bytes;
|
||||||
|
let r = fract % bytes;
|
||||||
|
// Quick soundness proof:
|
||||||
|
// r <= bytes by definition of remainder `%`
|
||||||
|
// => 10 * r <= 10 * bytes
|
||||||
|
// => fract <= 10 * bytes before next iteration, fract = r * 10
|
||||||
|
// => fract / bytes <= 10
|
||||||
|
// => q <= 10 next iteration, q = fract / bytes
|
||||||
|
debug_assert!(q <= 10);
|
||||||
|
f.write_char(char::from_digit(q as u32, 10).expect("q <= 10"))?;
|
||||||
|
i += 1;
|
||||||
|
if r == 0 || precision.is_some_and(|p| i >= p) {
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
fract = r * 10;
|
||||||
}
|
}
|
||||||
FilesizeUnit::KB
|
|
||||||
| FilesizeUnit::GB
|
|
||||||
| FilesizeUnit::MB
|
|
||||||
| FilesizeUnit::TB
|
|
||||||
| FilesizeUnit::PB
|
|
||||||
| FilesizeUnit::EB => {
|
|
||||||
// Format an exact, possibly fractional, string representation of `filesize`.
|
|
||||||
let bytes = unit.as_bytes() as i64;
|
|
||||||
let whole = filesize / bytes;
|
|
||||||
let fract = (filesize % bytes).unsigned_abs();
|
|
||||||
if precision == Some(0) || (precision.is_none() && fract == 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];
|
|
||||||
let stop = precision.unwrap_or(usize::MAX).min(buf.len());
|
|
||||||
let bytes = bytes.unsigned_abs();
|
|
||||||
let mut fract = fract * 10;
|
|
||||||
let mut i = 0;
|
|
||||||
loop {
|
|
||||||
let q = fract / bytes;
|
|
||||||
let r = fract % bytes;
|
|
||||||
debug_assert!(q < 10);
|
|
||||||
buf[i] += q as u8;
|
|
||||||
i += 1;
|
|
||||||
if r == 0 || i >= stop {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
fract = r * 10;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Safety: all the characters in `buf` are valid UTF-8.
|
if let Some(precision) = precision {
|
||||||
let fract = unsafe { std::str::from_utf8_unchecked(&buf[..i]) };
|
for _ in 0..(precision - i) {
|
||||||
|
f.write_char('0')?;
|
||||||
if let Some(p) = precision {
|
|
||||||
write!(f, "{whole}.{fract:0<p$} {unit}")
|
|
||||||
} else {
|
|
||||||
write!(f, "{whole}.{fract} {unit}")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
write!(f, " {unit}")?;
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -622,7 +856,13 @@ mod tests {
|
|||||||
#[case(3_000_000, FilesizeUnit::MB, "3 MB")]
|
#[case(3_000_000, FilesizeUnit::MB, "3 MB")]
|
||||||
#[case(3_000_000, FilesizeUnit::KB, "3000 kB")]
|
#[case(3_000_000, FilesizeUnit::KB, "3000 kB")]
|
||||||
fn display_unit(#[case] bytes: i64, #[case] unit: FilesizeUnit, #[case] exp: &str) {
|
fn display_unit(#[case] bytes: i64, #[case] unit: FilesizeUnit, #[case] exp: &str) {
|
||||||
assert_eq!(exp, Filesize::new(bytes).display(unit).to_string());
|
assert_eq!(
|
||||||
|
exp,
|
||||||
|
FilesizeFormatter::new()
|
||||||
|
.unit(unit)
|
||||||
|
.format(Filesize::new(bytes))
|
||||||
|
.to_string()
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[rstest]
|
#[rstest]
|
||||||
@ -630,10 +870,12 @@ mod tests {
|
|||||||
#[case(1024, "1 KiB")]
|
#[case(1024, "1 KiB")]
|
||||||
#[case(1025, "1.0009765625 KiB")]
|
#[case(1025, "1.0009765625 KiB")]
|
||||||
fn display_auto_binary(#[case] val: i64, #[case] exp: &str) {
|
fn display_auto_binary(#[case] val: i64, #[case] exp: &str) {
|
||||||
let filesize = Filesize::new(val);
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
exp,
|
exp,
|
||||||
filesize.display(filesize.largest_binary_unit()).to_string(),
|
FilesizeFormatter::new()
|
||||||
|
.unit(FilesizeUnitFormat::Binary)
|
||||||
|
.format(Filesize::new(val))
|
||||||
|
.to_string()
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -642,10 +884,12 @@ mod tests {
|
|||||||
#[case(1000, "1 kB")]
|
#[case(1000, "1 kB")]
|
||||||
#[case(1024, "1.024 kB")]
|
#[case(1024, "1.024 kB")]
|
||||||
fn display_auto_metric(#[case] val: i64, #[case] exp: &str) {
|
fn display_auto_metric(#[case] val: i64, #[case] exp: &str) {
|
||||||
let filesize = Filesize::new(val);
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
exp,
|
exp,
|
||||||
filesize.display(filesize.largest_metric_unit()).to_string(),
|
FilesizeFormatter::new()
|
||||||
|
.unit(FilesizeUnitFormat::Metric)
|
||||||
|
.format(Filesize::new(val))
|
||||||
|
.to_string()
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -936,7 +936,7 @@ impl Value {
|
|||||||
Value::Bool { val, .. } => val.to_string(),
|
Value::Bool { val, .. } => val.to_string(),
|
||||||
Value::Int { val, .. } => val.to_string(),
|
Value::Int { val, .. } => val.to_string(),
|
||||||
Value::Float { val, .. } => val.to_string(),
|
Value::Float { val, .. } => val.to_string(),
|
||||||
Value::Filesize { val, .. } => config.filesize.display(*val).to_string(),
|
Value::Filesize { val, .. } => config.filesize.format(*val).to_string(),
|
||||||
Value::Duration { val, .. } => format_duration(*val),
|
Value::Duration { val, .. } => format_duration(*val),
|
||||||
Value::Date { val, .. } => match &config.datetime_format.normal {
|
Value::Date { val, .. } => match &config.datetime_format.normal {
|
||||||
Some(format) => self.format_datetime(val, format),
|
Some(format) => self.format_datetime(val, format),
|
||||||
|
@ -22,9 +22,16 @@ pub fn get_system_locale() -> Locale {
|
|||||||
|
|
||||||
#[cfg(debug_assertions)]
|
#[cfg(debug_assertions)]
|
||||||
pub fn get_system_locale_string() -> Option<String> {
|
pub fn get_system_locale_string() -> Option<String> {
|
||||||
std::env::var(LOCALE_OVERRIDE_ENV_VAR)
|
std::env::var(LOCALE_OVERRIDE_ENV_VAR).ok().or_else(
|
||||||
.ok()
|
#[cfg(not(test))]
|
||||||
.or_else(sys_locale::get_locale)
|
{
|
||||||
|
sys_locale::get_locale
|
||||||
|
},
|
||||||
|
#[cfg(test)]
|
||||||
|
{
|
||||||
|
|| Some(Locale::en_US_POSIX.name().to_owned())
|
||||||
|
},
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(not(debug_assertions))]
|
#[cfg(not(debug_assertions))]
|
||||||
|
Loading…
Reference in New Issue
Block a user