File Coverage

lib/Array/Columnize/columnize.pm
Criterion Covered Total %
statement 140 141 99.2
branch 55 60 91.6
condition 14 18 77.7
subroutine 14 14 100.0
pod 0 2 0.0
total 223 235 94.8


line stmt bran cond sub pod time code
1             #!/usr/bin/env perl
2             # See doc in Array::Columnize
3             BEGIN {
4 4     4   83923 no strict;
  4         8  
  4         153  
5 4     4   85 @OLD_INC = @INC;
6             }
7 4     4   21 use rlib '../..';
  4         8  
  4         22  
8 4     4   2619 use Array::Columnize::options;
  4         18  
  4         101  
9             BEGIN {
10 4     4   18 no strict;
  4         7  
  4         106  
11 4     4   66 @INC = @OLD_INC;
12             }
13              
14 4     4   16 use Array::Columnize::options;
  4         7  
  4         71  
15             package Array::Columnize;
16 4     4   14 use strict;
  4         4  
  4         98  
17 4     4   27 use warnings;
  4         30  
  4         106  
18 4     4   3430 use POSIX;
  4         35248  
  4         31  
19              
20             =pod
21              
22             =head1 Subroutines
23              
24             =head2 cell_size
25              
26             Return the length of String I. If Boolean I is true,
27             ignore terminal sequences in I.
28              
29             =cut
30              
31             sub cell_size($$) {
32 671     671 0 1151 my ($cell, $term_adjust) = @_;
33 671 100       1434 $cell =~ s/\e\[.*?m//g if $term_adjust;
34 671         1481 return length($cell);
35             }
36              
37             =head2 columnize
38              
39             Return a list of strings with embedded newlines (\n) as a compact
40             set of columns arranged horizontally or vertically.
41              
42             For example, for a line width of 4 characters (arranged vertically):
43              
44             ['1', '2,', '3', '4'] => '1 3\n2 4\n'
45              
46             or arranged horizontally:
47              
48             ['1', '2,', '3', '4'] => '1 2\n3 4\n'
49              
50             Each column is only as wide possible, no larger than
51             C<$opts->{displaywidth}>. If Iis not an array reference, the
52             empty string, '', is returned. By default, columns are separated by
53             two spaces - one was not legible enough. Set C<$opts->{colsep}> to
54             adjust the string separate columns. If C<$opts->{arrange_vertical} is
55             set false, consecutive items will go across, left to right, top to
56             bottom.
57              
58             =cut
59              
60             sub columnize($;$) {
61 13     13 0 956 my($aref, $opts) = @_;
62 13         49 my @l = @$aref;
63              
64             # Some degenerate cases
65             # FIXME test for arrayness
66             # return '' if $aref is not an array
67 13 100       50 return "\n" if 0 == scalar(@l);
68 12 100       36 $opts = {} unless $opts;
69 12         46 merge_config $opts;
70 12 100       38 if ($opts->{arrange_array}) {
71 1   50     14 $opts->{array_prefix} ||= '(';
72 1   50     11 $opts->{lineprefix} ||= ' ';
73 1   50     14 $opts->{array_suffix} ||= ")";
74 1         4 $opts->{colsep} = ', ';
75 1         5 $opts->{arrange_vertical} = 0;
76             };
77 12 100       37 if (1 == scalar @l) {
78 1         7 my $ret = sprintf("%s%s%s\n", $opts->{array_prefix}, $l[0],
79             $opts->{array_suffix});
80 1         7 return $ret;
81             }
82              
83 11 50       34 @l = map(sprintf($opts->{colfmt}, $_), @l) if $opts->{colfmt};
84              
85 11         90 my %opts = %$opts;
86 11 50       38 return sprintf("%s%s%s",
87             $opts{array_prefix}, $opts{lineprefix},
88             $l[0], $opts{array_suffix}) if 1 == scalar(@l);
89              
90 11         24 my ($nrows, $ncols) = (0, 0); # Make nrows, ncols have more global scope
91 11         24 my @colwidths = (); # Same for colwidths
92              
93 11 50       44 if ($opts{displaywidth} - length($opts{lineprefix}) < 4) {
94 0         0 $opts{displaywidth} = length($opts{lineprefix}) + 4;
95             } else {
96 11         29 $opts{displaywidth} -= length($opts{lineprefix})
97             }
98 11 100       35 if ($opts{arrange_vertical}) {
99             my $array_index = sub ($$$) {
100 318     318   406 my ($num_rows, $row, $col) = @_;
101 318         548 ($num_rows * $col) + $row
102 6         33 };
103             # Try every row count from 1 upwards
104 6         21 for (my $_nrows=1; $_nrows < scalar @l; $_nrows++) {
105 12         17 $nrows = $_nrows;
106 12         42 $ncols = POSIX::ceil((scalar(@l)) / $nrows);
107 12         26 @colwidths = ();
108 12         21 my $totwidth = -length($opts{colsep});
109              
110 12         36 for (my $col=0; $col < $ncols; $col++) {
111             # get max column width for this column
112 83         96 my $colwidth = 0;
113 83         176 for (my $row=0; $row < $nrows; $row++) {
114 244         441 my $i = $array_index->($nrows, $row, $col);
115 244 100       488 last if ($i >= scalar(@l));
116 243         511 my $try_width = cell_size($l[$i], $opts{term_adjust});
117 243 100       746 $colwidth = $try_width if $try_width > $colwidth;
118             }
119 83         119 push(@colwidths, $colwidth);
120 83         122 $totwidth += $colwidth + length($opts{colsep});
121 83 100       260 if ($totwidth > $opts{displaywidth}) {
122 9         11 $ncols = $col;
123 9         16 last;
124             }
125             }
126 12 100       51 last if ($totwidth <= $opts{displaywidth});
127             }
128 6 100       16 $ncols = 1 if $ncols < 1;
129 6 100       26 $nrows = scalar(@l) if $ncols == 1;
130              
131             # The smallest number of rows computed and the max widths for
132             # each column has been obtained. Now we just have to format
133             # each of the rows.
134 6         16 my @s = ();
135 6         20 for (my $row=0; $row < $nrows; $row++) {
136 15         21 my @texts = ();
137 15         20 my $x;
138 15         46 for (my $col=0; $col < $ncols; $col++) {
139 74         133 my $i = $array_index->($nrows, $row, $col);
140 74 100       133 if ($i >= scalar(@l)) {
141 5         7 $x = '';
142             } else {
143 69         101 $x = $l[$i];
144             }
145 74         193 push @texts, $x;
146             }
147 15   66     96 pop(@texts) while (scalar(@texts) > 0 && $texts[-1] eq '');
148 15 50       37 if (scalar(@texts) > 0) {
149 15         64 for (my $col=0; $col < scalar(@texts); $col++) {
150 69 100 100     181 unless ($ncols == 1 && $opts{ljust}) {
151 65 100       217 my $fmt = sprintf("%%%s$colwidths[$col]s",
152             ($opts{ljust} ? '-': ''));
153 65         221 $texts[$col] = sprintf($fmt, $texts[$col]);
154             }
155             }
156 15         111 push(@s, sprintf("%s%s", $opts{lineprefix},
157             join($opts{colsep}, @texts)));
158             }
159             }
160 6         82 return join($opts{linesuffix}, @s) . "\n";
161             } else {
162             my $array_index = sub ($$$) {
163 763     763   1289 my ($num_rows, $row, $col) = @_;
164 763         1525 $ncols * ($row-1) + $col;
165 5         43 };
166             # Try every column count from size downwards.
167 5         47 my ($totwidth, $i, $rounded_size) = (0, 0, 0);
168 5         25 for (my $_ncols=scalar(@l); $_ncols >= 1; $_ncols--) {
169 25         37 $ncols = $_ncols;
170             # Try every row count from 1 upwards
171 25         134 my $min_rows = POSIX::ceil((scalar(@l)+$ncols-1) / $ncols);
172 25         105 for (my $_nrows=$min_rows; $_nrows <= scalar(@l); $_nrows++) {
173 25         63 $nrows = $_nrows;
174 25         37 $rounded_size = $nrows * $ncols;
175 25         53 @colwidths = ();
176 25         59 $totwidth = -length($opts{colsep});
177 25         38 my ($colwidth, $row) = (0,0);
178 25         72 for (my $col=0; $col < $ncols; $col++) {
179             # get max column width for this column
180 300         730 for (my $_row=1; $_row <= $nrows; $_row++) {
181 721         922 $row = $_row;
182 721         5421 $i = $array_index->($nrows, $row, $col);
183 721 100       3375 last if $i >= scalar(@l);
184 426         2026 my $try_size = cell_size($l[$i],
185             $opts{term_adjust});
186 426 100       1653 $colwidth = $try_size if $try_size > $colwidth;
187             }
188 300         429 push @colwidths, $colwidth;
189 300         515 $totwidth += $colwidth + length($opts{colsep});
190 300 100       1052 last if ($totwidth > $opts{displaywidth});
191             }
192 25 100       90 if ($totwidth <= $opts{displaywidth}) {
    50          
193             # Found the right nrows and ncols
194 4         7 $nrows = $row;
195 4         5 last;
196             }
197             elsif ($totwidth >= $opts{displaywidth}) {
198             # Need to reduce ncols
199 21         32 last;
200             }
201             }
202 25 100 100     151 last if ($totwidth <= $opts{displaywidth} && $i >= $rounded_size-1);
203             }
204 5 100       21 $nrows = scalar(@l) if $ncols == 1;
205              
206             # The smallest number of rows computed and the
207             # max widths for each column has been obtained.
208             # Now we just have to format each of the
209             # rows.
210 5         22 my @s = ();
211 5         24 my $prefix = $opts{array_prefix} = '' ?
212             $opts{lineprefix} : $opts{array_prefix};
213 5         17 for (my $row=1; $row <= $nrows; $row++) {
214 12         23 my @texts = ();
215 12         14 my $x;
216 12         32 for (my $col=0; $col < $ncols; $col++) {
217 42         83 my $i = $array_index->($nrows, $row, $col);
218 42 100       80 if ($i >= scalar(@l)) {
219 2         3 last;
220             } else {
221 40         67 $x = $l[$i];
222             }
223 40         115 push @texts, $x;
224             }
225 12         33 for (my $col=0; $col < scalar(@texts); $col++) {
226 40 100 100     129 unless ($ncols == 1 && $opts{ljust}) {
227 36 100       127 my $fmt = sprintf("%%%s$colwidths[$col]s",
228             ($opts{ljust} ? '-': ''));
229 36         120 $texts[$col] = sprintf($fmt, $texts[$col]);
230             }
231             }
232 12 100       61 push(@s, sprintf("%s%s", $prefix,
233             join($opts{colsep}, @texts))) if scalar(@texts);
234 12         43 $prefix = $opts->{lineprefix};
235             }
236 5         12 $s[-1] .= $opts->{array_suffix};
237 5         88 return join($opts{linesuffix}, @s) . "\n";
238             }
239             }
240              
241              
242             # Demo it
243             unless (caller) {
244              
245             my @ary = qw(bibrons golden madascar leopard mourning suras tokay);
246             print columnize(\@ary, {displaywidth => 18});
247              
248             my $line = 'require "irb"';
249             print cell_size($line, 1), "\n";
250             print cell_size($line, 0), "\n";
251              
252             print columnize(['hi']), "\n";
253             print columnize([]), "\n";
254              
255             for my $tuple ([4, 4], [4, 7], [100, 180]) {
256             my @data = ($tuple->[0]..$tuple->[1]);
257             print columnize(\@data, {colsep =>' ', arrange_vertical=>0});
258             print '------------------------';
259             print columnize(\@data, {colsep =>' ', arrange_vertical=>1});
260             print '========================';
261             }
262             print columnize(["a", 2, "c"], {displaywidth => 10, colsep => ', '});
263             print columnize(["oneitem"]);
264             print columnize(["one", "two", "three"]);
265             my @data = ("one", "two", "three",
266             "for", "five", "six",
267             "seven", "eight", "nine",
268             "ten", "eleven", "twelve",
269             "thirteen", "fourteen", "fifteen",
270             "sixteen", "seventeen", "eightteen",
271             "nineteen", "twenty", "twentyone",
272             "twentytwo", "twentythree", "twentyfour",
273             "twentyfive","twentysix", "twentyseven");
274              
275             print columnize(\@data);
276             @data = (1..30);
277             print columnize(\@data,
278             {arrange_array => 1, ljust =>0, displaywidth => 70});
279             }
280              
281             1;