+#!/usr/bin/perl
+# usage:
+# summarise-mailbox-preserving-privacy \
+# [<our options>] [--] <email>
+# our options:
+# -S<subject> default: "summary of messages on <mailname>"
+# -F<state file> default: $HOME/.summarise-mailbox/last<from options>
+# -f<mailbox> passed to from(1) must use -F if it contains / or |
+# -s<sender> passed to from(1) must use -F if it contains / or |
+# -q throw away stderr and always exit 0
+# -- end of our options
+
+use strict (qw(refs));
+use POSIX;
+use IO::Handle;
+use Fcntl (qw(:flock));
+
+@from_options=();
+$sendmail= '/usr/sbin/sendmail -odi -oee -oi -t';
+
+umask 077;
+while (@ARGV && $ARGV[0] =~ m/\-/) {
+ $_= shift;
+ if (m/^\-[fs]/) {
+ push @from_options, $_;
+ } elsif (s/^\-S//) {
+ $subject= $_;
+ } elsif (s/^\-F//) {
+ $statefile= $_;
+ } elsif (s/^\-q$//) {
+ $quiet= 1;
+ open STDERR, ">/dev/null";
+ eval('END { $?=0; }');
+ } elsif (m/^\-\-$/) {
+ last;
+ } else {
+ die "$0: unknown option \`$_'\n";
+ }
+}
+die unless @ARGV==1;
+$emailto= shift @ARGV;
+
+unless (defined $subject) {
+ my ($our_hostname);
+ open M, "/etc/mailname" or die $!;
+ defined($our_hostname= <M>) or die $!;
+ chomp($our_hostname);
+ close M;
+ $subject= "summary of messages on $our_hostname";
+}
+
+unless (defined $statefile) {
+ my ($dir);
+ die "$0: -F needed with that -f or -s\n"
+ if grep m:[|/]:, @from_options;
+ die "$0: no HOME in environment\n" unless defined $ENV{'HOME'};
+ $dir= $ENV{'HOME'}.'/.summarise-mailbox';
+ mkdir $dir, 02700 or $!==&EEXIST or die "$dir: $!";
+ $statefile= $dir.'/last'.
+ join('|',@from_options);
+}
+
+$statefile= "./$statefile" unless $statefile =~ m,^/,;
+$lockfile= $statefile.'.lock';
+$errfile= $statefile.'.err';
+
+open L, "+> $lockfile" or die "$lockfile: $!";
+flock L, LOCK_EX or die "$lockfile: $!";
+
+if ($quiet) {
+ open STDERR, "> $errfile";
+}
+
+sub parse($) {
+ my ($incr, $lasttime) = @_;
+ $lasttime= '';
+ while (defined($_= <F>)) {
+ print N or die "$statefile.new: $!" if $incr>0;
+ $have{$_} += $incr;
+ $have += $incr;
+ m/^From .* (\w+ \w+ \d+ [0-9:]+ \d+)$/ or die "$_ ?";
+ $lasttime= $1;
+ }
+ die $! if F->error;
+ return $lasttime;
+}
+
+if (open F, "< $statefile\0") {
+ $old_lasttime= parse(-1);
+ close F or die $!;
+} elsif ($! != &ENOENT) {
+ die "$statefile $!";
+}
+
+open N, "> $statefile.new" or die "$statefile.new: $!";
+
+$child= open F, "-|"; defined $child or die $!;
+if (!$child) {
+ exec "from",@from_options;
+ die "$!";
+}
+
+$did_have= $have;
+$new_lasttime= parse(+1);
+$?=0; close F or die "$? $!";
+
+close N or die "$statefile.new: $!";
+
+if ($new_lasttime ne $old_lasttime and $new_lasttime ne '') {
+ push @reasons, "Timestamp of last message in mailbox changed.";
+}
+
+map { $total_more+=$_ if $_>0 } values %have;
+
+if ($total_more) {
+ push @reasons, "$total_more message(s)".
+ " which were not previously present.";
+} elsif ($have>0) {
+ push @reasons, "More messages than previously reported.";
+}
+
+exit 0 unless @reasons;
+
+$new_have= $have - $did_have;
+
+$msg= <<END
+To: $emailto
+Subject: $subject
+
+Regarding your mailbox @from_options:
+
+Changes detected since last report:
+END
+ ;
+
+$msg .= join "\n", map { " $_" } @reasons;
+
+$msg .= <<END
+
+
+Now:
+ There are $new_have message(s).
+ The last is dated: $new_lasttime.
+
+--
+generated by $0
+END
+ ;
+
+open S, "| $sendmail" or die $!;
+print S $msg or die $!;
+$?=0; close S or die "$? $!";
+
+rename "$statefile.new", $statefile or die $!;