For the moment, I have taken over maintenance of cow of doom's node tracker.

This can also be found at

http://home.cinci.rr.com/dancers/code/e2info.pl

/msg avjewe or email ajewell@cinci.rr.com with comments, questions, fixes and such.


#!/usr/local/bin/perl -w
# e2info.pl - gathers user info from everything2.com.
# Copyright (C) 2000,2001 Will Woods (a.k.a. Cow Of Doom)
# Copyright (C) 2002 Andy JJewell (ajewell@cinci.rr.com)
# Distributed under the terms of the GNU General Public License,
# included here by reference.
#
# send comments, questions, and stories to the address above.
# history: 
# v1.0.22 27 january 2003. misc initialization fixes for the first run.
# v1.0.21 3 December 2002. Slight output change to show +/- in total as well as delta
# v1.0.20 23 November 2002. Bug fixes to yesterdays changes. Got some signs wrong in the output.
# v1.0.19 22 November 2002. Better formatting. Show new votes when  rep uchanged.
# v1.0.18 14 October 2002. Updated documentation, added --help
# v1.0.17 14 October 2002. General tidying of output. Added "ext" option.
# v1.0.16 27 September 2002. Subtract Nuke Requests and such. Add vote stats.
# v1.0.15 18 September 2002. Appends summary, with date to .e2history
# v1.0.14 8 September 2002. Now tracks merit, devotion and other stats.
# v1.0.13 28 August 2002. Now tracks number of messages.
# v1.0.12 31 July 2002. Changes in formats of a couple of different things.
# v1.0.11 23 May 2002. Yet another slight change in the login format.
# v1.0.10 19 March 2002. Another slight change in the login format.
# v1.0.9  18 Oct 2001. Integrated dotc's fix for fractional votes.
# v1.0.8  12 Sept 2001. Fixed new bug parsing saved data file.
# v1.0.7: 12 Sept 2001. show (+x/-y) with reputation.
#         change "update" to behave like "both".
# v1.0.6: 22 Aug 2001. Show number of C!s as well as change.
# v1.0.5: 21 Aug 2001. Generalize type handling. 
#         Types outside of the basic four no longer crash.
#         Added "use strict" and fixed as necessry.
# v1.0.4: 7 Aug 2001. Taken over by avjewe.
#         Updated for various format changes, multiple cools
# v1.0.3: The format of the Login screen changed, so no one
#         could log in. Fixed.
# v1.0.2: I was miscalculating Writeup Node-fu (WNF). fixed.
# v1.0.1: Fixed problem that would happen if file was saved with blanks
#          at the beginning of each line (e.g. copy-and-paste from Netscape)
#         Fixed bug where new nodes would not be displayed in Rep/Cool list
# v1.0.0: (initial release)

use strict;
$0="$0"; # Perl magic to clean the commandline from the process list
my $version = '1.0.21';

use LWP::UserAgent; # these are both part of libwww-perl, available
use HTTP::Cookies;  # at your friendly local CPAN mirror

my $baseurl="http://www.everything2.com/index.pl";
my $datafile="$ENV{HOME}/.e2info"; # save data here
my $historyfile="$ENV{HOME}/.e2history"; # save here
my $meritHist="$ENV{HOME}/.meritHist"; # save here
my $totalHist="$ENV{HOME}/.totalHist"; # save here
my $badLogin="$ENV{HOME}/.badLogin"; # save here
my $ext = 0;

$|=1;
my $ua = LWP::UserAgent->new(); 
$ua->env_proxy();
my $cookies = HTTP::Cookies->new();
$ua->cookie_jar($cookies);
$ARGV[0] = "" if (@ARGV == 0);

if ($ARGV[0] eq '--help') {
    HELP();
    exit(0);
}

if ((@ARGV < 2) || (@ARGV > 3)) {
    USAGE();
    exit(1);
}

