marker functions in report variables
[barcat.git] / barcat
diff --git a/barcat b/barcat
index 0f0cfb8d100aefe0a12665c8ffbd493d605b0050..08b3db2f9a5d00e5ce6d7efbfecbfdf291fd090a 100755 (executable)
--- a/barcat
+++ b/barcat
@@ -142,7 +142,6 @@ $opt{'graph-format'} //= '-';
 $opt{trim}   *= $opt{width} / 100 if $opt{trimpct};
 $opt{units}   = [split //, ' kMGTPEZYRQqryzafpn'.($opt{ascii} ? 'u' : 'μ').'m']
        if $opt{'human-readable'};
-$opt{anchor} //= qr/\A/;
 $opt{'value-length'} = 4 if $opt{units};
 $opt{'value-length'} = 1 if $opt{unmodified};
 $opt{'signal-stat'} //= exists $SIG{INFO} ? 'INFO' : 'QUIT';
@@ -211,13 +210,15 @@ if (defined $opt{interval}) {
 }
 
 my $float = qr<[0-9]* [.]? [0-9]+ (?: e[+-]?[0-9]+ )?>; # positive numberish
-my $valmatch = qr< $opt{anchor} ( \h* -? $float |) >;
+my $valmatch = $opt{anchor} // qr/\A/;
+$valmatch .= !$opt{count} ? qr/( \h* -? $float |)/ :
+       $opt{anchor} ? qr/(\S*)/ : qr/(.*)/;
+
 while (defined ($_ = $opt{input} ? shift @{ $opt{input} } : readline)) {
        s/\r?\n\z//;
        my $valnum;
        if ($opt{count}) {
-               ($valnum) = m/$opt{anchor} (\S*)/;
-               $valnum //= '';
+               $valnum = m/$valmatch/ && $1;
                $uniq{$valnum}++ and next;
                push @values, $valnum;
                s/\A/\n /;
@@ -286,21 +287,9 @@ my @barmark;
 if ($opt{markers} and $size > 0) {
        for my $markspec (split /\h/, $opt{markers}) {
                my ($char, $func) = split //, $markspec, 2;
+               my $increment = $func =~ s/[+]\z//;
                my @pos = eval {
-                       if ($func eq 'avg') {
-                               return sum(@order) / @order;
-                       }
-                       elsif ($func =~ /\A([0-9.]+)v\z/) {
-                               $1 <= 100 or die(
-                                       "Invalid marker $char: percentile $1 out of bounds\n"
-                               );
-                               my $index = $#order * $1 / 100;
-                               return ($order[$index] + $order[$index + .5]) / 2;
-                       }
-                       elsif ($func =~ /\A-?[0-9.]+\z/) {
-                               return $func;
-                       }
-                       elsif ($func =~ /\A\/($float)\z/) {
+                       if ($func =~ /\A\/($float)\z/) {
                                my @range = my $multiple = my $next = $1;
                                while ($next < $maxval) {
                                        $multiple *= 10 if $opt{log};
@@ -308,19 +297,17 @@ if ($opt{markers} and $size > 0) {
                                }
                                return @range;
                        }
-                       else {
-                               die "Unknown marker $char: $func\n";
-                       }
-               };
-               @pos or do {
-                       warn $@ if $@;
+                       return calc($func);
+               } or do {
+                       warn "Invalid marker $char: $@" if $@;
                        next;
                };
                for my $pos (@pos) {
                        $pos -= $minval;
                        $pos &&= log $pos if $opt{log};
                        $pos >= 0 or next;
-                       color(36) for $barmark[$pos / $range * $size] = $char;
+                       $increment ||= $minval && !$pos;
+                       color(36) for $barmark[$pos / $range * $size + $increment + .5] = $char;
                }
        }
 
@@ -401,6 +388,7 @@ say $opt{palette} ? color(0) : '' if $opt{spark};
 
 sub show_stat {
        my %vars = (
+               partsum => undef,
                count => int @order,
                lines => int @lines,
        );
@@ -422,12 +410,35 @@ sub show_stat {
        return 1;
 }
 
+sub calc {
+       my ($func) = @_;
+       if ($func eq 'avg') {
+               return sum(@order) / @order;
+       }
+       elsif ($func eq 'sum') {
+               return sum(@order);
+       }
+       elsif ($func =~ /\A([0-9.]+)v\z/) {
+               $1 <= 100 or die(
+                       "percentile $1 out of bounds\n"
+               );
+               my $index = $#order * $1 / 100;
+               return ($order[$index] + $order[$index + .5]) / 2;
+       }
+       elsif ($func =~ /\A-?[0-9.]+\z/) {
+               return $func;
+       }
+       else {
+               die "$func unknown\n";
+       }
+}
+
 sub varfmt {
        my ($fmt, $vars) = @_;
        $fmt =~ s[\$\{ \h*+ ((?: [^{}]++ | \{(?1)\} )+) \}]{
                my ($name, $op, $cmd) = split /\s*([;:])/, $1, 2;
                my $format = $name =~ s/\+// || $name !~ s/\#// && $opt{reformat};
-               local $_ = $vars->{$name};
+               local $_ = exists $vars->{$name} ? $vars->{$name} : calc($name);
                defined && do {
                        $_ = $opt{'value-format'}->($_) if $format;
                        if ($cmd and $op eq ':') {
@@ -508,7 +519,6 @@ Visualizes relative sizes of values read from input
 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
@@ -535,9 +545,13 @@ or the I<NO_COLOR> environment variable.
 =item B<-c>, B<--count>
 
 Omit repetitions and count the number of occurrences.
-Similar to piping input to C<sort | uniq -c>
+Similar to piping input through C<sort | uniq -c>
 but keeping the order of first appearances.
 
+Lines are omitted if they (or a specified field) are identical,
+and the amount of matches is prepended and used as values
+for bars and subsequent statistics.
+
 =item B<-f>, B<--field>=([B<+>]I<number> | I<regexp>)
 
 Compare values after a given number of whitespace separators,
@@ -633,7 +647,7 @@ to encompass all I<normal> results, or 68% of all entries, by I<< <--> >>.
 
 =item B<avg>
 
-Matches the average;
+Matches the average (arithmetic mean);
 the sum of all values divided by the number of counted lines.
 Indicated by default as C<=>.