diff options
-rw-r--r-- | Build.PL | 21 | ||||
-rw-r--r-- | Changes | 3 | ||||
-rw-r--r-- | README | 17 | ||||
-rw-r--r-- | lib/Template/Plugin/ASCIITable.pm | 634 | ||||
-rw-r--r-- | t/01use.t | 200 |
5 files changed, 875 insertions, 0 deletions
diff --git a/Build.PL b/Build.PL new file mode 100644 index 0000000..81cc5ce --- /dev/null +++ b/Build.PL @@ -0,0 +1,21 @@ +#!/usr/bin/perl +use Module::Build; +my $build=Module::Build + ->new( + module_name=>'Template::Plugin::ASCIITable', + dist_author=>'dakkar <dakkar@tehanutilus.net>', + license=>'perl', + dist_abstract=><<'END_ABSTRACT', +Allows you to use Text::ASCIITable in your templates. +END_ABSTRACT + requires=>{ + Text::ASCIITable=>0, + Template=>0, + }, + build_requires=>{ + Test::More=>0, + }, + create_makefile_pl =>'traditional', + sign=>1, + ); +$build->create_build_script; @@ -0,0 +1,3 @@ +Version 0.2 - 2007-08-21 + +- fixed tests due to changes in Text::ASCIITable @@ -0,0 +1,17 @@ +Template::Plugin::ASCIITable + +Allows you to use Text::ASCIITable in your templates. + +INSTALLATION + + perl Build.PL + ./Build + ./Build test + ./Build install + +COPYRIGHT AND LICENSE + +Copyright (C) 2005-2007 dakkar <dakkar@thenautilus.net> + +This program is free software; you can redistribute it and/or modify it +under the same terms as Perl itself. diff --git a/lib/Template/Plugin/ASCIITable.pm b/lib/Template/Plugin/ASCIITable.pm new file mode 100644 index 0000000..b29dafc --- /dev/null +++ b/lib/Template/Plugin/ASCIITable.pm @@ -0,0 +1,634 @@ +package Template::Plugin::ASCIITable; +use base 'Template::Plugin'; +use Text::ASCIITable; +use vars qw($VERSION $AUTOLOAD); + +$VERSION='0.2'; + +=head1 NAME + +Template::Plugin::ASCIITable + +=head1 SYNOPSIS + + [% USE ASCIITable %] + blah + [% ASCIITable.cols('a', 'b', 'c'); + ASCIITable.rows([1,2,3],['one','two','three']); + ASCIITable.draw() %] + +=head1 DESCRIPTION + +This module allows you to use L<Text::ASCIITable> in your templates. + +A plugin object will be instantiated with the directive: + + [% USE ASCIITable %] + +You can pass a number of parameters to the constructor, for example: + + [% USE ASCIITable(cols => ['a','b','c'], show=>'rowline') %] + +See L</Parameters> for details. + +To obtain the table, you invoke the C<draw> method, to which you can +pass the same parameters: + + [% ASCIITable.draw(rows=>[[1,2,3],['one','two','three']]) %] + +=head1 METHODS + +=head2 C<new> + +This is the plugin construtor. You should never call it directly: use +the C<USE> directive. + +=cut + +sub new { + my ($class,$context,$params)=@_; + my $self=bless {hide=>{rowline=>1}},$class; + $self->handleParms(%$params) if $params; + return $self; +} + +=head2 C<draw> + +This method invokes L<Text::ASCIITable> to obtain the textual +representation of the table, and returns it. + +You can pass various parameters to it, see L</Parameters>. + +=cut + +sub draw { + my ($self,@rest)=@_; + $self->handleParms(@rest) if @rest; + my $t=Text::ASCIITable->new({$self->_globals()}); + $t->setCols($self->_colnames()); + for my $c ($self->_colprops()) { + $t->alignCol($c->{name},$c->{align}) if $c->{align}; + $t->alignColName($c->{name},$c->{nalign}) if $c->{nalign}; + $t->setColWidth($c->{name},$c->{width},$c->{widen}) if ($c->{width}||-1)>0; + } + for my $r ($self->_rows()) { + $t->addRow($r) + } + my $ret=$t->draw($self->_style()); + my ($l,$r)=$self->_squeeze(); + $ret=~s/^.{$l}//mg if $l; + $ret=~s/.{$r}$//mg if $r; + + $ret=~s/\n$//; + return $ret; +} + +my %paramSubs=( + hide => \&hide, + show => \&show, + cols => \&cols, + errors => \&errors, + reporterrors => \&errors, + allow => \&allow, + deny => \&deny, + alignheadrow => \&alignHeadRow, + rows => \&rows, + style => \&style, +); + +=head1 Parameters + +These parameters can be set in three ways: + +=over 4 + +=item * + +passing them to the constructor + +=item * + +passing them to the C<draw> call + +=item * + +setting them with individual method calls + +=back + +All three forms are case-insensitive: you can do + + [% USE t=ASCIITable(allow=>'HTML') %] + [% t.Allow('html') %] + [% t.draw(ALLOW=>'HtMl') %] + +=cut + +sub handleParms { + my ($self,%params)=@_; + for my $k (keys %params) { + if (exists $paramSubs{lc($k)}) { + $paramSubs{lc($k)}->($self,$params{$k}); + } else { + _die("no such parameter $k"); + } + } + return; +} + +=head2 C<hide> + +This parameter accepts a list of names of features to hide. The +recognized names are: + +=over 4 + +=item C<firstline> + +The very first decoration line of the table, before the column names. + +=item C<headrow> + +The line containing the names of the columns. + +=item C<headline> + +The decoration line separating the column names from the data lines. + +=item C<rowline> + +The decoration line separating one data line from the next. I<Hidden +by default>. + +=item C<lastline> + +The very last decoration line, after all the data. + +=back + +Note: C<t.hide('headrow');t.hide('headline')> will cause I<both> +features to be hidden. See L</show>. + +=cut + +sub hide { + my ($self,@what)=@_; + if (@what==1 and ref($what[0]) eq 'ARRAY') { + @what=@{$what[0]}; + } + @{$self->{hide}}{grep {m{(headrow)|((head|first|last|row)line)}} map {lc $_} @what}=(); + return; +} + +=head2 C<show> + +This parameter does the opposite of C<hide>: sets the given features +to be shown. + +Note: C<t.show('headrow');t.show('headline')> will cause I<both> +features to be shown. See L</hide>. + +=cut + +sub show { + my ($self,@what)=@_; + if (@what==1 and ref($what[0]) eq 'ARRAY') { + @what=@{$what[0]}; + } + delete @{$self->{hide}}{grep {m{(head(row|line))|((first|last|row)line)}} map {lc $_} @what}; + return; +} + +=head2 C<errors> + +Setting this to a true value will set the C<reportErrors> option (see +L<Text::ASCIITable>). + +=cut + +sub errors { + my ($self,$val)=@_; + $self->{errors}=$val; + return; +} + +=head2 C<allow> + +This parameter accepts a list of markup names to allow inside the +cells. This in needed to let C<Text::ASCIITable> calculate the correct +column widths. The recognized markups are: + +=over 4 + +=item C<ansi> + +This will allow you to use ANSI escape sequences for things like +colors or baldface. This usually works only if you output to a +compliant terminal. + +=item C<html> + +This will allow you to use HTML tags. No check is performed on the +well-formedness of any such tag. + +=back + +Note: C<t.allow('html');t.allow('ansi')> will cause I<both> +markups to be recognized. See L</deny>. + +=cut + +sub allow { + my ($self,@what)=@_; + if (@what==1 and ref($what[0]) eq 'ARRAY') { + @what=@{$what[0]}; + } + @{$self->{allow}}{grep {m{ansi|html}} map {lc $_} @what}=(); + return; +} + +=head2 C<deny> + +This parameter does the opposite of C<allow>: ignores the given +markups inside cells, counting them as data. + +Note: C<t.deny('html');t.deny('ansi')> will cause I<both> +markups to be ignored. See L</allow>. + +=cut + +sub deny { + my ($self,@what)=@_; + if (@what==1 and ref($what[0]) eq 'ARRAY') { + @what=@{$what[0]}; + } + delete @{$self->{allow}}{grep {m{ansi|html}} map {lc $_} @what}; + return; +} + +=head2 C<alignHeadRow> + +Sets the alignment for the column names. Can be one of: + +=over 4 + +=item * + +left + +=item * + +right + +=item * + +center + +=item * + +auto + +=back + +=cut + +sub alignHeadRow { + my ($self,$val)=@_; + $self->{headrow}=$val; + return; +} + +sub _handleCols { + my ($self,@colspec)=@_; + if (@colspec==1 and ref($colspec[0]) eq 'ARRAY') { + @colspec=@{$colspec[0]}; + } + my %colpos=(); + my @cols=(); + for (@colspec) { + if (ref $_) { + push @cols,{name=>$_->[0], + align=>$_->[1], + width=>$_->[2], + widen=>$_->[3], + nalign=>$_->[4], + }; + $colpos{$_->[0]}=$#cols; + } else { + push @cols,{name=>$_}; + $colpos{$_}=$#cols; + } + } + return (\@cols,\%colpos) +} + +=head2 C<cols> + +This parameter sets the names, and optionally some properties, of all +the columns in the table. To add columns to an existing table, use +the L</addCols> parameter (usually as a method). + +This parameter accepts a list of column specifications. Each +specification can be: + +=over 4 + +=item a single string + +that becomes the name of the column, and width and alignment default +to 'auto' + +=item an array reference + +that contains, in order: + +=over 8 + +=item * + +the name + +=item * + +the alignment for the data, one of 'left', 'right', 'center', 'auto' (see +L<Text::ASCIITable/alignCol>) + +=item * + +the maximum width of the column, in characters (see +L<Text::ASCIITable/setColWidth>) + +=item * + +whether to force the width of the column, a boolean (see +L<Text::ASCIITable/setColWidth>, the last parameter) + +=item * + +the name alignment (see L<Text::ASCIITable/alignColName>) + +=back + +=back + +Note: this sets I<all of the columns>. C<t.cols('a','b');t.cols('c')> +will result in a table with I<only 1> column. See L</addCols>. + +=cut + +sub cols { + my $self=shift; + ($self->{cols},$self->{colpos})=$self->_handleCols(@_); + return; +} + +=head2 C<addCols> + +This parameter works like C<cols>, but adds the given columns to the +existing ones, instead of replacing them. So +C<t.cols('a','b');t.addCols('c')> will result in a table with 3 columns. + +=cut + +sub addCols { + my $self=shift; + my ($cols,$colpos)=$self->_handleCols(@_); + push @{$self->{cols}},@$cols; + @{$self->{colpos}}{keys %$colpos}=values %$colpos; + return; +} + +=head2 C<rows> + +This parameter accepts a list of array references, one per row. Each +row must have one scalar element per each column that will be +produced. + +Note that, because of the way this plugin works, you can do something +like: + + [% USE t=ASCIITable(cols=>['a','b']); + t.rows([1,2,3],[4,5,6]); + t.addCols('c'); + t.draw() %] + +And it will print a 3x2 table. + +Note: this sets I<all of the rows>. +C<t.rows([1,2,3],[4,5,6]);t.rows([7,8,9])> +will result in a table with I<only 1> row. See L</addRows>. + +=cut + +sub rows { + my ($self,@rows)=@_; + if (@rows==1 and ref($rows[0]) eq 'ARRAY') { + @rows=@{$rows[0]}; + } + $self->{rows}=[@rows]; + return; +} + +=head2 C<addRows> + +This parameter works like C<rows>, but adds the given rows to the +existing ones, instead of replacing them. So +C<t.rows([1,2,3],[4,5,6]);t.addRows([7,8,9])> +will result in a table with 3 rows. + +=cut + +sub addRows { + my ($self,@row)=@_; + if (@row==1 and ref($row[0]) eq 'ARRAY') { + @row=@{$row[0]}; + } + push @{$self->{rows}},[@row]; + return; +} + +my %styles=( + 'default'=> + {lines=>undef, + globals=>{hide=>'rowline'}, + }, + 'rest-simple'=> + {lines=>[ + ['<','>','=',' '], + ['<','>',' '], + ['<','>','=',' '], + ['<','>',' '], + ['<','>','=',' '], + ['<','>','-',' '], + 1,1, + ], + globals=>{hide=>'rowline'}, + }, + 'rest-grid'=> + {lines=>[ + ['+','+','-','+'], + ['|','|','|'], + ['+','+','=','+'], + ['|','|','|'], + ['+','+','-','+'], + ['+','+','-','+'], + 0,0, + ], + globals=>{show=>'rowline'}, + }, + ); + +=head2 C<style> + +This parameter sets the draw style for the table. You can set it to a +list of array references, or to a style name. + +If you pass it a list, it can have 5, 6, or 8 elements. The first 6 +elements are interpreted in the same way as the parameters of +C<Text::ASCIITable::draw> (see L<Text::ASCIITable/Custom tables>). The +last two, which default to 0, are the number of columns to remove on +the left- and right-hand side of the generated table; this is used to +have tables without vertical borders. + +If you pass it a style name, it must be one of the following: + +=over 4 + +=item C<default> + +this is the default C<Text::ASCIITable> style: + + .--------------------------. + | one | two | three | four | + +-----+-----+-------+------+ + | one | one | one | one | + | two | two | two | two | + '-----+-----+-------+------' + +=item C<rest-simple> + +this is the "simple table" style as used in reStructuredText: + + ===== ===== ======= ====== + one two three four + ===== ===== ======= ====== + one one one one + two two two two + ===== ===== ======= ====== + +If you give C<show('rowline')> you get: + + ===== ===== ======= ====== + one two three four + ===== ===== ======= ====== + one one one one + ----- ----- ------- ------ + two two two two + ===== ===== ======= ====== + +Note that there is no blank space on either side. + +=item C<rest-grid> + +this is the "grid table" style as used in reStructuredText: + + +-----+-----+-------+------+ + | one | two | three | four | + +=====+=====+=======+======+ + | one | one | one | one | + +-----+-----+-------+------+ + | two | two | two | two | + +-----+-----+-------+------+ + +=back + +=cut + +sub style { + my ($self,$style)=@_; + if (ref $style eq 'ARRAY') { + if (@$style==6) { + $self->{style}=[@$style,0,0] + } elsif (@$style==5) { + $self->{style}=[@$style,[],0,0] + } elsif (@$style==8) { + $self->{style}=[@$style] + } else { + _die('Wrong style specification') + } + } elsif (exists $styles{lc $style}) { + $self->{style}=$styles{lc $style}->{lines}; + $self->handleParms(%{$styles{lc $style}->{globals}}); + } else { + _die("No such style $style"); + } +} + +sub AUTOLOAD { + return if $AUTOLOAD=~/:?DESTROY$/; + if (exists $paramSubs{lc($AUTOLOAD)}) { + goto &{$paramSubs{lc($AUTOLOAD)}}; + } else { + _die("no such method $AUTOLOAD") + } +} + +sub _globals { + my ($self)=@_; + my @ret=(); + push @ret,'hide_HeadRow',1 if exists $self->{hide}{headrow}; + push @ret,'hide_HeadLine',1 if exists $self->{hide}{headline}; + push @ret,'hide_FirstLine',1 if exists $self->{hide}{firstline}; + push @ret,'hide_LastLine',1 if exists $self->{hide}{lastline}; + push @ret,'allowANSI',1 if exists $self->{allow}{ansi}; + push @ret,'allowHTML',1 if exists $self->{allow}{html}; + push @ret,'reportErrors',$self->{errors} if $self->{errors}; + push @ret,'alignHeadRow',$self->{headrow} if $self->{headrow}; + push @ret,'drawRowLine',1 unless exists $self->{hide}{rowline}; + return @ret; +} + +sub _colprops { + my ($self)=@_; + return @{$self->{cols}}; +} +sub _colnames { + my ($self)=@_; + return map {$_->{name}} @{$self->{cols}}; +} +sub _rows { + my ($self)=@_; + return ($self->{rows}?@{$self->{rows}}:()); +} + +sub _style { + my ($self)=@_; + return ($self->{style}?@{$self->{style}}[0..5]:()); +} +sub _squeeze { + my ($self)=@_; + return ($self->{style}?@{$self->{style}}[6,7]:(0,0)); +} + +sub _die { + die (Template::Exception->new('ASCIITable',$_[0])) +} + +=head1 TODO + +Better tests? + +=head1 BUGS + +None known so far. If you find any, please report them using +rt.cpan.org or e-mail. It would be great if you could provide a simple +test case that exercises the bug. + +=head1 COPYRIGHT AND LICENSE + +Copyright (C) 2005 dakkar <dakkar@thenautilus.net> + +This program is free software; you can redistribute it and/or modify it +under the same terms as Perl itself. + +1; diff --git a/t/01use.t b/t/01use.t new file mode 100644 index 0000000..d3f2ef0 --- /dev/null +++ b/t/01use.t @@ -0,0 +1,200 @@ +#!/usr/bin/perl +use Test::More tests=>22; +use strict; +use warnings; +use Template; + +my $t=Template->new(); + +my $input=<<'EOT'; +[% USE ASCIITable %] +EOT + +my $output=''; + +ok($t->process(\$input,{},\$output),'empty template'); +is($output,"\n",'empty template output'); + +$input=<<'EOT'; +[%- USE table=ASCIITable -%] +[%- table.cols('a','b','c') -%] +[%- table.rows([1,2,3],[4,5,6]) -%] +[%- table.draw %] +EOT +$output=''; +ok($t->process(\$input,{},\$output),'simple table'); +is($output,<<'OUT','simple output'); +.-----------. +| a | b | c | ++---+---+---+ +| 1 | 2 | 3 | +| 4 | 5 | 6 | +'---+---+---' +OUT + +$input=<<'EOT'; +[%- USE table=ASCIITable(cols=>['a','b','c'],show=>'rowline') -%] +[%- table.rows([1,2,3],[4,5,6]) -%] +[%- table.draw %] +EOT +$output=''; +ok($t->process(\$input,{},\$output),'rowline'); +is($output,<<'OUT','rowline output'); +.-----------. +| a | b | c | ++---+---+---+ +| 1 | 2 | 3 | ++---+---+---+ +| 4 | 5 | 6 | +'---+---+---' +OUT + +$input=<<'EOT'; +[%- USE table=ASCIITable -%] +[%- table.rows([1,2,3234],[4,'x',6345]) -%] +[%- table.cols('a',['b','auto',5,1],['c','left',3]) %] +[%- table.draw %] +EOT +$output=''; +ok($t->process(\$input,{},\$output),'widths'); +is($output,<<'OUT','widths output'); +.-----------------. +| a | b | c | ++---+-------+-----+ +| 1 | 2 | 323 | +| 4 | x | 634 | +'---+-------+-----' +OUT + +$input=<<'EOT'; +[%- USE table=ASCIITable(style=>'ReST-simple') -%] +[%- table.rows([1,2,3234],[4,'x',6345]) -%] +[%- table.cols('a','b','c') -%] +[%- table.draw %] +EOT +$output=''; +ok($t->process(\$input,{},\$output),'style simple'); +is($output,<<'OUT','style simple output'); +=== === ====== + a b c +=== === ====== + 1 2 3234 + 4 x 6345 +=== === ====== +OUT + +$input=<<'EOT'; +[%- USE table=ASCIITable(style=>'ReST-grid') -%] +[%- table.rows([1,2,3234],[4,'x',6345]) -%] +[%- table.cols('a','b','c') -%] +[%- table.draw %] +EOT +$output=''; +ok($t->process(\$input,{},\$output),'style grid'); +is($output,<<'OUT','style grid output'); ++---+---+------+ +| a | b | c | ++===+===+======+ +| 1 | 2 | 3234 | ++---+---+------+ +| 4 | x | 6345 | ++---+---+------+ +OUT + +$input=<<'EOT'; +[%- USE table=ASCIITable(style=>'default') -%] +[%- table.cols('a','b','c') -%] +[%- table.rows([1,2,3],[4,5,6]) -%] +[%- table.draw %] +EOT +$output=''; +ok($t->process(\$input,{},\$output),'style default'); +is($output,<<'OUT','style default output'); +.-----------. +| a | b | c | ++---+---+---+ +| 1 | 2 | 3 | +| 4 | 5 | 6 | +'---+---+---' +OUT + +$input=<<'EOT'; +[%- USE table=ASCIITable(style=>'default') -%] +[%- table.cols('a') -%] +[%- table.addCols('b','c') -%] +[%- table.addRows([1,2,3]) -%] +[%- table.addRows([4,5,6]) -%] +[%- table.draw %] +EOT +$output=''; +ok($t->process(\$input,{},\$output),'add'); +is($output,<<'OUT','add output'); +.-----------. +| a | b | c | ++---+---+---+ +| 1 | 2 | 3 | +| 4 | 5 | 6 | +'---+---+---' +OUT + +$input=<<'EOT'; +[%- USE table=ASCIITable(style=> + [[' /',' \\','-','T'], + ['#','#','#'], + ['>','<','=','+'], + ['|','|','-'], + [' \\','/ ','_','"'], + [':',';','.','*']]) -%] +[%- table.cols('a','b','c') -%] +[%- table.rows([1,2,3],[4,5,6]) -%] +[%- table.hide('firstline') -%] +[%- table.show('rowline') -%] +[%- table.draw %] +EOT +$output=''; +ok($t->process(\$input,{},\$output),'style by hand'); +is($output,<<'OUT','style by hand output'); +# a # b # c # +>===+===+===< +| 1 - 2 - 3 | +:...*...*...; +| 4 - 5 - 6 | + \__"___"__/ +OUT + +$input=<<'EOT'; +[%- USE table=ASCIITable(allow=>'html') -%] +[%- table.cols('a','b','c') -%] +[%- table.rows(['<b>1</b>',2,3],[4,5,6]) -%] +[%- table.draw %] +EOT +$output=''; +ok($t->process(\$input,{},\$output),'html'); +is($output,<<'OUT','html output'); +.-----------. +| a | b | c | ++---+---+---+ +| <b>1</b> | 2 | 3 | +| 4 | 5 | 6 | +'---+---+---' +OUT + +$input=<<'EOT'; +[%- USE table=ASCIITable(allow=>'html') -%] +[%- table.cols('a','b','c') -%] +[%- table.rows(['<b>1</b>',2,3],[4,5,6]) -%] +[%- table.deny('html') -%] +[%- table.draw %] +EOT +$output=''; +ok($t->process(\$input,{},\$output),'no html'); +is($output,<<'OUT','no html output'); +.------------------. +| a | b | c | ++----------+---+---+ +| <b>1</b> | 2 | 3 | +| 4 | 5 | 6 | +'----------+---+---' +OUT + +diag($t->error()); |