sub HELP {
    USAGE();

print "
Provides these statistics :

Nodes      : Number of write-ups by you
XP         : Your current XP total
Cools      : Sum of the cools of all your write-ups
Max Rep    : The Maximum reputaion of any of your write-ups
Min Rep    : The Minimum reputaion of any of your write-ups
Total Rep  : Sum of the reputaion of all your write-ups
Node Fu    : XP / Nodes
WNF        : Writeup Node Fu (Total Rep + Cools*10) / Nodes + 1
Cool Ratio : Cools / Nodes
Messages   : Number of messages in your in box
Average Rep: Arithmetic mean of the reps of all your writeups
Median Rep : The rep of the middle node, after sorting by rep
Merit      : Arithmetic mean of the reps of the middle half of your write-ups
Devotion   : Merit * Nodes
Merit Range: The rep range involved in your Merit
Up votes   : Total up votes you've received
Down votes : Total down votes you've received
Votes      : Total up or down votes you've received
Max Cools  : Number of cools on your most-cooled wu
Max Votes  : Number of votes on your most-voted writeup
Popularity : Up votes / Votes

a breakdown of your write-ups by type


If you specify 'ext' on the command line, you get these Top 5's :

Highest Rep
Lowest Rep
Most Votes
Fewest Votes
Most Cools
";

}

sub USAGE {
print "
E2 Node Tracker by Cow Of Doom and avjewe, version $version

USAGE: $0 <username> <password> [update|check|both|ext]

Displays various statistics about you and your writeups, and
shows you what has changed since your last run

By default, the new stats are written to ~/.e2info
If 'check' is specified, ~/.e2info is not updated
If 'ext' is specified, the report also contains the
   top 5 write-ups of yours based on each of a number
   of critera.

Also appends a line to  ~/.e2history with each run, with tab separated columns :
date, XP, node, cools, total rep, messages, merit, devotion, mean rep, median rep,
upvotes, downvotes, maxcools, maxvotes

For more help, use --help
";
print '
Feedback to ajewell@cinci.rr.com or avjewe at E2
';
}

my $update = 0;
my $postupdate = 0;
if (@ARGV == 3) {
    if (lc($ARGV[2]) eq "update") {
        $postupdate = 1;
    }
    elsif (lc($ARGV[2]) eq "both") {
        $postupdate = 1;
    }
    elsif (lc($ARGV[2]) eq "ext") {
        $ext = 5;
        $postupdate = 1;
    }
    elsif (lc($ARGV[2]) =~ m/^ext(\d+)$/) {
        $ext = $1;
        $postupdate = 1;
    }
    elsif (lc($ARGV[2]) ne "check") {
        USAGE();
    }
}

print "Logging in...";
(my $homenode = &login(@ARGV)) 
    or die "failed";

