| Mail-Thread-Chronological documentation | Contained in the Mail-Thread-Chronological distribution. |
Mail::Thread::Chronological - rearrange Mail::Thread::Containers into a Chronological structure
use Mail::Thread;
use Mail::Thread::Chronological;
my $threader = Mail::Thread->new( @messages );
my $lurker = Mail::Thread::Chronological->new;
$threader->thread;
for my $thread ($threader->rootset) {
for my $row ( $lurker->arrange( $thread ) ) {
my $container = grep { ref $_ } @$row;
print join('', map { ref $_ ? '*' : $_ } @$row),
" ", $container->messageid, "\n";
}
}
Given a Mail::Thread::Container, Mail::Thread::Chronological transforms the tree structure into a 2-dimensional array representing the history of a given thread in time.
The output is similar to that of the Lurker mail archiving system, with a couple of small exceptions:
The grid is populated with the characters ' ' (space), '-', '+', '|', '{', or Mail::Thread::Container objects. Lurker uses [a-g], and differentiates T-junctions from corners for you, this module assumes you will do that for yourself.
The characters mean:
empty cell
horizontal line
T junction or corner
vertical line
vertical line crossing over a horizontal line
In the original lurker crossing a path vertically is not allowed, this results in a lot of horizontal space being used.
your common or garden constructor
Returns an array of arrays representing the thread tree.
Extracts the time from a Mail::Thread::Container, returned as epoch seconds used to decide the order of adding messages to the rows.
Richard Clamp <richardc@unixbeard.net>
Lurker, the application that seems to have originated this form of time-based thread display.
| Mail-Thread-Chronological documentation | Contained in the Mail-Thread-Chronological distribution. |
use strict; package Mail::Thread::Chronological; use Mail::Thread (); use Date::Parse qw( str2time ); use List::Util qw( max ); use vars qw/$VERSION/; $VERSION = '1.22'; use constant debug => 0;
sub new { bless {}, $_[0] }
# identify the co-ordinates of something sub _cell { my $cells = shift; my $find = shift; for (my $y = 0; $y < @$cells; ++$y) { for (my $x = 0; $x < @{ $cells->[$y] }; ++$x) { my $here = $cells->[$y][$x]; return [$y, $x] if ref $here && $here == $find; } } return; } sub _draw_cells { my $cells = shift; # and again in their new state print map { $_ % 10 } 0..20; print "\n"; for my $row (@$cells) { my $this; for (@$row) { $this = $_ if ref $_; print ref $_ ? '*' : $_ ? $_ : ' '; } print "\t", $this->messageid, "\n"; } print "\n"; } sub arrange { my $self = shift; my $thread = shift; # show them in the old order, and take a copy of the containers # with messages on while we're at it my @messages; $thread->iterate_down( sub { my ($c, $d) = @_; print ' ' x $d, $c->messageid, "\n" if debug; push @messages, $c if $c->message; } ); # cells is the 2-d representation, row, col. the first # message will be at [0][0], it's first reply, [0][1] my @cells; # okay, wander them in date order @messages = sort { $self->extract_time( $a ) <=> $self->extract_time( $b ) } @messages; for (my $row = 0; $row < @messages; ++$row) { my $c = $messages[$row]; # and place them in cells # the first one - [0][0] unless (@cells) { $cells[$row][0] = $c; next; } # look up our parent my $first_parent = $c->parent; while ($first_parent && !$first_parent->message) { $first_parent = $first_parent->parent; } unless ($first_parent && $first_parent->message && _cell(\@cells, $first_parent) ) { # just drop it randomly to one side, since it doesn't # have a clearly identifiable parent my $col = (max map { scalar @$_ } @cells ); $cells[$row][$col] = $c; next; } my $col; my ($parent_row, $parent_col) = @{ _cell( \@cells, $first_parent ) }; if ($first_parent->child == $c) { # if we're the first child, then we directly beneath # them $col = $parent_col; } else { # otherwise, we have to shuffle accross into the first # free column # okay, figure out what the max col is $col = my $max_col = (max map { scalar @$_ } @cells ); # would drawing the simple horizontal line cross the streams? if (grep { ($cells[$parent_row][$_] || '') eq '|' } $parent_col+1..$max_col) { # we must not cross the streams (that would be bad). # if given this tree: # a + + # b | | # c | # d # # e arrives, and is a reply to b, we can't just do this: # a + + # b - - + # c | | # d | # e # # it's messy and confusing. instead we have to do # extra work so we end up at # a - + + # b + | | # | c | # | d # e print "Crossing the streams, horizontally\n" if debug; # we want to end up in $parent_col + 1 and # everything in that column needs to get shuffled # over one $col = $parent_col + 1; for my $r (@cells[0 .. $row - 1]) { next if @$r < $col; my $here = $r->[$col] || ''; # what to splice in my $splice = $here =~/[+\-]/ ? '-' : ' '; splice(@$r, $col, 0, $splice); } $col = $parent_col + 1; } # the path is now clear, add the line in for ($parent_col..$col) { $cells[$parent_row][$_] ||= '-'; } $cells[$parent_row][$col] = '+'; } # place the message $cells[$row][$col] = $c; # link with vertical dashes for ($parent_row+1..$row-1) { $cells[$_][$col] = ($cells[$_][$col] || '') eq '-' ? '{' : '|'; } _draw_cells(\@cells) if debug; } # pad the rows with spaces my $maxcol = max map { scalar @$_ } @cells; for my $row (@cells) { $row->[$_] ||= ' ' for (0..$maxcol-1); } return @cells; }
sub extract_time { my $self = shift; my $container = shift; my $date = Mail::Thread->_get_hdr( $container->message, 'date' ); return str2time( $date ); } 1; __END__