#!/usr/bin/perl package Mail::Summary::Tools::CLI::ToText; use base qw/Mail::Summary::Tools::CLI::Command/; use strict; use warnings; use Class::Autouse (<<'#\'END_USE' =~ m!(\w+::[\w:]+)!g); #\ use Mail::Summary::Tools::Summary; use Mail::Summary::Tools::Output::TT; #'END_USE use Text::Wrap (); use constant options => ( [ 'verbose|v!' => "Output progress information" ], [ 'input|i=s' => 'Summary file to read' ], [ 'output|o=s' => 'The file to output (defaults to STDOUT)' ], [ 'shortening|s!' => "Enable URI shortening (defaults to true)", { default => 1 } ], [ 'shorten=s' => 'shortening service (defaults to "Metamark" -- http://xrl.us/)' ], #{ implies => "shortening", default => "Metamark" } ], [ 'archive|a=s' => 'The on-line archive to use (defaults to "google")', { default => "google" }], [ 'columns|c=i' => 'The column width to wrap to (defaults to 75)', { default => 75 } ], [ 'force_wrap|w!' => 'Force wrapping of overflowed text (like long URIs)' ], # whether or not to force wrapping of overflowing text [ 'template=s' => "Override the template toolkit file used to format the text" ], ); sub usage_desc { "%c totext %o [summary.yaml]" } sub wrap { my ( $self, $text, $columns, $first_indent, $rest_indent ) = @_; $columns ||= $self->{opt}{columns} || 80; $first_indent ||= ' '; $rest_indent ||= ' '; no warnings 'once'; local $Text::Wrap::huge = $self->_wrap_huge; local $Text::Wrap::columns = $columns; $text =~ s/\\(\S)/$1/g; # unquotemeta Text::Wrap::fill( $first_indent, $rest_indent, $self->process_body($text) ); } sub process_body { my ( $self, $text ) = @_; $text =~ s/<(\w+:\S+?)>/$self->expand_uri($1)/ge; $text =~ s/\[(.*?)\]\((\w+:\S+?)\)/$self->expand_uri($2, $1)/sge; return $text; } sub bullet { my ( $self, $text, $columns ) = @_; $self->wrap( $text, $columns, ' * ', ' ' ); } sub subject { my ( $self, $text, $columns ) = @_; $self->wrap( $text, $columns, ' ', ' ' ); } sub heading { my ( $self, $text, $columns ) = @_; $self->wrap( $text, $columns, ' ', ' ' ); } sub _wrap_huge { my $self = shift; return $self->{opt}{force_wrap} ? 'wrap' : 'overflow'; # default to not breaking URIs } sub shorten { my ( $self, $uri ) = @_; if ( $self->should_shorten($uri) ) { $self->really_shorten( $uri ); } else { return $uri; } } sub rt_uri { my ( $self, $rt, $id ) = @_; if ( $rt eq "perl" ) { return $self->shorten("http://rt.perl.org/rt3/Ticket/Display.html?id=$id"); } else { die "unknown rt installation: $rt"; } } sub link_to_message { my ( $self, $message_id, $text ) = @_; my $thread = $self->{__summary}->get_thread_by_id( $message_id ) || die "The link to <$message_id> could not be resolved, because no thread with that message ID is in the summary data"; my $uri = $self->shorten($thread->archive_link->thread_uri); $text ||= $thread->subject; "$text <$uri>"; } sub expand_uri { my ( $self, $uri_string, $text ) = @_; my $uri = URI->new($uri_string); if ( $uri->scheme eq 'rt' ) { my ( $rt, $id ) = ( $uri->authority, substr($uri->path, 1) ); my $rt_uri = $self->rt_uri($rt, $id); $text ||= "[$rt #$id]"; return "$text <$rt_uri>"; } elsif ( $uri->scheme eq 'msgid' ) { return $self->link_to_message( join("", grep { defined } $uri->authority, $uri->path), $text ); } else { my $short_uri = $self->shorten($uri) || $uri; return $text ? "$text <$short_uri>" : "<$short_uri>"; } } sub really_shorten { my ( $self, $uri ) = @_; my $service = $self->{opt}{shorten}; my $cache = $self->app->context->cache; my $cache_key = join(":", "shorten", $service, $uri); if ( my $short = $cache->get($cache_key) ) { return $short; } else { $self->diag( "Shortening URI (cache miss): $uri" ); my $mod = "WWW::Shorten::$service"; unless ( $mod->can("makeashorterlink") ) { my $file = join("/", split("::", $mod ) ) . ".pm"; require $file; } no strict 'refs'; my $short = &{"${mod}::makeashorterlink"}( $uri ); $cache->set( $cache_key, $short ) if $short; return $short || "$uri"; } } sub shortening_enabled { my ( $self, $uri ) = @_; if ( $self->{opt}{shorten} ) { return 1; } else { return; } } sub should_shorten { my ( $self, $uri ) = @_; return unless $self->shortening_enabled; length($uri) > 40 || $uri =~ /gmane|groups\.google/ } sub template_input { my $self = shift; if ( my $file = $self->{opt}{template} ) { open my $fh, "<", $file or die "Couldn't open template (open($file): $!)\n"; binmode $fh, ":utf8"; return $fh; } else { binmode DATA, ":utf8"; return \*DATA; } } sub template_output { my $self = shift; my $opt = $self->{opt}; if ( !$opt->{output} or $opt->{output} eq '-' ) { binmode STDOUT, ":utf8"; return \*STDOUT; } elsif ( my $file = $opt->{output} ) { open my $fh, ">", $file or die "Couldn't open output (open($file): $!)\n"; binmode $fh, ":utf8"; return $fh; } } sub validate { my ( $self, $opt, $args ) = @_; @$args and $opt->{$_} ||= shift @$args for qw/input output/; unless ( $opt->{input} ) { $self->usage_error("Please specify an input summary YAML file."); } if ( @$args ) { $self->usage_error("Unknown arguments: @$args."); } # FIXME implies is broken $opt->{shortening} = 1 if exists $opt->{shorten}; $opt->{shorten} ||= 'Metamark' if $opt->{shortening}; $self->{opt} = $opt; } sub run { my ( $self, $opt, $args ) = @_; my $summary = Mail::Summary::Tools::Summary->load( $opt->{input}, thread => { default_archive => $opt->{archive} || "google", archive_link_params => { cache => $self->app->context->cache }, }, ); my $o = Mail::Summary::Tools::Output::TT->new( template_input => $self->template_input, template_output => $self->template_output, ); $o->process( $self->{__summary} = $summary, # FIXME Output::Plain { shorten => sub { $self->shorten(shift) }, wrap => sub { $self->wrap(shift) }, bullet => sub { $self->bullet(shift) }, heading => sub { $self->heading(shift) }, subject => sub { $self->subject(shift) }, }, ); } __PACKAGE__; =pod =head1 NAME Mail::Summary::Tools::CLI::ToText - Emit a formatted plain text summary =head1 SYNOPSIS # see command line usage =head1 DESCRIPTION =cut __DATA__ [% summary.title %] [% IF summary.extra.header %][% FOREACH section IN summary.extra.header %][% heading(section.title) %] [% wrap(section.body) %] [% END %] [% END %][% FOREACH list IN summary.lists %][% num_threads = 0 %][% list_block = BLOCK %] [% list.title %] [% IF list.extra.description %] [% wrap(list.extra.description) %] [% END %][% FOREACH thread IN list.threads %][% IF thread.hidden %][% NEXT %][% END %][% num_threads = num_threads + 1 %] [% head = BLOCK %][% thread.subject %] <[% shorten(thread.archive_link.thread_uri) %]>[% END %][% subject(head) %] [% IF thread.summary %][% wrap(thread.summary) %] [% ELSE %] Posters:[% FOREACH participant IN thread.extra.posters %] - [% participant.name %][% END %] [% END %][% END %][% END %][% IF num_threads > 0 %][% list_block %][% END %][% END %][% IF summary.extra.footer %][% FOREACH section IN summary.extra.footer %] [% heading(section.title) %] [% wrap(section.body) %] [% END %] [% END %][% IF summary.extra.see_also %][% heading("See Also") %] [% FOREACH item IN summary.extra.see_also %][% link = BLOCK %][% item.name %] <[% shorten(item.uri ) %]>[% END %][% bullet(link) %] [% END %] [% END %]