1use super::UIfmt;
2use alloy_primitives::{Address, Bytes, FixedBytes, I256, U256};
3use comfy_table::{ContentLineStyle, LineStyle, Table, TableStyle};
4use std::fmt::{self, Write};
5
6const MAX_EXPONENTIAL_PRECISION: usize = 1024;
8
9#[derive(Clone, Debug, PartialEq, Eq)]
11pub enum Piece<'a> {
12 String(&'a str),
14 NextArgument(FormatSpec),
16}
17
18#[derive(Clone, Debug, Default, PartialEq, Eq)]
20pub enum FormatSpec {
21 #[default]
23 String,
24 Number,
26 Integer,
28 Object,
30 Exponential(Option<usize>),
32 Hexadecimal,
34}
35
36impl fmt::Display for FormatSpec {
37 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
38 f.write_str("%")?;
39 match *self {
40 Self::String => f.write_str("s"),
41 Self::Number => f.write_str("d"),
42 Self::Integer => f.write_str("i"),
43 Self::Object => f.write_str("o"),
44 Self::Exponential(Some(n)) => write!(f, "{n}e"),
45 Self::Exponential(None) => f.write_str("e"),
46 Self::Hexadecimal => f.write_str("x"),
47 }
48 }
49}
50
51enum ParseArgError {
52 Err,
54 Skip,
56}
57
58#[derive(Debug)]
60pub struct Parser<'a> {
61 input: &'a str,
62 chars: std::str::CharIndices<'a>,
63}
64
65impl<'a> Parser<'a> {
66 pub fn new(input: &'a str) -> Self {
68 Self { input, chars: input.char_indices() }
69 }
70
71 fn string(&mut self, start: usize, mut skip: usize) -> &'a str {
76 while let Some((pos, c)) = self.peek() {
77 if c == '%' {
78 if skip == 0 {
79 return &self.input[start..pos];
80 }
81 skip -= 1;
82 }
83 self.chars.next();
84 }
85 &self.input[start..]
86 }
87
88 fn argument(&mut self) -> Result<FormatSpec, ParseArgError> {
93 let (start, ch) = self.peek().ok_or(ParseArgError::Err)?;
94 let simple_spec = match ch {
95 's' => Some(FormatSpec::String),
96 'd' => Some(FormatSpec::Number),
97 'i' => Some(FormatSpec::Integer),
98 'o' => Some(FormatSpec::Object),
99 'e' => Some(FormatSpec::Exponential(None)),
100 'x' => Some(FormatSpec::Hexadecimal),
101 '%' => return Err(ParseArgError::Skip),
103 _ => None,
104 };
105 if let Some(spec) = simple_spec {
106 self.chars.next();
107 return Ok(spec);
108 }
109
110 if ch.is_ascii_digit() {
112 let n = self.integer(start);
113 if let Some((_, 'e')) = self.peek() {
114 self.chars.next();
115 return n
116 .filter(|&precision| precision <= MAX_EXPONENTIAL_PRECISION)
117 .map(|precision| FormatSpec::Exponential(Some(precision)))
118 .ok_or(ParseArgError::Err);
119 }
120 }
121
122 Err(ParseArgError::Err)
123 }
124
125 fn integer(&mut self, start: usize) -> Option<usize> {
126 let mut end = start;
127 while let Some((pos, ch)) = self.peek() {
128 if !ch.is_ascii_digit() {
129 end = pos;
130 break;
131 }
132 self.chars.next();
133 }
134 self.input[start..end].parse().ok()
135 }
136
137 fn current_pos(&mut self) -> usize {
138 self.peek().map(|(n, _)| n).unwrap_or(self.input.len())
139 }
140
141 fn peek(&mut self) -> Option<(usize, char)> {
142 self.peek_n(0)
143 }
144
145 fn peek_n(&mut self, n: usize) -> Option<(usize, char)> {
146 self.chars.clone().nth(n)
147 }
148}
149
150impl<'a> Iterator for Parser<'a> {
151 type Item = Piece<'a>;
152
153 fn next(&mut self) -> Option<Self::Item> {
154 let (mut start, ch) = self.peek()?;
155 let mut skip = 0;
156 if ch == '%' {
157 let prev = self.chars.clone();
158 self.chars.next();
159 match self.argument() {
160 Ok(arg) => {
161 debug_assert_eq!(arg.to_string(), self.input[start..self.current_pos()]);
162 return Some(Piece::NextArgument(arg));
163 }
164
165 Err(ParseArgError::Skip) => {
167 start = self.current_pos();
168 skip += 1;
169 }
170
171 Err(ParseArgError::Err) => {
174 self.chars = prev;
175 skip += 1;
176 }
177 }
178 }
179 Some(Piece::String(self.string(start, skip)))
180 }
181}
182
183pub trait ConsoleFmt {
185 fn fmt(&self, spec: FormatSpec) -> String;
187}
188
189impl ConsoleFmt for String {
190 fn fmt(&self, spec: FormatSpec) -> String {
191 match spec {
192 FormatSpec::String => self.clone(),
193 FormatSpec::Object => format!("'{}'", self.clone()),
194 FormatSpec::Number
195 | FormatSpec::Integer
196 | FormatSpec::Exponential(_)
197 | FormatSpec::Hexadecimal => Self::from("NaN"),
198 }
199 }
200}
201
202impl ConsoleFmt for bool {
203 fn fmt(&self, spec: FormatSpec) -> String {
204 match spec {
205 FormatSpec::String => self.pretty(),
206 FormatSpec::Object => format!("'{}'", self.pretty()),
207 FormatSpec::Number => (*self as i32).to_string(),
208 FormatSpec::Integer | FormatSpec::Exponential(_) | FormatSpec::Hexadecimal => {
209 String::from("NaN")
210 }
211 }
212 }
213}
214
215impl ConsoleFmt for U256 {
216 fn fmt(&self, spec: FormatSpec) -> String {
217 match spec {
218 FormatSpec::String | FormatSpec::Object | FormatSpec::Number | FormatSpec::Integer => {
219 self.pretty()
220 }
221 FormatSpec::Hexadecimal => {
222 let hex = format!("{self:x}");
223 let digits = hex.trim_start_matches('0');
224 format!("0x{}", if digits.is_empty() { "0" } else { digits })
225 }
226 FormatSpec::Exponential(None) => {
227 let log = self.pretty().len() - 1;
228 let exp10 = Self::from(10).pow(Self::from(log));
229 let amount = *self;
230 let integer = amount / exp10;
231 let decimal = (amount % exp10).to_string();
232 let decimal = format!("{decimal:0>log$}").trim_end_matches('0').to_string();
233 if decimal.is_empty() {
234 format!("{integer}e{log}")
235 } else {
236 format!("{integer}.{decimal}e{log}")
237 }
238 }
239 FormatSpec::Exponential(Some(precision)) => format_fixed(*self, "", precision),
240 }
241 }
242}
243
244impl ConsoleFmt for I256 {
245 fn fmt(&self, spec: FormatSpec) -> String {
246 match spec {
247 FormatSpec::String | FormatSpec::Object | FormatSpec::Number | FormatSpec::Integer => {
248 self.pretty()
249 }
250 FormatSpec::Hexadecimal => {
251 let hex = format!("{self:x}");
252 let digits = hex.trim_start_matches('0');
253 format!("0x{}", if digits.is_empty() { "0" } else { digits })
254 }
255 FormatSpec::Exponential(None) => {
256 let amount = *self;
257 let sign = if amount.is_negative() { "-" } else { "" };
258 let log = if amount.is_negative() {
259 self.pretty().len() - 2
260 } else {
261 self.pretty().len() - 1
262 };
263 let exp10 = Self::exp10(log);
264 let integer = (amount / exp10).twos_complement();
265 let decimal = (amount % exp10).twos_complement().to_string();
266 let decimal = format!("{decimal:0>log$}").trim_end_matches('0').to_string();
267 if decimal.is_empty() {
268 format!("{sign}{integer}e{log}")
269 } else {
270 format!("{sign}{integer}.{decimal}e{log}")
271 }
272 }
273 FormatSpec::Exponential(Some(precision)) => {
274 let amount = *self;
275 let sign = if amount.is_negative() { "-" } else { "" };
276 format_fixed(amount.unsigned_abs(), sign, precision)
277 }
278 }
279 }
280}
281
282fn format_fixed(amount: U256, sign: &str, precision: usize) -> String {
283 let (integer, decimal) = U256::from(10)
284 .checked_pow(U256::from(precision))
285 .map_or((U256::ZERO, amount), |exp10| (amount / exp10, amount % exp10));
286 let decimal = decimal.to_string();
287 let decimal = format!("{decimal:0>precision$}").trim_end_matches('0').to_string();
288 if decimal.is_empty() {
289 format!("{sign}{integer}")
290 } else {
291 format!("{sign}{integer}.{decimal}")
292 }
293}
294
295impl ConsoleFmt for Address {
296 fn fmt(&self, spec: FormatSpec) -> String {
297 match spec {
298 FormatSpec::String | FormatSpec::Hexadecimal => self.pretty(),
299 FormatSpec::Object => format!("'{}'", self.pretty()),
300 FormatSpec::Number | FormatSpec::Integer | FormatSpec::Exponential(_) => {
301 String::from("NaN")
302 }
303 }
304 }
305}
306
307impl ConsoleFmt for Vec<u8> {
308 fn fmt(&self, spec: FormatSpec) -> String {
309 self[..].fmt(spec)
310 }
311}
312
313impl ConsoleFmt for Bytes {
314 fn fmt(&self, spec: FormatSpec) -> String {
315 self[..].fmt(spec)
316 }
317}
318
319impl<const N: usize> ConsoleFmt for [u8; N] {
320 fn fmt(&self, spec: FormatSpec) -> String {
321 self[..].fmt(spec)
322 }
323}
324
325impl<const N: usize> ConsoleFmt for FixedBytes<N> {
326 fn fmt(&self, spec: FormatSpec) -> String {
327 self[..].fmt(spec)
328 }
329}
330
331impl ConsoleFmt for [u8] {
332 fn fmt(&self, spec: FormatSpec) -> String {
333 match spec {
334 FormatSpec::String | FormatSpec::Hexadecimal => self.pretty(),
335 FormatSpec::Object => format!("'{}'", self.pretty()),
336 FormatSpec::Number | FormatSpec::Integer | FormatSpec::Exponential(_) => {
337 String::from("NaN")
338 }
339 }
340 }
341}
342
343pub fn console_format(spec: &str, values: &[&dyn ConsoleFmt]) -> String {
369 let mut values = values.iter().copied();
370 let mut result = String::with_capacity(spec.len());
371
372 let mut write_space = if spec.is_empty() {
374 false
375 } else {
376 format_spec(spec, &mut values, &mut result);
377 true
378 };
379
380 for v in values {
382 let fmt = v.fmt(FormatSpec::String);
383 if write_space {
384 result.push(' ');
385 }
386 result.push_str(&fmt);
387 write_space = true;
388 }
389
390 result
391}
392
393fn format_spec<'a>(
394 s: &str,
395 mut values: impl Iterator<Item = &'a dyn ConsoleFmt>,
396 result: &mut String,
397) {
398 for piece in Parser::new(s) {
399 match piece {
400 Piece::String(s) => result.push_str(s),
401 Piece::NextArgument(spec) => {
402 if let Some(value) = values.next() {
403 result.push_str(&value.fmt(spec));
404 } else {
405 write!(result, "{spec}").unwrap();
407 }
408 }
409 }
410 }
411}
412
413pub fn console_table_format(
414 keys: Option<&[&dyn ConsoleFmt]>,
415 values: &[&dyn ConsoleFmt],
416) -> String {
417 let keys_strings: Vec<String> = match keys {
418 Some(keys) => keys.iter().map(|k| k.fmt(FormatSpec::String)).collect(),
419 None => (0..values.len()).map(|i| i.to_string()).collect(),
420 };
421 let values_strings: Vec<String> = values.iter().map(|v| v.fmt(FormatSpec::String)).collect();
422
423 const STYLE: TableStyle = TableStyle::new()
424 .top_border(LineStyle::new('┌', '─', '┬', '┐'))
425 .header_lines(ContentLineStyle::new('│', '│', '│'))
426 .header_separator(LineStyle::new('├', '─', '┼', '┤'))
427 .content_lines(ContentLineStyle::new('│', '│', '│'))
428 .bottom_border(LineStyle::new('└', '─', '┴', '┘'));
429
430 let mut table = Table::new();
431 table.load_style(STYLE);
432 table.set_header(vec!["(index)", "Values"]);
433 for i in 0..keys_strings.len().max(values_strings.len()) {
434 let key = keys_strings.get(i).map(String::as_str).unwrap_or("");
435 let value = values_strings.get(i).map(String::as_str).unwrap_or("");
436 table.add_row(vec![key, value]);
437 }
438 table.to_string()
439}
440
441#[cfg(test)]
442mod tests {
443
444 use super::*;
445 use alloy_primitives::{address, b256, bytes};
446 use foundry_macros::ConsoleFmt;
447 use std::str::FromStr;
448
449 macro_rules! logf1 {
450 ($a:ident) => {
451 console_format(&$a.p_0, &[&$a.p_1])
452 };
453 }
454
455 macro_rules! logf2 {
456 ($a:ident) => {
457 console_format(&$a.p_0, &[&$a.p_1, &$a.p_2])
458 };
459 }
460
461 macro_rules! logf3 {
462 ($a:ident) => {
463 console_format(&$a.p_0, &[&$a.p_1, &$a.p_2, &$a.p_3])
464 };
465 }
466
467 #[derive(Clone, Debug, ConsoleFmt)]
468 struct Log1 {
469 p_0: String,
470 p_1: U256,
471 }
472
473 #[derive(Clone, Debug, ConsoleFmt)]
474 struct Log2 {
475 p_0: String,
476 p_1: bool,
477 p_2: U256,
478 }
479
480 #[derive(Clone, Debug, ConsoleFmt)]
481 struct Log3 {
482 p_0: String,
483 p_1: Address,
484 p_2: bool,
485 p_3: U256,
486 }
487
488 #[expect(unused)]
489 #[derive(Clone, Debug, ConsoleFmt)]
490 enum Logs {
491 Log1(Log1),
492 Log2(Log2),
493 Log3(Log3),
494 }
495
496 #[test]
497 fn test_console_log_format_specifiers() {
498 let fmt_1 = |spec: &str, arg: &dyn ConsoleFmt| console_format(spec, &[arg]);
499
500 assert_eq!("foo", fmt_1("%s", &String::from("foo")));
501 assert_eq!("NaN", fmt_1("%d", &String::from("foo")));
502 assert_eq!("NaN", fmt_1("%i", &String::from("foo")));
503 assert_eq!("NaN", fmt_1("%e", &String::from("foo")));
504 assert_eq!("NaN", fmt_1("%x", &String::from("foo")));
505 assert_eq!("'foo'", fmt_1("%o", &String::from("foo")));
506 assert_eq!("%s foo", fmt_1("%%s", &String::from("foo")));
507 assert_eq!("% foo", fmt_1("%", &String::from("foo")));
508 assert_eq!("% foo", fmt_1("%%", &String::from("foo")));
509
510 assert_eq!("true", fmt_1("%s", &true));
511 assert_eq!("1", fmt_1("%d", &true));
512 assert_eq!("0", fmt_1("%d", &false));
513 assert_eq!("NaN", fmt_1("%i", &true));
514 assert_eq!("NaN", fmt_1("%e", &true));
515 assert_eq!("NaN", fmt_1("%x", &true));
516 assert_eq!("'true'", fmt_1("%o", &true));
517
518 let b32 = b256!("0xdeadbeef00000000000000000000000000000000000000000000000000000000");
519 assert_eq!(
520 "0xdeadbeef00000000000000000000000000000000000000000000000000000000",
521 fmt_1("%s", &b32)
522 );
523 assert_eq!(
524 "0xdeadbeef00000000000000000000000000000000000000000000000000000000",
525 fmt_1("%x", &b32)
526 );
527 assert_eq!("NaN", fmt_1("%d", &b32));
528 assert_eq!("NaN", fmt_1("%i", &b32));
529 assert_eq!("NaN", fmt_1("%e", &b32));
530 assert_eq!(
531 "'0xdeadbeef00000000000000000000000000000000000000000000000000000000'",
532 fmt_1("%o", &b32)
533 );
534
535 let addr = address!("0xdEADBEeF00000000000000000000000000000000");
536 assert_eq!("0xdEADBEeF00000000000000000000000000000000", fmt_1("%s", &addr));
537 assert_eq!("NaN", fmt_1("%d", &addr));
538 assert_eq!("NaN", fmt_1("%i", &addr));
539 assert_eq!("NaN", fmt_1("%e", &addr));
540 assert_eq!("0xdEADBEeF00000000000000000000000000000000", fmt_1("%x", &addr));
541 assert_eq!("'0xdEADBEeF00000000000000000000000000000000'", fmt_1("%o", &addr));
542
543 let bytes = bytes!("0xdeadbeef");
544 assert_eq!("0xdeadbeef", fmt_1("%s", &bytes));
545 assert_eq!("NaN", fmt_1("%d", &bytes));
546 assert_eq!("NaN", fmt_1("%i", &bytes));
547 assert_eq!("NaN", fmt_1("%e", &bytes));
548 assert_eq!("0xdeadbeef", fmt_1("%x", &bytes));
549 assert_eq!("'0xdeadbeef'", fmt_1("%o", &bytes));
550
551 assert_eq!("100", fmt_1("%s", &U256::from(100)));
552 assert_eq!("100", fmt_1("%d", &U256::from(100)));
553 assert_eq!("100", fmt_1("%i", &U256::from(100)));
554 assert_eq!("1e2", fmt_1("%e", &U256::from(100)));
555 assert_eq!("1.0023e6", fmt_1("%e", &U256::from(1002300)));
556 assert_eq!("1.23e5", fmt_1("%e", &U256::from(123000)));
557 assert_eq!("0x64", fmt_1("%x", &U256::from(100)));
558 assert_eq!("0x0", fmt_1("%x", &U256::ZERO));
559 assert_eq!("100", fmt_1("%o", &U256::from(100)));
560
561 assert_eq!("100", fmt_1("%s", &I256::try_from(100).unwrap()));
562 assert_eq!("100", fmt_1("%d", &I256::try_from(100).unwrap()));
563 assert_eq!("100", fmt_1("%i", &I256::try_from(100).unwrap()));
564 assert_eq!("1e2", fmt_1("%e", &I256::try_from(100).unwrap()));
565 assert_eq!("-1e2", fmt_1("%e", &I256::try_from(-100).unwrap()));
566 assert_eq!("-1.0023e6", fmt_1("%e", &I256::try_from(-1002300).unwrap()));
567 assert_eq!("-1.23e5", fmt_1("%e", &I256::try_from(-123000).unwrap()));
568 assert_eq!("1.0023e6", fmt_1("%e", &I256::try_from(1002300).unwrap()));
569 assert_eq!("1.23e5", fmt_1("%e", &I256::try_from(123000).unwrap()));
570
571 assert_eq!("10", fmt_1("%1e", &I256::try_from(100).unwrap()));
573 assert_eq!("-1", fmt_1("%2e", &I256::try_from(-100).unwrap()));
574 assert_eq!("123000", fmt_1("%0e", &I256::try_from(123000).unwrap()));
575 assert_eq!("12300", fmt_1("%1e", &I256::try_from(123000).unwrap()));
576 assert_eq!("0.0123", fmt_1("%7e", &I256::try_from(123000).unwrap()));
577 assert_eq!("-0.0123", fmt_1("%7e", &I256::try_from(-123000).unwrap()));
578
579 assert_eq!("0x64", fmt_1("%x", &I256::try_from(100).unwrap()));
580 assert_eq!("0x0", fmt_1("%x", &I256::ZERO));
581 assert_eq!(
582 "0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff9c",
583 fmt_1("%x", &I256::try_from(-100).unwrap())
584 );
585 assert_eq!(
586 "0xffffffffffffffffffffffffffffffffffffffffffffffffffffffe8b7891800",
587 fmt_1("%x", &I256::try_from(-100000000000i64).unwrap())
588 );
589 assert_eq!("100", fmt_1("%o", &I256::try_from(100).unwrap()));
590
591 assert_eq!("%333d%3e%5F", console_format("%333d%3e%5F", &[]));
593 assert_eq!(
594 "%5d123456.789%2f%3f%e1",
595 console_format("%5d%3e%2f%3f%e1", &[&U256::from(123456789)])
596 );
597 }
598
599 #[test]
601 fn test_console_log_exponential_precision_overflow() {
602 let fmt_1 = |spec: &str, arg: &dyn ConsoleFmt| console_format(spec, &[arg]);
603
604 assert_eq!(format!("0.{}1", "0".repeat(255)), fmt_1("%256e", &U256::ONE));
606
607 let ten_pow_77 = U256::from(10).pow(U256::from(77u64));
609 assert_eq!("0.1", fmt_1("%78e", &ten_pow_77));
610
611 assert_eq!("1", fmt_1("%77e", &ten_pow_77));
612
613 assert_eq!(format!("0.{}1", "0".repeat(76)), fmt_1("%77e", &I256::try_from(1).unwrap()));
615 assert_eq!(format!("-0.{}1", "0".repeat(76)), fmt_1("%77e", &I256::try_from(-1).unwrap()));
616
617 assert_eq!(format!("0.{}1", "0".repeat(1023)), fmt_1("%1024e", &U256::ONE));
619
620 assert_eq!("%1025e 1", fmt_1("%1025e", &U256::ONE));
622 assert_eq!("%99999999999999999999e 1", fmt_1("%99999999999999999999e", &U256::ONE));
623
624 assert_eq!("1", fmt_1("%18e", &U256::from(1_000_000_000_000_000_000u64)));
625
626 assert_eq!("0", fmt_1("%0e", &U256::ZERO));
627 assert_eq!("0", fmt_1("%256e", &U256::ZERO));
628
629 let expect_fallback = |digits: String, precision: usize, sign: &str| {
631 let padded = format!("{digits:0>precision$}");
632 let trimmed = padded.trim_end_matches('0');
633 if trimmed.is_empty() { format!("{sign}0") } else { format!("{sign}0.{trimmed}") }
634 };
635 assert_eq!(expect_fallback(U256::MAX.to_string(), 78, ""), fmt_1("%78e", &U256::MAX));
636 assert_eq!(
637 expect_fallback(I256::MIN.unsigned_abs().to_string(), 77, "-"),
638 fmt_1("%77e", &I256::MIN)
639 );
640 assert_eq!(
641 expect_fallback(I256::MAX.unsigned_abs().to_string(), 77, ""),
642 fmt_1("%77e", &I256::MAX)
643 );
644 }
645
646 #[test]
647 fn test_console_log_format() {
648 let mut log1 = Log1 { p_0: "foo %s".to_string(), p_1: U256::from(100) };
649 assert_eq!("foo 100", logf1!(log1));
650 log1.p_0 = String::from("foo");
651 assert_eq!("foo 100", logf1!(log1));
652 log1.p_0 = String::from("%s foo");
653 assert_eq!("100 foo", logf1!(log1));
654
655 let mut log2 = Log2 { p_0: "foo %s %s".to_string(), p_1: true, p_2: U256::from(100) };
656 assert_eq!("foo true 100", logf2!(log2));
657 log2.p_0 = String::from("foo");
658 assert_eq!("foo true 100", logf2!(log2));
659 log2.p_0 = String::from("%s %s foo");
660 assert_eq!("true 100 foo", logf2!(log2));
661
662 let log3 = Log3 {
663 p_0: String::from("foo %s %%s %s and %d foo %%"),
664 p_1: address!("0xdEADBEeF00000000000000000000000000000000"),
665 p_2: true,
666 p_3: U256::from(21),
667 };
668 assert_eq!(
669 "foo 0xdEADBEeF00000000000000000000000000000000 %s true and 21 foo %",
670 logf3!(log3)
671 );
672
673 let log4 = Log1 { p_0: String::from("%5e"), p_1: U256::from(123456789) };
675 assert_eq!("1234.56789", logf1!(log4));
676
677 let log5 = Log1 { p_0: String::from("foo %3e bar"), p_1: U256::from(123456789) };
678 assert_eq!("foo 123456.789 bar", logf1!(log5));
679
680 let log6 =
681 Log2 { p_0: String::from("%e and %12e"), p_1: false, p_2: U256::from(123456789) };
682 assert_eq!("NaN and 0.000123456789", logf2!(log6));
683 }
684
685 #[test]
686 fn test_derive_format() {
687 let log1 = Log1 { p_0: String::from("foo %s bar"), p_1: U256::from(42) };
688 assert_eq!(log1.fmt(Default::default()), "foo 42 bar");
689 let call = Logs::Log1(log1);
690 assert_eq!(call.fmt(Default::default()), "foo 42 bar");
691 }
692
693 #[test]
694 fn test_console_table_format() {
695 let values: &[&dyn ConsoleFmt] = &[&U256::from(100), &U256::from(200), &U256::from(300)];
697 assert_eq!(
698 console_table_format(None, values),
699 "┌─────────┬────────┐\n\
700 │ (index) │ Values │\n\
701 ├─────────┼────────┤\n\
702 │ 0 │ 100 │\n\
703 │ 1 │ 200 │\n\
704 │ 2 │ 300 │\n\
705 └─────────┴────────┘"
706 );
707
708 let keys: &[&dyn ConsoleFmt] =
711 &[&String::from("alice"), &String::from("bob"), &String::from("charlie123")];
712 let values: &[&dyn ConsoleFmt] = &[
713 &U256::ONE,
714 &U256::from_str("20000000000000000").unwrap(),
715 &U256::from_str("30000000000").unwrap(),
716 ];
717 assert_eq!(
718 console_table_format(Some(keys), values),
719 "┌────────────┬───────────────────┐\n\
720 │ (index) │ Values │\n\
721 ├────────────┼───────────────────┤\n\
722 │ alice │ 1 │\n\
723 │ bob │ 20000000000000000 │\n\
724 │ charlie123 │ 30000000000 │\n\
725 └────────────┴───────────────────┘"
726 );
727
728 assert_eq!(
730 console_table_format(None, &[]),
731 "┌─────────┬────────┐\n\
732 │ (index) │ Values │\n\
733 ├─────────┼────────┤\n\
734 └─────────┴────────┘"
735 );
736
737 let keys: &[&dyn ConsoleFmt] =
739 &[&String::from("alice"), &String::from("bob"), &String::from("charlie")];
740 let values: &[&dyn ConsoleFmt] = &[&U256::ONE, &U256::from(2)];
741 assert_eq!(
742 console_table_format(Some(keys), values),
743 "┌─────────┬────────┐\n\
744 │ (index) │ Values │\n\
745 ├─────────┼────────┤\n\
746 │ alice │ 1 │\n\
747 │ bob │ 2 │\n\
748 │ charlie │ │\n\
749 └─────────┴────────┘"
750 );
751
752 let keys: &[&dyn ConsoleFmt] = &[&String::from("alice"), &String::from("bob")];
754 let values: &[&dyn ConsoleFmt] =
755 &[&U256::ONE, &U256::from(2), &U256::from(3), &U256::from(4)];
756 assert_eq!(
757 console_table_format(Some(keys), values),
758 "┌─────────┬────────┐\n\
759 │ (index) │ Values │\n\
760 ├─────────┼────────┤\n\
761 │ alice │ 1 │\n\
762 │ bob │ 2 │\n\
763 │ │ 3 │\n\
764 │ │ 4 │\n\
765 └─────────┴────────┘"
766 );
767 }
768}