7 tabularize - Takes TAB-delimited lines of text and outputs formatted table.
11 I<COMMAND> | tabularize [I<OPTIONS>]
23 borders with nice graphical chars
25 =item -H, --no-horizontal
27 no horizontal lines in the output
29 =item -M, --no-margins
31 no margins, ie. no right-most and left-most vertical borders
33 =item -p, --padding I<NUM>
35 add space padding in cells.
36 I<NUM> is how many spaces.
38 =item -v, --output-vertical-separator I<CHAR>
40 vertical separator character(s) in the output
42 =item -r, --align-right I<NUM>
44 align these columns (0-indexed) to the right,
45 others are auto-detected and if they seem to hold mostly numeric data,
46 then aligned to the right;
47 otherwise to the left.
48 this option is repeatable.
50 =item -l, --align-left I<NUM>
52 similar to --align-right option
62 If B<$PAGER> is set and standard output is a terminal
63 and the resulting table is wider than the terminal,
64 then pipe the table through B<$PAGER>.
70 column(1), untabularize(1)
77 use DateTime
::Format
::Strptime
;
78 use Encode qw
/decode encode decode_utf8 encode_utf8/;
79 use Getopt
::Long qw
/:config no_ignore_case bundling no_getopt_compat no_auto_abbrev require_order/;
81 use List
::MoreUtils qw
/all any none zip/;
84 no if ($] >= 5.018), 'warnings' => 'experimental::smartmatch';
89 $verticalBorder = '|';
93 $toprightCorner = '+';
94 $bottomleftCorner = '+';
95 $bottomrightCorner = '+';
105 $verticalBorder = '│';
107 $horizontalBar = '─';
108 $topleftCorner = '┌';
109 $toprightCorner = '┐';
110 $bottomleftCorner = '└';
111 $bottomrightCorner = '┘';
121 use constant
{ ALIGN_LEFT
=> 1, ALIGN_RIGHT
=>2, };
126 @columnsAlignment = ();
129 'a|ascii' => \
&set_ascii
,
130 'u|unicode' => \
&set_unicode
,
131 'H|no-horizontal' => sub { $OptHorizontal = 0; },
132 'M|no-margins' => sub { $OptMargins = 0; },
133 'p|padding=i' => \
$OptPadding,
134 'v|output-vertical-separator=s' => sub {
135 my ($getopt_obj, $param) = @_;
136 $verticalBorder = $param;
137 $verticalBar = $param;
138 $topleftCorner = $param;
139 $toprightCorner = $param;
140 $bottomleftCorner = $param;
141 $bottomrightCorner = $param;
143 $middleCross = $param;
144 $rightCross = $param;
146 $bottomCross = $param;
148 'r|align-right=i@' => sub {
149 my ($getopt_obj, $param) = @_;
150 $columnsAlignment[$param] = ALIGN_RIGHT
;
152 'l|align-left=i@' => sub {
153 my ($getopt_obj, $param) = @_;
154 $columnsAlignment[$param] = ALIGN_LEFT
;
156 'help' => sub { pod2usage
(-exitval
=>0, -verbose
=>99); },
157 '<>' => sub { unshift @ARGV, @_[0]; die '!FINISH'; },
158 ) or pod2usage
(-exitval
=>2, -verbose
=>99);
164 # consider a cell's value numeric if it has only
165 # - optional sign prefix,
167 # - and optionally a common thousands- and/or fraction separator.
168 # I don't like the anglo-saxon ".123" notation, missing leading zero before the fraction separator.
170 /^[+-]?\d+((?'thousands_sep'[,. ])\d+(\g{thousands_sep}\d+)*)?([.,]\d+(\g{thousands_sep}\d+)*)?$/ and return 1;
175 # compute the width of each column
179 @numericals_by_col = ();
180 @non_numericals_by_col = ();
185 my @cells = split /\t/;
187 for my $idx (0 .. $#cells)
189 my $cell = $cells[$idx] || '';
190 my $width = length $cell; # TODO multibyte chars?
191 $columnsWidth[$idx] = $width if $columnsWidth[$idx] < $width;
193 # guess if this cell has numerical content (skip 1st line as it's likely a header)
194 if($. > 0 and not defined $columnsAlignment[$idx])
196 is_numeric
($cell) ?
($numericals_by_col[$idx]++) : ($non_numericals_by_col[$idx]++);
200 push @Table, \
@cells;
209 # compose the format string
213 $verticalBorder = '';
215 $toprightCorner = '';
216 $bottomleftCorner = '';
217 $bottomrightCorner = '';
222 for my $idx (0 .. $#columnsWidth)
224 if(not defined $columnsAlignment[$idx])
226 if($numericals_by_col[$idx] >= $non_numericals_by_col[$idx])
228 $columnAlignment[$idx] = ALIGN_RIGHT
;
232 $columnAlignment[$idx] = ALIGN_LEFT
;
236 $full_table_width += $idx == 0 ?
length($verticalBorder) : length($verticalBar);
237 $full_table_width += $columnsWidth[$idx] + 2*$OptPadding;
240 $full_table_width += length($verticalBorder);
241 $n_cols = scalar @columnsWidth;
242 @verticalBars = ($verticalBar) x
($n_cols-1);
246 $gridlineTop = $topleftCorner . join($topCross, map {$horizontalBar x
($_+2*$OptPadding)} @columnsWidth) . $toprightCorner;
247 $gridlineInner = $leftCross . join($middleCross, map {$horizontalBar x
($_+2*$OptPadding)} @columnsWidth) . $rightCross;
248 $gridlineBottom = $bottomleftCorner . join($bottomCross, map {$horizontalBar x
($_+2*$OptPadding)} @columnsWidth) . $bottomrightCorner;
252 map {(' 'x
$OptPadding).'%'.$_.'s'.(' 'x
$OptPadding)}
253 map {($columnAlignment[$_] == ALIGN_RIGHT ?
1 : -1) * $columnsWidth[$_]} 0 .. $#columnsWidth
266 print {$fh} ($row_num == 1 ?
$gridlineTop : $gridlineInner) . "\n";
269 my @cells = map {$row->[$_] || ''} 0 .. $#columnsWidth;
270 my @cells_and_inner_borders = zip
@cells, @verticalBars;
271 delete $cells_and_inner_borders[-1];
272 printf {$fh} $line_format."\n", $verticalBorder, @cells_and_inner_borders, $verticalBorder;
277 print {$fh} $gridlineBottom . "\n";
282 # display the rendered table
284 if(-t
1 and $ENV{PAGER
})
286 my ($terminal_cols, $terminal_rows) = Term
::Size
::chars
(*STDOUT
);
287 if($terminal_cols <= $full_table_width)
289 my ($p_read, $p_write);
290 pipe($p_read, $p_write) or die "$0: pipe: $!\n";
292 die "$0: fork: $!\n" if not defined $pid;
296 print_table
($p_write);
300 open \
*STDIN
, '<&', $p_read;
302 exec {$ENV{PAGER
}} [$ENV{PAGER
}] or warn "$0: exec: $!\n";
307 print_table
(\
*STDOUT
);