# get the XP count from the user's homenode.
print "ok.\nGetting homenode...";
my %info;
my $snork = &getnode($homenode);
($info{xp}) = ($snork =~ 
m|HREF="/index.pl\?.*createtime%20DESC.*</a>/(-?\d+)|)
    or die "failed";

# get the User Search XML page, and array-ify it
print "ok.\nDoing user search...";
my $ffoo = getnode(762826);
my @data = split(/\n/,$ffoo) # 762826 = User Search XML Ticker
    or die "failed";
print "ok.\n";

foreach ('nodes') {$info{$_} = 0;} # initialize counters

my %node;
my %types;
# Read the info out of the User Search page.
my @reps;
my @wudata;

foreach (@data) { # loop over each line in the page
  if (/^<writeup/g) { # if this line is about a writeup..
    my %n;
    while (/ (\w+)=\"(.*?)\"/gc) { $n{$1}=$2; } # get node info
    my ($name, $type) = />(.*) \(([a-z]+)\)<\/writeup>/gc;
    if (($name eq "E2 Nuke Request") or
        ($name eq "Edit these E2 titles") or
        ($name eq "Nodeshells marked for destruction") or
        ($name eq "Broken Nodes")) {
        $info{xp}--;
        next;
    }
    $n{name} = $name;
    $n{type} = $type;
    $n{votes} = $n{downvotes} + $n{upvotes};
    push(@wudata, \%n);
    $types{$type} = 0 unless defined($types{$type});
    $types{$type}++; $info{nodes}++;
    push(@reps, $n{reputation});
    $info{totalrep} += $n{reputation};
    $info{cools} += $n{cooled};
    $info{downvotes} += $n{downvotes};
    $info{upvotes} += $n{upvotes};
    my $totVotes = $n{downvotes}+$n{upvotes};
    if ($info{nodes} == 1) {$info{maxrep}=$info{minrep}=$n{reputation}}
    if ($info{nodes} == 1) {$info{maxvotes}=$totVotes}
    if ($info{nodes} == 1) {$info{maxcools}=$n{cooled}}
    if ($n{reputation} > $info{maxrep}) {$info{maxrep}=$n{reputation}}
    if ($n{reputation} < $info{minrep}) {$info{minrep}=$n{reputation}}
    if ($totVotes > $info{maxvotes}) {$info{maxvotes} = $totVotes}
    if ($n{cooled} > $info{maxcools}) {$info{maxcools} = $n{cooled}}
    $node{$n{node_id}} = [$n{reputation},$n{cooled},$name,$n{upvotes},$n{downvotes}];
  }
}
my @rep2 = sort {$a <=> $b} @reps;

my $sz = 0 + @rep2;
my $stt = int(($sz)/4);
my $stp = int(($sz*3)/4 + 0.5);
my $tot = 0;
my $tot2 = 0;

my @counts;

for (my $i=0; $i<500; ++$i) {
   push @counts, 0;
}

for (my $i=$stt; $i<$stp; ++$i) {
   my $rep = $rep2[$i];
   $tot += $rep;
   $counts[$rep]++ if ($rep >= 0);
}

open(DATAFILE, ">>$meritHist") or die "Couldn't open $meritHist: $!";
print DATAFILE scalar gmtime;

for (my $i=0; $i<50; ++$i) {
   print DATAFILE "\t$i:$counts[$i]", if ($counts[$i]);
}
print DATAFILE "\n";
close(DATAFILE);

for (my $i=0; $i<500; ++$i) {
   $counts[$i] = 0;
}
my $minrep = $rep2[0];

for (my $i=0; $i<$sz; ++$i) {
   my $rep = $rep2[$i];
   $tot2 += $rep;
   $counts[$rep - $minrep]++;
}

open(DATAFILE, ">>$totalHist") or die "Couldn't open $totalHist: $!";
print DATAFILE scalar gmtime;

for (my $i=0; $i<500; ++$i) {
   my $rep = $i + $minrep;
   print DATAFILE "\t$rep:$counts[$i]" if ($counts[$i]);
}
print DATAFILE "\n";
close(DATAFILE);

my $foo = $stp-$stt;


my $median = $rep2[$sz/2];
my $average = $tot2/$sz;
my $merit = $tot/($stp-$stt);
my $devotion = int($merit * @rep2 + 0.5);
$info{average} = $average;
$info{median} = $median;
$info{merit} = $merit;
$info{devotion} = $devotion;
my $minmerit = $rep2[$stt];
my $maxmerit = $rep2[$stp-1];

undef(@data); # free the memory used by @data
if ($info{nodes}) {
  $info{wnf} = (($info{totalrep}+(10*$info{cools}))/$info{nodes})+1;
  $info{nodefu} = $info{xp}/$info{nodes};
  $info{coolratio} = ($info{cools}*100)/$info{nodes};
}

my ($oir, $onr) = &readdatafile($datafile);
my %oldinfo;
my %oldnode;
if ($oir == 0) {
  $update=1;
  print "missing. Will be created.\n";
} else {
  %oldinfo = %$oir, %oldnode = %$onr;
}

sub Update {
  open(DATAFILE,">$datafile") or die "Couldn't open $datafile: $!";
  print DATAFILE "$info{xp}:$info{nodes}:$info{cools}:$info{totalrep}:$info{messages}:",
  "$info{merit}:$info{devotion}:$info{average}:$info{median}:$info{upvotes}:$info{downvotes}:",
  "$info{maxcools}:$info{maxvotes}\n";
  foreach (sort {$b<=>$a} keys(%node)) {
    print DATAFILE "$_:",join(":",@{$node{$_}}),"\n";
  }    
  close(DATAFILE);

  open(DATAFILE, ">>$historyfile") or die "Couldn't open $datafile: $!";
  my $foo = scalar gmtime;
  print DATAFILE "$foo\t$info{xp}\t$info{nodes}\t$info{cools}\t$info{totalrep}\t$info{messages}\t",
  "$info{merit}\t$info{devotion}\t$info{average}\t$info{median}\t$info{upvotes}\t$info{downvotes}\t",
  "$info{maxcools}\t$info{maxvotes}\n";
  close(DATAFILE);
}

if ($update) {
    Update();
    exit(0); # no point in printing stats right after an update.
}

sub MakeEven {
    my ($aref) = @_;
    my $len = 0;
    foreach (@$aref) {
        $len = length($_) if (length($_) > $len)
    }
    foreach (@$aref) {
        if (m/--$/) {
            $_ .= "-" x ($len - length($_));
        }
        else {
            $_ .= " " x ($len - length($_));
        }
    }
};

$oldinfo{votes} = $oldinfo{upvotes} + $oldinfo{downvotes};
$info{votes} = $info{upvotes} + $info{downvotes};

$info{popularity} = 0;
$oldinfo{popularity} = 0;
$info{popularity} = $info{upvotes} * 100 / $info{votes} if ($info{votes});
$oldinfo{popularity} = $oldinfo{upvotes} * 100 / $oldinfo{votes} if ($oldinfo{votes});

my $line='-'x65;
printf("
        E2 USER INFO: last update %s
-----------------------------------------------------------------\n",
       scalar(localtime((stat($datafile))[9])));

my @c1;
my @c2;
my @c3;

push @c1, "Nodes: " . infodiff('nodes');
push @c2, "XP: "    . infodiff('xp');
push @c3, "Cools: " . infodiff('cools');

push @c1, "Max Rep: "   . infodiff('maxrep');
push @c2, "Min Rep: "   . infodiff('minrep');
push @c3, "Total Rep: " . infodiff('totalrep');

push @c1, "Node Fu: "    . infodiff_fp('nodefu');
push @c2, "WNF: "        . infodiff_fp('wnf');
push @c3, "Cool Ratio: " . infodiff_fp('coolratio', 'yes');

push @c1, "Messages: "    . infodiff('messages');
push @c2, "Average Rep: " . infodiff_fp('average');
push @c3, "Median rep: "  . infodiff('median');

push @c1, "Merit: "    . infodiff_fp('merit');
push @c2, "Devotion: " . infodiff('devotion');
push @c3, "Merit Range: $minmerit to $maxmerit";

push @c1, "Up votes: "   . infodiff('upvotes');
push @c2, "Down votes: " . infodiff('downvotes');
push @c3, "Votes : "     . infodiff('votes');

push @c1, "Max Cools: "  . infodiff('maxcools');
push @c2, "Max Votes : " . infodiff('maxvotes');
push @c3, "Popularity: " . infodiff_fp('popularity', 'yes');

MakeEven(\@c1);
MakeEven(\@c2);

for (my $i=0; $i<7; ++$i) {
    print "$c1[$i]   $c2[$i]   $c3[$i]\n";
}

print "\n";

if ($info{nodes}) {
  foreach (keys %types) {
    printf("%s: %3.1f%%    ",$_,(100 * $types{$_})/($info{nodes}));
  }
}


print("\n$line\n");

my $head="
                    Created/Nuked/Renamed:
Change    Title\n$line\n";
my $didhead = 0;
my @nodes;
foreach (uniq(sort({$b<=>$a} keys(%node),keys(%oldnode)))) {
    if (!exists($oldnode{$_})) {
        if (!$didhead) {print $head; $didhead = 1;}
        printf("Created | %s\n",$node{$_}->[2]);
        $oldnode{$_} = [0,0,$node{$_}->[2]];
        push @nodes, $_;
    } elsif (!exists($node{$_})) {
        if (!$didhead) {print $head; $didhead = 1;}
        printf("Nuked   | %s\n",$oldnode{$_}->[2]);
    } else {
        if ($node{$_}->[0] != $oldnode{$_}->[0]) {push (@nodes, $_);}
        if ($node{$_}->[1] != $oldnode{$_}->[1]) {push (@nodes, $_);}
        if ($node{$_}->[3] != $oldnode{$_}->[3]) {push (@nodes, $_);}
        if ($node{$_}->[4] != $oldnode{$_}->[4]) {push (@nodes, $_);}
        if ($node{$_}->[2] ne $oldnode{$_}->[2]) {
            if (!$didhead) {print $head; $didhead = 1;}
            printf("Renamed | %s->%s\n",$oldnode{$_}->[2],$node{$_}->[2]);
        }
    }
}
my $change;
if ($didhead) {print $line, "\n"; $change = 1;}

$head="
                  Reputation Changes / Cools:\n";

$didhead = 0;
my (@a1, @a2, @a3, @a4, @a5);
foreach (uniq(@nodes)) {
  if (!$didhead) {
      $didhead = 1;
      print $head; 
      push @a1, "Rep";
      push @a1, "---";
      push @a2, "+/-";
      push @a2, "---";
      push @a3, "C!";
      push @a3, "--";
      push @a4, "+/-";
      push @a4, "---";
      push @a5, "Title";
      push @a5, "-----";
  }
  $oldnode{$_}->[3] = 0 unless defined($oldnode{$_}->[3]);
  $oldnode{$_}->[4] = 0 unless defined($oldnode{$_}->[4]);
  my $d  = $node{$_}->[0]-$oldnode{$_}->[0];
  my $d1 = $node{$_}->[3]-$oldnode{$_}->[3];
  my $d2 = $node{$_}->[4]-$oldnode{$_}->[4];
  my $dcool = $node{$_}->[1] - $oldnode{$_}->[1];
  if ($node{$_}->[3] && $node{$_}->[4]) {
      push @a1, sprintf "%+i (%+i/%+i)", $node{$_}->[0], $node{$_}->[3], -$node{$_}->[4];
  }
  else {
      push @a1, sprintf "%+i", $node{$_}->[0];
  }
  if ($d1 and $d2) {
      push @a2, sprintf "%+i (%+i/%+i)", $d, $d1, -$d2;
  }
  else {
      push @a2, sprintf "%+i", $d;
  }
  push @a3, sprintf "%i", $node{$_}->[1];
  if ($dcool) {
      push @a4, sprintf "%+i", $dcool;
  }
  else {
      push @a4, "";
  }
  push @a5, $node{$_}->[2];
}

if ($didhead) {
    MakeEven(\@a1);
    MakeEven(\@a2);
    MakeEven(\@a3);
    MakeEven(\@a4);
    
    for (my $i=0; $i < @a1; ++$i) {
        print "$a1[$i]  $a2[$i]  $a3[$i]  $a4[$i]  $a5[$i]\n";
    }
    print "\n";
}
elsif (!$change) {
  print "No nodes changed.\n";
}

sub PrintOne {
    my $node = $_[0];
    print "$node->{reputation} (+$node->{upvotes}/-$node->{downvotes})";
    print "   $node->{cooled} C!   $node->{name}\n";
}

if ($postupdate) {
    Update();
}

if ($ext) {
    my @wu = sort {$a->{reputation} <=> $b->{reputation}} @wudata;

    print "\nTop $ext Lowest Rep : \n";
    for (my $i=0; $i<$ext; ++$i) {
        PrintOne($wu[$i]);
    }
    print "\nTop $ext Highest Rep : \n";
    for (my $i=0; $i<$ext; ++$i) {
        PrintOne($wu[-$i-1]);
    }

    @wu = sort {$a->{votes} <=> $b->{votes}} @wudata;

    print "\nTop $ext Fewest Votes : \n";
    for (my $i=0; $i<$ext; ++$i) {
        PrintOne($wu[$i]);
    }
    print "\nTop $ext Most Votes : \n";
    for (my $i=0; $i<$ext; ++$i) {
        PrintOne($wu[-$i-1]);
    }

    @wu = sort {$a->{cooled} <=> $b->{cooled}} @wudata;

    print "\nTop $ext Most Cools : \n";
    for (my $i=0; $i<$ext; ++$i) {
        PrintOne($wu[-$i-1]);
    }
    print "\n";

    @wu = sort {$a->{downvotes} <=> $b->{downvotes}} @wudata;

    print "\nTop $ext Most Downvotes : \n";
    for (my $i=0; $i<$ext; ++$i) {
        PrintOne($wu[-$i-1]);
    }
    print "\n";
}

#----- end of main program ------------------------------

#----- subroutines --------------------------------------

sub uniq {
# takes a list argument
# assumes the list is sorted (use sort() if not)
# returns that list with duplicates removed
# examples: @foo=uniq(@sorted); @foo=uniq(sort(@random));
  my (@list, @result);
  @list = @_; @result = ();
  foreach (@list) {
    if ((!@result) || ($result[-1] != $_)) {push(@result,$_);}
  }
  return(@result);
}



sub getnode {
# takes one argument: $node_id
# assumes that $ua is a valid HTTP::UserAgent object
# returns the contents of the page in a scalar variable
# example: $page = getnode($node_id);
    my $req = HTTP::Request->new('GET', "$baseurl?node_id=$_[0]");
    return($ua->request($req)->content());
}

sub login {
# takes two arguments: $username, $password
# assumes that $ua is a valid HTTP::UserAgent object
# returns node_id of homenode on success, 0 on failure
# example: $homenode = login($username, $password);
    my $homenode = 0;
    my $req = HTTP::Request->new('POST', "$baseurl?node_id=109");
    $req->content_type('application/x-www-form-urlencoded');
    $req->content("op=login&node_id=109&user=$_[0]&passwd=$_[1]");
    my $response = $ua->request($req);

    if ($response->content() =~ m|Log Out.*\n.*node_id=(\d+)|i) {
        $homenode = $1; 
    }
    else {
        open(FILE, ">$badLogin");
        print FILE $response->content();
        close(FILE);
    }
    if ($response->content() =~ m|you have <A [^>]+>(\d+)</a> messages|i) {
        $info{messages} = $1;
    }
    else {
        $info{messages} = 0;
    }
    return($homenode);
}

sub readdatafile {
# takes one argument: $filename
# gets stored user info from $filename.
# returns a list of two hash references.
# example: ($inforef, $noderef) = getinfo($filename);
    if (! -f $_[0]) {
        return(0);
    } else {
        my (%info, %node);
        open(DATA,$_[0]);
        ($info{xp}, $info{nodes}, $info{cools}, $info{totalrep}, $info{messages}, $info{merit}, 
         $info{devotion}, $info{average}, $info{median}, $info{upvotes}, $info{downvotes},
         $info{maxcools},$info{maxvotes}) = split(/:/,<DATA>);
        $info{xp} = 0 unless defined($info{xp}) && ($info{xp}) =~ m/^\d+$/;
        $info{cools} = 0 unless defined($info{cools}) && ($info{cools}) =~ m/^\d+$/;
        $info{totalrep} = 0 unless defined($info{totalrep}) && ($info{totalrep}) =~ m/^\d+$/;
        $info{nodes} = 0 unless defined($info{nodes}) && ($info{nodes}) =~ m/^\d+$/;
        $info{messages} = 0 unless defined($info{messages}) && ($info{messages}) =~ m/^\d+$/;
        $info{merit} = 0 unless defined($info{merit}) && ($info{merit}) =~ m/^[\d.]+$/;
        $info{devotion} = 0 unless defined($info{devotion}) && ($info{devotion}) =~ m/^[\d.]+$/;
        $info{average} = 0 unless defined($info{average}) && ($info{average}) =~ m/^[\d.]+$/;
        $info{median} = 0 unless defined($info{median}) && ($info{median}) =~ m/^[\d.]+$/;
        $info{upvotes} = 0 unless defined($info{upvotes}) && ($info{upvotes}) =~ m/^\d+$/;
        $info{downvotes} = 0 unless defined($info{downvotes}) && ($info{downvotes}) =~ m/^\d+$/;
        $info{maxcools} = 0 unless defined($info{maxcools}) && ($info{maxcools}) =~ m/^\d+$/;
        $info{maxvotes} = 0 unless defined($info{maxvotes}) && ($info{maxvotes}) =~ m/^\d+$/;
        chomp($info{xp}, $info{nodes}, $info{cools}, $info{totalrep}, $info{messages}, $info{merit}, 
              $info{devotion}, $info{average}, $info{median}, $info{upvotes}, $info{downvotes},
              $info{maxcools},$info{maxvotes});
        if ($info{nodes}) {
            $info{wnf} = (($info{totalrep}+(10*$info{cools}))/$info{nodes})+1;
            $info{nodefu} = $info{xp}/$info{nodes};
            $info{coolratio} = ($info{cools}*100)/$info{nodes};
        }
        while (<DATA>) {
            chomp;
            if (/^(\d+):(-?\d+):(\d+):(.+):(\d+):(\d+)$/) {
                @{$node{$1}} = ($2,$3,$4,$5,$6);
            }
            elsif (/^(\d+):(-?\d+):(\d+):(.*)$/) {
                @{$node{$1}} = ($2,$3,$4,0,0);
            }
            else {
                print STDERR "You Suck!\n";
            }
            if ($.<=2) {$info{maxrep}=$info{minrep}=$2}
            if ($2 > $info{maxrep}) {$info{maxrep}=$2}
            if ($2 < $info{minrep}) {$info{minrep}=$2}
        }
        close(DATA);
        return(\%info,\%node);
    }
}

sub infodiff {
# takes one argument. returns a string that shows the value of that 
# piece of the %info hash, and the change in the value, if any.
# example: infodiff('xp') could return "132", "133 (+1)", etc.
    my $arg = $_[0];
    my $str = $info{$arg};
    $oldinfo{$arg} = 0 unless defined($oldinfo{$arg});
    if ($oldinfo{$arg} != $info{$arg}) {
        $str .= " (".($info{$arg}>$oldinfo{$arg}?'+':'');
        $str .= ($info{$arg}-$oldinfo{$arg}).")";
    }
    return($str);
}

sub infodiff_fp {
# takes one argument. returns a string that shows the value of that 
# piece of the %info hash, and the change in the value, if any.
# example: infodiff('xp') could return "132", "133 (+1)", etc.
    my $arg = $_[0];
    my $perc = $_[1];
    
    my $str = sprintf "%1.2f", $info{$arg};
    if (defined($perc)) {$str .= '%';}

    $oldinfo{$arg} = 0 unless defined($oldinfo{$arg});

    my $diff = $info{$arg} - $oldinfo{$arg};
    if (($diff > 0.001) || ($diff < -0.001)) {
        $str .= " (";
        $str .= "+" if ($diff > 0);
        $str .= sprintf "%1.3f%s)", $diff, defined($perc) ? "%" : "";
    }
    return($str);
}

Y'know, if you log in, you can write something here, or contact authors directly on the site. Create a New User if you don't already have an account.