package Zoidberg::Utils::Output; our $VERSION = '0.96'; use strict; use Data::Dumper; use POSIX qw/floor ceil/; use Exporter::Tidy default => [qw/output message debug complain/], other => [qw/typed_output output_is_captured/]; our %colours = ( # Copied from Term::ANSIScreen 'clear' => 0, 'reset' => 0, 'bold' => 1, 'dark' => 2, 'underline' => 4, 'underscore' => 4, 'blink' => 5, 'reverse' => 7, 'concealed' => 8, 'black' => 30, 'on_black' => 40, 'red' => 31, 'on_red' => 41, 'green' => 32, 'on_green' => 42, 'yellow' => 33, 'on_yellow' => 43, 'blue' => 34, 'on_blue' => 44, 'magenta' => 35, 'on_magenta' => 45, 'cyan' => 36, 'on_cyan' => 46, 'white' => 37, 'on_white' => 47, ); sub output_is_captured { return $Zoidberg::CURRENT->{_builtin_output} ? 1 : 0; } sub output { if ($Zoidberg::CURRENT->{_builtin_output}) { # capturing output from builtin push @{ $Zoidberg::CURRENT->{_builtin_output} }, @_; return 1; } else { typed_output('output', @_) } } sub message { return 1 if ! $Zoidberg::CURRENT->{settings}{interactive}; typed_output('message', @_); } sub debug { my $class = caller; no strict 'refs'; #local $Data::Dumper::Maxdepth = 2; return 1 unless $Zoidberg::CURRENT->{settings}{debug} || ${$class.'::DEBUG'}; my $fh = select STDERR; my @caller = caller; typed_output('debug', "$caller[0]: $caller[2]: ", @_); select $fh; 1; } sub complain { # strip @INC: for little less verbose output return 0 unless @_ || $@; my @error = @_ ? (@_) : ($@); my $fh = select STDERR; typed_output('error', map {s/\(\@INC contains\: (.*?)\)\s*//g; $_} @error); select $fh; 1; } sub typed_output { my $type = shift; my @dinge = @_; return unless @dinge > 0; $type = $Zoidberg::CURRENT->{settings}{output}{$type} || $type; return 1 if $type eq 'mute'; my $coloured; print "\e[$colours{$type}m" and $coloured = 1 if exists $colours{$type} and $Zoidberg::CURRENT->{settings}{interactive} and $ENV{CLICOLOR}; $dinge[-1] .= "\n" unless ref $dinge[-1]; for (@dinge) { $_ = $_->scalar() if ref($_) eq 'Zoidberg::Utils::Output::Scalar'; unless (ref $_) { print $_ } elsif (ref($_) eq 'ARRAY' and ! grep { ref($_) } @$_) { output_list(@$_) } elsif (ref($_) eq 'Zoidberg::Utils::Error') { if ($$_{debug}) { print map {s/^\$VAR1 = //; $_} Dumper $_ } else { next if $$_{silent} || $$_{printed}++; print $_->stringify(format => 'gnu'); } } elsif (ref($_) =~ /Zoidberg/) { complain 'Cowardly refusing to dump object of class '.ref($_); } else { print map {s/^\$VAR1 = //; $_} Dumper $_ } } print "\e[$colours{reset}m" if $coloured; 1; } sub output_list { # takes minimum number of rows, but fills cols first my (@items) = @_; my $width = $ENV{COLUMNS}; return print join("\n", @items), "\n" unless $Zoidberg::CURRENT->{settings}{interactive}; my $len = 0; $_ > $len and $len = $_ for map {s/\t/ /g; length $_} @items; $len += 2; # spacing return print join("\n", @items), "\n" if $width < (2 * $len); # rows == items return print join(' ', @items), "\n" if $width > (@items * $len); # 1 row my $cols = int($width / $len ) - 1; # 0 based my $rows = int(@items / ($cols+1)); # 0 based ceil $rows -= 1 unless @items % ($cols+1); # tune ceil my @rows; for my $r (0 .. $rows) { my @row = map { $items[ ($_ * ($rows+1)) + $r] } 0 .. $cols; push @rows, join '', map { $_ .= ' 'x($len - length $_) } @row; } #print STDERR scalar(@items)." items, $len long, $width width, $cols+1 cols, $rows+1 rows\n"; print join("\n", @rows), "\n"; } sub output_sql { # kan vast schoner shift unless ref($_[0]) eq 'ARRAY'; my $width = $ENV{COLUMNS}; if (! $Zoidberg::CURRENT->{settings}{interactive} || !defined $width) { return (print join("\n", map {join(', ', @{$_})} @_)."\n"); } my @records = @_; my @longest = (); @records = map {[map {s/\'/\\\'/g; "'".$_."'"} @{$_}]} @records; # escape quotes + safety quotes foreach my $i (0..$#{$records[0]}) { map {if (length($_) > $longest[$i]) {$longest[$i] = length($_);} } map {$_->[$i]} @records; } #print "debug: records: ".Dumper(\@records)." longest: ".Dumper(\@longest); my $record_length = 0; # '[' + ']' - ', ' for (@longest) { $record_length += $_ + 2; } # length (', ') = 2 if ($record_length <= $width) { # it fits ! => horizontal lay-out my $cols = floor($width / ($record_length+2)); # we want two spaces to saperate coloms my @strings = (); for (@records) { my @record = @{$_}; for (0..$#record-1) { $record[$_] .= ', '.(' 'x($longest[$_] - length($record[$_]))); } $record[$#record] .= (' 'x($longest[$#record] - length($record[$#record]))); if ($cols > 1) { push @strings, "[".join('', @record)."]"; } else { print "[".join('', @record)."]\n"; } } if ($cols > 1) { my $rows = ceil(($#strings+1) / $cols); foreach my $i (0..$rows-1) { for (0..$cols) { print $strings[$_*$rows+$i]." "; } print "\n"; } } } else { for (@records) { print "[\n ".join(",\n ", @{$_})."\n]\n"; } } # vertical lay-out return 1; } package Zoidberg::Utils::Output::Scalar; use overload '""' => \&scalar, 'bool' => \&error, '@{}' => \&array, fallback => 'TRUE'; sub new { bless \[@_[1,2,3]], $_[0] } sub error { my $s = ${ shift() }; $$s[0] } sub scalar { my $s = ${ shift() }; $$s[1] = join "\n", @{$$s[2]} if ! defined $$s[1] and $$s[2]; return $$s[1]; } sub array { my $s = ${ shift() }; if (! defined $$s[2]) { $$s[2] = (ref($$s[1]) eq 'ARRAY') ? $$s[1] : ref($$s[1]) ? [$$s[1]] : [ split /\n/, $$s[1] ]; } return $$s[2]; } 1; __END__ =head1 NAME Zoidberg::Utils::Output - Zoidberg output routines =head1 SYNOPSIS use Zoidberg::Utils qw/:output/; # use this instead of perlfunc print output "get some string outputted"; output { string => 'or some data struct dumped' }; =head1 DESCRIPTION This module provides some routines used by various Zoidberg modules to output data. Although when working within the Zoidberg framework this module should be used through the L interface, it also can be used on it's own. =head1 EXPORT By default all of the below except C. =head1 METHODS =over 4 =item C Output a list of possibly mixed data structs as nice as possible. A reference to an array of plain scalars may be outputted as a multicolumn list, more complex data will be dumped using L. =item C Like C but tags data as a message, in non-interactive mode these may not be printed at all. =item C Like C tags the data as debug output, will only be printed when in debug mode. Debug ouput will be printed to STDERR if printed at all. =item C Like C but intended for error messages, data will be printed to STDERR. Has some glue for error objects created by L. Prints C<$@> when no argument is given. =item C Method that can be used to define output types that don't fit in the above group. C<$type> must be a plain string that is used as output 'tag'. =item C Method that returns a boolean that tells whether output is captured or not. This can be used to make terminal output different from data struct output. =back =head1 AUTHOR Jaap Karssenberg || Pardus [Larus] Epardus@cpan.orgE Copyright (c) 2003 Jaap G Karssenberg. All rights reserved. This program is free software; you can redistribute it and/or modify it under the same terms as Perl itself. =head1 SEE ALSO L, L =cut