mirror of
https://github.com/astral-sh/ruff.git
synced 2025-08-03 18:28:24 +00:00
Remove unused runtime string formatting logic (#6624)
In https://github.com/astral-sh/ruff/pull/6616 we are adding support for nested replacements in format specifiers which makes actually formatting strings infeasible without a great deal of complexity. Since we're not using these functions (they just exist for runtime use in RustPython), we can just remove them.
This commit is contained in:
parent
0a5be74be3
commit
6253d8e2c8
4 changed files with 4 additions and 949 deletions
|
@ -1,15 +1,13 @@
|
|||
//! Implementation of Printf-Style string formatting
|
||||
//! as per the [Python Docs](https://docs.python.org/3/library/stdtypes.html#printf-style-string-formatting).
|
||||
use bitflags::bitflags;
|
||||
use num_traits::Signed;
|
||||
use std::{
|
||||
cmp, fmt,
|
||||
fmt,
|
||||
iter::{Enumerate, Peekable},
|
||||
str::FromStr,
|
||||
};
|
||||
|
||||
use crate::{float, Case};
|
||||
use num_bigint::{BigInt, Sign};
|
||||
use crate::Case;
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub enum CFormatErrorType {
|
||||
|
@ -165,249 +163,6 @@ impl CFormatSpec {
|
|||
format_char,
|
||||
})
|
||||
}
|
||||
|
||||
fn compute_fill_string(fill_char: char, fill_chars_needed: usize) -> String {
|
||||
(0..fill_chars_needed)
|
||||
.map(|_| fill_char)
|
||||
.collect::<String>()
|
||||
}
|
||||
|
||||
fn fill_string(
|
||||
&self,
|
||||
string: String,
|
||||
fill_char: char,
|
||||
num_prefix_chars: Option<usize>,
|
||||
) -> String {
|
||||
let mut num_chars = string.chars().count();
|
||||
if let Some(num_prefix_chars) = num_prefix_chars {
|
||||
num_chars += num_prefix_chars;
|
||||
}
|
||||
let num_chars = num_chars;
|
||||
|
||||
let width = match &self.min_field_width {
|
||||
Some(CFormatQuantity::Amount(width)) => cmp::max(width, &num_chars),
|
||||
_ => &num_chars,
|
||||
};
|
||||
let fill_chars_needed = width.saturating_sub(num_chars);
|
||||
let fill_string = CFormatSpec::compute_fill_string(fill_char, fill_chars_needed);
|
||||
|
||||
if fill_string.is_empty() {
|
||||
string
|
||||
} else {
|
||||
if self.flags.contains(CConversionFlags::LEFT_ADJUST) {
|
||||
format!("{string}{fill_string}")
|
||||
} else {
|
||||
format!("{fill_string}{string}")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn fill_string_with_precision(&self, string: String, fill_char: char) -> String {
|
||||
let num_chars = string.chars().count();
|
||||
|
||||
let width = match &self.precision {
|
||||
Some(CFormatPrecision::Quantity(CFormatQuantity::Amount(width))) => {
|
||||
cmp::max(width, &num_chars)
|
||||
}
|
||||
_ => &num_chars,
|
||||
};
|
||||
let fill_chars_needed = width.saturating_sub(num_chars);
|
||||
let fill_string = CFormatSpec::compute_fill_string(fill_char, fill_chars_needed);
|
||||
|
||||
if fill_string.is_empty() {
|
||||
string
|
||||
} else {
|
||||
// Don't left-adjust if precision-filling: that will always be prepending 0s to %d
|
||||
// arguments, the LEFT_ADJUST flag will be used by a later call to fill_string with
|
||||
// the 0-filled string as the string param.
|
||||
format!("{fill_string}{string}")
|
||||
}
|
||||
}
|
||||
|
||||
fn format_string_with_precision(
|
||||
&self,
|
||||
string: String,
|
||||
precision: Option<&CFormatPrecision>,
|
||||
) -> String {
|
||||
// truncate if needed
|
||||
let string = match precision {
|
||||
Some(CFormatPrecision::Quantity(CFormatQuantity::Amount(precision)))
|
||||
if string.chars().count() > *precision =>
|
||||
{
|
||||
string.chars().take(*precision).collect::<String>()
|
||||
}
|
||||
Some(CFormatPrecision::Dot) => {
|
||||
// truncate to 0
|
||||
String::new()
|
||||
}
|
||||
_ => string,
|
||||
};
|
||||
self.fill_string(string, ' ', None)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn format_string(&self, string: String) -> String {
|
||||
self.format_string_with_precision(string, self.precision.as_ref())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn format_char(&self, ch: char) -> String {
|
||||
self.format_string_with_precision(
|
||||
ch.to_string(),
|
||||
Some(&(CFormatQuantity::Amount(1).into())),
|
||||
)
|
||||
}
|
||||
|
||||
pub fn format_bytes(&self, bytes: &[u8]) -> Vec<u8> {
|
||||
let bytes = if let Some(CFormatPrecision::Quantity(CFormatQuantity::Amount(precision))) =
|
||||
self.precision
|
||||
{
|
||||
&bytes[..cmp::min(bytes.len(), precision)]
|
||||
} else {
|
||||
bytes
|
||||
};
|
||||
if let Some(CFormatQuantity::Amount(width)) = self.min_field_width {
|
||||
let fill = cmp::max(0, width - bytes.len());
|
||||
let mut v = Vec::with_capacity(bytes.len() + fill);
|
||||
if self.flags.contains(CConversionFlags::LEFT_ADJUST) {
|
||||
v.extend_from_slice(bytes);
|
||||
v.append(&mut vec![b' '; fill]);
|
||||
} else {
|
||||
v.append(&mut vec![b' '; fill]);
|
||||
v.extend_from_slice(bytes);
|
||||
}
|
||||
v
|
||||
} else {
|
||||
bytes.to_vec()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn format_number(&self, num: &BigInt) -> String {
|
||||
use CNumberType::{Decimal, Hex, Octal};
|
||||
let magnitude = num.abs();
|
||||
let prefix = if self.flags.contains(CConversionFlags::ALTERNATE_FORM) {
|
||||
match self.format_type {
|
||||
CFormatType::Number(Octal) => "0o",
|
||||
CFormatType::Number(Hex(Case::Lower)) => "0x",
|
||||
CFormatType::Number(Hex(Case::Upper)) => "0X",
|
||||
_ => "",
|
||||
}
|
||||
} else {
|
||||
""
|
||||
};
|
||||
|
||||
let magnitude_string: String = match self.format_type {
|
||||
CFormatType::Number(Decimal) => magnitude.to_str_radix(10),
|
||||
CFormatType::Number(Octal) => magnitude.to_str_radix(8),
|
||||
CFormatType::Number(Hex(Case::Lower)) => magnitude.to_str_radix(16),
|
||||
CFormatType::Number(Hex(Case::Upper)) => {
|
||||
let mut result = magnitude.to_str_radix(16);
|
||||
result.make_ascii_uppercase();
|
||||
result
|
||||
}
|
||||
_ => unreachable!(), // Should not happen because caller has to make sure that this is a number
|
||||
};
|
||||
|
||||
let sign_string = match num.sign() {
|
||||
Sign::Minus => "-",
|
||||
_ => self.flags.sign_string(),
|
||||
};
|
||||
|
||||
let padded_magnitude_string = self.fill_string_with_precision(magnitude_string, '0');
|
||||
|
||||
if self.flags.contains(CConversionFlags::ZERO_PAD) {
|
||||
let fill_char = if self.flags.contains(CConversionFlags::LEFT_ADJUST) {
|
||||
' ' // '-' overrides the '0' conversion if both are given
|
||||
} else {
|
||||
'0'
|
||||
};
|
||||
let signed_prefix = format!("{sign_string}{prefix}");
|
||||
format!(
|
||||
"{}{}",
|
||||
signed_prefix,
|
||||
self.fill_string(
|
||||
padded_magnitude_string,
|
||||
fill_char,
|
||||
Some(signed_prefix.chars().count()),
|
||||
),
|
||||
)
|
||||
} else {
|
||||
self.fill_string(
|
||||
format!("{sign_string}{prefix}{padded_magnitude_string}"),
|
||||
' ',
|
||||
None,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn format_float(&self, num: f64) -> String {
|
||||
let sign_string = if num.is_sign_negative() && !num.is_nan() {
|
||||
"-"
|
||||
} else {
|
||||
self.flags.sign_string()
|
||||
};
|
||||
|
||||
let precision = match &self.precision {
|
||||
Some(CFormatPrecision::Quantity(quantity)) => match quantity {
|
||||
CFormatQuantity::Amount(amount) => *amount,
|
||||
CFormatQuantity::FromValuesTuple => 6,
|
||||
},
|
||||
Some(CFormatPrecision::Dot) => 0,
|
||||
None => 6,
|
||||
};
|
||||
|
||||
let magnitude_string = match &self.format_type {
|
||||
CFormatType::Float(CFloatType::PointDecimal(case)) => {
|
||||
let magnitude = num.abs();
|
||||
float::format_fixed(
|
||||
precision,
|
||||
magnitude,
|
||||
*case,
|
||||
self.flags.contains(CConversionFlags::ALTERNATE_FORM),
|
||||
)
|
||||
}
|
||||
CFormatType::Float(CFloatType::Exponent(case)) => {
|
||||
let magnitude = num.abs();
|
||||
float::format_exponent(
|
||||
precision,
|
||||
magnitude,
|
||||
*case,
|
||||
self.flags.contains(CConversionFlags::ALTERNATE_FORM),
|
||||
)
|
||||
}
|
||||
CFormatType::Float(CFloatType::General(case)) => {
|
||||
let precision = if precision == 0 { 1 } else { precision };
|
||||
let magnitude = num.abs();
|
||||
float::format_general(
|
||||
precision,
|
||||
magnitude,
|
||||
*case,
|
||||
self.flags.contains(CConversionFlags::ALTERNATE_FORM),
|
||||
false,
|
||||
)
|
||||
}
|
||||
_ => unreachable!(),
|
||||
};
|
||||
|
||||
if self.flags.contains(CConversionFlags::ZERO_PAD) {
|
||||
let fill_char = if self.flags.contains(CConversionFlags::LEFT_ADJUST) {
|
||||
' '
|
||||
} else {
|
||||
'0'
|
||||
};
|
||||
format!(
|
||||
"{}{}",
|
||||
sign_string,
|
||||
self.fill_string(
|
||||
magnitude_string,
|
||||
fill_char,
|
||||
Some(sign_string.chars().count()),
|
||||
)
|
||||
)
|
||||
} else {
|
||||
self.fill_string(format!("{sign_string}{magnitude_string}"), ' ', None)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_spec_mapping_key<T, I>(iter: &mut ParseIter<I>) -> Result<Option<String>, ParsingError>
|
||||
|
@ -741,38 +496,6 @@ impl CFormatString {
|
|||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_fill_and_align() {
|
||||
assert_eq!(
|
||||
"%10s"
|
||||
.parse::<CFormatSpec>()
|
||||
.unwrap()
|
||||
.format_string("test".to_owned()),
|
||||
" test".to_owned()
|
||||
);
|
||||
assert_eq!(
|
||||
"%-10s"
|
||||
.parse::<CFormatSpec>()
|
||||
.unwrap()
|
||||
.format_string("test".to_owned()),
|
||||
"test ".to_owned()
|
||||
);
|
||||
assert_eq!(
|
||||
"%#10x"
|
||||
.parse::<CFormatSpec>()
|
||||
.unwrap()
|
||||
.format_number(&BigInt::from(0x1337)),
|
||||
" 0x1337".to_owned()
|
||||
);
|
||||
assert_eq!(
|
||||
"%-#10x"
|
||||
.parse::<CFormatSpec>()
|
||||
.unwrap()
|
||||
.format_number(&BigInt::from(0x1337)),
|
||||
"0x1337 ".to_owned()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parse_key() {
|
||||
let expected = Ok(CFormatSpec {
|
||||
|
@ -844,165 +567,6 @@ mod tests {
|
|||
});
|
||||
let parsed = "% 0 -+++###10d".parse::<CFormatSpec>();
|
||||
assert_eq!(parsed, expected);
|
||||
assert_eq!(
|
||||
parsed.unwrap().format_number(&BigInt::from(12)),
|
||||
"+12 ".to_owned()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parse_and_format_string() {
|
||||
assert_eq!(
|
||||
"%5.4s"
|
||||
.parse::<CFormatSpec>()
|
||||
.unwrap()
|
||||
.format_string("Hello, World!".to_owned()),
|
||||
" Hell".to_owned()
|
||||
);
|
||||
assert_eq!(
|
||||
"%-5.4s"
|
||||
.parse::<CFormatSpec>()
|
||||
.unwrap()
|
||||
.format_string("Hello, World!".to_owned()),
|
||||
"Hell ".to_owned()
|
||||
);
|
||||
assert_eq!(
|
||||
"%.s"
|
||||
.parse::<CFormatSpec>()
|
||||
.unwrap()
|
||||
.format_string("Hello, World!".to_owned()),
|
||||
String::new()
|
||||
);
|
||||
assert_eq!(
|
||||
"%5.s"
|
||||
.parse::<CFormatSpec>()
|
||||
.unwrap()
|
||||
.format_string("Hello, World!".to_owned()),
|
||||
" ".to_owned()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parse_and_format_unicode_string() {
|
||||
assert_eq!(
|
||||
"%.2s"
|
||||
.parse::<CFormatSpec>()
|
||||
.unwrap()
|
||||
.format_string("❤❤❤❤❤❤❤❤".to_owned()),
|
||||
"❤❤".to_owned()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parse_and_format_number() {
|
||||
assert_eq!(
|
||||
"%5d"
|
||||
.parse::<CFormatSpec>()
|
||||
.unwrap()
|
||||
.format_number(&BigInt::from(27)),
|
||||
" 27".to_owned()
|
||||
);
|
||||
assert_eq!(
|
||||
"%05d"
|
||||
.parse::<CFormatSpec>()
|
||||
.unwrap()
|
||||
.format_number(&BigInt::from(27)),
|
||||
"00027".to_owned()
|
||||
);
|
||||
assert_eq!(
|
||||
"%.5d"
|
||||
.parse::<CFormatSpec>()
|
||||
.unwrap()
|
||||
.format_number(&BigInt::from(27)),
|
||||
"00027".to_owned()
|
||||
);
|
||||
assert_eq!(
|
||||
"%+05d"
|
||||
.parse::<CFormatSpec>()
|
||||
.unwrap()
|
||||
.format_number(&BigInt::from(27)),
|
||||
"+0027".to_owned()
|
||||
);
|
||||
assert_eq!(
|
||||
"%-d"
|
||||
.parse::<CFormatSpec>()
|
||||
.unwrap()
|
||||
.format_number(&BigInt::from(-27)),
|
||||
"-27".to_owned()
|
||||
);
|
||||
assert_eq!(
|
||||
"% d"
|
||||
.parse::<CFormatSpec>()
|
||||
.unwrap()
|
||||
.format_number(&BigInt::from(27)),
|
||||
" 27".to_owned()
|
||||
);
|
||||
assert_eq!(
|
||||
"% d"
|
||||
.parse::<CFormatSpec>()
|
||||
.unwrap()
|
||||
.format_number(&BigInt::from(-27)),
|
||||
"-27".to_owned()
|
||||
);
|
||||
assert_eq!(
|
||||
"%08x"
|
||||
.parse::<CFormatSpec>()
|
||||
.unwrap()
|
||||
.format_number(&BigInt::from(0x1337)),
|
||||
"00001337".to_owned()
|
||||
);
|
||||
assert_eq!(
|
||||
"%#010x"
|
||||
.parse::<CFormatSpec>()
|
||||
.unwrap()
|
||||
.format_number(&BigInt::from(0x1337)),
|
||||
"0x00001337".to_owned()
|
||||
);
|
||||
assert_eq!(
|
||||
"%-#010x"
|
||||
.parse::<CFormatSpec>()
|
||||
.unwrap()
|
||||
.format_number(&BigInt::from(0x1337)),
|
||||
"0x1337 ".to_owned()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parse_and_format_float() {
|
||||
assert_eq!(
|
||||
"%f".parse::<CFormatSpec>().unwrap().format_float(1.2345),
|
||||
"1.234500"
|
||||
);
|
||||
assert_eq!(
|
||||
"%.2f".parse::<CFormatSpec>().unwrap().format_float(1.2345),
|
||||
"1.23"
|
||||
);
|
||||
assert_eq!(
|
||||
"%.f".parse::<CFormatSpec>().unwrap().format_float(1.2345),
|
||||
"1"
|
||||
);
|
||||
assert_eq!(
|
||||
"%+.f".parse::<CFormatSpec>().unwrap().format_float(1.2345),
|
||||
"+1"
|
||||
);
|
||||
assert_eq!(
|
||||
"%+f".parse::<CFormatSpec>().unwrap().format_float(1.2345),
|
||||
"+1.234500"
|
||||
);
|
||||
assert_eq!(
|
||||
"% f".parse::<CFormatSpec>().unwrap().format_float(1.2345),
|
||||
" 1.234500"
|
||||
);
|
||||
assert_eq!(
|
||||
"%f".parse::<CFormatSpec>().unwrap().format_float(-1.2345),
|
||||
"-1.234500"
|
||||
);
|
||||
assert_eq!(
|
||||
"%f".parse::<CFormatSpec>()
|
||||
.unwrap()
|
||||
.format_float(1.234_567_890_1),
|
||||
"1.234568"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
|
|
@ -1,12 +1,9 @@
|
|||
use itertools::{Itertools, PeekingNext};
|
||||
|
||||
use num_traits::{cast::ToPrimitive, FromPrimitive, Signed};
|
||||
use std::error::Error;
|
||||
use std::ops::Deref;
|
||||
use std::{cmp, str::FromStr};
|
||||
use std::str::FromStr;
|
||||
|
||||
use crate::{float, Case};
|
||||
use num_bigint::{BigInt, Sign};
|
||||
use crate::Case;
|
||||
|
||||
trait FormatParse {
|
||||
fn parse(text: &str) -> (Option<Self>, &str)
|
||||
|
@ -325,418 +322,6 @@ impl FormatSpec {
|
|||
format_type,
|
||||
})
|
||||
}
|
||||
|
||||
fn compute_fill_string(fill_char: char, fill_chars_needed: i32) -> String {
|
||||
(0..fill_chars_needed)
|
||||
.map(|_| fill_char)
|
||||
.collect::<String>()
|
||||
}
|
||||
|
||||
#[allow(
|
||||
clippy::cast_possible_wrap,
|
||||
clippy::cast_possible_truncation,
|
||||
clippy::cast_sign_loss
|
||||
)]
|
||||
fn add_magnitude_separators_for_char(
|
||||
magnitude_str: &str,
|
||||
inter: i32,
|
||||
sep: char,
|
||||
disp_digit_cnt: i32,
|
||||
) -> String {
|
||||
// Don't add separators to the floating decimal point of numbers
|
||||
let mut parts = magnitude_str.splitn(2, '.');
|
||||
let magnitude_int_str = parts.next().unwrap().to_string();
|
||||
let dec_digit_cnt = magnitude_str.len() as i32 - magnitude_int_str.len() as i32;
|
||||
let int_digit_cnt = disp_digit_cnt - dec_digit_cnt;
|
||||
let mut result = FormatSpec::separate_integer(magnitude_int_str, inter, sep, int_digit_cnt);
|
||||
if let Some(part) = parts.next() {
|
||||
result.push_str(&format!(".{part}"));
|
||||
}
|
||||
result
|
||||
}
|
||||
|
||||
#[allow(
|
||||
clippy::cast_sign_loss,
|
||||
clippy::cast_possible_wrap,
|
||||
clippy::cast_possible_truncation
|
||||
)]
|
||||
fn separate_integer(
|
||||
magnitude_str: String,
|
||||
inter: i32,
|
||||
sep: char,
|
||||
disp_digit_cnt: i32,
|
||||
) -> String {
|
||||
let magnitude_len = magnitude_str.len() as i32;
|
||||
let offset = i32::from(disp_digit_cnt % (inter + 1) == 0);
|
||||
let disp_digit_cnt = disp_digit_cnt + offset;
|
||||
let pad_cnt = disp_digit_cnt - magnitude_len;
|
||||
let sep_cnt = disp_digit_cnt / (inter + 1);
|
||||
let diff = pad_cnt - sep_cnt;
|
||||
if pad_cnt > 0 && diff > 0 {
|
||||
// separate with 0 padding
|
||||
let padding = "0".repeat(diff as usize);
|
||||
let padded_num = format!("{padding}{magnitude_str}");
|
||||
FormatSpec::insert_separator(padded_num, inter, sep, sep_cnt)
|
||||
} else {
|
||||
// separate without padding
|
||||
let sep_cnt = (magnitude_len - 1) / inter;
|
||||
FormatSpec::insert_separator(magnitude_str, inter, sep, sep_cnt)
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(
|
||||
clippy::cast_sign_loss,
|
||||
clippy::cast_possible_truncation,
|
||||
clippy::cast_possible_wrap
|
||||
)]
|
||||
fn insert_separator(mut magnitude_str: String, inter: i32, sep: char, sep_cnt: i32) -> String {
|
||||
let magnitude_len = magnitude_str.len() as i32;
|
||||
for i in 1..=sep_cnt {
|
||||
magnitude_str.insert((magnitude_len - inter * i) as usize, sep);
|
||||
}
|
||||
magnitude_str
|
||||
}
|
||||
|
||||
fn validate_format(&self, default_format_type: FormatType) -> Result<(), FormatSpecError> {
|
||||
let format_type = self.format_type.as_ref().unwrap_or(&default_format_type);
|
||||
match (&self.grouping_option, format_type) {
|
||||
(
|
||||
Some(FormatGrouping::Comma),
|
||||
FormatType::String
|
||||
| FormatType::Character
|
||||
| FormatType::Binary
|
||||
| FormatType::Octal
|
||||
| FormatType::Hex(_)
|
||||
| FormatType::Number(_),
|
||||
) => {
|
||||
let ch = char::from(format_type);
|
||||
Err(FormatSpecError::UnspecifiedFormat(',', ch))
|
||||
}
|
||||
(
|
||||
Some(FormatGrouping::Underscore),
|
||||
FormatType::String | FormatType::Character | FormatType::Number(_),
|
||||
) => {
|
||||
let ch = char::from(format_type);
|
||||
Err(FormatSpecError::UnspecifiedFormat('_', ch))
|
||||
}
|
||||
_ => Ok(()),
|
||||
}
|
||||
}
|
||||
|
||||
fn get_separator_interval(&self) -> usize {
|
||||
match self.format_type {
|
||||
Some(FormatType::Binary | FormatType::Octal | FormatType::Hex(_)) => 4,
|
||||
Some(FormatType::Decimal | FormatType::Number(_) | FormatType::FixedPoint(_)) => 3,
|
||||
None => 3,
|
||||
_ => panic!("Separators only valid for numbers!"),
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(clippy::cast_possible_wrap, clippy::cast_possible_truncation)]
|
||||
fn add_magnitude_separators(&self, magnitude_str: String, prefix: &str) -> String {
|
||||
match &self.grouping_option {
|
||||
Some(fg) => {
|
||||
let sep = match fg {
|
||||
FormatGrouping::Comma => ',',
|
||||
FormatGrouping::Underscore => '_',
|
||||
};
|
||||
let inter = self.get_separator_interval().try_into().unwrap();
|
||||
let magnitude_len = magnitude_str.len();
|
||||
let width = self.width.unwrap_or(magnitude_len) as i32 - prefix.len() as i32;
|
||||
let disp_digit_cnt = cmp::max(width, magnitude_len as i32);
|
||||
FormatSpec::add_magnitude_separators_for_char(
|
||||
&magnitude_str,
|
||||
inter,
|
||||
sep,
|
||||
disp_digit_cnt,
|
||||
)
|
||||
}
|
||||
None => magnitude_str,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn format_bool(&self, input: bool) -> Result<String, FormatSpecError> {
|
||||
let x = u8::from(input);
|
||||
match &self.format_type {
|
||||
Some(
|
||||
FormatType::Binary
|
||||
| FormatType::Decimal
|
||||
| FormatType::Octal
|
||||
| FormatType::Number(Case::Lower)
|
||||
| FormatType::Hex(_)
|
||||
| FormatType::GeneralFormat(_)
|
||||
| FormatType::Character,
|
||||
) => self.format_int(&BigInt::from_u8(x).unwrap()),
|
||||
Some(FormatType::Exponent(_) | FormatType::FixedPoint(_) | FormatType::Percentage) => {
|
||||
self.format_float(f64::from(x))
|
||||
}
|
||||
None => {
|
||||
let first_letter = (input.to_string().as_bytes()[0] as char).to_uppercase();
|
||||
Ok(first_letter.collect::<String>() + &input.to_string()[1..])
|
||||
}
|
||||
_ => Err(FormatSpecError::InvalidFormatSpecifier),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn format_float(&self, num: f64) -> Result<String, FormatSpecError> {
|
||||
self.validate_format(FormatType::FixedPoint(Case::Lower))?;
|
||||
let precision = self.precision.unwrap_or(6);
|
||||
let magnitude = num.abs();
|
||||
let raw_magnitude_str: Result<String, FormatSpecError> = match &self.format_type {
|
||||
Some(FormatType::FixedPoint(case)) => Ok(float::format_fixed(
|
||||
precision,
|
||||
magnitude,
|
||||
*case,
|
||||
self.alternate_form,
|
||||
)),
|
||||
Some(
|
||||
FormatType::Decimal
|
||||
| FormatType::Binary
|
||||
| FormatType::Octal
|
||||
| FormatType::Hex(_)
|
||||
| FormatType::String
|
||||
| FormatType::Character
|
||||
| FormatType::Number(Case::Upper),
|
||||
) => {
|
||||
let ch = char::from(self.format_type.as_ref().unwrap());
|
||||
Err(FormatSpecError::UnknownFormatCode(ch, "float"))
|
||||
}
|
||||
Some(FormatType::GeneralFormat(case) | FormatType::Number(case)) => {
|
||||
let precision = if precision == 0 { 1 } else { precision };
|
||||
Ok(float::format_general(
|
||||
precision,
|
||||
magnitude,
|
||||
*case,
|
||||
self.alternate_form,
|
||||
false,
|
||||
))
|
||||
}
|
||||
Some(FormatType::Exponent(case)) => Ok(float::format_exponent(
|
||||
precision,
|
||||
magnitude,
|
||||
*case,
|
||||
self.alternate_form,
|
||||
)),
|
||||
Some(FormatType::Percentage) => match magnitude {
|
||||
magnitude if magnitude.is_nan() => Ok("nan%".to_owned()),
|
||||
magnitude if magnitude.is_infinite() => Ok("inf%".to_owned()),
|
||||
_ => {
|
||||
let result = format!("{:.*}", precision, magnitude * 100.0);
|
||||
let point = float::decimal_point_or_empty(precision, self.alternate_form);
|
||||
Ok(format!("{result}{point}%"))
|
||||
}
|
||||
},
|
||||
None => match magnitude {
|
||||
magnitude if magnitude.is_nan() => Ok("nan".to_owned()),
|
||||
magnitude if magnitude.is_infinite() => Ok("inf".to_owned()),
|
||||
_ => match self.precision {
|
||||
Some(precision) => Ok(float::format_general(
|
||||
precision,
|
||||
magnitude,
|
||||
Case::Lower,
|
||||
self.alternate_form,
|
||||
true,
|
||||
)),
|
||||
None => Ok(float::to_string(magnitude)),
|
||||
},
|
||||
},
|
||||
};
|
||||
let format_sign = self.sign.unwrap_or(FormatSign::Minus);
|
||||
let sign_str = if num.is_sign_negative() && !num.is_nan() {
|
||||
"-"
|
||||
} else {
|
||||
match format_sign {
|
||||
FormatSign::Plus => "+",
|
||||
FormatSign::Minus => "",
|
||||
FormatSign::MinusOrSpace => " ",
|
||||
}
|
||||
};
|
||||
let magnitude_str = self.add_magnitude_separators(raw_magnitude_str?, sign_str);
|
||||
Ok(
|
||||
self.format_sign_and_align(
|
||||
&AsciiStr::new(&magnitude_str),
|
||||
sign_str,
|
||||
FormatAlign::Right,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn format_int_radix(&self, magnitude: &BigInt, radix: u32) -> Result<String, FormatSpecError> {
|
||||
match self.precision {
|
||||
Some(_) => Err(FormatSpecError::PrecisionNotAllowed),
|
||||
None => Ok(magnitude.to_str_radix(radix)),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn format_int(&self, num: &BigInt) -> Result<String, FormatSpecError> {
|
||||
self.validate_format(FormatType::Decimal)?;
|
||||
let magnitude = num.abs();
|
||||
let prefix = if self.alternate_form {
|
||||
match self.format_type {
|
||||
Some(FormatType::Binary) => "0b",
|
||||
Some(FormatType::Octal) => "0o",
|
||||
Some(FormatType::Hex(Case::Lower)) => "0x",
|
||||
Some(FormatType::Hex(Case::Upper)) => "0X",
|
||||
_ => "",
|
||||
}
|
||||
} else {
|
||||
""
|
||||
};
|
||||
let raw_magnitude_str = match self.format_type {
|
||||
Some(FormatType::Binary) => self.format_int_radix(&magnitude, 2),
|
||||
Some(FormatType::Decimal) => self.format_int_radix(&magnitude, 10),
|
||||
Some(FormatType::Octal) => self.format_int_radix(&magnitude, 8),
|
||||
Some(FormatType::Hex(Case::Lower)) => self.format_int_radix(&magnitude, 16),
|
||||
Some(FormatType::Hex(Case::Upper)) => {
|
||||
if self.precision.is_some() {
|
||||
Err(FormatSpecError::PrecisionNotAllowed)
|
||||
} else {
|
||||
let mut result = magnitude.to_str_radix(16);
|
||||
result.make_ascii_uppercase();
|
||||
Ok(result)
|
||||
}
|
||||
}
|
||||
|
||||
Some(FormatType::Number(Case::Lower)) => self.format_int_radix(&magnitude, 10),
|
||||
Some(FormatType::Number(Case::Upper)) => {
|
||||
Err(FormatSpecError::UnknownFormatCode('N', "int"))
|
||||
}
|
||||
Some(FormatType::String) => Err(FormatSpecError::UnknownFormatCode('s', "int")),
|
||||
Some(FormatType::Character) => match (self.sign, self.alternate_form) {
|
||||
(Some(_), _) => Err(FormatSpecError::NotAllowed("Sign")),
|
||||
(_, true) => Err(FormatSpecError::NotAllowed("Alternate form (#)")),
|
||||
(_, _) => match num.to_u32() {
|
||||
Some(n) if n <= 0x0010_ffff => Ok(std::char::from_u32(n).unwrap().to_string()),
|
||||
Some(_) | None => Err(FormatSpecError::CodeNotInRange),
|
||||
},
|
||||
},
|
||||
Some(
|
||||
FormatType::GeneralFormat(_)
|
||||
| FormatType::FixedPoint(_)
|
||||
| FormatType::Exponent(_)
|
||||
| FormatType::Percentage,
|
||||
) => match num.to_f64() {
|
||||
Some(float) => return self.format_float(float),
|
||||
_ => Err(FormatSpecError::UnableToConvert),
|
||||
},
|
||||
None => self.format_int_radix(&magnitude, 10),
|
||||
}?;
|
||||
let format_sign = self.sign.unwrap_or(FormatSign::Minus);
|
||||
let sign_str = match num.sign() {
|
||||
Sign::Minus => "-",
|
||||
_ => match format_sign {
|
||||
FormatSign::Plus => "+",
|
||||
FormatSign::Minus => "",
|
||||
FormatSign::MinusOrSpace => " ",
|
||||
},
|
||||
};
|
||||
let sign_prefix = format!("{sign_str}{prefix}");
|
||||
let magnitude_str = self.add_magnitude_separators(raw_magnitude_str, &sign_prefix);
|
||||
Ok(self.format_sign_and_align(
|
||||
&AsciiStr::new(&magnitude_str),
|
||||
&sign_prefix,
|
||||
FormatAlign::Right,
|
||||
))
|
||||
}
|
||||
|
||||
pub fn format_string<T>(&self, s: &T) -> Result<String, FormatSpecError>
|
||||
where
|
||||
T: CharLen + Deref<Target = str>,
|
||||
{
|
||||
self.validate_format(FormatType::String)?;
|
||||
match self.format_type {
|
||||
Some(FormatType::String) | None => {
|
||||
let mut value = self.format_sign_and_align(s, "", FormatAlign::Left);
|
||||
if let Some(precision) = self.precision {
|
||||
value.truncate(precision);
|
||||
}
|
||||
Ok(value)
|
||||
}
|
||||
_ => {
|
||||
let ch = char::from(self.format_type.as_ref().unwrap());
|
||||
Err(FormatSpecError::UnknownFormatCode(ch, "str"))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(clippy::cast_possible_wrap, clippy::cast_possible_truncation)]
|
||||
fn format_sign_and_align<T>(
|
||||
&self,
|
||||
magnitude_str: &T,
|
||||
sign_str: &str,
|
||||
default_align: FormatAlign,
|
||||
) -> String
|
||||
where
|
||||
T: CharLen + Deref<Target = str>,
|
||||
{
|
||||
let align = self.align.unwrap_or(default_align);
|
||||
|
||||
let num_chars = magnitude_str.char_len();
|
||||
let fill_char = self.fill.unwrap_or(' ');
|
||||
let fill_chars_needed: i32 = self.width.map_or(0, |w| {
|
||||
cmp::max(0, (w as i32) - (num_chars as i32) - (sign_str.len() as i32))
|
||||
});
|
||||
|
||||
let magnitude_str = &**magnitude_str;
|
||||
match align {
|
||||
FormatAlign::Left => format!(
|
||||
"{}{}{}",
|
||||
sign_str,
|
||||
magnitude_str,
|
||||
FormatSpec::compute_fill_string(fill_char, fill_chars_needed)
|
||||
),
|
||||
FormatAlign::Right => format!(
|
||||
"{}{}{}",
|
||||
FormatSpec::compute_fill_string(fill_char, fill_chars_needed),
|
||||
sign_str,
|
||||
magnitude_str
|
||||
),
|
||||
FormatAlign::AfterSign => format!(
|
||||
"{}{}{}",
|
||||
sign_str,
|
||||
FormatSpec::compute_fill_string(fill_char, fill_chars_needed),
|
||||
magnitude_str
|
||||
),
|
||||
FormatAlign::Center => {
|
||||
let left_fill_chars_needed = fill_chars_needed / 2;
|
||||
let right_fill_chars_needed = fill_chars_needed - left_fill_chars_needed;
|
||||
let left_fill_string =
|
||||
FormatSpec::compute_fill_string(fill_char, left_fill_chars_needed);
|
||||
let right_fill_string =
|
||||
FormatSpec::compute_fill_string(fill_char, right_fill_chars_needed);
|
||||
format!("{left_fill_string}{sign_str}{magnitude_str}{right_fill_string}")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub trait CharLen {
|
||||
/// Returns the number of characters in the text
|
||||
fn char_len(&self) -> usize;
|
||||
}
|
||||
|
||||
struct AsciiStr<'a> {
|
||||
inner: &'a str,
|
||||
}
|
||||
|
||||
impl<'a> AsciiStr<'a> {
|
||||
fn new(inner: &'a str) -> Self {
|
||||
Self { inner }
|
||||
}
|
||||
}
|
||||
|
||||
impl CharLen for AsciiStr<'_> {
|
||||
fn char_len(&self) -> usize {
|
||||
self.inner.len()
|
||||
}
|
||||
}
|
||||
|
||||
impl Deref for AsciiStr<'_> {
|
||||
type Target = str;
|
||||
fn deref(&self) -> &Self::Target {
|
||||
self.inner
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
|
@ -1122,98 +707,6 @@ mod tests {
|
|||
assert_eq!(FormatSpec::parse("<>-#23,.11b"), expected);
|
||||
}
|
||||
|
||||
fn format_bool(text: &str, value: bool) -> Result<String, FormatSpecError> {
|
||||
FormatSpec::parse(text).and_then(|spec| spec.format_bool(value))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_format_bool() {
|
||||
assert_eq!(format_bool("b", true), Ok("1".to_owned()));
|
||||
assert_eq!(format_bool("b", false), Ok("0".to_owned()));
|
||||
assert_eq!(format_bool("d", true), Ok("1".to_owned()));
|
||||
assert_eq!(format_bool("d", false), Ok("0".to_owned()));
|
||||
assert_eq!(format_bool("o", true), Ok("1".to_owned()));
|
||||
assert_eq!(format_bool("o", false), Ok("0".to_owned()));
|
||||
assert_eq!(format_bool("n", true), Ok("1".to_owned()));
|
||||
assert_eq!(format_bool("n", false), Ok("0".to_owned()));
|
||||
assert_eq!(format_bool("x", true), Ok("1".to_owned()));
|
||||
assert_eq!(format_bool("x", false), Ok("0".to_owned()));
|
||||
assert_eq!(format_bool("X", true), Ok("1".to_owned()));
|
||||
assert_eq!(format_bool("X", false), Ok("0".to_owned()));
|
||||
assert_eq!(format_bool("g", true), Ok("1".to_owned()));
|
||||
assert_eq!(format_bool("g", false), Ok("0".to_owned()));
|
||||
assert_eq!(format_bool("G", true), Ok("1".to_owned()));
|
||||
assert_eq!(format_bool("G", false), Ok("0".to_owned()));
|
||||
assert_eq!(format_bool("c", true), Ok("\x01".to_owned()));
|
||||
assert_eq!(format_bool("c", false), Ok("\x00".to_owned()));
|
||||
assert_eq!(format_bool("e", true), Ok("1.000000e+00".to_owned()));
|
||||
assert_eq!(format_bool("e", false), Ok("0.000000e+00".to_owned()));
|
||||
assert_eq!(format_bool("E", true), Ok("1.000000E+00".to_owned()));
|
||||
assert_eq!(format_bool("E", false), Ok("0.000000E+00".to_owned()));
|
||||
assert_eq!(format_bool("f", true), Ok("1.000000".to_owned()));
|
||||
assert_eq!(format_bool("f", false), Ok("0.000000".to_owned()));
|
||||
assert_eq!(format_bool("F", true), Ok("1.000000".to_owned()));
|
||||
assert_eq!(format_bool("F", false), Ok("0.000000".to_owned()));
|
||||
assert_eq!(format_bool("%", true), Ok("100.000000%".to_owned()));
|
||||
assert_eq!(format_bool("%", false), Ok("0.000000%".to_owned()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_format_int() {
|
||||
assert_eq!(
|
||||
FormatSpec::parse("d")
|
||||
.unwrap()
|
||||
.format_int(&BigInt::from_bytes_be(Sign::Plus, b"\x10")),
|
||||
Ok("16".to_owned())
|
||||
);
|
||||
assert_eq!(
|
||||
FormatSpec::parse("x")
|
||||
.unwrap()
|
||||
.format_int(&BigInt::from_bytes_be(Sign::Plus, b"\x10")),
|
||||
Ok("10".to_owned())
|
||||
);
|
||||
assert_eq!(
|
||||
FormatSpec::parse("b")
|
||||
.unwrap()
|
||||
.format_int(&BigInt::from_bytes_be(Sign::Plus, b"\x10")),
|
||||
Ok("10000".to_owned())
|
||||
);
|
||||
assert_eq!(
|
||||
FormatSpec::parse("o")
|
||||
.unwrap()
|
||||
.format_int(&BigInt::from_bytes_be(Sign::Plus, b"\x10")),
|
||||
Ok("20".to_owned())
|
||||
);
|
||||
assert_eq!(
|
||||
FormatSpec::parse("+d")
|
||||
.unwrap()
|
||||
.format_int(&BigInt::from_bytes_be(Sign::Plus, b"\x10")),
|
||||
Ok("+16".to_owned())
|
||||
);
|
||||
assert_eq!(
|
||||
FormatSpec::parse("^ 5d")
|
||||
.unwrap()
|
||||
.format_int(&BigInt::from_bytes_be(Sign::Minus, b"\x10")),
|
||||
Ok(" -16 ".to_owned())
|
||||
);
|
||||
assert_eq!(
|
||||
FormatSpec::parse("0>+#10x")
|
||||
.unwrap()
|
||||
.format_int(&BigInt::from_bytes_be(Sign::Plus, b"\x10")),
|
||||
Ok("00000+0x10".to_owned())
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_format_int_sep() {
|
||||
let spec = FormatSpec::parse(",").expect("");
|
||||
assert_eq!(spec.grouping_option, Some(FormatGrouping::Comma));
|
||||
assert_eq!(
|
||||
spec.format_int(&BigInt::from_str("1234567890123456789012345678").unwrap()),
|
||||
Ok("1,234,567,890,123,456,789,012,345,678".to_owned())
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_format_parse() {
|
||||
let expected = Ok(FormatString {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue