1use 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 }
22
23pub type ParsingError = (CFormatErrorType, usize);
25
26#[derive(Debug, PartialEq)]
27pub struct CFormatError {
28 pub typ: CFormatErrorType, 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 }
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 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 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 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!(), };
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 ' ' };
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}