use utf8;
use List::Util qw( min max sum );
use open qw( :std :utf8 );
+use re '/msx';
-our $VERSION = '1.06';
+our $VERSION = '1.07';
use Getopt::Long '2.33', qw( :config gnu_getopt );
my %opt;
'field|f=s' => sub {
eval {
local $_ = $_[1];
- $opt{anchor} = /^[0-9]+$/ ? qr/(?:\S*\h+){$_}\K/ : qr/$_/;
- } or die $@ =~ s/(?: at .+)?$/ for option $_[0]/r;
+ $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}++;
- $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"
);
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"
);
exit;
},
'usage|h' => sub {
- local $/;
+ local $/ = undef; # slurp
my $pod = readline *DATA;
- $pod =~ s/^=over\K/ 25/m; # indent options list
- $pod =~ s/^=item \N*\n\n\N*\n\K(?:(?:^=over.*?^=back\n)?(?!=)\N*\n)*/\n/msg;
+ $pod =~ s/^=over\K/ 25/; # indent options list
+ $pod =~ s{
+ ^=item \h \N*\n\n \N*\n \K # first line
+ (?: (?: ^=over .*? ^=back\n )? (?!=) \N*\n )*
+ }{\n}g; # abbreviate options
$pod =~ s/[.,](?=\n)//g; # trailing punctuation
- $pod =~ s/^=item \K(?=--)/____/gm; # align long options
+ $pod =~ s/^=item\ \K(?=--)/____/g; # align long options
# abbreviate <variable> indicators
$pod =~ s/\Q>.../s>/g;
$pod =~ s/<(?:number|count|seconds)>/N/g;
$parser->parse_string_document($pod);
$contents =~ s/\n(?=\n\h)//msg; # strip space between items
- $contents =~ s/^ \K____/ /gm; # nbsp substitute
+ $contents =~ s/^\ \ \K____/ /g; # nbsp substitute
print $contents;
exit;
},
$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
+$opt{input} = (@ARGV && $ARGV[0] =~ m/\A[-0-9]/) ? \@ARGV : undef
and undef $opt{interval};
my (@lines, @values, @order);
}
my $valmatch = qr<
- $opt{anchor} ( \h* -? [0-9]* \.? [0-9]+ (?: e[+-]?[0-9]+ )? |)
+ $opt{anchor} ( \h* -? [0-9]* [.]? [0-9]+ (?: e[+-]?[0-9]+ )? |)
>x;
while (defined ($_ = $opt{input} ? shift @{ $opt{input} } : readline)) {
s/\r?\n\z//;
- s/^\h*// unless $opt{unmodified};
- push @values, s/$valmatch/\n/ && $1;
- push @order, $1 if length $1;
- if (defined $opt{trim} and defined $1) {
+ 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};
- $trimpos -= length $1 if $opt{unmodified};
+ $trimpos -= length $valnum if $opt{unmodified};
if ($trimpos <= 1) {
$_ = substr $_, 0, 2;
}
elsif (length > $trimpos) {
+ # cut and replace (intentional lvalue for speed, contrary to PBP)
substr($_, $trimpos - 1) = '…';
}
}
sub sival {
my $unit = int(log(abs $_[0] || 1) / log(10) - 3*($_[0] < 1) + 1e-15);
- my $float = $_[0] !~ /^0*[-0-9]{1,3}$/;
- sprintf('%3.*f%1s',
+ 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]
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 = $range &&
+my $size = defined $opt{width} && $range &&
($opt{width} - $lenval - $len) / $range; # bar multiplication
my @barmark;
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;
}
- else {
+ elsif ($func =~ /\A-?[0-9.]+\z/) {
return $func;
}
- } - $minval;
+ 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;
}
if ($opt{spark}) {
say '' if $opt{width} and $nr and $nr % $opt{width} == 0;
print color($color), $opt{spark}->[
- !$val ? 0 : # blank
+ !$val || !$#{$opt{spark}} ? 0 : # blank
$val == $order[0] ? -1 : # max
$val == $order[-1] ? 1 : # min
$#{$opt{spark}} < 3 ? 1 :
}
say $opt{palette} ? color(0) : '' if $opt{spark};
+ return $nr;
}
sub show_stat {
);
}
say '';
+ return 1;
}
sub show_exit {
Earthquakes worldwide magnitude 1+ in the last 24 hours:
- https://earthquake.usgs.gov/earthquakes/feed/v1.0/summary/1.0_day.csv |
+ 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 |
+ 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
Pokémon height comparison:
- curl https://github.com/Biuni/PokemonGO-Pokedex/raw/master/pokedex.json |
+ 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:
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