File Coverage

blib/lib/Number/Pad.pm
Criterion Covered Total %
statement 47 54 87.0
branch 11 14 78.5
condition 1 2 50.0
subroutine 7 7 100.0
pod 1 1 100.0
total 67 78 85.9


line stmt bran cond sub pod time code
1             package Number::Pad;
2              
3             our $AUTHORITY = 'cpan:PERLANCAR'; # AUTHORITY
4             our $DATE = '2021-08-01'; # DATE
5             our $DIST = 'Number-Pad'; # DIST
6             our $VERSION = '0.001'; # VERSION
7              
8 1     1   198542 use 5.010001;
  1         7  
9 1     1   5 use strict;
  1         2  
  1         20  
10 1     1   5 use warnings;
  1         1  
  1         27  
11              
12 1     1   5 use List::Util qw(max);
  1         3  
  1         60  
13              
14 1     1   5 use Exporter;
  1         2  
  1         60  
15             our @ISA = qw(Exporter);
16             our @EXPORT_OK = qw(
17             pad_numbers
18             );
19              
20             sub pad_numbers {
21             # note: the same logic of this function is also in
22             # Perinci::Result::Format::Lite. perhaps in the future that module will be
23             # refactored to use us.
24              
25             # XXX we just want to turn off 'uninitialized' and 'negative repeat
26             # count does nothing' from the operator x
27 1     1   7 no warnings;
  1         2  
  1         779  
28              
29 2     2 1 10899 require String::Pad;
30              
31 2         608 my ($numbers, $width, $which, $padchar, $truncate) = @_;
32              
33 2   50     14 $which //= 'l';
34              
35             # determine max widths
36 2         5 my ($maxw_bd, $maxw_d, $maxw_ad); # before digit, digit, after d
37              
38 2         0 my (@w_bd, @w_d, @w_ad);
39 2         4 for my $i (0..$#{$numbers}) {
  2         9  
40 18         29 my $number = $numbers->[$i];
41 18         23 my $width = length($number);
42 18 50       97 if (!defined $number) {
    100          
    100          
    50          
43 0         0 push @w_bd, 0;
44 0         0 push @w_bd, 0;
45 0         0 push @w_ad, 0;
46             } elsif ($number =~ /\A([+-]?\d+)(\.?)(\d*)[%]?\z/) {
47             # decimal notation number (with optional percent sign). TODO: allow
48             # arbitraty units after number, e.g. ml, mcg, etc? but should we
49             # align the unit too?
50 8         19 push @w_bd, length($1);
51 8         20 push @w_d , length($2);
52 8         13 push @w_ad, length($3);
53             } elsif ($number =~ /\A([+-]?\d+\.?\d*)([eE])([+-]?\d+)\z/) {
54             # scientific notation number
55 6         17 push @w_bd, length($1);
56 6         9 push @w_d , length($2);
57 6         12 push @w_ad, length($3);
58             } elsif ($number =~ /\A([+-]?(?:Inf|NaN))\z/i) {
59 4         9 push @w_bd, length($1);
60 4         6 push @w_d , 1;
61 4         8 push @w_ad, 0;
62             } else {
63             # not a number
64 0         0 push @w_bd, length($number);
65 0         0 push @w_bd, 0;
66 0         0 push @w_ad, 0;
67             }
68             }
69 2         9 my $maxw_bd = max(@w_bd);
70 2         6 my $maxw_d = max(@w_d);
71 2         6 my $maxw_ad = max(@w_ad);
72              
73             # align the decimal point/"E" in the numbers first
74 2         3 my @aligned_numbers;
75 2         5 for my $number (@$numbers) {
76 18         29 my ($bd, $d, $ad);
77 18 100       97 if (($bd, $d, $ad) = $number =~ /\A([+-]?\d+)(\.?)(\d*)\z/) {
    100          
    50          
78 8         31 push @aligned_numbers, join(
79             '',
80             (' ' x ($maxw_bd - length($bd))), $bd,
81             $d , (' ' x ($maxw_d - length($d ))),
82             $ad, (' ' x ($maxw_ad - length($ad))),
83             );
84             } elsif (($bd, $d, $ad) = $number =~ /\A([+-]?\d+\.?\d*)([eE])([+-]?\d+)\z/) {
85 6         22 push @aligned_numbers, join(
86             '',
87             (' ' x ($maxw_bd - length($bd))), $bd,
88             $d , (' ' x ($maxw_d - length($d ))),
89             $ad, (' ' x ($maxw_ad - length($ad))),
90             );
91             } elsif (($bd, undef, undef) = $number =~ /\A([+-]?(?:Inf|NaN))\z/i) {
92 4         18 push @aligned_numbers, join(
93             '',
94             (' ' x ($maxw_bd - length($bd))), $bd,
95             '' , (' ' x ($maxw_d - length($d ))),
96             '', (' ' x ($maxw_ad - length($ad))),
97             );
98             } else {
99             # not a number
100 0         0 push @aligned_numbers, $number;
101             }
102             }
103              
104             # then pad with String::Pad
105             String::Pad::pad(
106 2         7 \@aligned_numbers,
107             $width,
108             $which,
109             $padchar,
110             $truncate,
111             );
112             }
113              
114             1;
115             # ABSTRACT: Pad numbers so the decimal point (or "E" if in exponential notation) align
116              
117             __END__