Sam's Blog
Text::Matrix.pm Released
Date: Wednesday, 14 April 2010, 17:31.
Categories: perl, ironman, text-matrix, template-benchmark, qa.
Even with twenty thousand distributions on CPAN, a figure that should truly boggle the mind, I'm still often surprised to find myself trying to do something reasonably basic that hasn't been covered already.
While writing Template::Benchmark I wanted to lay out a "feature matrix", a matrix of template engines and the features they supported: a simple grid of Y/N characters in even, regular spacing.
To my surprise none of the CPAN table modules covered this, they'd all force the layout to depend on the width of the column labels or try to force the column labels to wrap at single-character width. So I hacked together some ugly code myself and got on with writing the rest of Template::Benchmark.
As with all ugly one-off code though, you find yourself wanting to use it elsewhere and constrained by how un-resuable it is.
Well, I did what I should have done in the first place, I made it Text::Matrix - Text table layout for matrices of short regular data.
Only a beta 0.99_01 release, but should be hitting a CPAN mirror near you shortly if it isn't there already.
See below the cut for example output and more details.
The core aim of the module is to display the matrix of data in a consistent and readable grid: the layout of the column labels may be ugly, but it allows the data to be presented in a manner that fits the data rather than the labels.
Simple:
Column 1 | Column 2 | | Column 3 | | | v v v Row A Y Y Y Row B Y - Y Row C - Y - Row D - - -
Breaks into sections to fit fixed-width display:
Column 1 | Column 2 | | Column 3 | | | Column 4 | | | | Column 5 | | | | | Column 6 | | | | | | Column 7 | | | | | | | Column 8 | | | | | | | | Column 9 | | | | | | | | | Column 10 | | | | | | | | | | Column 11 | | | | | | | | | | | Column 12 | | | | | | | | | | | | Column 13 | | | | | | | | | | | | | v v v v v v v v v v v v v Row A Y Y Y Y Y Y Y Y Y Y Y Y Y Row B Y Y Y Y Y Y Y Y Y Y Y Y Y Row C Y Y Y Y Y Y Y Y Y Y Y Y Y Row D Y Y Y Y Y Y Y Y Y Y Y Y Y Column 14 | Column 15 | | Column 16 | | | Column 17 | | | | Column 18 | | | | | Column 19 | | | | | | Column 20 | | | | | | | v v v v v v v Row A Y Y Y Y Y Y Y Row B Y Y Y Y Y Y Y Row C Y Y Y Y Y Y Y Row D Y Y Y Y Y Y Y
Slightly wider data:
A | B | | v v 1 A1 B1 2 A2 B2
Huge spam from Template::Benchmark, this would be completely unusable in a traditional table layout:
HTMLTemplate | HTMLTemplateCompiled | | HTMLTemplateExpr | | | HTMLTemplateJIT | | | | HTMLTemplatePro | | | | | MojoTemplate | | | | | | NTSTemplate | | | | | | | TemplateAlloyHT | | | | | | | | TemplateAlloyTT | | | | | | | | | TemplateSandbox | | | | | | | | | | TemplateTiny | | | | | | | | | | | TemplateToolkit | | | | | | | | | | | | Tenjin | | | | | | | | | | | | | TextClearSilver | | | | | | | | | | | | | | TextMicroMasonHM | | | | | | | | | | | | | | | TextMicroMasonTeTe | | | | | | | | | | | | | | | | TextMicroTemplate | | | | | | | | | | | | | | | | | TextTemplate | | | | | | | | | | | | | | | | | | TextTemplateSimple | | | | | | | | | | | | | | | | | | | TextTmpl | | | | | | | | | | | | | | | | | | | | v v v v v v v v v v v v v v v v v v v v literal_text Y Y Y Y Y Y Y Y Y Y Y Y Y Y Y Y Y Y Y Y scalar_variable Y Y Y Y Y Y Y Y Y Y Y Y Y Y Y Y Y Y Y Y hash_variable_value - Y - - - Y Y - Y Y Y Y Y Y Y Y Y Y Y - array_variable_value - - - - - Y - - Y Y Y Y Y Y Y Y Y Y Y - deep_data_structure_value - Y - - - Y Y - Y Y Y Y Y Y Y Y Y Y Y - array_loop_value - - - - - Y Y - Y Y Y Y Y Y Y Y Y Y Y Y hash_loop_value - - - - - Y - - Y Y - Y Y - Y Y Y Y Y Y records_loop_value Y Y Y Y Y Y Y Y Y Y Y Y Y Y Y Y Y Y Y Y array_loop_template - - - - - Y Y - Y Y Y Y Y Y Y - Y - Y Y hash_loop_template - - - - - Y - - Y Y - Y Y - Y - Y - Y Y records_loop_template Y Y Y Y Y Y Y Y Y Y Y Y Y Y Y - Y - Y Y constant_if_literal - - Y - Y Y Y Y Y Y - Y Y Y Y Y Y Y Y - variable_if_literal Y Y Y Y Y Y Y Y Y Y Y Y Y Y Y Y Y Y Y Y constant_if_else_literal - - Y - Y Y Y Y Y Y - Y Y Y Y Y Y Y Y - variable_if_else_literal Y Y Y Y Y Y Y Y Y Y Y Y Y Y Y Y Y Y Y Y constant_if_template - - Y - Y Y Y Y Y Y - Y Y Y Y - Y - Y - variable_if_template Y Y Y Y Y Y Y Y Y Y Y Y Y Y Y - Y - Y Y constant_if_else_template - - Y - Y Y Y Y Y Y - Y Y Y Y - Y - Y - variable_if_else_template Y Y Y Y Y Y Y Y Y Y Y Y Y Y Y - Y - Y Y constant_expression - - Y - Y Y - Y Y Y - Y Y Y Y Y Y Y Y - variable_expression - - Y - Y Y - Y Y Y - Y Y Y Y Y Y Y Y - complex_variable_expression - - Y - Y Y - Y Y Y - Y Y Y Y Y Y Y Y - constant_function - - Y - Y Y - Y - Y - - Y Y Y Y Y Y Y - variable_function - - Y - Y Y - Y Y Y - Y Y Y Y Y Y Y Y -
And some code snippets:
use Text::Matrix;
my $rows = [ 'Row A', 'Row B', 'Row C', 'Row D' ];
my $columns = [ 'Column 1', 'Column 2', 'Column 3' ];
my $data =
[
[ qw/Y Y Y/ ],
[ qw/Y - Y/ ],
[ qw/- Y -/ ],
[ qw/- - -/ ],
];
# Standard OO form;
my $matrix = Text::Matrix->new(
rows => $rows,
columns => $columns,
data => $data,
);
print $matrix->matrix();
# Anonymous chain form:
print Text::Matrix->columns( $columns )->rows( $rows )->data( $data )->matrix();
# Shorter but equivilent:
print Text::Matrix->matrix( $rows, $columns, $data );
# Paging by column width:
$rows = [ map { "Row $_" } ( 'A'..'D' ) ];
$columns = [ map { "Column $_" } ( 1..20 ) ];
$data = [ ( [ ( 'Y' ) x @{$columns} ] ) x @{$rows} ];
print Text::Matrix->max_width( 40 )->matrix( $rows, $columns, $data );
# Just want the body?
my $sections = Text::Matrix->new(
rows => $rows,
columns => $columns,
data => $data,
)->body();
print @{$sections};
# Gives:
#Row A Y Y Y Y Y Y Y Y Y Y Y Y Y Y Y Y Y Y Y Y
#Row B Y Y Y Y Y Y Y Y Y Y Y Y Y Y Y Y Y Y Y Y
#Row C Y Y Y Y Y Y Y Y Y Y Y Y Y Y Y Y Y Y Y Y
#Row D Y Y Y Y Y Y Y Y Y Y Y Y Y Y Y Y Y Y Y Y
Of course in typical "as soon as you've released" fashion, I've already found one bug in the process of writing this blog.
When I rewrote the mapping code I inadvertently changed it from scalar to list context, breaking one of the synopsis examples... which, er, weren't in the test suite. Doh.
So, the moral for this week is: always make sure that the examples from your documentation form part of your test-suite unless you want egg on your face.
Guess a 0.99_02 release is closer than I thought.