#!/usr/bin/perl

package App::Siesh;

use strict;
use warnings;

use Term::ShellUI;
use Net::ManageSieve::Siesh;
use IO::Prompt;
use Getopt::Long;
use Pod::Usage;
use File::Temp qw/tempfile/;
use Config::Find;

our $VERSION = '0.06';

my ( $user, $host, %config );

if ( my $filename = Config::Find->find ) {
    %config = %{ equal_sep($filename) };
}

my $result = GetOptions( \%config, 'user=s', 'host=s' ) or pod2usage(2);

$config{user} ||= $ENV{USER};
$config{host} ||= 'imap';

my $sieve = Net::ManageSieve::Siesh->new( $config{host}, tls => 'require', )
  or die "Can't connect to $config{host}: $!\n";

my $password = prompt( "Password: ", -e => '*' );

$sieve->auth( $config{user}, $password ) or die "$@\n";

my $term = new Term::ShellUI(
    history_file => '~/.siesh_history',
    prompt       => 'siesh> ',
    commands     => {
        "help" => {
            desc   => "Print helpful information",
            args   => sub { shift->help_args( undef, @_ ); },
            method => sub { shift->help_call( undef, @_ ); }
        },
        "put" => {
            desc    => "Stores the FILE as NAME",
            maxargs => 2,
            minargs => 2,
            proc => sub { $sieve->putfile(@_) or die $sieve->error() . "\n" },
            args => sub { shift->complete_files(@_); },
        },
        "get" => {
            desc => "This command gets the contents of the specified script.",
            maxargs => 2,
            minargs => 2,
            proc => sub { $sieve->getfile(@_) or die $sieve->error() . "\n" },
            args => sub { shift->complete_files(@_); },
        },
        "quit" => {
            desc    => "Quit using siesh",
            maxargs => 0,
            method  => sub { $sieve->logout; shift->exit_requested(1); }
        },
        "list" => {
            desc =>
              "This command lists the scripts the user has on the server.",
            maxargs => 0,
            proc    => \&list_scripts,
        },
        "activate" => {
            desc    => "This command sets a script active.",
            maxargs => 1,
            proc =>
              sub { $sieve->setactive(shift) or die $sieve->error() . "\n" },
            args => \&complete_scripts,
        },
        "edit" => {
            desc    => "This command sets a script active.",
            maxargs => 1,
            proc    => sub { edit_script(shift) },
            args    => \&complete_scripts,
        },
        "view" => {
            desc    => "This command views a script.",
            maxargs => 1,
            proc    => sub { view_script(shift) },
            args    => \&complete_scripts,
        },
        "delete" => {
            desc    => "This command is used to delete a user's Sieve script.",
            minargs => 1,
            proc    => \&delete,
            args    => \&complete_scripts,
        },
        "cat" => {
            minargs => 1,
            proc    => \&cat,
            args    => \&complete_scripts,
        },
        "copy" => {
            maxargs => 2,
            minargs => 2,
            proc =>
              sub { $sieve->copyscript(@_) or die $sieve->error() . "\n" },
            args => \&complete_scripts,
        },
        "move" => {
            maxargs => 2,
            minargs => 2,
            proc =>
              sub { $sieve->movescript(@_) or die $sieve->error() . "\n" },
            args => \&complete_scripts,
        },
        "deactivate" => {
            maxargs => 0,
            proc => sub { $sieve->deactivate() or die $sieve->error() . "\n" },
            args => \&complete_scripts,
        },
        "q"      => { alias => 'quit',       exclude_from_completion => 1 },
        "logout" => { alias => 'quit',       exclude_from_completion => 1 },
        "h"      => { alias => "help",       exclude_from_completion => 1 },
        "ls"     => { alias => "list",       exclude_from_completion => 1 },
        "dir"    => { alias => "list",       exclude_from_completion => 1 },
        "rm"     => { alias => "delete",     exclude_from_completion => 1 },
        "vi"     => { alias => "edit",       exclude_from_completion => 1 },
        "less"   => { alias => "less",       exclude_from_completion => 1 },
        "more"   => { alias => "less",       exclude_from_completion => 1 },
        "touch"  => { alias => "edit",       exclude_from_completion => 1 },
        "type"   => { alias => "cat",        exclude_from_completion => 1 },
        "cp"     => { alias => "copy",       exclude_from_completion => 1 },
        "mv"     => { alias => "move",       exclude_from_completion => 1 },
        "set"    => { alias => "activate",   exclude_from_completion => 1 },
        "unset"  => { alias => "deactivate", exclude_from_completion => 1 },
    },
);

$term->{term}->ornaments(0);
$term->run();

sub cat {
    my $content = "";
    for my $script (@_) {
	my $new_content = $sieve->getscript($script) or die $sieve->error() . "\n";
        $content .= $new_content;
    }
    print $content;
}

