App-RecordStream
view release on metacpan or search on metacpan
lib/App/RecordStream/Operation/toptable.pm view on Meta::CPAN
package App::RecordStream::Operation::toptable;
our $VERSION = "4.0.25";
use strict;
use warnings;
use base qw(App::RecordStream::Accumulator App::RecordStream::Operation);
use App::RecordStream::Record;
# TODO: amling, this format is so ugly it hurts. Think of something better.
sub init {
my $this = shift;
my $args = shift;
my %pins;
my $headers = 1;
my $full = 0;
my $xgroup = App::RecordStream::KeyGroups->new();
my $ygroup = App::RecordStream::KeyGroups->new();
my $vgroup = App::RecordStream::KeyGroups->new();
my $output_records = 0;
my $all_at_end = 0;
my %sorts = ();
my $spec = {
"x-field|x=s" => sub { $xgroup->add_groups($_[1]); },
"y-field|y=s" => sub { $ygroup->add_groups($_[1]); },
"v-field|v=s" => sub { $vgroup->add_groups($_[1]); },
"pin=s" => sub { for(split(/,/, $_[1])) { if(/^(.*)=(.*)$/) { $pins{$1} = $2; } } },
"sort=s" => sub { for(split(/,/, $_[1])) { my ($comparator, $field) = App::RecordStream::Record::get_comparator_and_field($_); $sorts{$field} = $comparator; } },
'noheaders' => sub { $headers = 0; },
'records|recs' => \$output_records,
'sort-all-to-end|sa' => \$all_at_end,
};
$this->parse_options($args, $spec);
if ( %sorts && $all_at_end ) {
die "Cannot specify both --sort and --sort-all-to-end\n";
}
my $do_vfields = !$vgroup->has_any_group();
$this->{'XGROUP'} = $xgroup;
$this->{'YGROUP'} = $ygroup;
$this->{'VGROUP'} = $vgroup;
$this->{'PINS_HASH'} = \%pins;
$this->{'SORTS'} = \%sorts;
$this->{'SORT_ALL_TO_END'} = $all_at_end;
$this->{'HEADERS'} = $headers;
$this->{'DO_VFIELDS'} = $do_vfields;
$this->{'OUTPUT_RECORDS'} = $output_records;
}
sub stream_done {
my $this = shift;
my %pins = %{$this->{'PINS_HASH'}};
my $headers = $this->{'HEADERS'};
my $do_vfields = $this->{'DO_VFIELDS'};
my $xgroup = $this->{'XGROUP'};
my $ygroup = $this->{'YGROUP'};
my $vgroup = $this->{'VGROUP'};
my $xfields_hash = {};
my $yfields_hash = {};
my $vfields_hash = {};
my $records = $this->get_records();
my (@xfields, @yfields, @vfields);
# Prep x and y fields
foreach my $record (@$records) {
foreach my $spec ( @{$xgroup->get_keyspecs_for_record($record)} ) {
if ( !$xfields_hash->{$spec} ) {
$xfields_hash->{$spec} = 1;
push @xfields, $spec;
}
}
foreach my $spec ( @{$ygroup->get_keyspecs_for_record($record)} ) {
if ( !$yfields_hash->{$spec} ) {
$yfields_hash->{$spec} = 1;
push @yfields, $spec;
}
}
}
if ( $this->{'SORT_ALL_TO_END' } ) {
foreach my $field (@xfields, @yfields) {
print "creating comp for $field\n";
my ($comparator, $comp_field) = App::RecordStream::Record::get_comparator_and_field("$field=*");
$this->{'SORTS'}->{$field} = $comparator;
}
}
# Prep v fields
if($do_vfields) {
my %vfields;
my %used_first_level_keys;
for my $record (@$records) {
foreach my $spec (@xfields, @yfields, keys %pins) {
my $key_list = $record->get_key_list_for_spec($spec);
if (scalar @$key_list > 0) {
$used_first_level_keys{$key_list->[0]} = 1;
}
}
foreach my $field (keys(%$record)) {
if ( !exists($used_first_level_keys{$field}) &&
!exists($vfields{$field}) ) {
lib/App/RecordStream/Operation/toptable.pm view on Meta::CPAN
for my $vfield (@vfields) {
# nothing to see here
if(!$record->has_key_spec($vfield)) {
next;
}
# if field is pinned, skip other vfields
if(exists($pins{"FIELD"}) && $pins{"FIELD"} ne $vfield) {
next;
}
my @xv;
for my $xfield (@xfields) {
my $v = "";
if($xfield eq "FIELD") {
$v = $vfield;
}
elsif($record->has_key_spec($xfield)) {
$v = ${$record->guess_key_from_spec($xfield)};
}
push @xv, $v;
}
my @yv;
for my $yfield (@yfields) {
my $v = "";
if($yfield eq "FIELD") {
$v = $vfield;
}
elsif($record->has_key_spec($yfield)) {
$v = ${$record->guess_key_from_spec($yfield)};
}
push @yv, $v;
}
my $v = "";
if($record->has_key_spec($vfield)) {
$v = ${$record->guess_key_from_spec($vfield)};
}
_touch_node_recurse($x_values_tree, @xv);
_touch_node_recurse($y_values_tree, @yv);
push @r2, [\@xv, \@yv, $v];
}
}
# Start constructing the ASCII table
# we dump the tree out into all possible x value tuples (saved in
# @x_value_list) and tag each node in the tree with the index in
# @x_values_list so we can look it up later
my @x_values_list;
$this->_dump_node_recurse($x_values_tree, \@x_values_list, [@xfields], []);
my @y_values_list;
$this->_dump_node_recurse($y_values_tree, \@y_values_list, [@yfields], []);
# Collected the data, if we're only outputing records, stop here.
if ( $this->{'OUTPUT_RECORDS'} ) {
$this->output_records(\@xfields, \@yfields, \@r2, \@x_values_list, \@y_values_list);
return;
}
my $width_offset = scalar @yfields;
my $height_offset = scalar @xfields;
if ( $headers ) {
$width_offset += 1;
$height_offset += 1;
}
my $w = $width_offset + scalar(@x_values_list);
my $h = $height_offset + scalar(@y_values_list);
my @table = map { [map { "" } (1..$w)] } (1..$h);
if ( $headers ) {
for(my $i = 0; $i < @xfields; ++$i) {
$table[$i]->[scalar(@yfields)] = $xfields[$i];
}
for(my $i = 0; $i < @yfields; ++$i) {
$table[scalar(@xfields)]->[$i] = $yfields[$i];
}
}
my @last_xv = map { "" } (1..@xfields);
for(my $i = 0; $i < @x_values_list; ++$i) {
my $xv = $x_values_list[$i];
for(my $j = 0; $j < @xfields; ++$j) {
if($last_xv[$j] ne $xv->[$j]) {
$last_xv[$j] = $xv->[$j];
$table[$j]->[$width_offset + $i] = $xv->[$j];
for(my $k = $j + 1; $k < @xfields; ++$k) {
$last_xv[$k] = "";
}
}
}
}
my @last_yv = map { "" } (1..@yfields);
for(my $i = 0; $i < @y_values_list; ++$i) {
my $yv = $y_values_list[$i];
for(my $j = 0; $j < @yfields; ++$j) {
if($last_yv[$j] ne $yv->[$j]) {
$last_yv[$j] = $yv->[$j];
$table[$height_offset + $i]->[$j] = $yv->[$j];
for(my $k = $j + 1; $k < @yfields; ++$k) {
$last_yv[$k] = "";
}
}
}
}
for my $r2 (@r2) {
my ($xv, $yv, $v) = @$r2;
# now we have our x value tuple, we need to know where it was in @x_values_list so we can know its x coordinate
my $i = _find_index_recursive($x_values_tree, @$xv);
( run in 0.789 second using v1.01-cache-2.11-cpan-39bf76dae61 )