File Coverage

blib/lib/Event/Join.pm
Criterion Covered Total %
statement 1 3 33.3
branch n/a
condition n/a
subroutine 1 1 100.0
pod n/a
total 2 4 50.0


line stmt bran cond sub pod time code
1             package Event::Join;
2 4     4   224606 use Moose;
  0            
  0            
3             use List::Util qw(reduce first);
4              
5             our $VERSION = '0.06';
6              
7             has 'events' => (
8             is => 'ro',
9             isa => 'ArrayRef[Str]',
10             required => 1,
11             auto_deref => 1,
12             );
13              
14             has 'on_event' => (
15             is => 'ro',
16             isa => 'CodeRef',
17             default => sub { sub {} },
18             required => 1,
19             );
20              
21             has 'on_completion' => (
22             is => 'ro',
23             isa => 'CodeRef',
24             required => 1,
25             );
26              
27             has 'received_events' => (
28             traits => ['Hash'],
29             is => 'ro',
30             isa => 'HashRef',
31             default => sub { +{} },
32             required => 1,
33             handles => {
34             'send_event' => 'set',
35             'event_sent' => 'exists',
36             },
37             );
38              
39             sub _check_event_name {
40             my ($self, $event_name) = @_;
41             confess "'$event_name' is an unknown event"
42             unless first { $event_name eq $_ } $self->events;
43             }
44              
45             before send_event => sub {
46             my ($self, $event_name) = @_;
47             confess "Already sent event '$event_name'"
48             if $self->event_sent($event_name);
49              
50             $self->_check_event_name($event_name);
51             };
52              
53             around send_event => sub {
54             my ($orig, $self, $event_name, $value) = @_;
55             $self->$orig($event_name, $value);
56             };
57              
58             after send_event => sub {
59             my ($self, @args) = @_;
60              
61             $self->on_event->(@args);
62              
63             my $done = reduce { $a && $b } (
64             1, map { $self->event_sent($_) } $self->events,
65             );
66              
67             if($done){
68             $self->on_completion->( $self->received_events );
69             }
70             };
71              
72             sub event_sender_for {
73             my ($self, $event) = @_;
74             $self->_check_event_name($event);
75             return sub {
76             $self->send_event($event, @_);
77             };
78             }
79              
80             1;
81              
82             __END__
83              
84             =head1 NAME
85              
86             Event::Join - join multiple "events" into one
87              
88             =head1 SYNOPSIS
89              
90             use Event::Join;
91              
92             my $joiner = Event::Join->new(
93             on_completion => sub {
94             my $events = shift;
95             say 'Child exited with status '. $events->{child_done};
96             },
97             events => [qw/stdout_closed child_done/],
98             );
99              
100             watch_fh $stdout, on_eof => sub { $joiner->send_event('stdout_closed') };
101             watch_child $pid, on_exit => sub { $joiner->send_event('child_done', $_[0]) };
102              
103             start_main_loop;
104              
105             =head1 DESCRIPTION
106              
107             When writing event-based programs, you often want to wait for a number
108             of events to occur, and then do something. This module allows you to
109             do that without blocking. It simply acts as a receiver for a number
110             of events, and then calls a callback when all events have occurred.
111              
112             Note that although I mainly use this for "real" event-based
113             programming, the technique is rather versatile. A config file parser
114             could be implemented like this:
115              
116             my $parsed_doc;
117             my $parser_state = Event::Join->new(
118             events => [qw/username password machine_name/],
119             on_completion => sub { $parsed_doc = shift },
120             );
121              
122             while(!$parsed_doc && (my $line = <$fh>)){
123             chomp $line;
124             my ($k, $v) = split /:/, $line;
125             $parser_state->send_event($k, $v);
126             }
127              
128             say 'Username is '. $parsed_doc->{username};
129              
130             =head1 METHODS
131              
132             =head2 new
133              
134             Create an instance. Needs to be passed C<events>, an arrayref of
135             valid event names, and C<on_completion>, a coderef to call after all
136             events have been received. This coderef is passed a hashref of events
137             and their values, and will only ever be called once (or not at all, if
138             the events never arrive).
139              
140             =head2 send_event( $event_name, [$event_value] )
141              
142             Send an event. C<$event_name> is required, and must be an event that
143             was passed to the constructor. An exception will be thrown if the
144             name is not valid.
145              
146             C<$event_value> is optional; is is the value that goes into the hash
147             to be passed to the callback. It can be true or false -- its value
148             does not affect whether or not the completino callback is called.
149              
150             Finally, an exception is thrown if an event is sent more than once.
151              
152             =head2 event_sent( $event_name )
153              
154             Returns true if the event has been sent, false otherwise. Note that
155             the true value is I<not> the value that was passed to C<send_event>,
156             it is just an arbitrary non-false value.
157              
158             =head2 event_sender_for( $event_name )
159              
160             Returns a coderef that sends C<$event_name> when run. The first
161             argument to the coderef will become the second argument to
162             C<send_event>.
163              
164             =head1 PATCHES
165              
166             Is the module totally broken? Patch my repository at:
167              
168             http://github.com/jrockway/event-join
169              
170             =head1 AUTHOR
171              
172             Jonathan Rockway C<< <jrockway@cpan.org> >>
173              
174             =head1 COPYRIGHT
175              
176             Copyright (c) 2009 Jonathan Rockway.
177              
178             This module is Free Software. You may distribute it under the same
179             terms as Perl itself.