diff --git a/Cargo.lock b/Cargo.lock index 6221b284767d7..64150585eefcd 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2378,7 +2378,6 @@ dependencies = [ "is-macro", "itertools", "lexical-parse-float", - "num-bigint", "num-traits", "rand", "unic-ucd-category", diff --git a/crates/ruff_python_literal/Cargo.toml b/crates/ruff_python_literal/Cargo.toml index 819dd59149093..fd6a8dc439347 100644 --- a/crates/ruff_python_literal/Cargo.toml +++ b/crates/ruff_python_literal/Cargo.toml @@ -17,7 +17,6 @@ hexf-parse = "0.2.1" is-macro.workspace = true itertools = { workspace = true } lexical-parse-float = { version = "0.8.0", features = ["format"] } -num-bigint = { workspace = true } num-traits = { workspace = true } unic-ucd-category = "0.9" diff --git a/crates/ruff_python_literal/src/cformat.rs b/crates/ruff_python_literal/src/cformat.rs index f0aa883bdcd80..e635faa3845ba 100644 --- a/crates/ruff_python_literal/src/cformat.rs +++ b/crates/ruff_python_literal/src/cformat.rs @@ -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::() - } - - fn fill_string( - &self, - string: String, - fill_char: char, - num_prefix_chars: Option, - ) -> 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::() - } - 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 { - 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(iter: &mut ParseIter) -> Result, ParsingError> @@ -741,38 +496,6 @@ impl CFormatString { mod tests { use super::*; - #[test] - fn test_fill_and_align() { - assert_eq!( - "%10s" - .parse::() - .unwrap() - .format_string("test".to_owned()), - " test".to_owned() - ); - assert_eq!( - "%-10s" - .parse::() - .unwrap() - .format_string("test".to_owned()), - "test ".to_owned() - ); - assert_eq!( - "%#10x" - .parse::() - .unwrap() - .format_number(&BigInt::from(0x1337)), - " 0x1337".to_owned() - ); - assert_eq!( - "%-#10x" - .parse::() - .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::(); 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::() - .unwrap() - .format_string("Hello, World!".to_owned()), - " Hell".to_owned() - ); - assert_eq!( - "%-5.4s" - .parse::() - .unwrap() - .format_string("Hello, World!".to_owned()), - "Hell ".to_owned() - ); - assert_eq!( - "%.s" - .parse::() - .unwrap() - .format_string("Hello, World!".to_owned()), - String::new() - ); - assert_eq!( - "%5.s" - .parse::() - .unwrap() - .format_string("Hello, World!".to_owned()), - " ".to_owned() - ); - } - - #[test] - fn test_parse_and_format_unicode_string() { - assert_eq!( - "%.2s" - .parse::() - .unwrap() - .format_string("❤❤❤❤❤❤❤❤".to_owned()), - "❤❤".to_owned() - ); - } - - #[test] - fn test_parse_and_format_number() { - assert_eq!( - "%5d" - .parse::() - .unwrap() - .format_number(&BigInt::from(27)), - " 27".to_owned() - ); - assert_eq!( - "%05d" - .parse::() - .unwrap() - .format_number(&BigInt::from(27)), - "00027".to_owned() - ); - assert_eq!( - "%.5d" - .parse::() - .unwrap() - .format_number(&BigInt::from(27)), - "00027".to_owned() - ); - assert_eq!( - "%+05d" - .parse::() - .unwrap() - .format_number(&BigInt::from(27)), - "+0027".to_owned() - ); - assert_eq!( - "%-d" - .parse::() - .unwrap() - .format_number(&BigInt::from(-27)), - "-27".to_owned() - ); - assert_eq!( - "% d" - .parse::() - .unwrap() - .format_number(&BigInt::from(27)), - " 27".to_owned() - ); - assert_eq!( - "% d" - .parse::() - .unwrap() - .format_number(&BigInt::from(-27)), - "-27".to_owned() - ); - assert_eq!( - "%08x" - .parse::() - .unwrap() - .format_number(&BigInt::from(0x1337)), - "00001337".to_owned() - ); - assert_eq!( - "%#010x" - .parse::() - .unwrap() - .format_number(&BigInt::from(0x1337)), - "0x00001337".to_owned() - ); - assert_eq!( - "%-#010x" - .parse::() - .unwrap() - .format_number(&BigInt::from(0x1337)), - "0x1337 ".to_owned() - ); - } - - #[test] - fn test_parse_and_format_float() { - assert_eq!( - "%f".parse::().unwrap().format_float(1.2345), - "1.234500" - ); - assert_eq!( - "%.2f".parse::().unwrap().format_float(1.2345), - "1.23" - ); - assert_eq!( - "%.f".parse::().unwrap().format_float(1.2345), - "1" - ); - assert_eq!( - "%+.f".parse::().unwrap().format_float(1.2345), - "+1" - ); - assert_eq!( - "%+f".parse::().unwrap().format_float(1.2345), - "+1.234500" - ); - assert_eq!( - "% f".parse::().unwrap().format_float(1.2345), - " 1.234500" - ); - assert_eq!( - "%f".parse::().unwrap().format_float(-1.2345), - "-1.234500" - ); - assert_eq!( - "%f".parse::() - .unwrap() - .format_float(1.234_567_890_1), - "1.234568" - ); } #[test] diff --git a/crates/ruff_python_literal/src/format.rs b/crates/ruff_python_literal/src/format.rs index 89e7ef852a383..dd6fdcc8e743f 100644 --- a/crates/ruff_python_literal/src/format.rs +++ b/crates/ruff_python_literal/src/format.rs @@ -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, &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::() - } - - #[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 { - 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::() + &input.to_string()[1..]) - } - _ => Err(FormatSpecError::InvalidFormatSpecifier), - } - } - - pub fn format_float(&self, num: f64) -> Result { - self.validate_format(FormatType::FixedPoint(Case::Lower))?; - let precision = self.precision.unwrap_or(6); - let magnitude = num.abs(); - let raw_magnitude_str: Result = 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 { - match self.precision { - Some(_) => Err(FormatSpecError::PrecisionNotAllowed), - None => Ok(magnitude.to_str_radix(radix)), - } - } - - pub fn format_int(&self, num: &BigInt) -> Result { - 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(&self, s: &T) -> Result - where - T: CharLen + Deref, - { - 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( - &self, - magnitude_str: &T, - sign_str: &str, - default_align: FormatAlign, - ) -> String - where - T: CharLen + Deref, - { - 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 { - 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 {