follow download redirects in example scripts
[barcat.git] / barcat
diff --git a/barcat b/barcat
index 5ac55bab9cfbe5987aa818c2a5ef512644d40e40..d4a8274f6c9af8015332215138b8d46c3158a30d 100755 (executable)
--- a/barcat
+++ b/barcat
@@ -1,29 +1,30 @@
-#!/usr/bin/env perl
-use 5.018;
+#!/usr/bin/perl -CA
+use 5.014;
 use warnings;
 use utf8;
 use List::Util qw( min max sum );
 use open qw( :std :utf8 );
 use warnings;
 use utf8;
 use List::Util qw( min max sum );
 use open qw( :std :utf8 );
-use experimental qw( lexical_subs );
+use re '/msx';
 
 
-our $VERSION = '1.03';
+our $VERSION = '1.07';
 
 use Getopt::Long '2.33', qw( :config gnu_getopt );
 
 use Getopt::Long '2.33', qw( :config gnu_getopt );
-sub podexit {
-       require Pod::Usage;
-       Pod::Usage::pod2usage(-exitval => 0, -perldocopt => '-oman', @_);
-}
 my %opt;
 GetOptions(\%opt,
        'color|c!',
        'C' => sub { $opt{color} = 0 },
 my %opt;
 GetOptions(\%opt,
        'color|c!',
        'C' => sub { $opt{color} = 0 },
-       'field|f=s',
+       'field|f=s' => sub {
+               eval {
+                       local $_ = $_[1];
+                       $opt{anchor} = /\A[0-9]+\z/ ? qr/(?:\S*\h+){$_}\K/ : qr/$_/;
+               } or die $@ =~ s/(?:\ at\ \N+)?\Z/ for option $_[0]/r;
+       },
        'human-readable|H!',
        'interval|t:i',
        'trim|length|l=s' => sub {
                my ($optname, $optval) = @_;
                $optval =~ s/%$// and $opt{trimpct}++;
        'human-readable|H!',
        'interval|t:i',
        'trim|length|l=s' => sub {
                my ($optname, $optval) = @_;
                $optval =~ s/%$// and $opt{trimpct}++;
-               $optval =~ m/^-?[0-9]+$/ or die(
+               $optval =~ m/\A-?[0-9]+\z/ or die(
                        "Value \"$optval\" invalid for option $optname",
                        " (number or percentage expected)\n"
                );
                        "Value \"$optval\" invalid for option $optname",
                        " (number or percentage expected)\n"
                );
@@ -32,124 +33,308 @@ GetOptions(\%opt,
        'value-length=i',
        'hidemin=i',
        'hidemax=i',
        'value-length=i',
        'hidemin=i',
        'hidemax=i',
-       'limit|L=s' => sub {
+       'minval=f',
+       'maxval=f',
+       'limit|L:s' => sub {
                my ($optname, $optval) = @_;
                $optval ||= 0;
                ($opt{hidemin}, $opt{hidemax}) =
                my ($optname, $optval) = @_;
                $optval ||= 0;
                ($opt{hidemin}, $opt{hidemax}) =
-               $optval =~ m/\A (?: ([0-9]+)? - )? ([0-9]+)? \z/x or die(
+               $optval =~ m/\A (?: ([0-9]+)? - )? ([0-9]+)? \z/ or die(
                        "Value \"$optval\" invalid for option limit",
                        " (range expected)\n"
                );
        },
                        "Value \"$optval\" invalid for option limit",
                        " (range expected)\n"
                );
        },
+       'header!',
        'markers|m=s',
        'markers|m=s',
+       'graph-format=s' => sub {
+               $opt{'graph-format'} = substr $_[1], 0, 1;
+       },
+       'spark:s' => sub {
+               $opt{spark} = [split //, $_[1] || ' ▁▂▃▄▅▆▇█'];
+       },
+       'palette=s' => sub {
+               $opt{palette} = {
+                       fire   => [qw( 90 31 91 33 93 97 96 )],
+                       fire88 => [map {"38;5;$_"} qw(
+                               80  32 48 64  68 72 76  77 78 79  47
+                       )],
+                       fire256=> [map {"38;5;$_"} qw(
+                               235  52 88 124 160 196
+                               202 208 214 220 226  227 228 229 230 231  159
+                       )],
+                       ramp88 => [map {"38;5;$_"} qw(
+                               64 65 66 67 51 35 39 23 22 26 25 28
+                       )],
+                       whites => [qw( 1;30 0;37 1;37 )],
+                       greys  => [map {"38;5;$_"} 52, 235..255, 47],
+               }->{$_[1]} // [ split /[^0-9;]/, $_[1] ];
+       },
+       'stat|s!',
+       'signal-stat=s',
        'unmodified|u!',
        'width|w=i',
        'unmodified|u!',
        'width|w=i',
-       'usage|h' => sub { podexit() },
-       'help'    => sub { podexit(-verbose => 2) },
+       'version' => sub {
+               say "barcat version $VERSION";
+               exit;
+       },
+       'usage|h' => sub {
+               local $/ = undef;  # slurp
+               my $pod = readline *DATA;
+               $pod =~ s/^=over\K/ 25/;  # indent options list
+               $pod =~ s/^=item\ \N*\n\n\N*\n\K (?:(?:^=over.*?^=back\n)?(?!=)\N*\n)*/\n/g;
+               $pod =~ s/[.,](?=\n)//g;  # trailing punctuation
+               $pod =~ s/^=item\ \K(?=--)/____/g;  # align long options
+               # abbreviate <variable> indicators
+               $pod =~ s/\Q>.../s>/g;
+               $pod =~ s/<(?:number|count|seconds)>/N/g;
+               $pod =~ s/<character(s?)>/\Uchar$1/g;
+               $pod =~ s/\Q | /|/g;
+               $pod =~ s/(?<!\w)<([a-z]+)>/\U$1/g;  # uppercase
+
+               require Pod::Usage;
+               my $parser = Pod::Usage->new(USAGE_OPTIONS => {
+                       -indent => 2, -width => 78,
+               });
+               $parser->select('SYNOPSIS', 'OPTIONS');
+               $parser->output_string(\my $contents);
+               $parser->parse_string_document($pod);
+
+               $contents =~ s/\n(?=\n\h)//msg;  # strip space between items
+               $contents =~ s/^\ \ \K____/    /g;  # nbsp substitute
+               print $contents;
+               exit;
+       },
+       'help|?'  => sub {
+               require Pod::Usage;
+               Pod::Usage::pod2usage(
+                       -exitval => 0, -perldocopt => '-oman', -verbose => 2,
+               );
+       },
 ) or exit 64;  # EX_USAGE
 
 ) or exit 64;  # EX_USAGE
 
-$opt{width} ||= $ENV{COLUMNS} || 80;
+$opt{width} ||= $ENV{COLUMNS} || qx(tput cols) || 80 unless $opt{spark};
 $opt{color} //= -t *STDOUT;  # enable on tty
 $opt{color} //= -t *STDOUT;  # enable on tty
+$opt{'graph-format'} //= '-';
 $opt{trim}   *= $opt{width} / 100 if $opt{trimpct};
 $opt{trim}   *= $opt{width} / 100 if $opt{trimpct};
-$opt{units}   = $opt{'human-readable'} && ['', qw( k M G T <> n μ m )];
+$opt{units}   = [split //, ' kMGTPEZYyzafpnμm'] if $opt{'human-readable'};
+$opt{anchor} //= qr/\A/;
+$opt{'value-length'} = 6 if $opt{units};
+$opt{'value-length'} = 1 if $opt{unmodified};
+$opt{'signal-stat'} //= exists $SIG{INFO} ? 'INFO' : 'QUIT';
+$opt{markers} //= '=avg >31.73v <68.27v +50v |0';
+$opt{palette} //= $opt{color} && [31, 90, 32];
+$opt{hidemin} = ($opt{hidemin} || 1) - 1;
+$opt{input} = (@ARGV && $ARGV[0] =~ m/\A[-0-9]/) ? \@ARGV : undef
+       and undef $opt{interval};
+
+my (@lines, @values, @order);
+
+$SIG{$_} = \&show_stat for $opt{'signal-stat'} || ();
+$SIG{ALRM} = sub {
+       show_lines();
+       alarm $opt{interval} if defined $opt{interval} and $opt{interval} > 0;
+};
+$SIG{INT} = \&show_exit;
 
 if (defined $opt{interval}) {
        $opt{interval} ||= 1;
 
 if (defined $opt{interval}) {
        $opt{interval} ||= 1;
-       $SIG{ALRM} = sub {
-               show_lines();
-               alarm $opt{interval};
-       };
-       alarm $opt{interval};
-}
+       alarm $opt{interval} if $opt{interval} > 0;
 
 
-$SIG{INT} = 'IGNORE';  # continue after assumed eof
+       eval {
+               require Tie::Array::Sorted;
+               tie @order, 'Tie::Array::Sorted', sub { $_[1] <=> $_[0] };
+       } or warn $@, "Expect slowdown with large datasets!\n";
+}
 
 
-my (@lines, @values);
-my $anchor = !defined $opt{field} ? qr/\A/ :
-       $opt{field} =~ /^[0-9]+$/ ? qr/(?:\S*\h+){$opt{field}}\K/ :
-       $opt{field};
-while (readline) {
+my $valmatch = qr<
+       $opt{anchor} ( \h* -? [0-9]* [.]? [0-9]+ (?: e[+-]?[0-9]+ )? |)
+>x;
+while (defined ($_ = $opt{input} ? shift @{ $opt{input} } : readline)) {
        s/\r?\n\z//;
        s/\r?\n\z//;
-       s/^\h*// unless $opt{unmodified};
-       push @values, s/$anchor ( \h* -? [0-9]* \.? [0-9]+ |)/\n/x && $1;
-       if (defined $opt{trim}) {
+       s/\A\h*// unless $opt{unmodified};
+       my $valnum = s/$valmatch/\n/ && $1;
+       push @values, $valnum;
+       push @order, $valnum if length $valnum;
+       if (defined $opt{trim} and defined $valnum) {
                my $trimpos = abs $opt{trim};
                my $trimpos = abs $opt{trim};
+               $trimpos -= length $valnum if $opt{unmodified};
                if ($trimpos <= 1) {
                if ($trimpos <= 1) {
-                       $_ = substr $_, 0, 1;
+                       $_ = substr $_, 0, 2;
                }
                elsif (length > $trimpos) {
                }
                elsif (length > $trimpos) {
+                       # cut and replace (intentional lvalue for speed, contrary to PBP)
                        substr($_, $trimpos - 1) = '…';
                }
        }
        push @lines, $_;
                        substr($_, $trimpos - 1) = '…';
                }
        }
        push @lines, $_;
+       show_lines() if defined $opt{interval} and $opt{interval} < 0
+               and $. % $opt{interval} == 0;
+}
+
+if ($opt{'zero-missing'}) {
+       push @values, (0) x 10;
 }
 
 $SIG{INT} = 'DEFAULT';
 
 }
 
 $SIG{INT} = 'DEFAULT';
 
-sub show_lines {
+sub color {
+       $opt{color} and defined $_[0] or return '';
+       return "\e[$_[0]m" if defined wantarray;
+       $_ = color(@_) . $_ . color(0) if defined;
+}
+
+sub sival {
+       my $unit = int(log(abs $_[0] || 1) / log(10) - 3*($_[0] < 1) + 1e-15);
+       my $float = $_[0] !~ /\A0*[-0-9]{1,3}\z/;
+       return sprintf('%3.*f%1s',
+               $float && ($unit % 3) == ($unit < 0),  # tenths
+               $_[0] / 1000 ** int($unit/3),   # number
+               $#{$opt{units}} * 1.5 < abs $unit ? "e$unit" : $opt{units}->[$unit/3]
+       );
+}
 
 
-state $nr = $opt{hidemin} ? $opt{hidemin} - 1 : 0;
-@lines and @lines > $nr or return;
+sub show_lines {
 
 
-my @order  = sort { $b <=> $a } grep { length } @values;
-my $maxval = $opt{hidemax} ? max @values[0 .. $opt{hidemax} - 1] : $order[0];
-my $minval = min $order[-1], 0;
+state $nr = $opt{hidemin};
+@lines or return;
+@lines > $nr or return;
+
+@order = sort { $b <=> $a } @order unless tied @order;
+my $maxval = $opt{maxval} // (
+       $opt{hidemax} ? max grep { length } @values[0 .. $opt{hidemax} - 1] :
+       $order[0]
+) // 0;
+my $minval = $opt{minval} // min $order[-1] // (), 0;
+my $range = $maxval - $minval;
 my $lenval = $opt{'value-length'} // max map { length } @order;
 my $len    = defined $opt{trim} && $opt{trim} <= 0 ? -$opt{trim} + 1 :
        max map { length $values[$_] && length $lines[$_] }
                0 .. min $#lines, $opt{hidemax} || ();  # left padding
 my $lenval = $opt{'value-length'} // max map { length } @order;
 my $len    = defined $opt{trim} && $opt{trim} <= 0 ? -$opt{trim} + 1 :
        max map { length $values[$_] && length $lines[$_] }
                0 .. min $#lines, $opt{hidemax} || ();  # left padding
-my $size   = ($maxval - $minval) &&
-       ($opt{width} - $lenval - $len) / ($maxval - $minval);  # bar multiplication
+my $size   = defined $opt{width} && $range &&
+       ($opt{width} - $lenval - $len) / $range;  # bar multiplication
 
 my @barmark;
 
 my @barmark;
-if ($opt{markers} // 1 and $size > 0) {
-       my sub orderpos { (($order[$_[0]] + $order[$_[0] + .5]) / 2 - $minval) * $size }
-       $barmark[ (sum(@order) / @order - $minval) * $size ] = '=';  # average
-       $barmark[ orderpos($#order * .31731) ] = '>';
-       $barmark[ orderpos($#order * .68269) ] = '<';
-       $barmark[ orderpos($#order / 2) ] = '+';  # mean
-       $barmark[ -$minval * $size ] = '|' if $minval < 0;  # zero
-       defined and $opt{color} and $_ = "\e[36m$_\e[0m" for @barmark;
+if ($opt{markers} and $size > 0) {
+       for my $markspec (split /\h/, $opt{markers}) {
+               my ($char, $func) = split //, $markspec, 2;
+               my $pos = eval {
+                       if ($func eq 'avg') {
+                               return sum(@order) / @order;
+                       }
+                       elsif ($func =~ /\A([0-9.]+)v\z/) {
+                               die "Invalid marker $char: percentile $1 out of bounds\n" if $1 > 100;
+                               my $index = $#order * $1 / 100;
+                               return ($order[$index] + $order[$index + .5]) / 2;
+                       }
+                       elsif ($func =~ /\A-?[0-9.]+\z/) {
+                               return $func;
+                       }
+                       else {
+                               die "Unknown marker $char: $func\n";
+                       }
+               };
+               defined $pos or do {
+                       warn $@ if $@;
+                       next;
+               };
+               $pos -= $minval;
+               $pos >= 0 or next;
+               color(36) for $barmark[$pos * $size] = $char;
+       }
 
        state $lastmax = $maxval;
        if ($maxval > $lastmax) {
                print ' ' x ($lenval + $len);
 
        state $lastmax = $maxval;
        if ($maxval > $lastmax) {
                print ' ' x ($lenval + $len);
-               printf "\e[90m" if $opt{color};
+               printf color(90);
                printf '%-*s',
                        ($lastmax - $minval) * $size + .5,
                        '-' x (($values[$nr - 1] - $minval) * $size);
                printf '%-*s',
                        ($lastmax - $minval) * $size + .5,
                        '-' x (($values[$nr - 1] - $minval) * $size);
-               print "\e[92m" if $opt{color};
-               say '+' x (($maxval - $lastmax - $minval) * $size + .5);
-               print "\e[0m" if $opt{color};
+               print color(92);
+               say '+' x (($range - $lastmax) * $size + .5);
+               print color(0);
                $lastmax = $maxval;
        }
 }
 
                $lastmax = $maxval;
        }
 }
 
+say(
+       color(31), sprintf('%*s', $lenval, $minval),
+       color(90), '-', color(36), '+',
+       color(32), sprintf('%*s', $size * $range - 3, $maxval),
+       color(90), '-', color(36), '+',
+       color(0),
+) if $opt{header};
+
 while ($nr <= $#lines) {
 while ($nr <= $#lines) {
-       $nr >= $opt{hidemax} and last if $opt{hidemax};
+       $nr >= $opt{hidemax} and last if defined $opt{hidemax};
        my $val = $values[$nr];
        my $val = $values[$nr];
+       my $rel = length $val && $range && ($val - $minval) / $range;
+       my $color = !length $val || !$opt{palette} ? undef :
+               $val == $order[0] ? $opt{palette}->[-1] : # max
+               $val == $order[-1] ? $opt{palette}->[0] : # min
+               $opt{palette}->[ $rel * ($#{$opt{palette}} - 1) + 1 ];
+
+       if ($opt{spark}) {
+               say '' if $opt{width} and $nr and $nr % $opt{width} == 0;
+               print color($color), $opt{spark}->[
+                       !$val || !$#{$opt{spark}} ? 0 : # blank
+                       $val == $order[0] ? -1 : # max
+                       $val == $order[-1] ? 1 : # min
+                       $#{$opt{spark}} < 3 ? 1 :
+                       $rel * ($#{$opt{spark}} - 3) + 2.5
+               ];
+               next;
+       }
+
        if (length $val) {
        if (length $val) {
-               my $color = !$opt{color} ? 0 :
-                       $val == $order[0] ? 32 : # max
-                       $val == $order[-1] ? 31 : # min
-                       90;
-               $val = sprintf "%3.1f%1s", $val / 1000**$_, $opt{units}->[$_]
-                       for $opt{units} ? int(log($val) / log(1000) - ($val < 1)) : ();
-               $val = sprintf "%*s", $lenval, $val;
-               $val = "\e[${color}m$val\e[0m" if $color;
+               $val = $opt{units} ? sival($val) : sprintf "%*s", $lenval, $val;
+               color($color) for $val;
        }
        my $line = $lines[$nr] =~ s/\n/$val/r;
        printf '%-*s', $len + length($val), $line;
        }
        my $line = $lines[$nr] =~ s/\n/$val/r;
        printf '%-*s', $len + length($val), $line;
-       print $barmark[$_] // '-' for 1 .. $size && (($values[$nr] || 0) - $minval) * $size + .5;
+       print $barmark[$_] // $opt{'graph-format'}
+               for 1 .. $size && (($values[$nr] || 0) - $minval) * $size + .5;
        say '';
        say '';
-
+}
+continue {
        $nr++;
 }
        $nr++;
 }
+say $opt{palette} ? color(0) : '' if $opt{spark};
+
+       return $nr;
+}
+
+sub show_stat {
+       if ($opt{hidemin} or $opt{hidemax}) {
+               printf '%s of ', sum(grep { length }
+                       @values[$opt{hidemin} .. ($opt{hidemax} || @lines) - 1]
+               ) // 0;
+       }
+       if (@order) {
+               my $total = sum @order;
+               printf '%s total', color(1) . sprintf('%.8g', $total) . color(0);
+               printf ' in %d values', scalar @order;
+               printf ' over %d lines', scalar @lines if @order != @lines;
+               printf(' (%s min, %s avg, %s max)',
+                       color(31) . $order[-1] . color(0),
+                       color(36) . (sprintf '%*.*f', 0, 2, $total / @order) . color(0),
+                       color(32) . $order[0] . color(0),
+               );
+       }
+       say '';
+       return 1;
+}
 
 
+sub show_exit {
+       show_lines();
+       show_stat() if $opt{stat};
+       exit 130 if @_;  # 0x80+signo
+       exit;
 }
 }
-show_lines();
+
+show_exit();
 
 __END__
 
 __END__
+=encoding utf8
 
 =head1 NAME
 
 
 =head1 NAME
 
@@ -157,14 +342,21 @@ barcat - graph to visualize input values
 
 =head1 SYNOPSIS
 
 
 =head1 SYNOPSIS
 
-B<barcat> [<options>] [<input>]
+B<barcat> [<options>] [<file>... | <numbers>]
 
 =head1 DESCRIPTION
 
 
 =head1 DESCRIPTION
 
-Visualizes relative sizes of values read from input (file(s) or STDIN).
+Visualizes relative sizes of values read from input
+(parameters, file(s) or STDIN).
 Contents are concatenated similar to I<cat>,
 but numbers are reformatted and a bar graph is appended to each line.
 
 Contents are concatenated similar to I<cat>,
 but numbers are reformatted and a bar graph is appended to each line.
 
+Don't worry, barcat does not drink and divide.
+It can has various options for input and output (re)formatting,
+but remains limited to one-dimensional charts.
+For more complex graphing needs
+you'll need a larger animal like I<gnuplot>.
+
 =head1 OPTIONS
 
 =over
 =head1 OPTIONS
 
 =over
@@ -175,7 +367,7 @@ Force colored output of values and bar markers.
 Defaults on if output is a tty,
 disabled otherwise such as when piped or redirected.
 
 Defaults on if output is a tty,
 disabled otherwise such as when piped or redirected.
 
-=item -f, --field=(<number>|<regexp>)
+=item -f, --field=(<number> | <regexp>)
 
 Compare values after a given number of whitespace separators,
 or matching a regular expression.
 
 Compare values after a given number of whitespace separators,
 or matching a regular expression.
@@ -187,14 +379,21 @@ A string can indicate the starting position of a value
 or capture the numbers itself,
 for example I<-f'(\d+)'> for the first digits anywhere.
 
 or capture the numbers itself,
 for example I<-f'(\d+)'> for the first digits anywhere.
 
+=item --header
+
+Prepend a chart axis with minimum and maximum values labeled.
+
 =item -H, --human-readable
 
 Format values using SI unit prefixes,
 turning long numbers like I<12356789> into I<12.4M>.
 =item -H, --human-readable
 
 Format values using SI unit prefixes,
 turning long numbers like I<12356789> into I<12.4M>.
+Also changes an exponent I<1.602176634e-19> to I<160.2z>.
+Short integers are aligned but kept without decimal point.
 
 
-=item -t, --interval[=<seconds>]
+=item -t, --interval[=(<seconds> | -<lines>)]
 
 
-Interval time to output partial progress.
+Output partial progress every given number of seconds or input lines.
+An update can also be forced by sending a I<SIGALRM> alarm signal.
 
 =item -l, --length=[-]<size>[%]
 
 
 =item -l, --length=[-]<size>[%]
 
@@ -206,48 +405,88 @@ unless C<--length=0>.
 Prepend a dash (i.e. make negative) to enforce padding
 regardless of encountered contents.
 
 Prepend a dash (i.e. make negative) to enforce padding
 regardless of encountered contents.
 
-=item -L, --limit=(<count>|<start>-[<end>])
+=item -L, --limit[=(<count> | <start>-[<end>])]
 
 Stop output after a number of lines.
 All input is still counted and analyzed for statistics,
 but disregarded for padding and bar size.
 
 
 Stop output after a number of lines.
 All input is still counted and analyzed for statistics,
 but disregarded for padding and bar size.
 
-=item -m, --markers=
+=item --graph-format=<character>
 
 
-Statistical positions to indicate on bars.
-Cannot be customized yet,
-only disabled by providing an empty argument.
+Glyph to repeat for the graph line.
+Defaults to a dash C<->.
 
 
-Any value enables all marker characters:
+=item -m, --markers=<format>
+
+Statistical positions to indicate on bars.
+A single indicator glyph precedes each position:
 
 =over 2
 
 
 =over 2
 
-=item B<=>
+=item <number>
 
 
-Average:
-the sum of all values divided by the number of counted lines.
+Exact value to match on the axis.
+A vertical bar at the zero crossing is displayed by I<|0>
+for negative values.
+For example I<:3.14> would show a colon at pi.
 
 
-=item B<+>
+=item <percentage>I<v>
 
 
-Mean, median:
+Ranked value at the given percentile.
+The default shows I<+> at I<50v> for the mean or median;
 the middle value or average between middle values.
 the middle value or average between middle values.
+One standard deviation right of the mean is at about I<68.3v>.
+The default includes I<< >31.73v <68.27v >>
+to encompass all I<normal> results, or 68% of all entries, by B<< <--> >>.
+
+=item I<avg>
+
+Matches the average;
+the sum of all values divided by the number of counted lines.
+Indicated by default as I<=>.
 
 
-=item B<<>
+=back
 
 
-Standard deviation left of the mean.
-Only 16% of all values are lower.
+=item --min=<number>, --max=<number>
 
 
-=item B<< > >>
+Bars extend from 0 or the minimum value if lower,
+to the largest value encountered.
+These options can be set to customize this range.
 
 
-Standard deviation right of the mean.
-The part between B<< <--> >> encompass all I<normal> results,
-or 68% of all entries.
+=item --palette=(<preset> | <color>...)
 
 
-=back
+Override colors of parsed numbers.
+Can be any CSI escape, such as I<90> for default dark grey,
+or alternatively I<1;30> for bright black.
+
+In case of additional colors,
+the last is used for values equal to the maximum, the first for minima.
+If unspecified, these are green and red respectively (I<31 90 32>).
+Multiple intermediate colors will be distributed
+relative to the size of values.
+
+Predefined color schemes are named I<whites> and I<fire>,
+or I<greys> and I<fire256> for 256-color variants.
+
+=item --spark[=<characters>]
+
+Replace lines by I<sparklines>,
+single characters corresponding to input values.
+A specified sequence of unicode characters will be used for
+Of a specified sequence of unicode characters,
+the first one will be used for non-values,
+the last one for the maximum,
+the second (if any) for the minimum,
+and any remaining will be distributed over the range of values.
+Unspecified, block fill glyphs U+2581-2588 will be used.
+
+=item -s, --stat
+
+Total statistics after all data.
 
 =item -u, --unmodified
 
 
 =item -u, --unmodified
 
-Do not strip leading whitespace.
+Do not reformat values, keeping leading whitespace.
 Keep original value alignment, which may be significant in some programs.
 
 =item --value-length=<size>
 Keep original value alignment, which may be significant in some programs.
 
 =item --value-length=<size>
@@ -259,70 +498,109 @@ Reserved space for numbers.
 Override the maximum number of columns to use.
 Appended graphics will extend to fill up the entire screen.
 
 Override the maximum number of columns to use.
 Appended graphics will extend to fill up the entire screen.
 
+=item -h, --usage
+
+Overview of available options.
+
+=item --help
+
+Full documentation
+rendered by perldoc.
+
+=item --version
+
+Version information.
+
 =back
 
 =head1 EXAMPLES
 
 =back
 
 =head1 EXAMPLES
 
-Commonly used after counting, such as users on the current server:
+Draw a sine wave:
 
 
-    users | sed 's/ /\n/g' | sort | uniq -c | barcat
+    seq 30 | awk '{print sin($1/10)}' | barcat
 
 
-Letter frequencies in text files:
+Compare file sizes (with human-readable numbers):
 
 
-    cat /usr/share/games/fortunes/*.u8 |
-    perl -CO -nE 'say for grep length, split /\PL*/, uc' |
-    sort | uniq -c | barcat
+    du -d0 -b * | barcat -H
 
 
-Memory usage of user processes:
+Memory usage of user processes with long names truncated:
 
     ps xo %mem,pid,cmd | barcat -l40
 
 
     ps xo %mem,pid,cmd | barcat -l40
 
-Sizes (in megabytes) of all root files and directories:
+Monitor network latency from prefixed results:
+
+    ping google.com | barcat -f'time=\K' -t
+
+Commonly used after counting, for example users on the current server:
 
 
-    du -d0 -m * | barcat
+    users | tr ' ' '\n' | sort | uniq -c | barcat
+
+Letter frequencies in text files:
+
+    cat /usr/share/games/fortunes/*.u8 |
+    perl -CS -nE 'say for grep length, split /\PL*/, uc' |
+    sort | uniq -c | barcat
 
 Number of HTTP requests per day:
 
     cat log/access.log | cut -d\  -f4 | cut -d: -f1 | uniq -c | barcat
 
 
 Number of HTTP requests per day:
 
     cat log/access.log | cut -d\  -f4 | cut -d: -f1 | uniq -c | barcat
 
-Any kind of database query with leading counts:
+Any kind of database query with counts, preserving returned alignment:
 
     echo 'SELECT count(*),schemaname FROM pg_tables GROUP BY 2' |
     psql -t | barcat -u
 
 
     echo 'SELECT count(*),schemaname FROM pg_tables GROUP BY 2' |
     psql -t | barcat -u
 
-Exchange rate USD/EUR history from CSV download provided by ECB:
+In PostgreSQL from within the client:
+
+       postgres=> SELECT sin(generate_series(0, 3, .1)) \g |barcat
+
+Earthquakes worldwide magnitude 1+ in the last 24 hours:
+
+    curl https://earthquake.usgs.gov/earthquakes/feed/v1.0/summary/1.0_day.csv |
+    column -tns, | barcat -f4 -u -l80%
+
+External datasets, like movies per year:
+
+    curl https://github.com/prust/wikipedia-movie-data/raw/master/movies.json -L |
+    perl -054 -nlE 'say if s/^"year"://' | uniq -c | barcat
+
+But please get I<jq> to process JSON
+and replace the manual selection by C<< jq '.[].year' >>.
+
+Pokémon height comparison:
+
+    curl https://github.com/Biuni/PokemonGO-Pokedex/raw/master/pokedex.json -L |
+    jq -r '.pokemon[] | [.height,.num,.name] | join(" ")' | barcat
+
+USD/EUR exchange rate from CSV provided by the ECB:
 
     curl https://sdw.ecb.europa.eu/export.do \
          -Gd 'node=SEARCHRESULTS&q=EXR.D.USD.EUR.SP00.A&exportType=csv' |
     grep '^[12]' | barcat -f',\K' --value-length=7
 
 
     curl https://sdw.ecb.europa.eu/export.do \
          -Gd 'node=SEARCHRESULTS&q=EXR.D.USD.EUR.SP00.A&exportType=csv' |
     grep '^[12]' | barcat -f',\K' --value-length=7
 
-Total population history from the World Bank dataset (XML):
+Total population history in XML from the World Bank:
 
 
-    curl http://api.worldbank.org/v2/country/1W/indicator/SP.POP.TOTL |
+    curl http://api.worldbank.org/v2/country/1W/indicator/SP.POP.TOTL -L |
     xmllint --xpath '//*[local-name()="date" or local-name()="value"]' - |
     sed -r 's,</wb:value>,\n,g; s,(<[^>]+>)+, ,g' | barcat -f1 -H
 
     xmllint --xpath '//*[local-name()="date" or local-name()="value"]' - |
     sed -r 's,</wb:value>,\n,g; s,(<[^>]+>)+, ,g' | barcat -f1 -H
 
-Movies per year from prepared JSON data:
-
-    curl https://github.com/prust/wikipedia-movie-data/raw/master/movies.json |
-    jq '.[].year' | uniq -c | barcat
+And of course various Git statistics, such commit count by year:
 
 
-Pokémon height comparison:
+    git log --pretty=%ci | cut -b-4 | uniq -c | barcat
 
 
-       curl https://github.com/Biuni/PokemonGO-Pokedex/raw/master/pokedex.json |
-       jq -r '.pokemon[] | [.height,.num,.name] | join(" ")' | barcat
+Or the top 3 most frequent authors with statistics over all:
 
 
-Git statistics, such commit count by year:
+    git shortlog -sn | barcat -L3 -s
 
 
-    git log --pretty=%ci | cut -b-4 | uniq -c | barcat
+Sparkline graphics of simple input given as inline parameters:
 
 
-Or the most frequent authors:
+       barcat --spark= 3 1 4 1 5 0 9 2 4
 
 
-    git shortlog -sn | barcat -L3
+Activity graph of the last days (substitute date C<-v-{}d> on BSD):
 
 
-Latency history:
-
-    ping google.com | barcat -f'time=\K' -t
+    ( git log --pretty=%ci --since=30day | cut -b-10
+      seq 0 30 | xargs -i date +%F -d-{}day ) |
+    sort | uniq -c | awk '$1--' | barcat --spark
 
 =head1 AUTHOR
 
 
 =head1 AUTHOR