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