sub delete {
    for my $script (@_) {
        $sieve->deletescript($script) or die $sieve->error() . "\n";
    }
}

sub view_script {
    my $script = shift;
    my ( $fh, $filename ) = $sieve->temp_scriptfile($script);
    unless ($fh) { die $sieve->error() . "\n" }
    my $pager = $ENV{'PAGER'} || "less";
    system( $pager, $filename ) == 0 or die "$!\n";
    close $fh;
}

sub edit_script {
    my $script = shift;
    my ( $fh, $filename ) = $sieve->temp_scriptfile( $script, 1 );
    unless ($fh) { die $sieve->error() . "\n" }
    my $editor = $ENV{'VISUAL'} || $ENV{'EDITOR'} || "vi";
    do {
        system( $editor, $filename ) == 0 or die "$!\n";
      } until (
        $sieve->putfile( $filename, $script )
          || !do { print "$@\n"; prompt( "Re-edit script? ", -yn ) }
      );
    close $fh;

}

sub list_scripts {
    my ( $scripts, $active ) = $sieve->listscripts()
      or die $sieve->error() . "\n";
    for my $script ( @{$scripts} ) {
        my $marker = '';
        $marker = ' *' if $script eq $active;
        print "${script}${marker}\n";
    }
}

sub complete_scripts {
    my ( $self, $cmp ) = @_;
    return [ grep { index( $_, $cmp->{str} ) == 0 }
          @{ $sieve->listscripts() } ];
}

sub equal_sep {
    my $file = shift;
    open my $in, $file or die $!;
    my %config;
    while (<$in>) {
        next if /^\s*#/;
        /^\s*(.*?)\s*=\s*(.*)\s*$/ or next;
        my ( $k, $v ) = ( $1, $2 );
        my @v;
        if ( $v =~ /,/ ) {
            $config{$k} = [ split /\s*,\s*/, $v ];
        }
        elsif ( $v =~ / / ) {    # XXX: Foo = "Bar baz"
            $config{$k} = [ split /\s+/, $v ];
        }
        else {
            $config{$k} = $v;
        }
    }
    return \%config;
}

0;

__END__

=head1 NAME

siesh - Sieve Shell

=head1 SYNOPSIS

siesh --user USER --host SERVER

=head1 DESCRIPTION

Siesh provides a shell-like interface for manipulating sieve scripts
using the ManageSieve protocol.

=head1 OPTIONS

=over 4

=item B<--user>|B<-u> USERNAME

Specifies the username to use when logging into the sieve server. This
option defaults to the value of the environment variable C<USER>.

=item B<--host|>|B<-h> HOST

Specifies the machine to connect to. Defaults to C<imap>.

=back

=head1 CONFIGURATION FILE

When siesh is invoked, it first reads configurations variables from
F</etc/siesh.conf> or F<~/.siesh.conf> if one of these files exist. The
file is structured as a set of lines with name and values seperated by
a equal signs.

	user = mario
	host = sieve.example.com

Currently only these both variables are recognized. Values are overriden
by the configurations options specified on the command line.

=head1 COMMANDS

=over 4

=item B<list> 

Prints a list of all scripts on the server. The currently active script,
if any, is marked by a I<*> (astersik).

Synonyms: B<ls>

=item B<delete> I<script-name> I<...>

Deletes a script. It's not possible to delete a active script, so please
use deactivate first. There's no way to undelete this message.

Synonyms: B<rm>

=item B<edit> I<script-name>

Edits a script on the server without downloading it
explicitly to your disk first. Under the hood it creates a temporary file,
put the script content in it and calls C<$ENV{EDITOR}> on it. After that
the script is uploaded back. It's also possible to create and edit a
new script with this command.

If your script is syntactical incorrect, you will be prompted to
re-edit the file or throw away your changes.

=item B<activate> I<script-name>

Activates the listed script. User may have multiple Sieve scripts on
the server, yet only one script may be used for filtering of incoming
messages. This is the active script. Users may have zero or one active
scripts

=item B<deactivate>

Deactivate all scripts. Deactivation of all your scripts results in no
filtering at all.

=item B<cat> I<script-name> I<...>

Print scripts on the standard output.

=item B<quit>

Terminates the sessiion with the remote SIEVE server. An end of file will also terminate the session and exit.

=item B<help>

Print a short description of all commands.

=item B<put> I<file-name> I<script-name>

Store a local file as script on the remote machine.

=item B<get> I<file-name> I<local-name>

Retrieve a remote script and store it on the local machine.

=back

=head1 SEE ALSO

L<Net::ManageSieve::Siesh>, L<Net::ManageSieve>

=head1 COPYRIGHT & LICENSE

Copyright 2008 Mario Domgoergen, all rights reserved.

This program is free software; you can redistribute it and/or modify it
under the same terms as Perl itself.
