rustpython_format/
cformat.rs

1//! Implementation of Printf-Style string formatting
2//! as per the [Python Docs](https://docs.python.org/3/library/stdtypes.html#printf-style-string-formatting).
3use crate::bigint::{BigInt, Sign};
4use bitflags::bitflags;
5use num_traits::Signed;
6use rustpython_literal::{float, format::Case};
7use std::{
8    cmp, fmt,
9    iter::{Enumerate, Peekable},
10    str::FromStr,
11};
12
13#[derive(Debug, PartialEq)]
14pub enum CFormatErrorType {
15    UnmatchedKeyParentheses,
16    MissingModuloSign,
17    UnsupportedFormatChar(char),
18    IncompleteFormat,
19    IntTooBig,
20    // Unimplemented,
21}
22
23// also contains how many chars the parsing function consumed
24pub type ParsingError = (CFormatErrorType, usize);
25
26#[derive(Debug, PartialEq)]
27pub struct CFormatError {
28    pub typ: CFormatErrorType, // FIXME
29    pub index: usize,
30}
31
32impl fmt::Display for CFormatError {
33    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
34        use CFormatErrorType::*;
35        match self.typ {
36            UnmatchedKeyParentheses => write!(f, "incomplete format key"),
37            IncompleteFormat => write!(f, "incomplete format"),
38            UnsupportedFormatChar(c) => write!(
39                f,
40                "unsupported format character '{}' ({:#x}) at index {}",
41                c, c as u32, self.index
42            ),
43            IntTooBig => write!(f, "width/precision too big"),
44            _ => write!(f, "unexpected error parsing format string"),
45        }
46    }
47}
48
49pub type CFormatConversion = super::format::FormatConversion;
50
51#[derive(Debug, PartialEq)]
52pub enum CNumberType {
53    Decimal,
54    Octal,
55    Hex(Case),
56}
57
58#[derive(Debug, PartialEq)]
59pub enum CFloatType {
60    Exponent(Case),
61    PointDecimal(Case),
62    General(Case),
63}
64
65#[derive(Debug, PartialEq)]
66pub enum CFormatType {
67    Number(CNumberType),
68    Float(CFloatType),
69    Character,
70    String(CFormatConversion),
71}
72
73#[derive(Debug, PartialEq)]
74pub enum CFormatPrecision {
75    Quantity(CFormatQuantity),
76    Dot,
77}
78
79impl From<CFormatQuantity> for CFormatPrecision {
80    fn from(quantity: CFormatQuantity) -> Self {
81        CFormatPrecision::Quantity(quantity)
82    }
83}
84
85bitflags! {
86    #[derive(Copy, Clone, Debug, PartialEq)]
87    pub struct CConversionFlags: u32 {
88        const ALTERNATE_FORM = 0b0000_0001;
89        const ZERO_PAD = 0b0000_0010;
90        const LEFT_ADJUST = 0b0000_0100;
91        const BLANK_SIGN = 0b0000_1000;
92        const SIGN_CHAR = 0b0001_0000;
93    }
94}
95
96impl CConversionFlags {
97    #[inline]
98    pub fn sign_string(&self) -> &'static str {
99        if self.contains(CConversionFlags::SIGN_CHAR) {
100            "+"
101        } else if self.contains(CConversionFlags::BLANK_SIGN) {
102            " "
103        } else {
104            ""
105        }
106    }
107}
108
109#[derive(Debug, PartialEq)]
110pub enum CFormatQuantity {
111    Amount(usize),
112    FromValuesTuple,
113}
114
115#[derive(Debug, PartialEq)]
116pub struct CFormatSpec {
117    pub mapping_key: Option<String>,
118    pub flags: CConversionFlags,
119    pub min_field_width: Option<CFormatQuantity>,
120    pub precision: Option<CFormatPrecision>,
121    pub format_type: CFormatType,
122    pub format_char: char,
123    // chars_consumed: usize,
124}
125
126impl FromStr for CFormatSpec {
127    type Err = ParsingError;
128
129    fn from_str(text: &str) -> Result<Self, Self::Err> {
130        let mut chars = text.chars().enumerate().peekable();
131        if chars.next().map(|x| x.1) != Some('%') {
132            return Err((CFormatErrorType::MissingModuloSign, 1));
133        }
134
135        CFormatSpec::parse(&mut chars)
136    }
137}
138
139pub type ParseIter<I> = Peekable<Enumerate<I>>;
140
141impl CFormatSpec {
142    pub fn parse<T, I>(iter: &mut ParseIter<I>) -> Result<Self, ParsingError>
143    where
144        T: Into<char> + Copy,
145        I: Iterator<Item = T>,
146    {
147        let mapping_key = parse_spec_mapping_key(iter)?;
148        let flags = parse_flags(iter);
149        let min_field_width = parse_quantity(iter)?;
150        let precision = parse_precision(iter)?;
151        consume_length(iter);
152        let (format_type, format_char) = parse_format_type(iter)?;
153
154        Ok(CFormatSpec {
155            mapping_key,
156            flags,
157            min_field_width,
158            precision,
159            format_type,
160            format_char,
161        })
162    }
163
164    fn compute_fill_string(fill_char: char, fill_chars_needed: usize) -> String {
165        (0..fill_chars_needed)
166            .map(|_| fill_char)
167            .collect::<String>()
168    }
169
170    fn fill_string(
171        &self,
172        string: String,
173        fill_char: char,
174        num_prefix_chars: Option<usize>,
175    ) -> String {
176        let mut num_chars = string.chars().count();
177        if let Some(num_prefix_chars) = num_prefix_chars {
178            num_chars += num_prefix_chars;
179        }
180        let num_chars = num_chars;
181
182        let width = match &self.min_field_width {
183            Some(CFormatQuantity::Amount(width)) => cmp::max(width, &num_chars),
184            _ => &num_chars,
185        };
186        let fill_chars_needed = width.saturating_sub(num_chars);
187        let fill_string = CFormatSpec::compute_fill_string(fill_char, fill_chars_needed);
188
189        if !fill_string.is_empty() {
190            if self.flags.contains(CConversionFlags::LEFT_ADJUST) {
191                format!("{string}{fill_string}")
192            } else {
193                format!("{fill_string}{string}")
194            }
195        } else {
196            string
197        }
198    }
199
200    fn fill_string_with_precision(&self, string: String, fill_char: char) -> String {
201        let num_chars = string.chars().count();
202
203        let width = match &self.precision {
204            Some(CFormatPrecision::Quantity(CFormatQuantity::Amount(width))) => {
205                cmp::max(width, &num_chars)
206            }
207            _ => &num_chars,
208        };
209        let fill_chars_needed = width.saturating_sub(num_chars);
210        let fill_string = CFormatSpec::compute_fill_string(fill_char, fill_chars_needed);
211
212        if !fill_string.is_empty() {
213            // Don't left-adjust if precision-filling: that will always be prepending 0s to %d
214            // arguments, the LEFT_ADJUST flag will be used by a later call to fill_string with
215            // the 0-filled string as the string param.
216            format!("{fill_string}{string}")
217        } else {
218            string
219        }
220    }
221
222    fn format_string_with_precision(
223        &self,
224        string: String,
225        precision: Option<&CFormatPrecision>,
226    ) -> String {
227        // truncate if needed
228        let string = match precision {
229            Some(CFormatPrecision::Quantity(CFormatQuantity::Amount(precision)))
230                if string.chars().count() > *precision =>
231            {
232                string.chars().take(*precision).collect::<String>()
233            }
234            Some(CFormatPrecision::Dot) => {
235                // truncate to 0
236                String::new()
237            }
238            _ => string,
239        };
240        self.fill_string(string, ' ', None)
241    }
242
243    #[inline]
244    pub fn format_string(&self, string: String) -> String {
245        self.format_string_with_precision(string, self.precision.as_ref())
246    }
247
248    #[inline]
249    pub fn format_char(&self, ch: char) -> String {
250        self.format_string_with_precision(
251            ch.to_string(),
252            Some(&(CFormatQuantity::Amount(1).into())),
253        )
254    }
255
256    pub fn format_bytes(&self, bytes: &[u8]) -> Vec<u8> {
257        let bytes = if let Some(CFormatPrecision::Quantity(CFormatQuantity::Amount(precision))) =
258            self.precision
259        {
260            &bytes[..cmp::min(bytes.len(), precision)]
261        } else {
262            bytes
263        };
264        if let Some(CFormatQuantity::Amount(width)) = self.min_field_width {
265            let fill = cmp::max(0, width - bytes.len());
266            let mut v = Vec::with_capacity(bytes.len() + fill);
267            if self.flags.contains(CConversionFlags::LEFT_ADJUST) {
268                v.extend_from_slice(bytes);
269                v.append(&mut vec![b' '; fill]);
270            } else {
271                v.append(&mut vec![b' '; fill]);
272                v.extend_from_slice(bytes);
273            }
274            v
275        } else {
276            bytes.to_vec()
277        }
278    }
279
280    pub fn format_number(&self, num: &BigInt) -> String {
281        use CNumberType::*;
282        let magnitude = num.abs();
283        let prefix = if self.flags.contains(CConversionFlags::ALTERNATE_FORM) {
284            match self.format_type {
285                CFormatType::Number(Octal) => "0o",
286                CFormatType::Number(Hex(Case::Lower)) => "0x",
287                CFormatType::Number(Hex(Case::Upper)) => "0X",
288                _ => "",
289            }
290        } else {
291            ""
292        };
293
294        let magnitude_string: String = match self.format_type {
295            CFormatType::Number(Decimal) => magnitude.to_str_radix(10),
296            CFormatType::Number(Octal) => magnitude.to_str_radix(8),
297            CFormatType::Number(Hex(Case::Lower)) => magnitude.to_str_radix(16),
298            CFormatType::Number(Hex(Case::Upper)) => {
299                let mut result = magnitude.to_str_radix(16);
300                result.make_ascii_uppercase();
301                result
302            }
303            _ => unreachable!(), // Should not happen because caller has to make sure that this is a number
304        };
305
306        let sign_string = match num.sign() {
307            Sign::Minus => "-",
308            _ => self.flags.sign_string(),
309        };
310
311        let padded_magnitude_string = self.fill_string_with_precision(magnitude_string, '0');
312
313        if self.flags.contains(CConversionFlags::ZERO_PAD) {
314            let fill_char = if !self.flags.contains(CConversionFlags::LEFT_ADJUST) {
315                '0'
316            } else {
317                ' ' // '-' overrides the '0' conversion if both are given
318            };
319            let signed_prefix = format!("{sign_string}{prefix}");
320            format!(
321                "{}{}",
322                signed_prefix,
323                self.fill_string(
324                    padded_magnitude_string,
325                    fill_char,
326                    Some(signed_prefix.chars().count()),
327                ),
328            )
329        } else {
330            self.fill_string(
331                format!("{sign_string}{prefix}{padded_magnitude_string}"),
332                ' ',
333                None,
334            )
335        }
336    }
337
338    pub fn format_float(&self, num: f64) -> String {
339        let sign_string = if num.is_sign_negative() && !num.is_nan() {
340            "-"
341        } else {
342            self.flags.sign_string()
343        };
344
345        let precision = match &self.precision {
346            Some(CFormatPrecision::Quantity(quantity)) => match quantity {
347                CFormatQuantity::Amount(amount) => *amount,
348                CFormatQuantity::FromValuesTuple => 6,
349            },
350            Some(CFormatPrecision::Dot) => 0,
351            None => 6,
352        };
353
354        let magnitude_string = match &self.format_type {
355            CFormatType::Float(CFloatType::PointDecimal(case)) => {
356                let magnitude = num.abs();
357                float::format_fixed(
358                    precision,
359                    magnitude,
360                    *case,
361                    self.flags.contains(CConversionFlags::ALTERNATE_FORM),
362                )
363            }
364            CFormatType::Float(CFloatType::Exponent(case)) => {
365                let magnitude = num.abs();
366                float::format_exponent(
367                    precision,
368                    magnitude,
369                    *case,
370                    self.flags.contains(CConversionFlags::ALTERNATE_FORM),
371                )
372            }
373            CFormatType::Float(CFloatType::General(case)) => {
374                let precision = if precision == 0 { 1 } else { precision };
375                let magnitude = num.abs();
376                float::format_general(
377                    precision,
378                    magnitude,
379                    *case,
380                    self.flags.contains(CConversionFlags::ALTERNATE_FORM),
381                    false,
382                )
383            }
384            _ => unreachable!(),
385        };
386
387        if self.flags.contains(CConversionFlags::ZERO_PAD) {
388            let fill_char = if !self.flags.contains(CConversionFlags::LEFT_ADJUST) {
389                '0'
390            } else {
391                ' '
392            };
393            format!(
394                "{}{}",
395                sign_string,
396                self.fill_string(
397                    magnitude_string,
398                    fill_char,
399                    Some(sign_string.chars().count()),
400                )
401            )
402        } else {
403            self.fill_string(format!("{sign_string}{magnitude_string}"), ' ', None)
404        }
405    }
406}
407
408fn parse_spec_mapping_key<T, I>(iter: &mut ParseIter<I>) -> Result<Option<String>, ParsingError>
409where
410    T: Into<char> + Copy,
411    I: Iterator<Item = T>,
412{
413    if let Some(&(index, c)) = iter.peek() {
414        if c.into() == '(' {
415            iter.next().unwrap();
416            return match parse_text_inside_parentheses(iter) {
417                Some(key) => Ok(Some(key)),
418                None => Err((CFormatErrorType::UnmatchedKeyParentheses, index)),
419            };
420        }
421    }
422    Ok(None)
423}
424
425fn parse_flags<T, I>(iter: &mut ParseIter<I>) -> CConversionFlags
426where
427    T: Into<char> + Copy,
428    I: Iterator<Item = T>,
429{
430    let mut flags = CConversionFlags::empty();
431    while let Some(&(_, c)) = iter.peek() {
432        let flag = match c.into() {
433            '#' => CConversionFlags::ALTERNATE_FORM,
434            '0' => CConversionFlags::ZERO_PAD,
435            '-' => CConversionFlags::LEFT_ADJUST,
436            ' ' => CConversionFlags::BLANK_SIGN,
437            '+' => CConversionFlags::SIGN_CHAR,
438            _ => break,
439        };
440        iter.next().unwrap();
441        flags |= flag;
442    }
443    flags
444}
445
446fn consume_length<T, I>(iter: &mut ParseIter<I>)
447where
448    T: Into<char> + Copy,
449    I: Iterator<Item = T>,
450{
451    if let Some(&(_, c)) = iter.peek() {
452        let c = c.into();
453        if c == 'h' || c == 'l' || c == 'L' {
454            iter.next().unwrap();
455        }
456    }
457}
458
459fn parse_format_type<T, I>(iter: &mut ParseIter<I>) -> Result<(CFormatType, char), ParsingError>
460where
461    T: Into<char>,
462    I: Iterator<Item = T>,
463{
464    use CFloatType::*;
465    use CNumberType::*;
466    let (index, c) = match iter.next() {
467        Some((index, c)) => (index, c.into()),
468        None => {
469            return Err((
470                CFormatErrorType::IncompleteFormat,
471                iter.peek().map(|x| x.0).unwrap_or(0),
472            ));
473        }
474    };
475    let format_type = match c {
476        'd' | 'i' | 'u' => CFormatType::Number(Decimal),
477        'o' => CFormatType::Number(Octal),
478        'x' => CFormatType::Number(Hex(Case::Lower)),
479        'X' => CFormatType::Number(Hex(Case::Upper)),
480        'e' => CFormatType::Float(Exponent(Case::Lower)),
481        'E' => CFormatType::Float(Exponent(Case::Upper)),
482        'f' => CFormatType::Float(PointDecimal(Case::Lower)),
483        'F' => CFormatType::Float(PointDecimal(Case::Upper)),
484        'g' => CFormatType::Float(General(Case::Lower)),
485        'G' => CFormatType::Float(General(Case::Upper)),
486        'c' => CFormatType::Character,
487        'r' => CFormatType::String(CFormatConversion::Repr),
488        's' => CFormatType::String(CFormatConversion::Str),
489        'b' => CFormatType::String(CFormatConversion::Bytes),
490        'a' => CFormatType::String(CFormatConversion::Ascii),
491        _ => return Err((CFormatErrorType::UnsupportedFormatChar(c), index)),
492    };
493    Ok((format_type, c))
494}
495
496fn parse_quantity<T, I>(iter: &mut ParseIter<I>) -> Result<Option<CFormatQuantity>, ParsingError>
497where
498    T: Into<char> + Copy,
499    I: Iterator<Item = T>,
500{
501    if let Some(&(_, c)) = iter.peek() {
502        let c: char = c.into();
503        if c == '*' {
504            iter.next().unwrap();
505            return Ok(Some(CFormatQuantity::FromValuesTuple));
506        }
507        if let Some(i) = c.to_digit(10) {
508            let mut num = i as i32;
509            iter.next().unwrap();
510            while let Some(&(index, c)) = iter.peek() {
511                if let Some(i) = c.into().to_digit(10) {
512                    num = num
513                        .checked_mul(10)
514                        .and_then(|num| num.checked_add(i as i32))
515                        .ok_or((CFormatErrorType::IntTooBig, index))?;
516                    iter.next().unwrap();
517                } else {
518                    break;
519                }
520            }
521            return Ok(Some(CFormatQuantity::Amount(num.unsigned_abs() as usize)));
522        }
523    }
524    Ok(None)
525}
526
527fn parse_precision<T, I>(iter: &mut ParseIter<I>) -> Result<Option<CFormatPrecision>, ParsingError>
528where
529    T: Into<char> + Copy,
530    I: Iterator<Item = T>,
531{
532    if let Some(&(_, c)) = iter.peek() {
533        if c.into() == '.' {
534            iter.next().unwrap();
535            let quantity = parse_quantity(iter)?;
536            let precision = quantity.map_or(CFormatPrecision::Dot, CFormatPrecision::Quantity);
537            return Ok(Some(precision));
538        }
539    }
540    Ok(None)
541}
542
543fn parse_text_inside_parentheses<T, I>(iter: &mut ParseIter<I>) -> Option<String>
544where
545    T: Into<char>,
546    I: Iterator<Item = T>,
547{
548    let mut counter: i32 = 1;
549    let mut contained_text = String::new();
550    loop {
551        let (_, c) = iter.next()?;
552        let c = c.into();
553        match c {
554            _ if c == '(' => {
555                counter += 1;
556            }
557            _ if c == ')' => {
558                counter -= 1;
559            }
560            _ => (),
561        }
562
563        if counter > 0 {
564            contained_text.push(c);
565        } else {
566            break;
567        }
568    }
569
570    Some(contained_text)
571}
572
573#[derive(Debug, PartialEq)]
574pub enum CFormatPart<T> {
575    Literal(T),
576    Spec(CFormatSpec),
577}
578
579impl<T> CFormatPart<T> {
580    #[inline]
581    pub fn is_specifier(&self) -> bool {
582        matches!(self, CFormatPart::Spec(_))
583    }
584
585    #[inline]
586    pub fn has_key(&self) -> bool {
587        match self {
588            CFormatPart::Spec(s) => s.mapping_key.is_some(),
589            _ => false,
590        }
591    }
592}
593
594#[derive(Debug, PartialEq)]
595pub struct CFormatStrOrBytes<S> {
596    parts: Vec<(usize, CFormatPart<S>)>,
597}
598
599impl<S> CFormatStrOrBytes<S> {
600    pub fn check_specifiers(&self) -> Option<(usize, bool)> {
601        let mut count = 0;
602        let mut mapping_required = false;
603        for (_, part) in &self.parts {
604            if part.is_specifier() {
605                let has_key = part.has_key();
606                if count == 0 {
607                    mapping_required = has_key;
608                } else if mapping_required != has_key {
609                    return None;
610                }
611                count += 1;
612            }
613        }
614        Some((count, mapping_required))
615    }
616
617    #[inline]
618    pub fn iter(&self) -> impl Iterator<Item = &(usize, CFormatPart<S>)> {
619        self.parts.iter()
620    }
621
622    #[inline]
623    pub fn iter_mut(&mut self) -> impl Iterator<Item = &mut (usize, CFormatPart<S>)> {
624        self.parts.iter_mut()
625    }
626}
627
628pub type CFormatBytes = CFormatStrOrBytes<Vec<u8>>;
629
630impl CFormatBytes {
631    pub fn parse<I: Iterator<Item = u8>>(iter: &mut ParseIter<I>) -> Result<Self, CFormatError> {
632        let mut parts = vec![];
633        let mut literal = vec![];
634        let mut part_index = 0;
635        while let Some((index, c)) = iter.next() {
636            if c == b'%' {
637                if let Some(&(_, second)) = iter.peek() {
638                    if second == b'%' {
639                        iter.next().unwrap();
640                        literal.push(b'%');
641                        continue;
642                    } else {
643                        if !literal.is_empty() {
644                            parts.push((
645                                part_index,
646                                CFormatPart::Literal(std::mem::take(&mut literal)),
647                            ));
648                        }
649                        let spec = CFormatSpec::parse(iter).map_err(|err| CFormatError {
650                            typ: err.0,
651                            index: err.1,
652                        })?;
653                        parts.push((index, CFormatPart::Spec(spec)));
654                        if let Some(&(index, _)) = iter.peek() {
655                            part_index = index;
656                        }
657                    }
658                } else {
659                    return Err(CFormatError {
660                        typ: CFormatErrorType::IncompleteFormat,
661                        index: index + 1,
662                    });
663                }
664            } else {
665                literal.push(c);
666            }
667        }
668        if !literal.is_empty() {
669            parts.push((part_index, CFormatPart::Literal(literal)));
670        }
671        Ok(Self { parts })
672    }
673
674    pub fn parse_from_bytes(bytes: &[u8]) -> Result<Self, CFormatError> {
675        let mut iter = bytes.iter().cloned().enumerate().peekable();
676        Self::parse(&mut iter)
677    }
678}
679
680pub type CFormatString = CFormatStrOrBytes<String>;
681
682impl FromStr for CFormatString {
683    type Err = CFormatError;
684
685    fn from_str(text: &str) -> Result<Self, Self::Err> {
686        let mut iter = text.chars().enumerate().peekable();
687        Self::parse(&mut iter)
688    }
689}
690
691impl CFormatString {
692    pub(crate) fn parse<I: Iterator<Item = char>>(
693        iter: &mut ParseIter<I>,
694    ) -> Result<Self, CFormatError> {
695        let mut parts = vec![];
696        let mut literal = String::new();
697        let mut part_index = 0;
698        while let Some((index, c)) = iter.next() {
699            if c == '%' {
700                if let Some(&(_, second)) = iter.peek() {
701                    if second == '%' {
702                        iter.next().unwrap();
703                        literal.push('%');
704                        continue;
705                    } else {
706                        if !literal.is_empty() {
707                            parts.push((
708                                part_index,
709                                CFormatPart::Literal(std::mem::take(&mut literal)),
710                            ));
711                        }
712                        let spec = CFormatSpec::parse(iter).map_err(|err| CFormatError {
713                            typ: err.0,
714                            index: err.1,
715                        })?;
716                        parts.push((index, CFormatPart::Spec(spec)));
717                        if let Some(&(index, _)) = iter.peek() {
718                            part_index = index;
719                        }
720                    }
721                } else {
722                    return Err(CFormatError {
723                        typ: CFormatErrorType::IncompleteFormat,
724                        index: index + 1,
725                    });
726                }
727            } else {
728                literal.push(c);
729            }
730        }
731        if !literal.is_empty() {
732            parts.push((part_index, CFormatPart::Literal(literal)));
733        }
734        Ok(Self { parts })
735    }
736}
737
738#[cfg(test)]
739mod tests {
740    use super::*;
741
742    #[test]
743    fn test_fill_and_align() {
744        assert_eq!(
745            "%10s"
746                .parse::<CFormatSpec>()
747                .unwrap()
748                .format_string("test".to_owned()),
749            "      test".to_owned()
750        );
751        assert_eq!(
752            "%-10s"
753                .parse::<CFormatSpec>()
754                .unwrap()
755                .format_string("test".to_owned()),
756            "test      ".to_owned()
757        );
758        assert_eq!(
759            "%#10x"
760                .parse::<CFormatSpec>()
761                .unwrap()
762                .format_number(&BigInt::from(0x1337)),
763            "    0x1337".to_owned()
764        );
765        assert_eq!(
766            "%-#10x"
767                .parse::<CFormatSpec>()
768                .unwrap()
769                .format_number(&BigInt::from(0x1337)),
770            "0x1337    ".to_owned()
771        );
772    }
773
774    #[test]
775    fn test_parse_key() {
776        let expected = Ok(CFormatSpec {
777            mapping_key: Some("amount".to_owned()),
778            format_type: CFormatType::Number(CNumberType::Decimal),
779            format_char: 'd',
780            min_field_width: None,
781            precision: None,
782            flags: CConversionFlags::empty(),
783        });
784        assert_eq!("%(amount)d".parse::<CFormatSpec>(), expected);
785
786        let expected = Ok(CFormatSpec {
787            mapping_key: Some("m((u(((l((((ti))))p)))l))e".to_owned()),
788            format_type: CFormatType::Number(CNumberType::Decimal),
789            format_char: 'd',
790            min_field_width: None,
791            precision: None,
792            flags: CConversionFlags::empty(),
793        });
794        assert_eq!(
795            "%(m((u(((l((((ti))))p)))l))e)d".parse::<CFormatSpec>(),
796            expected
797        );
798    }
799
800    #[test]
801    fn test_format_parse_key_fail() {
802        assert_eq!(
803            "%(aged".parse::<CFormatString>(),
804            Err(CFormatError {
805                typ: CFormatErrorType::UnmatchedKeyParentheses,
806                index: 1
807            })
808        );
809    }
810
811    #[test]
812    fn test_format_parse_type_fail() {
813        assert_eq!(
814            "Hello %n".parse::<CFormatString>(),
815            Err(CFormatError {
816                typ: CFormatErrorType::UnsupportedFormatChar('n'),
817                index: 7
818            })
819        );
820    }
821
822    #[test]
823    fn test_incomplete_format_fail() {
824        assert_eq!(
825            "Hello %".parse::<CFormatString>(),
826            Err(CFormatError {
827                typ: CFormatErrorType::IncompleteFormat,
828                index: 7
829            })
830        );
831    }
832
833    #[test]
834    fn test_parse_flags() {
835        let expected = Ok(CFormatSpec {
836            format_type: CFormatType::Number(CNumberType::Decimal),
837            format_char: 'd',
838            min_field_width: Some(CFormatQuantity::Amount(10)),
839            precision: None,
840            mapping_key: None,
841            flags: CConversionFlags::all(),
842        });
843        let parsed = "%  0   -+++###10d".parse::<CFormatSpec>();
844        assert_eq!(parsed, expected);
845        assert_eq!(
846            parsed.unwrap().format_number(&BigInt::from(12)),
847            "+12       ".to_owned()
848        );
849    }
850
851    #[test]
852    fn test_parse_and_format_string() {
853        assert_eq!(
854            "%5.4s"
855                .parse::<CFormatSpec>()
856                .unwrap()
857                .format_string("Hello, World!".to_owned()),
858            " Hell".to_owned()
859        );
860        assert_eq!(
861            "%-5.4s"
862                .parse::<CFormatSpec>()
863                .unwrap()
864                .format_string("Hello, World!".to_owned()),
865            "Hell ".to_owned()
866        );
867        assert_eq!(
868            "%.s"
869                .parse::<CFormatSpec>()
870                .unwrap()
871                .format_string("Hello, World!".to_owned()),
872            "".to_owned()
873        );
874        assert_eq!(
875            "%5.s"
876                .parse::<CFormatSpec>()
877                .unwrap()
878                .format_string("Hello, World!".to_owned()),
879            "     ".to_owned()
880        );
881    }
882
883    #[test]
884    fn test_parse_and_format_unicode_string() {
885        assert_eq!(
886            "%.2s"
887                .parse::<CFormatSpec>()
888                .unwrap()
889                .format_string("❤❤❤❤❤❤❤❤".to_owned()),
890            "❤❤".to_owned()
891        );
892    }
893
894    #[test]
895    fn test_parse_and_format_number() {
896        assert_eq!(
897            "%5d"
898                .parse::<CFormatSpec>()
899                .unwrap()
900                .format_number(&BigInt::from(27)),
901            "   27".to_owned()
902        );
903        assert_eq!(
904            "%05d"
905                .parse::<CFormatSpec>()
906                .unwrap()
907                .format_number(&BigInt::from(27)),
908            "00027".to_owned()
909        );
910        assert_eq!(
911            "%.5d"
912                .parse::<CFormatSpec>()
913                .unwrap()
914                .format_number(&BigInt::from(27)),
915            "00027".to_owned()
916        );
917        assert_eq!(
918            "%+05d"
919                .parse::<CFormatSpec>()
920                .unwrap()
921                .format_number(&BigInt::from(27)),
922            "+0027".to_owned()
923        );
924        assert_eq!(
925            "%-d"
926                .parse::<CFormatSpec>()
927                .unwrap()
928                .format_number(&BigInt::from(-27)),
929            "-27".to_owned()
930        );
931        assert_eq!(
932            "% d"
933                .parse::<CFormatSpec>()
934                .unwrap()
935                .format_number(&BigInt::from(27)),
936            " 27".to_owned()
937        );
938        assert_eq!(
939            "% d"
940                .parse::<CFormatSpec>()
941                .unwrap()
942                .format_number(&BigInt::from(-27)),
943            "-27".to_owned()
944        );
945        assert_eq!(
946            "%08x"
947                .parse::<CFormatSpec>()
948                .unwrap()
949                .format_number(&BigInt::from(0x1337)),
950            "00001337".to_owned()
951        );
952        assert_eq!(
953            "%#010x"
954                .parse::<CFormatSpec>()
955                .unwrap()
956                .format_number(&BigInt::from(0x1337)),
957            "0x00001337".to_owned()
958        );
959        assert_eq!(
960            "%-#010x"
961                .parse::<CFormatSpec>()
962                .unwrap()
963                .format_number(&BigInt::from(0x1337)),
964            "0x1337    ".to_owned()
965        );
966    }
967
968    #[test]
969    fn test_parse_and_format_float() {
970        assert_eq!(
971            "%f".parse::<CFormatSpec>().unwrap().format_float(1.2345),
972            "1.234500"
973        );
974        assert_eq!(
975            "%.2f".parse::<CFormatSpec>().unwrap().format_float(1.2345),
976            "1.23"
977        );
978        assert_eq!(
979            "%.f".parse::<CFormatSpec>().unwrap().format_float(1.2345),
980            "1"
981        );
982        assert_eq!(
983            "%+.f".parse::<CFormatSpec>().unwrap().format_float(1.2345),
984            "+1"
985        );
986        assert_eq!(
987            "%+f".parse::<CFormatSpec>().unwrap().format_float(1.2345),
988            "+1.234500"
989        );
990        assert_eq!(
991            "% f".parse::<CFormatSpec>().unwrap().format_float(1.2345),
992            " 1.234500"
993        );
994        assert_eq!(
995            "%f".parse::<CFormatSpec>().unwrap().format_float(-1.2345),
996            "-1.234500"
997        );
998        assert_eq!(
999            "%f".parse::<CFormatSpec>()
1000                .unwrap()
1001                .format_float(1.2345678901),
1002            "1.234568"
1003        );
1004    }
1005
1006    #[test]
1007    fn test_format_parse() {
1008        let fmt = "Hello, my name is %s and I'm %d years old";
1009        let expected = Ok(CFormatString {
1010            parts: vec![
1011                (0, CFormatPart::Literal("Hello, my name is ".to_owned())),
1012                (
1013                    18,
1014                    CFormatPart::Spec(CFormatSpec {
1015                        format_type: CFormatType::String(CFormatConversion::Str),
1016                        format_char: 's',
1017                        mapping_key: None,
1018                        min_field_width: None,
1019                        precision: None,
1020                        flags: CConversionFlags::empty(),
1021                    }),
1022                ),
1023                (20, CFormatPart::Literal(" and I'm ".to_owned())),
1024                (
1025                    29,
1026                    CFormatPart::Spec(CFormatSpec {
1027                        format_type: CFormatType::Number(CNumberType::Decimal),
1028                        format_char: 'd',
1029                        mapping_key: None,
1030                        min_field_width: None,
1031                        precision: None,
1032                        flags: CConversionFlags::empty(),
1033                    }),
1034                ),
1035                (31, CFormatPart::Literal(" years old".to_owned())),
1036            ],
1037        });
1038        let result = fmt.parse::<CFormatString>();
1039        assert_eq!(
1040            result, expected,
1041            "left = {result:#?} \n\n\n right = {expected:#?}"
1042        );
1043    }
1044}