diff --git a/README b/README index c5d8b4b..bd75377 100644 --- a/README +++ b/README @@ -9,7 +9,7 @@ some basic capabilities for configuring the appearance of the tables. You can read a nicely formatted version of the documentation for this module online: - https://metacpan.org/pod/Module::Info + https://metacpan.org/pod/Text::Table::Tiny You should be able to install this using your usual method for installing diff --git a/lib/Text/Table/Tiny.pm b/lib/Text/Table/Tiny.pm index 3da0d0b..6707dff 100644 --- a/lib/Text/Table/Tiny.pm +++ b/lib/Text/Table/Tiny.pm @@ -44,20 +44,31 @@ sub generate_table { $format, map { defined($header_row->[$_]) ? $header_row->[$_] : '' } (0..$max_index) ); - push @table, $params{separate_rows} ? $head_row_sep : $row_sep; + push @table, $params{separate_rows} || $params{group_rows_by} ? $head_row_sep : $row_sep; } # then the data - foreach my $row ( @{ $rows }[$data_begins..$#$rows] ) { + foreach my $row_num ( $data_begins .. $#$rows ) { + my $row = $rows->[$row_num]; + push @table, sprintf( $format, map { defined($row->[$_]) ? $row->[$_] : '' } (0..$max_index) ); - push @table, $row_sep if $params{separate_rows}; + + my $next_row = $row_num < $#$rows ? $rows->[$row_num + 1] : undef; + my $is_last_in_rowgroup = $params{group_rows_by} + && not _is_same_colgroup($row, $next_row, $params{group_rows_by}); + if ($params{separate_rows}) { + push @table, $is_last_in_rowgroup ? $head_row_sep : $row_sep; + } + elsif ($is_last_in_rowgroup) { + push @table, $row_sep; + } } # this will have already done the bottom if called explicitly - push @table, $row_sep unless $params{separate_rows}; + push @table, $row_sep unless $params{separate_rows} || $params{group_rows_by}; return join("\n",grep {$_} @table); } @@ -100,6 +111,17 @@ sub _get_header_row_separator { return "$HEADER_CORNER_MARKER$HEADER_ROW_SEPARATOR".join("$HEADER_ROW_SEPARATOR$HEADER_CORNER_MARKER$HEADER_ROW_SEPARATOR",map { $HEADER_ROW_SEPARATOR x $_ } @$widths)."$HEADER_ROW_SEPARATOR$HEADER_CORNER_MARKER"; } +sub _is_same_colgroup { + my ($curr_row, $next_row, $col_spec) = @_; + + return unless $curr_row && $next_row; + + my @curr_columns = map { defined $_ ? $_ : "" } @$curr_row[@$col_spec]; + my @next_columns = map { defined $_ ? $_ : "" } @$next_row[@$col_spec]; + + return "@curr_columns" eq "@next_columns"; +} + # Back-compat: 'table' is an alias for 'generate_table', but isn't exported *table = \&generate_table; @@ -216,6 +238,65 @@ You get the maximally ornate: | carol | brig gen | 8745 | +-------+----------+----------+ +If you want to group consecutive rows which have same values in certain columns: + +Assuming the following data: + + my $rows = [ + # header row + ['Name', 'Rank', 'Serial'], + # rows + ['alice', 'pvt', '123456'], + ['alison', 'pvt', '123457'], + ['bob', 'cpl', '98765321'], + ['carol', 'brig gen', '8745'], + ['darla', 'brig gen', '9745'], + ]; + print generate_table(rows => $rows, header_row => 1, group_rows_by => [1]); + +This will group all consecutive rows with the same Rank (column 1) value. + + +--------+----------+----------+ + | Name | Rank | Serial | + O========O==========O==========O + | alice | pvt | 123456 | + | alison | pvt | 123457 | + +--------+----------+----------+ + | bob | cpl | 98765321 | + +--------+----------+----------+ + | carol | brig gen | 8745 | + | darla | brig gen | 8745 | + +--------+----------+----------+ + +B: The C option does *not* sort the rows to keep +rows with same column values together. The input list of rows is +assumed to be already ordered. + +When both C option and C option are +specified, the more ornate separator is used for separating rowgroups, +and the ordinary row separator is used for separating rows within the +same group. + + print generate_table(rows => $rows, header_row => 1, + separate_rows => 1 + group_rows_by => [1]); + +This will produce the following output: + + +--------+----------+----------+ + | Name | Rank | Serial | + O========O==========O==========O + | alice | pvt | 123456 | + +--------+----------+----------+ + | alison | pvt | 123457 | + O========O==========O==========O + | bob | cpl | 98765321 | + O========O==========O==========O + | carol | brig gen | 8745 | + +--------+----------+----------+ + | darla | brig gen | 9745 | + O========O==========O==========O + =head1 FORMAT VARIABLES You can set a number of package variables inside the C package diff --git a/t/03-group-rows.t b/t/03-group-rows.t new file mode 100644 index 0000000..3c56c7f --- /dev/null +++ b/t/03-group-rows.t @@ -0,0 +1,107 @@ +use strict; +use Test::More tests => 5; +use Text::Table::Tiny qw/ generate_table /; + +my $rows = [ + [ 'Group', 'Member' ], + [ 'Elvis', 'Priscilla' ], + [ 'Elvis', 'Lisa Marie' ], + [ 'Liquor', 'Beer' ], + [ 'Liquor', 'Wine' ], + [ 'Liquor', 'Brandy' ], + [ 'Liquor', 'Rum' ], + [ undef, "That's showbiz!" ], +]; + +my $t0 = generate_table( rows => $rows ); +# print $t0, $/; +is($t0, q%+--------+-----------------+ +| Group | Member | +| Elvis | Priscilla | +| Elvis | Lisa Marie | +| Liquor | Beer | +| Liquor | Wine | +| Liquor | Brandy | +| Liquor | Rum | +| | That's showbiz! | ++--------+-----------------+%, +'just rows' +); + +my $t1 = generate_table( rows => $rows, header_row => 1 ); +# print $t1, $/; +is($t1, q%+--------+-----------------+ +| Group | Member | ++--------+-----------------+ +| Elvis | Priscilla | +| Elvis | Lisa Marie | +| Liquor | Beer | +| Liquor | Wine | +| Liquor | Brandy | +| Liquor | Rum | +| | That's showbiz! | ++--------+-----------------+%, +'rows and header row' +); + +my $t2 = generate_table( rows => $rows, header_row => 1, group_rows_by => [0] ); +# print $t2, $/; +is($t2, q%+--------+-----------------+ +| Group | Member | +O========O=================O +| Elvis | Priscilla | +| Elvis | Lisa Marie | ++--------+-----------------+ +| Liquor | Beer | +| Liquor | Wine | +| Liquor | Brandy | +| Liquor | Rum | ++--------+-----------------+ +| | That's showbiz! | ++--------+-----------------+%, +'header row and rows grouped by first column' +); + +my $t3 = generate_table( rows => $rows, header_row => 1, group_rows_by => [1] ); +# print $t3, $/; +is($t3, q%+--------+-----------------+ +| Group | Member | +O========O=================O +| Elvis | Priscilla | ++--------+-----------------+ +| Elvis | Lisa Marie | ++--------+-----------------+ +| Liquor | Beer | ++--------+-----------------+ +| Liquor | Wine | ++--------+-----------------+ +| Liquor | Brandy | ++--------+-----------------+ +| Liquor | Rum | ++--------+-----------------+ +| | That's showbiz! | ++--------+-----------------+%, +'header row and rows grouped by non-first column' +); + +my $t4 = generate_table( rows => $rows, header_row => 1, separate_rows => 1, group_rows_by => [0] ); +# print $t4, $/; +is($t4, q%+--------+-----------------+ +| Group | Member | +O========O=================O +| Elvis | Priscilla | ++--------+-----------------+ +| Elvis | Lisa Marie | +O========O=================O +| Liquor | Beer | ++--------+-----------------+ +| Liquor | Wine | ++--------+-----------------+ +| Liquor | Brandy | ++--------+-----------------+ +| Liquor | Rum | +O========O=================O +| | That's showbiz! | +O========O=================O%, +'header row, rows grouped by first column, and separate rows' +);