rainbow (lolcat) colour palette
[barcat.git] / barcat
1 #!/usr/bin/perl -CA
2 use 5.014;
3 use warnings;
4 use utf8;
5 use List::Util qw( min max sum );
6 use open qw( :std :utf8 );
7 use re '/msx';
8
9 our $VERSION = '1.08';
10
11 my %opt;
12 if (@ARGV) {
13 require Getopt::Long;
14 Getopt::Long->import('2.33', qw( :config gnu_getopt ));
15 GetOptions(\%opt,
16         'ascii|a!',
17         'color|c!',
18         'C' => sub { $opt{color} = 0 },
19         'field|f=s' => sub {
20                 eval {
21                         local $_ = $_[1];
22                         $opt{anchor} = /\A[0-9]+\z/ ? qr/(?:\S*\h+){$_}\K/ : qr/$_/;
23                 } or die $@ =~ s/(?:\ at\ \N+)?\Z/ for option $_[0]/r;
24         },
25         'human-readable|H!',
26         'interval|t:i',
27         'trim|length|l=s' => sub {
28                 my ($optname, $optval) = @_;
29                 $optval =~ s/%$// and $opt{trimpct}++;
30                 $optval =~ m/\A-?[0-9]+\z/ or die(
31                         "Value \"$optval\" invalid for option $optname",
32                         " (number or percentage expected)\n"
33                 );
34                 $opt{trim} = $optval;
35         },
36         'value-length=i',
37         'hidemin=i',
38         'hidemax=i',
39         'minval=f',
40         'maxval=f',
41         'limit|L:s' => sub {
42                 my ($optname, $optval) = @_;
43                 $optval ||= 0;
44                 $optval =~ /\A-[0-9]+\z/ and $optval .= '-';  # tail shorthand
45                 ($opt{hidemin}, $opt{hidemax}) =
46                 $optval =~ m/\A (?: (-? [0-9]+)? - )? ([0-9]+)? \z/ or die(
47                         "Value \"$optval\" invalid for option limit",
48                         " (range expected)\n"
49                 );
50         },
51         'header!',
52         'markers|m=s',
53         'graph-format=s' => sub {
54                 $opt{'graph-format'} = substr $_[1], 0, 1;
55         },
56         'spark|_!',
57         'indicators:s',
58         'palette=s' => sub {
59                 $opt{palette} = {
60                         fire   => [qw( 90 31 91 33 93 97 96 )],
61                         fire256=> [map {"38;5;$_"} qw(
62                                 235  52 88 124 160 196
63                                 202 208 214 220 226  227 228 229 230 231  159
64                         )],
65                         whites => [qw( 1;30 0;37 1;37 )],
66                         greys  => [map {"38;5;$_"} 0, 232..255, 15],
67                         rainbow=> [map {"38;5;$_"}
68                                 196, # r
69                                 (map { 196 + $_*6   } 0..4), # +g
70                                 (map { 226 - $_*6*6 } 0..4), # -r
71                                 (map {  46 + $_     } 0..4), # +b
72                                 (map {  51 - $_*6   } 0..4), # -g
73                                 (map {  21 + $_*6*6 } 0..4), # +r
74                                 (map { 201 - $_     } 0..4), # -b
75                                 196,
76                         ],
77                 }->{$_[1]} // [ split /[^0-9;]/, $_[1] ];
78         },
79         'stat|s!',
80         'signal-stat=s',
81         'unmodified|u!',
82         'width|w=i',
83         'version|V' => sub {
84                 my $mascot = $opt{ascii} ? '=^,^=' : 'ฅ^•ﻌ•^ฅ';
85                 say "barcat $mascot version $VERSION";
86                 exit;
87         },
88         'usage|h' => sub {
89                 /^=/ ? last : print for readline *DATA;  # text between __END__ and pod
90                 exit;
91         },
92         'help|?'  => sub {
93                 require Pod::Usage;
94                 Pod::Usage::pod2usage(
95                         -exitval => 0, -perldocopt => '-oman', -verbose => 2,
96                 );
97         },
98 ) or exit 64;  # EX_USAGE
99 }
100
101 $opt{width} ||= $ENV{COLUMNS} || qx(tput cols) || 80 unless $opt{spark};
102 $opt{color} //= -t *STDOUT;  # enable on tty
103 $opt{'graph-format'} //= '-';
104 $opt{trim}   *= $opt{width} / 100 if $opt{trimpct};
105 $opt{units}   = [split //, ' kMGTPEZYyzafpn'.($opt{ascii} ? 'u' : 'μ').'m']
106         if $opt{'human-readable'};
107 $opt{anchor} //= qr/\A/;
108 $opt{'value-length'} = 6 if $opt{units};
109 $opt{'value-length'} = 1 if $opt{unmodified};
110 $opt{'signal-stat'} //= exists $SIG{INFO} ? 'INFO' : 'QUIT';
111 $opt{markers} //= '=avg >31.73v <68.27v +50v |0';
112 $opt{palette} //= $opt{color} && [31, 90, 32];
113 $opt{indicators} = [split //,
114         $opt{indicators} || ($opt{ascii} ? ' .oO' : ' ▁▂▃▄▅▆▇█')
115 ] if defined $opt{indicators} or $opt{spark};
116 $opt{hidemin} = ($opt{hidemin} || 1) - 1;
117 $opt{input} = (@ARGV && $ARGV[0] =~ m/\A[-0-9]/) ? \@ARGV : undef
118         and undef $opt{interval};
119
120 $opt{'sum-format'} = sub { sprintf '%.8g', $_[0] };
121 $opt{'calc-format'} = sub { sprintf '%*.*f', 0, 2, $_[0] };
122 $opt{'value-format'} = $opt{units} && sub {
123         my $unit = (
124                 log(abs $_[0] || 1) / log(10)
125                 - 3 * (abs($_[0]) < .9995)   # shift to smaller unit if below 1
126                 + 1e-15  # float imprecision
127         );
128         my $decimal = ($unit % 3) == ($unit < 0);
129         $unit -= log($decimal ? .995 : .9995) / log(10);  # rounded
130         $decimal = ($unit % 3) == ($unit < 0);
131         $decimal &&= $_[0] !~ /^-?0*[0-9]{1,3}$/;  # integer 0..999
132         sprintf('%*.*f%1s',
133                 3 + ($_[0] < 0), # digits plus optional negative sign
134                 $decimal,  # tenths
135                 $_[0] / 1000 ** int($unit/3),  # number
136                 $#{$opt{units}} * 1.5 < abs $unit ? sprintf('e%d', $unit) :
137                         $opt{units}->[$unit/3]  # suffix
138         );
139 };
140
141
142 my (@lines, @values, @order);
143
144 $SIG{$_} = \&show_stat for $opt{'signal-stat'} || ();
145 $SIG{ALRM} = sub {
146         show_lines();
147         alarm $opt{interval} if defined $opt{interval} and $opt{interval} > 0;
148 };
149 $SIG{INT} = \&show_exit;
150
151 if (defined $opt{interval}) {
152         $opt{interval} ||= 1;
153         alarm $opt{interval} if $opt{interval} > 0;
154
155         eval {
156                 require Tie::Array::Sorted;
157                 tie @order, 'Tie::Array::Sorted', sub { $_[1] <=> $_[0] };
158         } or warn $@, "Expect slowdown with large datasets!\n";
159 }
160
161 my $valmatch = qr<
162         $opt{anchor} ( \h* -? [0-9]* [.]? [0-9]+ (?: e[+-]?[0-9]+ )? |)
163 >x;
164 while (defined ($_ = $opt{input} ? shift @{ $opt{input} } : readline)) {
165         s/\r?\n\z//;
166         s/\A\h*// unless $opt{unmodified};
167         my $valnum = s/$valmatch/\n/ && $1;
168         push @values, $valnum;
169         push @order, $valnum if length $valnum;
170         if (defined $opt{trim} and defined $valnum) {
171                 my $trimpos = abs $opt{trim};
172                 $trimpos -= length $valnum if $opt{unmodified};
173                 if ($trimpos <= 1) {
174                         $_ = substr $_, 0, 2;
175                 }
176                 elsif (length > $trimpos) {
177                         # cut and replace (intentional lvalue for speed, contrary to PBP)
178                         substr($_, $trimpos - 1) = $opt{ascii} ? '>' : '…';
179                 }
180         }
181         push @lines, $_;
182         show_lines() if defined $opt{interval} and $opt{interval} < 0
183                 and $. % $opt{interval} == 0;
184 }
185
186 if ($opt{'zero-missing'}) {
187         push @values, (0) x 10;
188 }
189
190 $SIG{INT} = 'DEFAULT';
191
192 sub color {
193         $opt{color} and defined $_[0] or return '';
194         return "\e[$_[0]m" if defined wantarray;
195         $_ = color(@_) . $_ . color(0) if defined;
196 }
197
198 sub show_lines {
199
200 state $nr =
201         $opt{hidemin} < 0 ? max(0, @lines + $opt{hidemin} + 1) :
202         $opt{hidemin};
203 @lines > $nr or return;
204
205 my $limit = $#lines;
206 if (defined $opt{hidemax}) {
207         if ($opt{hidemin} and $opt{hidemin} < 0) {
208                 $limit -= $opt{hidemax} - 1;
209         }
210         elsif ($opt{hidemax} <= $limit) {
211                 $limit = $opt{hidemax} - 1;
212         }
213 }
214
215 @order = sort { $b <=> $a } @order unless tied @order;
216 my $maxval = $opt{maxval} // (
217         $opt{hidemax} ? max grep { length } @values[$nr .. $limit] :
218         $order[0]
219 ) // 0;
220 my $minval = $opt{minval} // min $order[-1] // (), 0;
221 my $range = $maxval - $minval;
222 my $lenval = $opt{'value-length'} // max map { length } @order;
223 my $len    = defined $opt{trim} && $opt{trim} <= 0 ? -$opt{trim} + 1 :
224         max map { length $values[$_] && length $lines[$_] }
225                 0 .. min $#lines, $opt{hidemax} || ();  # left padding
226 my $size   = defined $opt{width} && $range &&
227         ($opt{width} - $lenval - $len - !!$opt{indicators}) / $range;  # bar multiplication
228
229 my @barmark;
230 if ($opt{markers} and $size > 0) {
231         for my $markspec (split /\h/, $opt{markers}) {
232                 my ($char, $func) = split //, $markspec, 2;
233                 my $pos = eval {
234                         if ($func eq 'avg') {
235                                 return sum(@order) / @order;
236                         }
237                         elsif ($func =~ /\A([0-9.]+)v\z/) {
238                                 die "Invalid marker $char: percentile $1 out of bounds\n" if $1 > 100;
239                                 my $index = $#order * $1 / 100;
240                                 return ($order[$index] + $order[$index + .5]) / 2;
241                         }
242                         elsif ($func =~ /\A-?[0-9.]+\z/) {
243                                 return $func;
244                         }
245                         else {
246                                 die "Unknown marker $char: $func\n";
247                         }
248                 };
249                 defined $pos or do {
250                         warn $@ if $@;
251                         next;
252                 };
253                 $pos -= $minval;
254                 $pos >= 0 or next;
255                 color(36) for $barmark[$pos * $size] = $char;
256         }
257
258         state $lastmax = $maxval;
259         if ($maxval > $lastmax) {
260                 print ' ' x ($lenval + $len);
261                 printf color(90);
262                 printf '%-*s',
263                         ($lastmax - $minval) * $size + .5,
264                         '-' x (($values[$nr - 1] - $minval) * $size);
265                 print color(92);
266                 say '+' x (($range - $lastmax) * $size + .5);
267                 print color(0);
268                 $lastmax = $maxval;
269         }
270 }
271
272 say(
273         color(31), sprintf('%*s', $lenval, $minval),
274         color(90), '-', color(36), '+',
275         color(32), sprintf('%*s', $size * $range - 3, $maxval),
276         color(90), '-', color(36), '+',
277         color(0),
278 ) if $opt{header};
279
280 while ($nr <= $limit) {
281         my $val = $values[$nr];
282         my $rel = length $val && $range && ($val - $minval) / $range;
283         my $color = !length $val || !$opt{palette} ? undef :
284                 $val == $order[0] ? $opt{palette}->[-1] : # max
285                 $val == $order[-1] ? $opt{palette}->[0] : # min
286                 $opt{palette}->[ $rel * ($#{$opt{palette}} - 1) + 1 ];
287         my $indicator = $opt{indicators} && $opt{indicators}->[
288                 !$val || !$#{$opt{indicators}} ? 0 : # blank
289                 $#{$opt{indicators}} < 2 ? 1 :
290                 $val >= $order[0] ? -1 :
291                 $rel * ($#{$opt{indicators}} - 1e-14) + 1
292         ];
293
294         if ($opt{spark}) {
295                 say '' if $opt{width} and $nr and $nr % $opt{width} == 0;
296                 print color($color), $_ for $indicator;
297                 next;
298         }
299         print $indicator if defined $indicator;
300
301         if (length $val) {
302                 $val = $opt{'value-format'} ? $opt{'value-format'}->($val) :
303                         sprintf "%*s", $lenval, $val;
304                 color($color) for $val;
305         }
306         my $line = $lines[$nr] =~ s/\n/$val/r;
307         if (not length $val) {
308                 say $line;
309                 next;
310         }
311         printf '%-*s', $len + length($val), $line;
312         print $barmark[$_] // $opt{'graph-format'}
313                 for 1 .. $size && (($values[$nr] || 0) - $minval) * $size + .5;
314         say '';
315 }
316 continue {
317         $nr++;
318 }
319 say $opt{palette} ? color(0) : '' if $opt{spark};
320
321         return $nr;
322 }
323
324 sub show_stat {
325         if ($opt{hidemin} or $opt{hidemax}) {
326                 my $linemin = $opt{hidemin};
327                 my $linemax = ($opt{hidemax} || @lines) - 1;
328                 if ($linemin < 0) {
329                         $linemin += @lines;
330                         $linemax = @lines - $linemax;
331                 }
332                 printf '%.8g of ', $opt{'sum-format'}->(
333                         sum(grep {length} @values[$linemin .. $linemax]) // 0
334                 );
335         }
336         if (@order) {
337                 my $total = sum @order;
338                 printf '%s total', color(1) . $opt{'sum-format'}->($total) . color(0);
339                 printf ' in %d values', scalar @order;
340                 printf ' over %d lines', scalar @lines if @order != @lines;
341                 printf(' (%s min, %s avg, %s max)',
342                         color(31) . ($opt{'value-format'} || sub {$_[0]})->($order[-1]) . color(0),
343                         color(36) . ($opt{'value-format'} || $opt{'calc-format'})->($total / @order) . color(0),
344                         color(32) . ($opt{'value-format'} || sub {$_[0]})->($order[0]) . color(0),
345                 );
346         }
347         say '';
348         return 1;
349 }
350
351 sub show_exit {
352         show_lines();
353         show_stat() if $opt{stat};
354         exit 130 if @_;  # 0x80+signo
355         exit;
356 }
357
358 show_exit();
359
360 __END__
361 Usage:                                               /\_/\
362   barcat [OPTIONS] [FILES|NUMBERS]                  (=•.•=)
363                                                     (u   u)
364 Options:
365   -a, --[no-]ascii         Restrict user interface to ASCII characters
366   -c, --[no-]color         Force colored output of values and bar markers
367   -f, --field=(N|REGEXP)   Compare values after a given number of whitespace
368                            separators
369       --header             Prepend a chart axis with minimum and maximum
370                            values labeled
371   -H, --human-readable     Format values using SI unit prefixes
372   -t, --interval[=(N|-LINES)]
373                            Output partial progress every given number of
374                            seconds or input lines
375   -l, --length=[-]SIZE[%]  Trim line contents (between number and bars)
376   -L, --limit[=(N|-LAST|START-[END])]
377                            Stop output after a number of lines
378       --graph-format=CHAR  Glyph to repeat for the graph line
379   -m, --markers=FORMAT     Statistical positions to indicate on bars
380       --min=N, --max=N     Bars extend from 0 or the minimum value if lower
381       --palette=(PRESET|COLORS)
382                            Override colors of parsed numbers
383   -_, --spark              Replace lines by sparklines
384       --indicators[=CHARS] Prefix a unicode character corresponding to each
385                            value
386   -s, --stat               Total statistics after all data
387   -u, --unmodified         Do not reformat values, keeping leading whitespace
388       --value-length=SIZE  Reserved space for numbers
389   -w, --width=COLUMNS      Override the maximum number of columns to use
390   -h, --usage              Overview of available options
391       --help               Full pod documentation
392   -V, --version            Version information
393
394 =encoding utf8
395
396 =head1 NAME
397
398 barcat - concatenate texts with graph to visualize values
399
400 =head1 SYNOPSIS
401
402 B<barcat> [<options>] [<file>... | <numbers>]
403
404 =head1 DESCRIPTION
405
406 Visualizes relative sizes of values read from input
407 (parameters, file(s) or STDIN).
408 Contents are concatenated similar to I<cat>,
409 but numbers are reformatted and a bar graph is appended to each line.
410
411 Don't worry, barcat does not drink and divide.
412 It can has various options for input and output (re)formatting,
413 but remains limited to one-dimensional charts.
414 For more complex graphing needs
415 you'll need a larger animal like I<gnuplot>.
416
417 =head1 OPTIONS
418
419 =over
420
421 =item -a, --[no-]ascii
422
423 Restrict user interface to ASCII characters,
424 replacing default UTF-8 by their closest approximation.
425 Input is always interpreted as UTF-8 and shown as is.
426
427 =item -c, --[no-]color
428
429 Force colored output of values and bar markers.
430 Defaults on if output is a tty,
431 disabled otherwise such as when piped or redirected.
432
433 =item -f, --field=(<number> | <regexp>)
434
435 Compare values after a given number of whitespace separators,
436 or matching a regular expression.
437
438 Unspecified or I<-f0> means values are at the start of each line.
439 With I<-f1> the second word is taken instead.
440 A string can indicate the starting position of a value
441 (such as I<-f:> if preceded by colons),
442 or capture the numbers itself,
443 for example I<-f'(\d+)'> for the first digits anywhere.
444
445 =item --header
446
447 Prepend a chart axis with minimum and maximum values labeled.
448
449 =item -H, --human-readable
450
451 Format values using SI unit prefixes,
452 turning long numbers like I<12356789> into I<12.4M>.
453 Also changes an exponent I<1.602176634e-19> to I<160.2z>.
454 Short integers are aligned but kept without decimal point.
455
456 =item -t, --interval[=(<seconds> | -<lines>)]
457
458 Output partial progress every given number of seconds or input lines.
459 An update can also be forced by sending a I<SIGALRM> alarm signal.
460
461 =item -l, --length=[-]<size>[%]
462
463 Trim line contents (between number and bars)
464 to a maximum number of characters.
465 The exceeding part is replaced by an abbreviation sign,
466 unless C<--length=0>.
467
468 Prepend a dash (i.e. make negative) to enforce padding
469 regardless of encountered contents.
470
471 =item -L, --limit[=(<count> | -<last> | <start>-[<end>])]
472
473 Stop output after a number of lines.
474 A single value indicates the last line number (like C<head>),
475 or first line counting from the bottom if negative (like C<tail>).
476 A specific range can be given by two values.
477
478 All input is still counted and analyzed for statistics,
479 but disregarded for padding and bar size.
480
481 =item --graph-format=<character>
482
483 Glyph to repeat for the graph line.
484 Defaults to a dash C<->.
485
486 =item -m, --markers=<format>
487
488 Statistical positions to indicate on bars.
489 A single indicator glyph precedes each position:
490
491 =over 2
492
493 =item <number>
494
495 Exact value to match on the axis.
496 A vertical bar at the zero crossing is displayed by I<|0>
497 for negative values.
498 For example I<:3.14> would show a colon at pi.
499
500 =item <percentage>I<v>
501
502 Ranked value at the given percentile.
503 The default shows I<+> at I<50v> for the mean or median;
504 the middle value or average between middle values.
505 One standard deviation right of the mean is at about I<68.3v>.
506 The default includes I<< >31.73v <68.27v >>
507 to encompass all I<normal> results, or 68% of all entries, by B<< <--> >>.
508
509 =item I<avg>
510
511 Matches the average;
512 the sum of all values divided by the number of counted lines.
513 Indicated by default as I<=>.
514
515 =back
516
517 =item --min=<number>, --max=<number>
518
519 Bars extend from 0 or the minimum value if lower,
520 to the largest value encountered.
521 These options can be set to customize this range.
522
523 =item --palette=(<preset> | <color>...)
524
525 Override colors of parsed numbers.
526 Can be any CSI escape, such as I<90> for default dark grey,
527 or alternatively I<1;30> for bright black.
528
529 In case of additional colors,
530 the last is used for values equal to the maximum, the first for minima.
531 If unspecified, these are green and red respectively (I<31 90 32>).
532 Multiple intermediate colors will be distributed
533 relative to the size of values.
534
535 Predefined color schemes are named I<whites> and I<fire>,
536 or I<greys> and I<fire256> for 256-color variants.
537
538 =item -_, --spark
539
540 Replace lines by I<sparklines>,
541 single characters (configured by C<--indicators>)
542 corresponding to input values.
543
544 =item --indicators[=<characters>]
545
546 Prefix a unicode character corresponding to each value.
547 The first specified character will be used for non-values,
548 the remaining sequence will be distributed over the range of values.
549 Unspecified, block fill glyphs U+2581-2588 will be used.
550
551 =item -s, --stat
552
553 Total statistics after all data.
554
555 =item -u, --unmodified
556
557 Do not reformat values, keeping leading whitespace.
558 Keep original value alignment, which may be significant in some programs.
559
560 =item --value-length=<size>
561
562 Reserved space for numbers.
563
564 =item -w, --width=<columns>
565
566 Override the maximum number of columns to use.
567 Appended graphics will extend to fill up the entire screen.
568
569 =item -h, --usage
570
571 Overview of available options.
572
573 =item --help
574
575 Full pod documentation
576 as rendered by perldoc.
577
578 =item -V, --version
579
580 Version information.
581
582 =back
583
584 =head1 EXAMPLES
585
586 Draw a sine wave:
587
588     seq 30 | awk '{print sin($1/10)}' | barcat
589
590 Compare file sizes (with human-readable numbers):
591
592     du -d0 -b * | barcat -H
593
594 Memory usage of user processes with long names truncated:
595
596     ps xo rss,pid,cmd | barcat -l40
597
598 Monitor network latency from prefixed results:
599
600     ping google.com | barcat -f'time=\K' -t
601
602 Commonly used after counting, for example users on the current server:
603
604     users | tr ' ' '\n' | sort | uniq -c | barcat
605
606 Letter frequencies in text files:
607
608     cat /usr/share/games/fortunes/*.u8 |
609     perl -CS -nE 'say for grep length, split /\PL*/, uc' |
610     sort | uniq -c | barcat
611
612 Number of HTTP requests per day:
613
614     cat httpd/access.log | cut -d\  -f4 | cut -d: -f1 | uniq -c | barcat
615
616 Any kind of database query with counts, preserving returned alignment:
617
618     echo 'SELECT count(*),schemaname FROM pg_tables GROUP BY 2' |
619     psql -t | barcat -u
620
621 In PostgreSQL from within the client:
622
623     > SELECT sin(generate_series(0, 3, .1)) \g |barcat
624
625 Earthquakes worldwide magnitude 1+ in the last 24 hours:
626
627     curl https://earthquake.usgs.gov/earthquakes/feed/v1.0/summary/1.0_day.csv |
628     column -tns, | barcat -f4 -u -l80%
629
630 External datasets, like movies per year:
631
632     curl https://github.com/prust/wikipedia-movie-data/raw/master/movies.json -L |
633     jq .[].year | uniq -c | barcat
634
635 Pokémon height comparison:
636
637     curl https://github.com/Biuni/PokemonGO-Pokedex/raw/master/pokedex.json -L |
638     jq -r '.pokemon[] | [.height,.num,.name] | join(" ")' | barcat
639
640 USD/EUR exchange rate from CSV provided by the ECB:
641
642     curl https://sdw.ecb.europa.eu/export.do \
643          -Gd 'node=SEARCHRESULTS&q=EXR.D.USD.EUR.SP00.A&exportType=csv' |
644     barcat -f',\K' --value-length=7
645
646 Total population history in XML from the World Bank:
647
648     curl http://api.worldbank.org/v2/country/1W/indicator/SP.POP.TOTL |
649     xmlstarlet sel -t -m '*/*' -v wb:date -o ' ' -v wb:value -n |
650     barcat -f1 -H
651
652 And of course various Git statistics, such commit count by year:
653
654     git log --pretty=%ci | cut -b-4 | uniq -c | barcat
655
656 Or the top 3 most frequent authors with statistics over all:
657
658     git shortlog -sn | barcat -L3 -s
659
660 Activity graph of the last days (substitute date C<-v-{}d> on BSD):
661
662     ( git log --pretty=%ci --since=30day | cut -b-10
663       seq 0 30 | xargs -i date +%F -d-{}day ) |
664     sort | uniq -c | awk '$1--' | barcat --spark
665
666 Sparkline graphics of simple input given as inline parameters:
667
668     barcat -_ 3 1 4 1 5 0 9 2 4
669
670 Misusing the spark functionality to draw a lolcat line:
671
672     seq $(tput cols) | barcat --spark --indicator=- --palette=rainbow
673
674 =head1 AUTHOR
675
676 Mischa POSLAWSKY <perl@shiar.org>
677
678 =head1 LICENSE
679
680 GPL3+.