#!/usr/bin/perl

use Set::IntSpan;
use Tie::File;
use Fcntl ':flock';
sub mhc;

my $FILENAME = '.mjdseq';

my %canonical = ( 

                 '' => 'show',   # I AM THE DEFAULT

                 '+' => 'union',
                 'u' => 'union',
                 'o' => 'union',
                 'or' => 'union',
                 'union' => 'union',
                 'add' => 'union',
                 '*' => 'intersect',
                 'a' => 'intersect',
                 'and' => 'intersect',
                 'i' => 'intersect',
                 'intersection' => 'intersect',
                 'intersect' => 'intersect',
                 '-' => 'diff',
                 'd' => 'diff',
                 'w' => 'diff',
                 'without' => 'diff',
                 'difference' => 'diff',
                 'diff' => 'diff',
                 's' => 'show',
                 'p' => 'show',
                 'show' => 'show',
                 'print' => 'show',
                 '?' => 'show',
                 '=' => 'copy',
                 'copy' => 'copy',
                 'c' => 'copy',
                 'assign' => 'copy',
                 'set' => 'copy',
                 'x' => 'delete',
                 '0' => 'delete',
                 'del' => 'delete',
                 'delete' => 'delete',
                );

sub usage {
  warn "Usage: $0 [+folder] [sequence [OP [msglist]]]

  OPs are:\n";
  my %syn;
  for my $k (keys %canonical) {
    push @{$syn{$canonical{$k}}}, $k;
  }
  for my $op (sort keys %syn) {
    warn join " ", "", "", sort(@{$syn{$op}}), "\n";
  }
  exit 1;
}

my (@SEQUENCE, $TIED);
my $folder;
if ($ARGV[0] =~ /^\+(.*)/) {
  $folder = shift;
  mhc "folder -nocreate $folder";
  $folder =~ s/^\+//;
} else {
  $folder = mhc "folder -fast";
}
my $seqfile = seqfile($folder);
my $TIED = tie my @SEQUENCE, 'Tie::File', $seqfile 
  or die "Couldn't tie $seqfile: $!";
$TIED->flock(LOCK_EX) or die "Couldn't lock $seqfile: $!";
unless (@ARGV) {
  list_seqs();
  exit 0;
}

my $seqname = shift;
my $oop = shift;
my $op = $canonical{$oop};
unless (defined $op) {
  warn "Unknown operator '$oop'\n\n";
  usage();
}

my $seq = seq_read($seqname);
if ($op eq 'show') {            # special case
  print join "\n", $seq->elements, '';
  exit 0;
} elsif ($op eq 'delete') {     # another
  seq_delete($seqname);
  exit 0;
}
$seq = $seq->$op(\@ARGV);
seq_write($seqname, $seq);

exit 0;

################################################################

sub seqfile {
  my $folder = shift;
  my $folderdir = mhc "mhpath +$folder";
  "$folderdir/$FILENAME";
}

sub seq_read {
  my ($seqname) = @_;

  for my $n (0 .. $#SEQUENCE) {
    for ($SEQUENCE[$n]) {
      if (/^\Q$seqname\E:\s+(.*)/) {
        return Set::IntSpan->new($1);
      }
    }
  }
  return Set::IntSpan->new();
}

sub seq_write {
  my ($seqname, $data) = @_;
  my $newline = "$seqname: " . $data->run_list;

  for my $n (0 .. $#SEQUENCE) {
    for ($SEQUENCE[$n]) {
      if (/^\Q$seqname\E:\s+/) {
        $_ = $newline;
        return;
      }
    }
  }
  push @SEQUENCE, $newline;
}

sub seq_delete {
  my ($seqname) = @_;

  for my $n (0 .. $#SEQUENCE) {
    for ($SEQUENCE[$n]) {
      if (/^\Q$seqname\E:\s+(.*)/) {
        splice @SEQUENCE, $n, 1;
        return;
      }
    }
  }
}

sub list_seqs {
  for my $n (0 .. $#SEQUENCE) {
    for ($SEQUENCE[$n]) {
      if (/^([^:]*):/) {
        print "$1\n";
      } else {
        warn "Malformed line $n in sequence file!\n";
      }
    }
  }
}

sub mhc {
  my $cmd = shift;
  my $result = qx{$cmd};
  exit 1 unless defined $result;
  chomp $result;
  $result;
}

=head1 NAME

mark - mark messages in MH folders

=head1 SYNOPSIS

	mark [+folder]
	mark [+folder] sequencename
	mark [+folder] sequencename OP messagelist

=head1 DESCRIPTION

In all cases, the C<+folder> argument I<must> be first.  It specifies
the MH folder on which to operate.  Sequence namespaces are local to
each folder.  That is, the message set named C<fred> for folder
C<+inbox> is different from the message set named C<fred> for folder
C<+perl>.  if C<+folder> is omitted, the current MH folder is used.

C<mark> with no other arguments lists the names of all the marks you
have defined for the specified folder.

C<mark> with just a sequence name prints out a space-separated list of
the message numbers for the specified sequence.  It is not an error if
the sequence does not exist; the output will be empty.

C<mark> with an operator and a message list alters the specified
sequence.   The available operators are:

=over 4

=item C<union>

Adds the specified messages to the sequence.

Synonyms: C<+>, C<u>, C<or>, C<o>, C<add>.

=item C<diff>

Removes the specified messages from the sequence.

Synonyms: C<->, C<d>, C<difference>, C<without>, C<w>.

=item C<copy>

Sets the sequence contents to the specified message list, discarding
the old value entirely.

Synonyms: C<=>, C<assign>, C<set>.

=item C<intersect>

Removes all messages from the sequence except for those that appear in
the specified list.

Synonyms: C<*>, C<i>, C<and>, C<a>, C<intersection>.

B<Caution:> If you want to use the C<*> symbol in the shell, you will
probably have to put it in quotes.

=item C<delete>

Deletes the sequence entirely.

Synonyms: C<0>, C<x>, C<del>.

=item C<show>

Does not modify the sequence.  Instead, the sequence contents are printed.

Synonyms: C<?>, C<s>, C<print>, C<p>.

B<Caution:> If you want to use the C<?> symbol in the shell, you will
probably have to put it in quotes.


=back

=head1 AUTHOR

Mark Jason Dominus, C<mjd-mh-mark@plover.com>

=head1 COPYRIGHT

Copyright 2002 Mark Jason Dominus.  Unauthorized distribution prohibited. 

=cut

