The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.
package Growl::Tiny;
use strict;
use warnings;

our $VERSION = '0.0.6'; # VERSION
use base 'Exporter';
our @EXPORT_OK = ( 'notify' );

my $GROWL_COMMAND = "/usr/local/bin/growlnotify";

#_* POD

=head1 NAME

Growl::Tiny - tiny perl module for sending Growl notifications on Mac OS X


=head1 VERSION

version 0.0.6

=head1 SYNOPSIS

    use Growl::Tiny;

    Growl::Tiny::notify( { subject  => 'notification body here',
                           title    => 'notification title',
                           priority => 3,
                           sticky   => 1,
                           title    => 'notification title',
                           host     => 'localhost',
                           image    => '/path/to/image.png',
                        } );


=head1 DESCRIPTION

Following the Tiny perl module convention, this module attempts to
provide a way to use Mac::Growl on OS X without any of the prereqs
(Mac::Glue, Mac::Carbon, etc.).  I have repeatedly run into a number
of problems installing these modules (on an old or brand new version
of the OS, using a 64-bit perl, etc.), so I decided to write a tiny
one that got the job done without them.

The only way I knew to use Growl without any prereqs was to use the
growlnotify command-line tool.  For more information on this tool, see:

  http://growl.info/documentation/growlnotify.php

There are some limitations, please see the BUGS AND LIMITATIONS
section below.  If these are a problem for you, please use Mac::Growl
instead.


=cut


#_* Methods

=head1 SUBROUTINES/METHODS

=over 4

=item notify( { %options } )

Send a growl notification.  Accepts either a hash or a hash reference.

Returns true if a notification was submitted.

=back

=head2 Options

=over 8

=item subject

Text to be used for the notification body.

This is a required field--no notification will be generated if it is
not set.

=item title

Optional text to be used for the notification title.

=item priority

Optional priority.  This can be -2, -1, 0, 1, or 2.

=item sticky

If 'sticky' is true, then the notification will remain on-screen until
clicked.

=item quiet

Suppress this notification.

=item host

Send a network notification to the specified host by passing the -H
option to growlnotify.  Note that growl must be configured to accept
network notifications for this to work.  Set this to 'localhost' to
use local network delivery for improved reliability--see the BUGS AND
LIMITATIONS section below for more information.

If the environment variable GROWL_HOST is set, all notifications will
be sent to that host by default.  Individual notifications may still
be sent to other hosts using the 'host' option.

=item image

Set the path to an image to be used as an icon for notifications.

=item name

Set to the name of the application that is sending the notifications.
By default this will be set to 'Growl::Tiny'.  Setting this to the
name of your application allows you to customize the notification
options for your application in the growl preferences pane under the
'applications' tab.

=item identifier

Sets the identifier for the notification. The identifier is used for
coalescing, which means that an old notifications with the same
identifier will be replaced with the new notification.

=item url

Notification click will result in the URL being opened.

=back

=cut

sub notify {
    my $options;

    if ( ref $_[0] eq "HASH" ) {
        $options = $_[0];
    }
    else {
        $options = \%_;
    }

    # skip notifications with no subject
    return unless $options->{subject};

    # skip notifications with the 'quiet' flag set
    return if $options->{quiet};

    #
    # build the command line options
    #
    my @command_line_args = ( $GROWL_COMMAND );

    if ( $options->{sticky} ) {
        push @command_line_args, '-s';
    }

    push @command_line_args, ( '-n', $options->{name} || 'Growl::Tiny' );

    if ( $options->{priority} ) {
        push @command_line_args, ( '-p', $options->{priority} );
    }

    if ( $options->{url} ) {
        push @command_line_args, ( '--url', $options->{url} );
    }

    my $host = $options->{host} || $ENV{GROWL_HOST};
    if ( $host ) {
        push @command_line_args, ( '-H', $host );
    }

    if ( $options->{image} ) {
        push @command_line_args, ( '--image', $options->{image} );
    }

    push @command_line_args, ( '-m', $options->{subject} );

    if ( $options->{title} ) {
        push @command_line_args, ( '-t', $options->{title} );
    }

    if ( $options->{identifier} ) {
        push @command_line_args, ( '-d', $options->{identifier} );
    }

    #print "COMMAND: ", join " ", @command_line_args, "\n";
    return system( @command_line_args ) ? 0 : 1;
}

# for automated testing only
sub _set_growl_command {
    my ( $command ) = @_;

    $GROWL_COMMAND = $command;

    return 1;
}


#_* End


1;

__END__

=head1 DEPENDENCIES

Growl (http://growl.info) and GrowlNotify must be installed locally.
GrowlNotify is available here:

  http://growl.info/downloads

GrowlNotify will install the growlnotify script here:

    /usr/local/bin/growlnotify



=head1 BUGS AND LIMITATIONS

The 'growlnotify' script will drop notifications when multiple
notifications are being processed concurrently or are submitted too
rapidly.  I only discovered this while writing the test cases.  As a
result, this module is NOT recommended for any application where more
than one notification might be generated per second.  Note that
Growl::Tiny will actually send messages much faster than this, but
exceeding this rate may cause some notifications to be dropped.

The work-around for this is to use growlnotify to deliver network
notifications to localhost.  Unlike the default mechanism used by
growlnotify, the network delivery seems to be very reliable.  To
enable this, go into the growl preference pane, select the 'network'
tab, and enable 'listen for incoming connections'.  I did not have to
restart growl after changing this setting.  Once this has been done,
to use network notifications in Growl::Tiny, either set the
environment variable GROWL_HOST, or else set the 'host' property on
each notification to 'localhost'.

Note that there is no reasonable way to test if a notification has
actually been displayed and not dropped.  It is only possible to check
that growlnotify returned success.  This greatly limits the amount and
quality of automated testing that can be performed.

=head1 EXPORT

notify - send a notification

=head1 SEE ALSO

http://growl.info - The Growl Project site

Mac::Growl - Local Growl Notification Framework.

Net::Growl - Growl Notifications over the network

=head1 LICENCE AND COPYRIGHT

Copyright (c) 2009, VVu@geekfarm.org
All rights reserved.

Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met:

- Redistributions of source code must retain the above copyright
  notice, this list of conditions and the following disclaimer.

- Redistributions in binary form must reproduce the above copyright
  notice, this list of conditions and the following disclaimer in the
  documentation and/or other materials provided with the distribution.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.