File Coverage

blib/lib/Text/PrettyTable.pm
Criterion Covered Total %
statement 88 105 83.8
branch 45 82 54.8
condition 22 47 46.8
subroutine 9 9 100.0
pod 3 4 75.0
total 167 247 67.6


line stmt bran cond sub pod time code
1             package Text::PrettyTable;
2              
3             =head1 NAME
4              
5             Text::PrettyTable - Allow for auto-fixed-width formatting of raw data
6              
7             =head1 DEPENDENCIES
8              
9             This module doesn't require any dependencies.
10              
11             =cut
12              
13 6     6   795515 use strict;
  6         13  
  6         243  
14 6     6   36 use warnings;
  6         42  
  6         448  
15 6     6   46 use base qw(Exporter);
  6         10  
  6         13171  
16              
17             our $VERSION = '0.03';
18              
19             our @border = ('| ', ' | ', ' |', ' ',
20             '+-', '-+-', '-+', '-',
21             '+-', '-+-', '-+', '-',
22             '+-', '-+-', '-+', '-');
23             our @borderu = ("│ ", " │ ", " │", ' ',
24             '┌─', '─┬─', '─┐', '─',
25             '├─', '─┼─', '─┤', '─',
26             '└─', '─┴─', '─┘', '─');
27             our $unibox = 1;
28             our $split = 100;
29             our $qr_escape = "[^ -~]";
30             our @EXPORT = qw(pretty_table);
31              
32             sub new {
33 15     15 0 574732 my $class = shift;
34 15 100       107 return bless ref($_[0]) ? $_[0] : {@_}, $class;
35             }
36              
37 2     2 1 161632 sub pretty_table { __PACKAGE__->tablify(@_) }
38 1     1 1 1517 sub plain_text { goto &tablify }
39              
40             sub tablify {
41 26     26 1 240363 my ($self, $data, $args) = @_;
42 26 100       112 if (!ref $self) {
    100          
43 4   100     26 $self = $self->new($args || {});
44             } elsif ($args) {
45             # Override settings in new object
46 7         36 my $new_p = __PACKAGE__->new({ %$self, %$args });
47             # Clean call without $args
48 7         41 return $new_p->tablify($data);
49             }
50 19   50     125 local $self->{'_level'} = 1 + ($self->{'_level'} || 0);
51              
52 19 100       58 my $uni = exists($self->{'unibox'}) ? $self->{'unibox'} : $unibox;
53 19 50 33     102 local $split = $self->{'split'} || $split if $self->{'_level'} == 1;
54 240         561 local @border = ref($uni) ? @$uni : map {utf8::decode(my $c = $_); $c} @borderu
  240         553  
55 19 50 33     105 and !$uni or local $qr_escape = "[^ -~".join('', @border)."]" if $uni && $self->{'_level'} == 1;
    100 33        
      66        
56              
57 19         72 my @bucket;
58             my @title;
59 19         0 my @max;
60 19         0 my @dir;
61             my $add = sub {
62 32     32   66 my ($cols, $bucket) = @_;
63 32 50       77 if (!ref($cols)) {
64 0 0       0 for my $chunk ($split ? map {/(.{$split}|.+)/g} split /\n/, $cols : split /\n/, $cols) {
  0         0  
65 0         0 push @$bucket, $chunk;
66             }
67 0         0 return;
68             }
69 32         61 my $i = @$bucket;
70 32         112 for my $j (0 .. $#$cols) {
71 78         154 my $_split = $split;
72 78         163 my $val = $cols->[$j];
73 78 50       276 if (! defined $val) {
    100          
74 0         0 $val = '(undef)';
75             } elsif (ref $val) {
76 1 50       4 if (UNIVERSAL::isa($val, 'SCALAR')) {
77 1 50 33     16 $val = (defined(&JSON::true) && JSON::true() eq $val) ? '(true)'
    50 33        
78             : (defined(&JSON::false) && JSON::false() eq $val) ? '(false)'
79             : "\\\"$$val\"";
80             } else {
81 0         0 chomp($val = $self->tablify($val));
82 0 0 0     0 $_split = 0 if $_split && $val =~ /^\Q$border[4]\E/ && $val =~ /\Q$border[14]\E$/;
      0        
83             }
84             }
85 78 100 100     442 $dir[$j] = 1 if $val =~ /\D/ && $bucket == \@bucket; # TODO - we could work on our alignment
86 78         159 my $I = $i;
87 78 50       261 for my $chunk ($_split ? map {/(.{$_split}|.+)/g} split /\n/, $val : split /\n/, $val) {
  79         688  
88 79         683 $chunk =~ s/($qr_escape)/sprintf "\\%03o", ord $1/eg;
  6         20  
89 79         222 $bucket->[$I++]->[$j] = $chunk;
90 79 100 100     426 $max[$j] = length($chunk) if !$max[$j] || $max[$j] < length($chunk);
91             }
92             }
93 19         174 };
94              
95              
96 19 100       76 if (UNIVERSAL::isa($data, 'HASH')) {
    50          
97 14         26 my $title = $self->{'title'};
98 14 50       51 $add->($title, \@title) if $title;
99 14 0       21 my @keys = @{ $self->{'sort'} || [sort {($a eq 'id') ? -1 : ($b eq 'id') ? 1 : $a cmp $b } keys %$data] };
  14 0       93  
  0 50       0  
100 14         89 $add->([$_, $data->{$_}], \@bucket) for @keys;
101 14 50       50 $add->(['(empty hash)'], \@bucket) if !@bucket;
102             } elsif (UNIVERSAL::isa($data, 'ARRAY')) {
103 5         11 my %title;
104 5 100 66     38 if ($data->[0] && ref($data->[0]) eq 'HASH' && !$self->{'collapse'}) {
      66        
105 4         8 @title{keys %$_} = () for grep {ref($_) eq 'HASH'} @$data; # find all uniques
  12         56  
106 4 0       8 my @keys = @{ $self->{'sort'} || [sort {($a eq 'id') ? -1 : ($b eq 'id') ? 1 : $a cmp $b } keys %title] };
  4 0       19  
  0 50       0  
107 4         14 $add->(\@keys, \@title);
108 4         18 foreach my $row (@$data) {
109 12 50       35 if (ref($row) ne 'HASH') {
110 0         0 $add->($row, \@bucket);
111 0         0 next;
112             }
113 12         54 $add->([@$row{@keys}], \@bucket);
114             }
115             } else {
116 1         2 my $title = $self->{'title'};
117 1 50       4 $add->($title, \@title) if $title;
118 1         6 $add->([$_], \@bucket) for @$data;
119 1 50       5 $add->(['(empty array)'], \@bucket) if !@bucket;
120             }
121             }
122              
123 19   50     102 my $indent = $self->{'indent'} || '';
124 19         53 my $sep = "${indent}$border[8]".join($border[9], map {$border[11] x $_} @max)."$border[10]\n";
  41         148  
125 19 100       63 my $fmt = "${indent}$border[0]".join($border[1], map {'%'.($dir[$_] ? '-' : '').$max[$_].'s'} 0..$#max)."$border[2]\n";
  41         192  
126              
127 19 50 33     110 if (!$self->{'collapse'} and my $cols = $self->{'auto_collapse'}) {
128 0 0 0     0 $cols = $ENV{'COLUMNS'} || eval { die if ! -t STDOUT; require Term::ReadKey; (Term::ReadKey::GetTerminalSize(\*STDOUT))[0] } || 80 if $cols eq '1';
129 0 0       0 if (length($sep) - 1 > $cols) {
130 0         0 local $self->{'collapse'} = 1;
131 0 0       0 local $self->{'_level'} if $self->{'_level'} == 1;
132 0         0 return $self->tablify($data);
133             }
134             }
135              
136 19         36 my $out = "";
137 19         39 $out .= "${indent}$border[4]".join($border[5], map {$border[7] x $_} @max)."$border[6]\n";
  41         153  
138 6     6   79 no warnings 'uninitialized'; # because of multiline
  6         57  
  6         2867  
139 19         54 for my $buck (\@title, \@bucket) {
140 38         75 for my $row (@$buck) {
141 33 50       96 if (ref $row) {
142 33         154 $out .= sprintf($fmt, @$row);
143             } else {
144 0         0 $out .= sprintf("$border[0]%-*s$border[2]\n", length($sep) - (length($border[0])+length($border[2])+1), $row);
145             }
146             }
147 38 100       84 if ($buck == \@title) {
148 19 100       59 $out .= $sep if @title;
149             } else {
150 19         42 $out .= "${indent}$border[12]".join($border[13], map {$border[15] x $_} @max)."$border[14]\n";
  41         128  
151             }
152             }
153              
154 19 50       85 utf8::encode($out) if $self->{'_level'} == 1;
155 19         420 return $out;
156             }
157              
158             1;
159              
160             __END__