## Calculate a new field's values based on two others
$t->calc($Sub) ## Run $Sub for each row, with
## fields bound to local vars
$t->calc($Sub, $Sel) ## Use these row nums
$t->calc($Sub, undef, $Fields) ## Use only these fields
my $Col = $t->calc($Sub) ## Gather return vals in vector
o o o
## Example 2: Create empty column; fill fields 1 by 1
$t->col('PersonID');
$t->calc(sub{no strict 'vars'; $PersonID = "$Last$First"});
####
sub calc
{
## We operate in package main for this subroutine so the local
## object and row number can be available to the caller's $Sub.
## The following local vars will be available to $Sub
## $_r ($main::_r) -- the row number in the entire table
## $_s ($main::_s) -- the row number in selection or $Recs
## $_t ($main::_t) -- the table object itself
package main;
use vars qw($_r $_s $_t);
local ($_r, $_s, $_t);
$_t = shift;
my ($Sub, $Recs, $Fields) = @_;
## These optional params default to current field and current sel
$Recs ||= $_t->selection();
$Fields ||= $_t->fieldlist_all();
## Local copy of symbol table. Didn't seem to help. Odd.
## local %main::;
## We'll build a column of return values from $Sub if needed.
my $WantVals = defined(wantarray);
my $Vals = $_t->col_empty() if $WantVals;
## Call col() on each field in list to make sure it exists.
foreach (@$Fields) {$_t->col($_)};
foreach $_s (0..$#$Recs)
{
$_r = $Recs->[$_s];
## Bind $FieldName1, $FieldName2, (etc. for each field name in
## $Fields) point to address of the current value for that
## field in this record.
no strict 'refs';
foreach my $F (@$Fields) {*{$F} = \ $_t->{$F}->[$_r]};
## Now $Sub may refer to $_r, $_s, $_t, and ${any field name}
## Call $Sub and capture return values iff caller wants them
($WantVals ? $Vals->[$_r] = &$Sub() : &$Sub());
}
## Return scalar column ref unless return context is undef
return($WantVals ? $Vals : ());
}
####
## Don't overwrite fields in package main.
my @saveFields;
no strict 'refs'; package main;
foreach my $F (@$Fields) { push @saveFields, ${$F}; }
foreach $_s (0..$#$Recs)
{
$_r = $Recs->[$_s];
## Bind $FieldName1, $FieldName2, (etc. for each field name in
## $Fields) point to address of the current value for that
## field in this record.
no strict 'refs';
foreach my $F (@$Fields) {*{$F} = \ $_t->{$F}->[$_r]};
## Now $Sub may refer to $_r, $_s, $_t, and ${any field name}
## Call $Sub and capture return values iff caller wants them
($WantVals ? $Vals->[$_r] = &$Sub() : &$Sub());
}
## Restore fields in main
no strict 'refs'; package main;
foreach my $F (@$Fields) { ${$F} = shift @saveFields; };
####
#!/usr/bin/perl
## Unit testing script for the Data::CTable module
# Before `make install' is performed this script should be runnable with
# `make test'. After `make install' it should work as `perl test.pl'
use strict;
use Test;
BEGIN { plan tests => 30, todo => [] }
use Data::CTable;
use Data::CTable::Script;
use Data::CTable::Listing;
use Data::CTable::ProgressLogger;
## Suppress automatic built-in progress during testing in order to
## acheive nice unobscured testing output when all goes well.
## (Will still test the progress() features directly with specific
## subclasses that gather the messages into memory instead of printing
## them out.)
Data::CTable->progress_class(0);
## Note whether we're on Unix...
my $OnUnix = ((-d "/") && (-d "/tmp"));
## Figure out some platform-specific path details...
my ($Sep, $Up, $Cur) = @{Data::CTable->path_info()}{qw(sep up cur)};
my $TestDir = "test$Sep"; ## "test/"
## First test is to read a file that we'll make use of in many of the
## other tests.
my $People1 = Data::CTable->new("${TestDir}people.tabs.txt") and ok(1) or die;
## Now for tests 2..onward, run unit-tests of specific feature
## groups...
ok(test_calc()); ## Calc method
ok(test_calc_restore_main_vars()); ## check that calc restores Main vars
1;
sub test_calc
{
my $People2 = Data::CTable->new("${TestDir}people.tabs.txt") or die;
package FooBar;
no strict 'vars'; package main;
$People2->calc(sub{$First = "\U$First\E/@{[$_t->length()]}/$_r/$_s"});
return(0) unless ("@{$People2->col('First')}" eq
"CHRIS/3/0/0 MARCO/3/1/1 PEARL/3/2/2");
return(1);
}
sub test_calc_restore_main_vars
{
my $People2 = Data::CTable->new("${TestDir}people.tabs.txt") or die;
no strict 'vars'; package main;
# make sure that calc doesn't overwrite fields in Main
# especially important when calc is called repeatedly on tables with intersecting field names
$First = 'TEST';
$People2->calc(sub{$Last});
package main;
return(0) if ( $First ne 'TEST' );
return(1);
}