Skip to main content

rustpython_common/
format.rs

1// spell-checker:ignore ddfe
2use core::ops::Deref;
3use core::{cmp, str::FromStr};
4use itertools::{Itertools, PeekingNext};
5use malachite_base::num::basic::floats::PrimitiveFloat;
6use malachite_bigint::{BigInt, Sign};
7use num_complex::Complex64;
8use num_traits::FromPrimitive;
9use num_traits::{Signed, cast::ToPrimitive};
10use rustpython_literal::float;
11use rustpython_literal::format::Case;
12
13use crate::wtf8::{CodePoint, Wtf8, Wtf8Buf};
14
15/// Locale information for 'n' format specifier.
16/// Contains thousands separator, decimal point, and grouping pattern
17/// from the C library's `localeconv()`.
18#[derive(Clone, Debug)]
19pub struct LocaleInfo {
20    pub thousands_sep: String,
21    pub decimal_point: String,
22    /// Grouping pattern from `lconv.grouping`.
23    /// Each element is a group size. The last non-zero element repeats.
24    /// e.g. `[3, 0]` means groups of 3 repeating forever.
25    pub grouping: Vec<u8>,
26}
27
28trait FormatParse {
29    fn parse(text: &Wtf8) -> (Option<Self>, &Wtf8)
30    where
31        Self: Sized;
32}
33
34#[derive(Debug, Copy, Clone, PartialEq)]
35#[repr(u8)]
36pub enum FormatConversion {
37    Str = b's',
38    Repr = b'r',
39    Ascii = b'b',
40    Bytes = b'a',
41}
42
43impl FormatParse for FormatConversion {
44    fn parse(text: &Wtf8) -> (Option<Self>, &Wtf8) {
45        let Some(conversion) = Self::from_string(text) else {
46            return (None, text);
47        };
48        let mut chars = text.code_points();
49        chars.next(); // Consume the bang
50        chars.next(); // Consume one r,s,a char
51        (Some(conversion), chars.as_wtf8())
52    }
53}
54
55impl FormatConversion {
56    pub fn from_char(c: CodePoint) -> Option<Self> {
57        match c.to_char_lossy() {
58            's' => Some(Self::Str),
59            'r' => Some(Self::Repr),
60            'a' => Some(Self::Ascii),
61            'b' => Some(Self::Bytes),
62            _ => None,
63        }
64    }
65
66    fn from_string(text: &Wtf8) -> Option<Self> {
67        let mut chars = text.code_points();
68        if chars.next()? != '!' {
69            return None;
70        }
71
72        Self::from_char(chars.next()?)
73    }
74}
75
76#[derive(Debug, Copy, Clone, PartialEq)]
77pub enum FormatAlign {
78    Left,
79    Right,
80    AfterSign,
81    Center,
82}
83
84impl FormatAlign {
85    fn from_char(c: CodePoint) -> Option<Self> {
86        match c.to_char_lossy() {
87            '<' => Some(Self::Left),
88            '>' => Some(Self::Right),
89            '=' => Some(Self::AfterSign),
90            '^' => Some(Self::Center),
91            _ => None,
92        }
93    }
94}
95
96impl FormatParse for FormatAlign {
97    fn parse(text: &Wtf8) -> (Option<Self>, &Wtf8) {
98        let mut chars = text.code_points();
99        if let Some(maybe_align) = chars.next().and_then(Self::from_char) {
100            (Some(maybe_align), chars.as_wtf8())
101        } else {
102            (None, text)
103        }
104    }
105}
106
107#[derive(Debug, Copy, Clone, PartialEq)]
108pub enum FormatSign {
109    Plus,
110    Minus,
111    MinusOrSpace,
112}
113
114impl FormatParse for FormatSign {
115    fn parse(text: &Wtf8) -> (Option<Self>, &Wtf8) {
116        let mut chars = text.code_points();
117        match chars.next().and_then(CodePoint::to_char) {
118            Some('-') => (Some(Self::Minus), chars.as_wtf8()),
119            Some('+') => (Some(Self::Plus), chars.as_wtf8()),
120            Some(' ') => (Some(Self::MinusOrSpace), chars.as_wtf8()),
121            _ => (None, text),
122        }
123    }
124}
125
126#[derive(Clone, Copy, Debug, PartialEq)]
127pub enum FormatGrouping {
128    Comma,
129    Underscore,
130}
131
132impl FormatParse for FormatGrouping {
133    fn parse(text: &Wtf8) -> (Option<Self>, &Wtf8) {
134        let mut chars = text.code_points();
135        match chars.next().and_then(CodePoint::to_char) {
136            Some('_') => (Some(Self::Underscore), chars.as_wtf8()),
137            Some(',') => (Some(Self::Comma), chars.as_wtf8()),
138            _ => (None, text),
139        }
140    }
141}
142
143impl From<&FormatGrouping> for char {
144    fn from(fg: &FormatGrouping) -> Self {
145        match fg {
146            FormatGrouping::Comma => ',',
147            FormatGrouping::Underscore => '_',
148        }
149    }
150}
151
152#[derive(Clone, Copy, Debug, PartialEq)]
153pub enum FormatType {
154    String,
155    Binary,
156    Character,
157    Decimal,
158    Octal,
159    Number(Case),
160    Hex(Case),
161    Exponent(Case),
162    GeneralFormat(Case),
163    FixedPoint(Case),
164    Percentage,
165    Unknown(char),
166}
167
168impl From<&FormatType> for char {
169    fn from(from: &FormatType) -> Self {
170        match from {
171            FormatType::String => 's',
172            FormatType::Binary => 'b',
173            FormatType::Character => 'c',
174            FormatType::Decimal => 'd',
175            FormatType::Octal => 'o',
176            FormatType::Number(Case::Lower) => 'n',
177            FormatType::Number(Case::Upper) => 'N',
178            FormatType::Hex(Case::Lower) => 'x',
179            FormatType::Hex(Case::Upper) => 'X',
180            FormatType::Exponent(Case::Lower) => 'e',
181            FormatType::Exponent(Case::Upper) => 'E',
182            FormatType::GeneralFormat(Case::Lower) => 'g',
183            FormatType::GeneralFormat(Case::Upper) => 'G',
184            FormatType::FixedPoint(Case::Lower) => 'f',
185            FormatType::FixedPoint(Case::Upper) => 'F',
186            FormatType::Percentage => '%',
187            FormatType::Unknown(c) => *c,
188        }
189    }
190}
191
192impl FormatParse for FormatType {
193    fn parse(text: &Wtf8) -> (Option<Self>, &Wtf8) {
194        let mut chars = text.code_points();
195        match chars.next().and_then(CodePoint::to_char) {
196            Some('s') => (Some(Self::String), chars.as_wtf8()),
197            Some('b') => (Some(Self::Binary), chars.as_wtf8()),
198            Some('c') => (Some(Self::Character), chars.as_wtf8()),
199            Some('d') => (Some(Self::Decimal), chars.as_wtf8()),
200            Some('o') => (Some(Self::Octal), chars.as_wtf8()),
201            Some('n') => (Some(Self::Number(Case::Lower)), chars.as_wtf8()),
202            Some('N') => (Some(Self::Number(Case::Upper)), chars.as_wtf8()),
203            Some('x') => (Some(Self::Hex(Case::Lower)), chars.as_wtf8()),
204            Some('X') => (Some(Self::Hex(Case::Upper)), chars.as_wtf8()),
205            Some('e') => (Some(Self::Exponent(Case::Lower)), chars.as_wtf8()),
206            Some('E') => (Some(Self::Exponent(Case::Upper)), chars.as_wtf8()),
207            Some('f') => (Some(Self::FixedPoint(Case::Lower)), chars.as_wtf8()),
208            Some('F') => (Some(Self::FixedPoint(Case::Upper)), chars.as_wtf8()),
209            Some('g') => (Some(Self::GeneralFormat(Case::Lower)), chars.as_wtf8()),
210            Some('G') => (Some(Self::GeneralFormat(Case::Upper)), chars.as_wtf8()),
211            Some('%') => (Some(Self::Percentage), chars.as_wtf8()),
212            Some(c) => (Some(Self::Unknown(c)), chars.as_wtf8()),
213            _ => (None, text),
214        }
215    }
216}
217
218#[derive(Clone, Copy, Debug, PartialEq)]
219pub struct FormatSpec {
220    conversion: Option<FormatConversion>,
221    fill: Option<CodePoint>,
222    align: Option<FormatAlign>,
223    sign: Option<FormatSign>,
224    alternate_form: bool,
225    width: Option<usize>,
226    grouping_option: Option<FormatGrouping>,
227    precision: Option<usize>,
228    format_type: Option<FormatType>,
229}
230
231fn get_num_digits(text: &Wtf8) -> usize {
232    for (index, character) in text.code_point_indices() {
233        if !character.is_char_and(|c| c.is_ascii_digit()) {
234            return index;
235        }
236    }
237    text.len()
238}
239
240fn parse_fill_and_align(text: &Wtf8) -> (Option<CodePoint>, Option<FormatAlign>, &Wtf8) {
241    let char_indices: Vec<(usize, CodePoint)> = text.code_point_indices().take(3).collect();
242    if char_indices.is_empty() {
243        (None, None, text)
244    } else if char_indices.len() == 1 {
245        let (maybe_align, remaining) = FormatAlign::parse(text);
246        (None, maybe_align, remaining)
247    } else {
248        let (maybe_align, remaining) = FormatAlign::parse(&text[char_indices[1].0..]);
249        if maybe_align.is_some() {
250            (Some(char_indices[0].1), maybe_align, remaining)
251        } else {
252            let (only_align, only_align_remaining) = FormatAlign::parse(text);
253            (None, only_align, only_align_remaining)
254        }
255    }
256}
257
258fn parse_number(text: &Wtf8) -> Result<(Option<usize>, &Wtf8), FormatSpecError> {
259    let num_digits: usize = get_num_digits(text);
260    if num_digits == 0 {
261        return Ok((None, text));
262    }
263    if let Some(num) = parse_usize(&text[..num_digits]) {
264        Ok((Some(num), &text[num_digits..]))
265    } else {
266        // NOTE: this condition is different from CPython
267        Err(FormatSpecError::DecimalDigitsTooMany)
268    }
269}
270
271fn parse_alternate_form(text: &Wtf8) -> (bool, &Wtf8) {
272    let mut chars = text.code_points();
273    match chars.next().and_then(CodePoint::to_char) {
274        Some('#') => (true, chars.as_wtf8()),
275        _ => (false, text),
276    }
277}
278
279fn parse_zero(text: &Wtf8) -> (bool, &Wtf8) {
280    let mut chars = text.code_points();
281    match chars.next().and_then(CodePoint::to_char) {
282        Some('0') => (true, chars.as_wtf8()),
283        _ => (false, text),
284    }
285}
286
287fn parse_precision(text: &Wtf8) -> Result<(Option<usize>, &Wtf8), FormatSpecError> {
288    let mut chars = text.code_points();
289    Ok(match chars.next().and_then(CodePoint::to_char) {
290        Some('.') => {
291            let (size, remaining) = parse_number(chars.as_wtf8())?;
292            if let Some(size) = size {
293                if size > i32::MAX as usize {
294                    return Err(FormatSpecError::PrecisionTooBig);
295                }
296                (Some(size), remaining)
297            } else {
298                (None, text)
299            }
300        }
301        _ => (None, text),
302    })
303}
304
305impl FormatSpec {
306    pub fn parse(text: impl AsRef<Wtf8>) -> Result<Self, FormatSpecError> {
307        Self::_parse(text.as_ref())
308    }
309
310    fn _parse(text: &Wtf8) -> Result<Self, FormatSpecError> {
311        // get_integer in CPython
312        let (conversion, text) = FormatConversion::parse(text);
313        let (mut fill, mut align, text) = parse_fill_and_align(text);
314        let (sign, text) = FormatSign::parse(text);
315        let (alternate_form, text) = parse_alternate_form(text);
316        let (zero, text) = parse_zero(text);
317        let (width, text) = parse_number(text)?;
318        let (grouping_option, text) = FormatGrouping::parse(text);
319        if let Some(grouping) = &grouping_option {
320            Self::validate_separator(grouping, text)?;
321        }
322        let (precision, text) = parse_precision(text)?;
323        let (format_type, text) = FormatType::parse(text);
324        if !text.is_empty() {
325            return Err(FormatSpecError::InvalidFormatSpecifier);
326        }
327
328        if zero && fill.is_none() {
329            fill.replace('0'.into());
330            align = align.or(Some(FormatAlign::AfterSign));
331        }
332
333        Ok(Self {
334            conversion,
335            fill,
336            align,
337            sign,
338            alternate_form,
339            width,
340            grouping_option,
341            precision,
342            format_type,
343        })
344    }
345
346    fn validate_separator(grouping: &FormatGrouping, text: &Wtf8) -> Result<(), FormatSpecError> {
347        let mut chars = text.code_points().peekable();
348        match chars.peek().and_then(|cp| CodePoint::to_char(*cp)) {
349            Some(c) if c == ',' || c == '_' => {
350                if c == char::from(grouping) {
351                    Err(FormatSpecError::UnspecifiedFormat(c, c))
352                } else {
353                    Err(FormatSpecError::ExclusiveFormat(',', '_'))
354                }
355            }
356            _ => Ok(()),
357        }
358    }
359
360    fn compute_fill_string(fill_char: CodePoint, fill_chars_needed: i32) -> Wtf8Buf {
361        (0..fill_chars_needed).map(|_| fill_char).collect()
362    }
363
364    fn add_magnitude_separators_for_char(
365        magnitude_str: String,
366        inter: i32,
367        sep: char,
368        disp_digit_cnt: i32,
369    ) -> String {
370        // Don't add separators to the floating decimal point of numbers
371        let mut parts = magnitude_str.splitn(2, '.');
372        let magnitude_int_str = parts.next().unwrap().to_string();
373        let dec_digit_cnt = magnitude_str.len() as i32 - magnitude_int_str.len() as i32;
374        let int_digit_cnt = disp_digit_cnt - dec_digit_cnt;
375        let mut result = Self::separate_integer(magnitude_int_str, inter, sep, int_digit_cnt);
376        if let Some(part) = parts.next() {
377            result.push_str(&format!(".{part}"))
378        }
379        result
380    }
381
382    fn separate_integer(
383        magnitude_str: String,
384        inter: i32,
385        sep: char,
386        disp_digit_cnt: i32,
387    ) -> String {
388        let magnitude_len = magnitude_str.len() as i32;
389        let offset = (disp_digit_cnt % (inter + 1) == 0) as i32;
390        let disp_digit_cnt = disp_digit_cnt + offset;
391        let pad_cnt = disp_digit_cnt - magnitude_len;
392        let sep_cnt = disp_digit_cnt / (inter + 1);
393        let diff = pad_cnt - sep_cnt;
394        if pad_cnt > 0 && diff > 0 {
395            // separate with 0 padding
396            let padding = "0".repeat(diff as usize);
397            let padded_num = format!("{padding}{magnitude_str}");
398            Self::insert_separator(padded_num, inter, sep, sep_cnt)
399        } else {
400            // separate without padding
401            let sep_cnt = (magnitude_len - 1) / inter;
402            Self::insert_separator(magnitude_str, inter, sep, sep_cnt)
403        }
404    }
405
406    fn insert_separator(mut magnitude_str: String, inter: i32, sep: char, sep_cnt: i32) -> String {
407        let magnitude_len = magnitude_str.len() as i32;
408        for i in 1..=sep_cnt {
409            magnitude_str.insert((magnitude_len - inter * i) as usize, sep);
410        }
411        magnitude_str
412    }
413
414    fn validate_format(&self, default_format_type: FormatType) -> Result<(), FormatSpecError> {
415        let format_type = self.format_type.as_ref().unwrap_or(&default_format_type);
416        match (&self.grouping_option, format_type) {
417            (
418                Some(FormatGrouping::Comma),
419                FormatType::String
420                | FormatType::Character
421                | FormatType::Binary
422                | FormatType::Octal
423                | FormatType::Hex(_)
424                | FormatType::Number(_),
425            ) => {
426                let ch = char::from(format_type);
427                Err(FormatSpecError::UnspecifiedFormat(',', ch))
428            }
429            (
430                Some(FormatGrouping::Underscore),
431                FormatType::String | FormatType::Character | FormatType::Number(_),
432            ) => {
433                let ch = char::from(format_type);
434                Err(FormatSpecError::UnspecifiedFormat('_', ch))
435            }
436            _ => Ok(()),
437        }
438    }
439
440    const fn get_separator_interval(&self) -> usize {
441        match self.format_type {
442            Some(FormatType::Binary | FormatType::Octal | FormatType::Hex(_)) => 4,
443            Some(
444                FormatType::Decimal
445                | FormatType::FixedPoint(_)
446                | FormatType::GeneralFormat(_)
447                | FormatType::Exponent(_)
448                | FormatType::Percentage
449                | FormatType::Number(_),
450            ) => 3,
451            None => 3,
452            _ => panic!("Separators only valid for numbers!"),
453        }
454    }
455
456    fn add_magnitude_separators(&self, magnitude_str: String, prefix: &str) -> String {
457        match &self.grouping_option {
458            Some(fg) => {
459                let sep = char::from(fg);
460                let inter = self.get_separator_interval().try_into().unwrap();
461                let magnitude_len = magnitude_str.len();
462                let disp_digit_cnt = if self.fill == Some('0'.into())
463                    && self.align == Some(FormatAlign::AfterSign)
464                {
465                    let width = self.width.unwrap_or(magnitude_len) as i32 - prefix.len() as i32;
466                    cmp::max(width, magnitude_len as i32)
467                } else {
468                    magnitude_len as i32
469                };
470                Self::add_magnitude_separators_for_char(magnitude_str, inter, sep, disp_digit_cnt)
471            }
472            None => magnitude_str,
473        }
474    }
475
476    /// Returns true if this format spec uses the locale-aware 'n' format type.
477    pub fn has_locale_format(&self) -> bool {
478        matches!(self.format_type, Some(FormatType::Number(Case::Lower)))
479    }
480
481    /// Insert locale-aware thousands separators into an integer string.
482    /// Follows CPython's GroupGenerator logic for variable-width grouping.
483    fn insert_locale_grouping(int_part: &str, locale: &LocaleInfo) -> String {
484        if locale.grouping.is_empty() || locale.thousands_sep.is_empty() || int_part.len() <= 1 {
485            return int_part.to_string();
486        }
487
488        let mut group_idx = 0;
489        let mut group_size = locale.grouping[0] as usize;
490
491        if group_size == 0 {
492            return int_part.to_string();
493        }
494
495        // Collect groups of digits from right to left
496        let len = int_part.len();
497        let mut groups: Vec<&str> = Vec::new();
498        let mut pos = len;
499
500        loop {
501            if pos <= group_size {
502                groups.push(&int_part[..pos]);
503                break;
504            }
505
506            groups.push(&int_part[pos - group_size..pos]);
507            pos -= group_size;
508
509            // Advance to next group size
510            if group_idx + 1 < locale.grouping.len() {
511                let next = locale.grouping[group_idx + 1] as usize;
512                if next != 0 {
513                    group_size = next;
514                    group_idx += 1;
515                }
516                // 0 means repeat previous group size forever
517            }
518        }
519
520        // Groups were collected right-to-left, reverse to get left-to-right
521        groups.reverse();
522        groups.join(&locale.thousands_sep)
523    }
524
525    /// Apply locale-aware grouping and decimal point replacement to a formatted number.
526    fn apply_locale_formatting(magnitude_str: String, locale: &LocaleInfo) -> String {
527        let mut parts = magnitude_str.splitn(2, '.');
528        let int_part = parts.next().unwrap();
529        let grouped = Self::insert_locale_grouping(int_part, locale);
530
531        if let Some(frac_part) = parts.next() {
532            format!("{grouped}{}{frac_part}", locale.decimal_point)
533        } else {
534            grouped
535        }
536    }
537
538    /// Format an integer with locale-aware 'n' format.
539    pub fn format_int_locale(
540        &self,
541        num: &BigInt,
542        locale: &LocaleInfo,
543    ) -> Result<String, FormatSpecError> {
544        self.validate_format(FormatType::Decimal)?;
545        let magnitude = num.abs();
546
547        let raw_magnitude_str = match self.format_type {
548            Some(FormatType::Number(Case::Lower)) => self.format_int_radix(magnitude, 10),
549            _ => return self.format_int(num),
550        }?;
551
552        let magnitude_str = Self::apply_locale_formatting(raw_magnitude_str, locale);
553
554        let format_sign = self.sign.unwrap_or(FormatSign::Minus);
555        let sign_str = match num.sign() {
556            Sign::Minus => "-",
557            _ => match format_sign {
558                FormatSign::Plus => "+",
559                FormatSign::Minus => "",
560                FormatSign::MinusOrSpace => " ",
561            },
562        };
563
564        self.format_sign_and_align(&AsciiStr::new(&magnitude_str), sign_str, FormatAlign::Right)
565    }
566
567    /// Format a float with locale-aware 'n' format.
568    pub fn format_float_locale(
569        &self,
570        num: f64,
571        locale: &LocaleInfo,
572    ) -> Result<String, FormatSpecError> {
573        self.validate_format(FormatType::FixedPoint(Case::Lower))?;
574        let precision = self.precision.unwrap_or(6);
575        let magnitude = num.abs();
576
577        let raw_magnitude_str = match &self.format_type {
578            Some(FormatType::Number(case)) => {
579                let precision = if precision == 0 { 1 } else { precision };
580                Ok(float::format_general(
581                    precision,
582                    magnitude,
583                    *case,
584                    self.alternate_form,
585                    false,
586                ))
587            }
588            _ => return self.format_float(num),
589        }?;
590
591        let magnitude_str = Self::apply_locale_formatting(raw_magnitude_str, locale);
592
593        let format_sign = self.sign.unwrap_or(FormatSign::Minus);
594        let sign_str = if num.is_sign_negative() && !num.is_nan() {
595            "-"
596        } else {
597            match format_sign {
598                FormatSign::Plus => "+",
599                FormatSign::Minus => "",
600                FormatSign::MinusOrSpace => " ",
601            }
602        };
603
604        self.format_sign_and_align(&AsciiStr::new(&magnitude_str), sign_str, FormatAlign::Right)
605    }
606
607    /// Format a complex number with locale-aware 'n' format.
608    pub fn format_complex_locale(
609        &self,
610        num: &Complex64,
611        locale: &LocaleInfo,
612    ) -> Result<String, FormatSpecError> {
613        // Reuse format_complex_re_im with 'g' type to get the base formatted parts,
614        // then apply locale grouping. This matches CPython's format_complex_internal:
615        // 'n' → 'g', add_parens=0, skip_re=0.
616        let locale_spec = FormatSpec {
617            format_type: Some(FormatType::GeneralFormat(Case::Lower)),
618            ..*self
619        };
620        let (formatted_re, formatted_im) = locale_spec.format_complex_re_im(num)?;
621
622        // Apply locale grouping to both parts
623        let grouped_re = if formatted_re.is_empty() {
624            formatted_re
625        } else {
626            // Split sign from magnitude, apply grouping, recombine
627            let (sign, mag) = if formatted_re.starts_with('-')
628                || formatted_re.starts_with('+')
629                || formatted_re.starts_with(' ')
630            {
631                formatted_re.split_at(1)
632            } else {
633                ("", formatted_re.as_str())
634            };
635            format!(
636                "{sign}{}",
637                Self::apply_locale_formatting(mag.to_string(), locale)
638            )
639        };
640
641        // formatted_im is like "+1234j" or "-1234j" or "1234j"
642        // Split sign, magnitude, and 'j' suffix
643        let im_str = &formatted_im;
644        let (im_sign, im_rest) = if im_str.starts_with('+') || im_str.starts_with('-') {
645            im_str.split_at(1)
646        } else {
647            ("", im_str.as_str())
648        };
649        let im_mag = im_rest.strip_suffix('j').unwrap_or(im_rest);
650        let im_grouped = Self::apply_locale_formatting(im_mag.to_string(), locale);
651        let grouped_im = format!("{im_sign}{im_grouped}j");
652
653        // No parentheses for 'n' format (CPython: add_parens=0)
654        let magnitude_str = format!("{grouped_re}{grouped_im}");
655
656        self.format_sign_and_align(&AsciiStr::new(&magnitude_str), "", FormatAlign::Right)
657    }
658
659    pub fn format_bool(&self, input: bool) -> Result<String, FormatSpecError> {
660        let x = u8::from(input);
661        match &self.format_type {
662            Some(
663                FormatType::Binary
664                | FormatType::Decimal
665                | FormatType::Octal
666                | FormatType::Number(Case::Lower)
667                | FormatType::Hex(_)
668                | FormatType::GeneralFormat(_)
669                | FormatType::Character,
670            ) => self.format_int(&BigInt::from_u8(x).unwrap()),
671            Some(FormatType::Exponent(_) | FormatType::FixedPoint(_) | FormatType::Percentage) => {
672                self.format_float(x as f64)
673            }
674            None => {
675                let first_letter = (input.to_string().as_bytes()[0] as char).to_uppercase();
676                Ok(first_letter.collect::<String>() + &input.to_string()[1..])
677            }
678            Some(FormatType::Unknown(c)) => Err(FormatSpecError::UnknownFormatCode(*c, "int")),
679            _ => Err(FormatSpecError::InvalidFormatSpecifier),
680        }
681    }
682
683    pub fn format_float(&self, num: f64) -> Result<String, FormatSpecError> {
684        self.validate_format(FormatType::FixedPoint(Case::Lower))?;
685        let precision = self.precision.unwrap_or(6);
686        let magnitude = num.abs();
687        let raw_magnitude_str: Result<String, FormatSpecError> = match &self.format_type {
688            Some(FormatType::FixedPoint(case)) => Ok(float::format_fixed(
689                precision,
690                magnitude,
691                *case,
692                self.alternate_form,
693            )),
694            Some(FormatType::Decimal)
695            | Some(FormatType::Binary)
696            | Some(FormatType::Octal)
697            | Some(FormatType::Hex(_))
698            | Some(FormatType::String)
699            | Some(FormatType::Character)
700            | Some(FormatType::Number(Case::Upper))
701            | Some(FormatType::Unknown(_)) => {
702                let ch = char::from(self.format_type.as_ref().unwrap());
703                Err(FormatSpecError::UnknownFormatCode(ch, "float"))
704            }
705            Some(FormatType::GeneralFormat(case)) | Some(FormatType::Number(case)) => {
706                let precision = if precision == 0 { 1 } else { precision };
707                Ok(float::format_general(
708                    precision,
709                    magnitude,
710                    *case,
711                    self.alternate_form,
712                    false,
713                ))
714            }
715            Some(FormatType::Exponent(case)) => Ok(float::format_exponent(
716                precision,
717                magnitude,
718                *case,
719                self.alternate_form,
720            )),
721            Some(FormatType::Percentage) => match magnitude {
722                magnitude if magnitude.is_nan() => Ok("nan%".to_owned()),
723                magnitude if magnitude.is_infinite() => Ok("inf%".to_owned()),
724                _ => {
725                    let result = format!("{:.*}", precision, magnitude * 100.0);
726                    let point = float::decimal_point_or_empty(precision, self.alternate_form);
727                    Ok(format!("{result}{point}%"))
728                }
729            },
730            None => match magnitude {
731                magnitude if magnitude.is_nan() => Ok("nan".to_owned()),
732                magnitude if magnitude.is_infinite() => Ok("inf".to_owned()),
733                _ => match self.precision {
734                    Some(precision) => Ok(float::format_general(
735                        precision,
736                        magnitude,
737                        Case::Lower,
738                        self.alternate_form,
739                        true,
740                    )),
741                    None => Ok(float::to_string(magnitude)),
742                },
743            },
744        };
745        let format_sign = self.sign.unwrap_or(FormatSign::Minus);
746        let sign_str = if num.is_sign_negative() && !num.is_nan() {
747            "-"
748        } else {
749            match format_sign {
750                FormatSign::Plus => "+",
751                FormatSign::Minus => "",
752                FormatSign::MinusOrSpace => " ",
753            }
754        };
755        let magnitude_str = self.add_magnitude_separators(raw_magnitude_str?, sign_str);
756        self.format_sign_and_align(&AsciiStr::new(&magnitude_str), sign_str, FormatAlign::Right)
757    }
758
759    #[inline]
760    fn format_int_radix(&self, magnitude: BigInt, radix: u32) -> Result<String, FormatSpecError> {
761        match self.precision {
762            Some(_) => Err(FormatSpecError::PrecisionNotAllowed),
763            None => Ok(magnitude.to_str_radix(radix)),
764        }
765    }
766
767    pub fn format_int(&self, num: &BigInt) -> Result<String, FormatSpecError> {
768        self.validate_format(FormatType::Decimal)?;
769        let magnitude = num.abs();
770        let prefix = if self.alternate_form {
771            match self.format_type {
772                Some(FormatType::Binary) => "0b",
773                Some(FormatType::Octal) => "0o",
774                Some(FormatType::Hex(Case::Lower)) => "0x",
775                Some(FormatType::Hex(Case::Upper)) => "0X",
776                _ => "",
777            }
778        } else {
779            ""
780        };
781        let raw_magnitude_str = match self.format_type {
782            Some(FormatType::Binary) => self.format_int_radix(magnitude, 2),
783            Some(FormatType::Decimal) => self.format_int_radix(magnitude, 10),
784            Some(FormatType::Octal) => self.format_int_radix(magnitude, 8),
785            Some(FormatType::Hex(Case::Lower)) => self.format_int_radix(magnitude, 16),
786            Some(FormatType::Hex(Case::Upper)) => match self.precision {
787                Some(_) => Err(FormatSpecError::PrecisionNotAllowed),
788                None => {
789                    let mut result = magnitude.to_str_radix(16);
790                    result.make_ascii_uppercase();
791                    Ok(result)
792                }
793            },
794            Some(FormatType::Number(Case::Lower)) => self.format_int_radix(magnitude, 10),
795            Some(FormatType::Number(Case::Upper)) => {
796                Err(FormatSpecError::UnknownFormatCode('N', "int"))
797            }
798            Some(FormatType::String) => Err(FormatSpecError::UnknownFormatCode('s', "int")),
799            Some(FormatType::Character) => match (self.sign, self.alternate_form) {
800                (Some(_), _) => Err(FormatSpecError::NotAllowed("Sign")),
801                (_, true) => Err(FormatSpecError::NotAllowed("Alternate form (#)")),
802                (_, _) => match num.to_u32() {
803                    Some(n) if n <= 0x10ffff => Ok(core::char::from_u32(n).unwrap().to_string()),
804                    Some(_) | None => Err(FormatSpecError::CodeNotInRange),
805                },
806            },
807            Some(FormatType::GeneralFormat(_))
808            | Some(FormatType::FixedPoint(_))
809            | Some(FormatType::Exponent(_))
810            | Some(FormatType::Percentage) => match num.to_f64() {
811                Some(float) => return self.format_float(float),
812                _ => Err(FormatSpecError::UnableToConvert),
813            },
814            Some(FormatType::Unknown(c)) => Err(FormatSpecError::UnknownFormatCode(c, "int")),
815            None => self.format_int_radix(magnitude, 10),
816        }?;
817        let format_sign = self.sign.unwrap_or(FormatSign::Minus);
818        let sign_str = match num.sign() {
819            Sign::Minus => "-",
820            _ => match format_sign {
821                FormatSign::Plus => "+",
822                FormatSign::Minus => "",
823                FormatSign::MinusOrSpace => " ",
824            },
825        };
826        let sign_prefix = format!("{sign_str}{prefix}");
827        let magnitude_str = self.add_magnitude_separators(raw_magnitude_str, &sign_prefix);
828        self.format_sign_and_align(
829            &AsciiStr::new(&magnitude_str),
830            &sign_prefix,
831            FormatAlign::Right,
832        )
833    }
834
835    pub fn format_string<T>(&self, s: &T) -> Result<String, FormatSpecError>
836    where
837        T: CharLen + Deref<Target = str>,
838    {
839        self.validate_format(FormatType::String)?;
840        match self.format_type {
841            Some(FormatType::String) | None => self
842                .format_sign_and_align(s, "", FormatAlign::Left)
843                .map(|mut value| {
844                    if let Some(precision) = self.precision {
845                        value.truncate(precision);
846                    }
847                    value
848                }),
849            _ => {
850                let ch = char::from(self.format_type.as_ref().unwrap());
851                Err(FormatSpecError::UnknownFormatCode(ch, "str"))
852            }
853        }
854    }
855
856    pub fn format_complex(&self, num: &Complex64) -> Result<String, FormatSpecError> {
857        let (formatted_re, formatted_im) = self.format_complex_re_im(num)?;
858        // Enclose in parentheses if there is no format type and formatted_re is not empty
859        let magnitude_str = if self.format_type.is_none() && !formatted_re.is_empty() {
860            format!("({formatted_re}{formatted_im})")
861        } else {
862            format!("{formatted_re}{formatted_im}")
863        };
864        if let Some(FormatAlign::AfterSign) = &self.align {
865            return Err(FormatSpecError::AlignmentFlag);
866        }
867        match &self.fill.unwrap_or(' '.into()).to_char() {
868            Some('0') => Err(FormatSpecError::ZeroPadding),
869            _ => self.format_sign_and_align(&AsciiStr::new(&magnitude_str), "", FormatAlign::Right),
870        }
871    }
872
873    fn format_complex_re_im(&self, num: &Complex64) -> Result<(String, String), FormatSpecError> {
874        // Format real part
875        let mut formatted_re = String::new();
876        if num.re != 0.0 || num.re.is_negative_zero() || self.format_type.is_some() {
877            let sign_re = if num.re.is_sign_negative() && !num.is_nan() {
878                "-"
879            } else {
880                match self.sign.unwrap_or(FormatSign::Minus) {
881                    FormatSign::Plus => "+",
882                    FormatSign::Minus => "",
883                    FormatSign::MinusOrSpace => " ",
884                }
885            };
886            let re = self.format_complex_float(num.re)?;
887            formatted_re = format!("{sign_re}{re}");
888        }
889        // Format imaginary part
890        let sign_im = if num.im.is_sign_negative() && !num.im.is_nan() {
891            "-"
892        } else if formatted_re.is_empty() {
893            ""
894        } else {
895            "+"
896        };
897        let im = self.format_complex_float(num.im)?;
898        Ok((formatted_re, format!("{sign_im}{im}j")))
899    }
900
901    fn format_complex_float(&self, num: f64) -> Result<String, FormatSpecError> {
902        self.validate_format(FormatType::FixedPoint(Case::Lower))?;
903        let precision = self.precision.unwrap_or(6);
904        let magnitude = num.abs();
905        let magnitude_str = match &self.format_type {
906            Some(FormatType::Decimal)
907            | Some(FormatType::Binary)
908            | Some(FormatType::Octal)
909            | Some(FormatType::Hex(_))
910            | Some(FormatType::String)
911            | Some(FormatType::Character)
912            | Some(FormatType::Number(Case::Upper))
913            | Some(FormatType::Percentage)
914            | Some(FormatType::Unknown(_)) => {
915                let ch = char::from(self.format_type.as_ref().unwrap());
916                Err(FormatSpecError::UnknownFormatCode(ch, "complex"))
917            }
918            Some(FormatType::FixedPoint(case)) => Ok(float::format_fixed(
919                precision,
920                magnitude,
921                *case,
922                self.alternate_form,
923            )),
924            Some(FormatType::GeneralFormat(case)) | Some(FormatType::Number(case)) => {
925                let precision = if precision == 0 { 1 } else { precision };
926                Ok(float::format_general(
927                    precision,
928                    magnitude,
929                    *case,
930                    self.alternate_form,
931                    false,
932                ))
933            }
934            Some(FormatType::Exponent(case)) => Ok(float::format_exponent(
935                precision,
936                magnitude,
937                *case,
938                self.alternate_form,
939            )),
940            None => match magnitude {
941                magnitude if magnitude.is_nan() => Ok("nan".to_owned()),
942                magnitude if magnitude.is_infinite() => Ok("inf".to_owned()),
943                _ => match self.precision {
944                    Some(precision) => Ok(float::format_general(
945                        precision,
946                        magnitude,
947                        Case::Lower,
948                        self.alternate_form,
949                        true,
950                    )),
951                    None => {
952                        if magnitude.fract() == 0.0 {
953                            Ok(magnitude.trunc().to_string())
954                        } else {
955                            Ok(magnitude.to_string())
956                        }
957                    }
958                },
959            },
960        }?;
961        match &self.grouping_option {
962            Some(fg) => {
963                let sep = char::from(fg);
964                let inter = self.get_separator_interval().try_into().unwrap();
965                let len = magnitude_str.len() as i32;
966                let separated_magnitude =
967                    Self::add_magnitude_separators_for_char(magnitude_str, inter, sep, len);
968                Ok(separated_magnitude)
969            }
970            None => Ok(magnitude_str),
971        }
972    }
973
974    fn format_sign_and_align<T>(
975        &self,
976        magnitude_str: &T,
977        sign_str: &str,
978        default_align: FormatAlign,
979    ) -> Result<String, FormatSpecError>
980    where
981        T: CharLen + Deref<Target = str>,
982    {
983        let align = self.align.unwrap_or(default_align);
984
985        let num_chars = magnitude_str.char_len();
986        let fill_char = self.fill.unwrap_or(' '.into());
987        let fill_chars_needed: i32 = self.width.map_or(0, |w| {
988            cmp::max(0, (w as i32) - (num_chars as i32) - (sign_str.len() as i32))
989        });
990
991        let magnitude_str = magnitude_str.deref();
992        Ok(match align {
993            FormatAlign::Left => format!(
994                "{}{}{}",
995                sign_str,
996                magnitude_str,
997                Self::compute_fill_string(fill_char, fill_chars_needed)
998            ),
999            FormatAlign::Right => format!(
1000                "{}{}{}",
1001                Self::compute_fill_string(fill_char, fill_chars_needed),
1002                sign_str,
1003                magnitude_str
1004            ),
1005            FormatAlign::AfterSign => format!(
1006                "{}{}{}",
1007                sign_str,
1008                Self::compute_fill_string(fill_char, fill_chars_needed),
1009                magnitude_str
1010            ),
1011            FormatAlign::Center => {
1012                let left_fill_chars_needed = fill_chars_needed / 2;
1013                let right_fill_chars_needed = fill_chars_needed - left_fill_chars_needed;
1014                let left_fill_string = Self::compute_fill_string(fill_char, left_fill_chars_needed);
1015                let right_fill_string =
1016                    Self::compute_fill_string(fill_char, right_fill_chars_needed);
1017                format!("{left_fill_string}{sign_str}{magnitude_str}{right_fill_string}")
1018            }
1019        })
1020    }
1021}
1022
1023pub trait CharLen {
1024    /// Returns the number of characters in the text
1025    fn char_len(&self) -> usize;
1026}
1027
1028struct AsciiStr<'a> {
1029    inner: &'a str,
1030}
1031
1032impl<'a> AsciiStr<'a> {
1033    const fn new(inner: &'a str) -> Self {
1034        Self { inner }
1035    }
1036}
1037
1038impl CharLen for AsciiStr<'_> {
1039    fn char_len(&self) -> usize {
1040        self.inner.len()
1041    }
1042}
1043
1044impl Deref for AsciiStr<'_> {
1045    type Target = str;
1046
1047    fn deref(&self) -> &Self::Target {
1048        self.inner
1049    }
1050}
1051
1052#[derive(Clone, Copy, Debug, PartialEq)]
1053pub enum FormatSpecError {
1054    DecimalDigitsTooMany,
1055    PrecisionTooBig,
1056    InvalidFormatSpecifier,
1057    UnspecifiedFormat(char, char),
1058    ExclusiveFormat(char, char),
1059    UnknownFormatCode(char, &'static str),
1060    PrecisionNotAllowed,
1061    NotAllowed(&'static str),
1062    UnableToConvert,
1063    CodeNotInRange,
1064    ZeroPadding,
1065    AlignmentFlag,
1066    NotImplemented(char, &'static str),
1067}
1068
1069#[derive(Clone, Copy, Debug, PartialEq)]
1070pub enum FormatParseError {
1071    UnmatchedBracket,
1072    MissingStartBracket,
1073    UnescapedStartBracketInLiteral,
1074    InvalidFormatSpecifier,
1075    UnknownConversion,
1076    EmptyAttribute,
1077    MissingRightBracket,
1078    InvalidCharacterAfterRightBracket,
1079}
1080
1081impl FromStr for FormatSpec {
1082    type Err = FormatSpecError;
1083    fn from_str(s: &str) -> Result<Self, Self::Err> {
1084        Self::parse(s)
1085    }
1086}
1087
1088#[derive(Debug, PartialEq)]
1089pub enum FieldNamePart {
1090    Attribute(Wtf8Buf),
1091    Index(usize),
1092    StringIndex(Wtf8Buf),
1093}
1094
1095impl FieldNamePart {
1096    fn parse_part(
1097        chars: &mut impl PeekingNext<Item = CodePoint>,
1098    ) -> Result<Option<Self>, FormatParseError> {
1099        chars
1100            .next()
1101            .map(|ch| match ch.to_char_lossy() {
1102                '.' => {
1103                    let mut attribute = Wtf8Buf::new();
1104                    for ch in chars.peeking_take_while(|ch| *ch != '.' && *ch != '[') {
1105                        attribute.push(ch);
1106                    }
1107                    if attribute.is_empty() {
1108                        Err(FormatParseError::EmptyAttribute)
1109                    } else {
1110                        Ok(Self::Attribute(attribute))
1111                    }
1112                }
1113                '[' => {
1114                    let mut index = Wtf8Buf::new();
1115                    for ch in chars {
1116                        if ch == ']' {
1117                            return if index.is_empty() {
1118                                Err(FormatParseError::EmptyAttribute)
1119                            } else if let Some(index) = parse_usize(&index) {
1120                                Ok(Self::Index(index))
1121                            } else {
1122                                Ok(Self::StringIndex(index))
1123                            };
1124                        }
1125                        index.push(ch);
1126                    }
1127                    Err(FormatParseError::MissingRightBracket)
1128                }
1129                _ => Err(FormatParseError::InvalidCharacterAfterRightBracket),
1130            })
1131            .transpose()
1132    }
1133}
1134
1135#[derive(Debug, PartialEq)]
1136pub enum FieldType {
1137    Auto,
1138    Index(usize),
1139    Keyword(Wtf8Buf),
1140}
1141
1142#[derive(Debug, PartialEq)]
1143pub struct FieldName {
1144    pub field_type: FieldType,
1145    pub parts: Vec<FieldNamePart>,
1146}
1147
1148fn parse_usize(s: &Wtf8) -> Option<usize> {
1149    s.as_str().ok().and_then(|s| s.parse().ok())
1150}
1151
1152impl FieldName {
1153    pub fn parse(text: &Wtf8) -> Result<Self, FormatParseError> {
1154        let mut chars = text.code_points().peekable();
1155        let first: Wtf8Buf = chars
1156            .peeking_take_while(|ch| *ch != '.' && *ch != '[')
1157            .collect();
1158
1159        let field_type = if first.is_empty() {
1160            FieldType::Auto
1161        } else if let Some(index) = parse_usize(&first) {
1162            FieldType::Index(index)
1163        } else {
1164            FieldType::Keyword(first)
1165        };
1166
1167        let mut parts = Vec::new();
1168        while let Some(part) = FieldNamePart::parse_part(&mut chars)? {
1169            parts.push(part)
1170        }
1171
1172        Ok(Self { field_type, parts })
1173    }
1174}
1175
1176#[derive(Debug, PartialEq)]
1177pub enum FormatPart {
1178    Field {
1179        field_name: Wtf8Buf,
1180        conversion_spec: Option<CodePoint>,
1181        format_spec: Wtf8Buf,
1182    },
1183    Literal(Wtf8Buf),
1184}
1185
1186#[derive(Debug, PartialEq)]
1187pub struct FormatString {
1188    pub format_parts: Vec<FormatPart>,
1189}
1190
1191impl FormatString {
1192    fn parse_literal_single(text: &Wtf8) -> Result<(CodePoint, &Wtf8), FormatParseError> {
1193        let mut chars = text.code_points();
1194        // This should never be called with an empty str
1195        let first_char = chars.next().unwrap();
1196        // isn't this detectable only with bytes operation?
1197        if first_char == '{' || first_char == '}' {
1198            let maybe_next_char = chars.next();
1199            // if we see a bracket, it has to be escaped by doubling up to be in a literal
1200            return if maybe_next_char.is_none() || maybe_next_char.unwrap() != first_char {
1201                Err(FormatParseError::UnescapedStartBracketInLiteral)
1202            } else {
1203                Ok((first_char, chars.as_wtf8()))
1204            };
1205        }
1206        Ok((first_char, chars.as_wtf8()))
1207    }
1208
1209    fn parse_literal(text: &Wtf8) -> Result<(FormatPart, &Wtf8), FormatParseError> {
1210        let mut cur_text = text;
1211        let mut result_string = Wtf8Buf::new();
1212        while !cur_text.is_empty() {
1213            match Self::parse_literal_single(cur_text) {
1214                Ok((next_char, remaining)) => {
1215                    result_string.push(next_char);
1216                    cur_text = remaining;
1217                }
1218                Err(err) => {
1219                    return if !result_string.is_empty() {
1220                        Ok((FormatPart::Literal(result_string), cur_text))
1221                    } else {
1222                        Err(err)
1223                    };
1224                }
1225            }
1226        }
1227        Ok((FormatPart::Literal(result_string), "".as_ref()))
1228    }
1229
1230    fn parse_part_in_brackets(text: &Wtf8) -> Result<FormatPart, FormatParseError> {
1231        let mut chars = text.code_points().peekable();
1232
1233        let mut left = Wtf8Buf::new();
1234        let mut right = Wtf8Buf::new();
1235
1236        let mut split = false;
1237        let mut selected = &mut left;
1238        let mut inside_brackets = false;
1239
1240        while let Some(char) = chars.next() {
1241            if char == '[' {
1242                inside_brackets = true;
1243
1244                selected.push(char);
1245
1246                while let Some(next_char) = chars.next() {
1247                    selected.push(next_char);
1248
1249                    if next_char == ']' {
1250                        inside_brackets = false;
1251                        break;
1252                    }
1253                    if chars.peek().is_none() {
1254                        return Err(FormatParseError::MissingRightBracket);
1255                    }
1256                }
1257            } else if char == ':' && !split && !inside_brackets {
1258                split = true;
1259                selected = &mut right;
1260            } else {
1261                selected.push(char);
1262            }
1263        }
1264
1265        // before the comma is a keyword or arg index, after the comma is maybe a spec.
1266        let arg_part: &Wtf8 = &left;
1267
1268        let format_spec = if split { right } else { Wtf8Buf::new() };
1269
1270        // left can still be the conversion (!r, !s, !a)
1271        let parts: Vec<&Wtf8> = arg_part.splitn(2, "!".as_ref()).collect();
1272        // before the bang is a keyword or arg index, after the comma is maybe a conversion spec.
1273        let arg_part = parts[0];
1274
1275        let conversion_spec = parts
1276            .get(1)
1277            .map(|conversion| {
1278                // conversions are only every one character
1279                conversion
1280                    .code_points()
1281                    .exactly_one()
1282                    .map_err(|_| FormatParseError::UnknownConversion)
1283            })
1284            .transpose()?;
1285
1286        Ok(FormatPart::Field {
1287            field_name: arg_part.to_owned(),
1288            conversion_spec,
1289            format_spec,
1290        })
1291    }
1292
1293    fn parse_spec(text: &Wtf8) -> Result<(FormatPart, &Wtf8), FormatParseError> {
1294        let mut nested = false;
1295        let mut end_bracket_pos = None;
1296        let mut left = Wtf8Buf::new();
1297
1298        // There may be one layer nesting brackets in spec
1299        for (idx, c) in text.code_point_indices() {
1300            if idx == 0 {
1301                if c != '{' {
1302                    return Err(FormatParseError::MissingStartBracket);
1303                }
1304            } else if c == '{' {
1305                if nested {
1306                    return Err(FormatParseError::InvalidFormatSpecifier);
1307                } else {
1308                    nested = true;
1309                    left.push(c);
1310                    continue;
1311                }
1312            } else if c == '}' {
1313                if nested {
1314                    nested = false;
1315                    left.push(c);
1316                    continue;
1317                } else {
1318                    end_bracket_pos = Some(idx);
1319                    break;
1320                }
1321            } else {
1322                left.push(c);
1323            }
1324        }
1325        if let Some(pos) = end_bracket_pos {
1326            let right = &text[pos..];
1327            let format_part = Self::parse_part_in_brackets(&left)?;
1328            Ok((format_part, &right[1..]))
1329        } else {
1330            Err(FormatParseError::UnmatchedBracket)
1331        }
1332    }
1333}
1334
1335pub trait FromTemplate<'a>: Sized {
1336    type Err;
1337    fn from_str(s: &'a Wtf8) -> Result<Self, Self::Err>;
1338}
1339
1340impl<'a> FromTemplate<'a> for FormatString {
1341    type Err = FormatParseError;
1342
1343    fn from_str(text: &'a Wtf8) -> Result<Self, Self::Err> {
1344        let mut cur_text: &Wtf8 = text;
1345        let mut parts: Vec<FormatPart> = Vec::new();
1346        while !cur_text.is_empty() {
1347            // Try to parse both literals and bracketed format parts until we
1348            // run out of text
1349            cur_text = Self::parse_literal(cur_text)
1350                .or_else(|_| Self::parse_spec(cur_text))
1351                .map(|(part, new_text)| {
1352                    parts.push(part);
1353                    new_text
1354                })?;
1355        }
1356        Ok(Self {
1357            format_parts: parts,
1358        })
1359    }
1360}
1361
1362#[cfg(test)]
1363mod tests {
1364    use super::*;
1365
1366    #[test]
1367    fn test_fill_and_align() {
1368        let parse_fill_and_align = |text| {
1369            let (fill, align, rest) = parse_fill_and_align(str::as_ref(text));
1370            (
1371                fill.and_then(CodePoint::to_char),
1372                align,
1373                rest.as_str().unwrap(),
1374            )
1375        };
1376        assert_eq!(
1377            parse_fill_and_align(" <"),
1378            (Some(' '), Some(FormatAlign::Left), "")
1379        );
1380        assert_eq!(
1381            parse_fill_and_align(" <22"),
1382            (Some(' '), Some(FormatAlign::Left), "22")
1383        );
1384        assert_eq!(
1385            parse_fill_and_align("<22"),
1386            (None, Some(FormatAlign::Left), "22")
1387        );
1388        assert_eq!(
1389            parse_fill_and_align(" ^^"),
1390            (Some(' '), Some(FormatAlign::Center), "^")
1391        );
1392        assert_eq!(
1393            parse_fill_and_align("==="),
1394            (Some('='), Some(FormatAlign::AfterSign), "=")
1395        );
1396    }
1397
1398    #[test]
1399    fn test_width_only() {
1400        let expected = Ok(FormatSpec {
1401            conversion: None,
1402            fill: None,
1403            align: None,
1404            sign: None,
1405            alternate_form: false,
1406            width: Some(33),
1407            grouping_option: None,
1408            precision: None,
1409            format_type: None,
1410        });
1411        assert_eq!(FormatSpec::parse("33"), expected);
1412    }
1413
1414    #[test]
1415    fn test_fill_and_width() {
1416        let expected = Ok(FormatSpec {
1417            conversion: None,
1418            fill: Some('<'.into()),
1419            align: Some(FormatAlign::Right),
1420            sign: None,
1421            alternate_form: false,
1422            width: Some(33),
1423            grouping_option: None,
1424            precision: None,
1425            format_type: None,
1426        });
1427        assert_eq!(FormatSpec::parse("<>33"), expected);
1428    }
1429
1430    #[test]
1431    fn test_all() {
1432        let expected = Ok(FormatSpec {
1433            conversion: None,
1434            fill: Some('<'.into()),
1435            align: Some(FormatAlign::Right),
1436            sign: Some(FormatSign::Minus),
1437            alternate_form: true,
1438            width: Some(23),
1439            grouping_option: Some(FormatGrouping::Comma),
1440            precision: Some(11),
1441            format_type: Some(FormatType::Binary),
1442        });
1443        assert_eq!(FormatSpec::parse("<>-#23,.11b"), expected);
1444    }
1445
1446    fn format_bool(text: &str, value: bool) -> Result<String, FormatSpecError> {
1447        FormatSpec::parse(text).and_then(|spec| spec.format_bool(value))
1448    }
1449
1450    #[test]
1451    fn test_format_bool() {
1452        assert_eq!(format_bool("b", true), Ok("1".to_owned()));
1453        assert_eq!(format_bool("b", false), Ok("0".to_owned()));
1454        assert_eq!(format_bool("d", true), Ok("1".to_owned()));
1455        assert_eq!(format_bool("d", false), Ok("0".to_owned()));
1456        assert_eq!(format_bool("o", true), Ok("1".to_owned()));
1457        assert_eq!(format_bool("o", false), Ok("0".to_owned()));
1458        assert_eq!(format_bool("n", true), Ok("1".to_owned()));
1459        assert_eq!(format_bool("n", false), Ok("0".to_owned()));
1460        assert_eq!(format_bool("x", true), Ok("1".to_owned()));
1461        assert_eq!(format_bool("x", false), Ok("0".to_owned()));
1462        assert_eq!(format_bool("X", true), Ok("1".to_owned()));
1463        assert_eq!(format_bool("X", false), Ok("0".to_owned()));
1464        assert_eq!(format_bool("g", true), Ok("1".to_owned()));
1465        assert_eq!(format_bool("g", false), Ok("0".to_owned()));
1466        assert_eq!(format_bool("G", true), Ok("1".to_owned()));
1467        assert_eq!(format_bool("G", false), Ok("0".to_owned()));
1468        assert_eq!(format_bool("c", true), Ok("\x01".to_owned()));
1469        assert_eq!(format_bool("c", false), Ok("\x00".to_owned()));
1470        assert_eq!(format_bool("e", true), Ok("1.000000e+00".to_owned()));
1471        assert_eq!(format_bool("e", false), Ok("0.000000e+00".to_owned()));
1472        assert_eq!(format_bool("E", true), Ok("1.000000E+00".to_owned()));
1473        assert_eq!(format_bool("E", false), Ok("0.000000E+00".to_owned()));
1474        assert_eq!(format_bool("f", true), Ok("1.000000".to_owned()));
1475        assert_eq!(format_bool("f", false), Ok("0.000000".to_owned()));
1476        assert_eq!(format_bool("F", true), Ok("1.000000".to_owned()));
1477        assert_eq!(format_bool("F", false), Ok("0.000000".to_owned()));
1478        assert_eq!(format_bool("%", true), Ok("100.000000%".to_owned()));
1479        assert_eq!(format_bool("%", false), Ok("0.000000%".to_owned()));
1480    }
1481
1482    #[test]
1483    fn test_format_int() {
1484        assert_eq!(
1485            FormatSpec::parse("d")
1486                .unwrap()
1487                .format_int(&BigInt::from_bytes_be(Sign::Plus, b"\x10")),
1488            Ok("16".to_owned())
1489        );
1490        assert_eq!(
1491            FormatSpec::parse("x")
1492                .unwrap()
1493                .format_int(&BigInt::from_bytes_be(Sign::Plus, b"\x10")),
1494            Ok("10".to_owned())
1495        );
1496        assert_eq!(
1497            FormatSpec::parse("b")
1498                .unwrap()
1499                .format_int(&BigInt::from_bytes_be(Sign::Plus, b"\x10")),
1500            Ok("10000".to_owned())
1501        );
1502        assert_eq!(
1503            FormatSpec::parse("o")
1504                .unwrap()
1505                .format_int(&BigInt::from_bytes_be(Sign::Plus, b"\x10")),
1506            Ok("20".to_owned())
1507        );
1508        assert_eq!(
1509            FormatSpec::parse("+d")
1510                .unwrap()
1511                .format_int(&BigInt::from_bytes_be(Sign::Plus, b"\x10")),
1512            Ok("+16".to_owned())
1513        );
1514        assert_eq!(
1515            FormatSpec::parse("^ 5d")
1516                .unwrap()
1517                .format_int(&BigInt::from_bytes_be(Sign::Minus, b"\x10")),
1518            Ok(" -16 ".to_owned())
1519        );
1520        assert_eq!(
1521            FormatSpec::parse("0>+#10x")
1522                .unwrap()
1523                .format_int(&BigInt::from_bytes_be(Sign::Plus, b"\x10")),
1524            Ok("00000+0x10".to_owned())
1525        );
1526    }
1527
1528    #[test]
1529    fn test_format_int_sep() {
1530        let spec = FormatSpec::parse(",").expect("");
1531        assert_eq!(spec.grouping_option, Some(FormatGrouping::Comma));
1532        assert_eq!(
1533            spec.format_int(&BigInt::from_str("1234567890123456789012345678").unwrap()),
1534            Ok("1,234,567,890,123,456,789,012,345,678".to_owned())
1535        );
1536    }
1537
1538    #[test]
1539    fn test_format_int_width_and_grouping() {
1540        // issue #5922: width + comma grouping should pad left, not inside the number
1541        let spec = FormatSpec::parse("10,").unwrap();
1542        let result = spec.format_int(&BigInt::from(1234)).unwrap();
1543        assert_eq!(result, "     1,234"); // CPython 3.13.5
1544    }
1545
1546    #[test]
1547    fn test_format_int_padding_with_grouping() {
1548        // CPython behavior: f'{1234:010,}' results in "00,001,234"
1549        let spec1 = FormatSpec::parse("010,").unwrap();
1550        let result1 = spec1.format_int(&BigInt::from(1234)).unwrap();
1551        assert_eq!(result1, "00,001,234");
1552
1553        // CPython behavior: f'{-1234:010,}' results in "-0,001,234"
1554        let spec2 = FormatSpec::parse("010,").unwrap();
1555        let result2 = spec2.format_int(&BigInt::from(-1234)).unwrap();
1556        assert_eq!(result2, "-0,001,234");
1557
1558        // CPython behavior: f'{-1234:=10,}' results in "-    1,234"
1559        let spec3 = FormatSpec::parse("=10,").unwrap();
1560        let result3 = spec3.format_int(&BigInt::from(-1234)).unwrap();
1561        assert_eq!(result3, "-    1,234");
1562
1563        // CPython behavior: f'{1234:=10,}' results in "     1,234" (same as right-align for positive numbers)
1564        let spec4 = FormatSpec::parse("=10,").unwrap();
1565        let result4 = spec4.format_int(&BigInt::from(1234)).unwrap();
1566        assert_eq!(result4, "     1,234");
1567    }
1568
1569    #[test]
1570    fn test_format_int_non_aftersign_zero_padding() {
1571        // CPython behavior: f'{1234:0>10,}' results in "000001,234"
1572        let spec = FormatSpec::parse("0>10,").unwrap();
1573        let result = spec.format_int(&BigInt::from(1234)).unwrap();
1574        assert_eq!(result, "000001,234");
1575    }
1576
1577    #[test]
1578    fn test_format_parse() {
1579        let expected = Ok(FormatString {
1580            format_parts: vec![
1581                FormatPart::Literal("abcd".into()),
1582                FormatPart::Field {
1583                    field_name: "1".into(),
1584                    conversion_spec: None,
1585                    format_spec: "".into(),
1586                },
1587                FormatPart::Literal(":".into()),
1588                FormatPart::Field {
1589                    field_name: "key".into(),
1590                    conversion_spec: None,
1591                    format_spec: "".into(),
1592                },
1593            ],
1594        });
1595
1596        assert_eq!(FormatString::from_str("abcd{1}:{key}".as_ref()), expected);
1597    }
1598
1599    #[test]
1600    fn test_format_parse_multi_byte_char() {
1601        assert!(FormatString::from_str("{a:%ЫйЯЧ}".as_ref()).is_ok());
1602    }
1603
1604    #[test]
1605    fn test_format_parse_fail() {
1606        assert_eq!(
1607            FormatString::from_str("{s".as_ref()),
1608            Err(FormatParseError::UnmatchedBracket)
1609        );
1610    }
1611
1612    #[test]
1613    fn test_square_brackets_inside_format() {
1614        assert_eq!(
1615            FormatString::from_str("{[:123]}".as_ref()),
1616            Ok(FormatString {
1617                format_parts: vec![FormatPart::Field {
1618                    field_name: "[:123]".into(),
1619                    conversion_spec: None,
1620                    format_spec: "".into(),
1621                }],
1622            }),
1623        );
1624
1625        assert_eq!(FormatString::from_str("{asdf[:123]asdf}".as_ref()), {
1626            Ok(FormatString {
1627                format_parts: vec![FormatPart::Field {
1628                    field_name: "asdf[:123]asdf".into(),
1629                    conversion_spec: None,
1630                    format_spec: "".into(),
1631                }],
1632            })
1633        });
1634
1635        assert_eq!(FormatString::from_str("{[1234}".as_ref()), {
1636            Err(FormatParseError::MissingRightBracket)
1637        });
1638    }
1639
1640    #[test]
1641    fn test_format_parse_escape() {
1642        let expected = Ok(FormatString {
1643            format_parts: vec![
1644                FormatPart::Literal("{".into()),
1645                FormatPart::Field {
1646                    field_name: "key".into(),
1647                    conversion_spec: None,
1648                    format_spec: "".into(),
1649                },
1650                FormatPart::Literal("}ddfe".into()),
1651            ],
1652        });
1653
1654        assert_eq!(FormatString::from_str("{{{key}}}ddfe".as_ref()), expected);
1655    }
1656
1657    #[test]
1658    fn test_format_invalid_specification() {
1659        assert_eq!(
1660            FormatSpec::parse("%3"),
1661            Err(FormatSpecError::InvalidFormatSpecifier)
1662        );
1663        assert_eq!(
1664            FormatSpec::parse(".2fa"),
1665            Err(FormatSpecError::InvalidFormatSpecifier)
1666        );
1667        assert_eq!(
1668            FormatSpec::parse("ds"),
1669            Err(FormatSpecError::InvalidFormatSpecifier)
1670        );
1671        assert_eq!(
1672            FormatSpec::parse("x+"),
1673            Err(FormatSpecError::InvalidFormatSpecifier)
1674        );
1675        assert_eq!(
1676            FormatSpec::parse("b4"),
1677            Err(FormatSpecError::InvalidFormatSpecifier)
1678        );
1679        assert_eq!(
1680            FormatSpec::parse("o!"),
1681            Err(FormatSpecError::InvalidFormatSpecifier)
1682        );
1683        assert_eq!(
1684            FormatSpec::parse("d "),
1685            Err(FormatSpecError::InvalidFormatSpecifier)
1686        );
1687    }
1688
1689    #[test]
1690    fn test_parse_field_name() {
1691        let parse = |s: &str| FieldName::parse(s.as_ref());
1692        assert_eq!(
1693            parse(""),
1694            Ok(FieldName {
1695                field_type: FieldType::Auto,
1696                parts: Vec::new(),
1697            })
1698        );
1699        assert_eq!(
1700            parse("0"),
1701            Ok(FieldName {
1702                field_type: FieldType::Index(0),
1703                parts: Vec::new(),
1704            })
1705        );
1706        assert_eq!(
1707            parse("key"),
1708            Ok(FieldName {
1709                field_type: FieldType::Keyword("key".into()),
1710                parts: Vec::new(),
1711            })
1712        );
1713        assert_eq!(
1714            parse("key.attr[0][string]"),
1715            Ok(FieldName {
1716                field_type: FieldType::Keyword("key".into()),
1717                parts: vec![
1718                    FieldNamePart::Attribute("attr".into()),
1719                    FieldNamePart::Index(0),
1720                    FieldNamePart::StringIndex("string".into())
1721                ],
1722            })
1723        );
1724        assert_eq!(parse("key.."), Err(FormatParseError::EmptyAttribute));
1725        assert_eq!(parse("key[]"), Err(FormatParseError::EmptyAttribute));
1726        assert_eq!(parse("key["), Err(FormatParseError::MissingRightBracket));
1727        assert_eq!(
1728            parse("key[0]after"),
1729            Err(FormatParseError::InvalidCharacterAfterRightBracket)
1730        );
1731    }
1732}