File Coverage

blib/lib/POE/Declarative.pm
Criterion Covered Total %
statement 99 101 98.0
branch 19 26 73.0
condition 21 38 55.2
subroutine 23 24 95.8
pod 8 8 100.0
total 170 197 86.2


line stmt bran cond sub pod time code
1 12     12   2764831 use strict;
  12         31  
  12         589  
2 12     12   73 use warnings;
  12         25  
  12         935  
3              
4             package POE::Declarative;
5             BEGIN {
6 12     12   702 $POE::Declarative::VERSION = '0.09';
7             }
8              
9             require Exporter;
10             our @ISA = qw( Exporter );
11              
12 12     12   74 use Carp;
  12         30  
  12         928  
13 12     12   2188 use POE;
  12         142762  
  12         91  
14 12     12   276604 use Scalar::Util qw/ blessed reftype /;
  12         25  
  12         7137  
15              
16             our @EXPORT = qw(
17             call delay post yield
18              
19             get
20              
21             on run
22             );
23              
24             =head1 NAME
25              
26             POE::Declarative - write POE applications without the mess
27              
28             =head1 VERSION
29              
30             version 0.09
31              
32             =head1 SYNOPSIS
33              
34             use POE;
35             use POE::Declarative;
36              
37             on _start => run {
38             yield 'count_to_10';
39             };
40              
41             on count_to_10 => run {
42             for ( 1 .. 10 ) {
43             yield say => $_;
44             }
45             };
46              
47             on say => run {
48             print get(ARG0);
49             };
50              
51             POE::Declarative->setup;
52             POE::Kernel->run;
53              
54             =head1 DESCRIPTION
55              
56             Taking the lessons learned from writing dispatchers and templates in L and L, I've applied the same declarative language to L. The goal is to make writing a POE application less painful so that I can concentrate on the more important aspects of my programming.
57              
58             =head1 DECLARATIONS
59              
60             =head2 on STATE => CODE
61              
62             =head2 on [ STATE1, STATE2, ... ] => CODE
63              
64             Use the C rule to specify what code to run on a given state (or states). The usual way to say this is:
65              
66             on _start => run { ... };
67              
68             But you could also say:
69              
70             on _start => sub { ... };
71              
72             or:
73              
74             on _start => run _start_handler;
75              
76             or:
77              
78             on _start => \&_start_handler;
79              
80             =head3 MULTIPLE STATES FOR A SINGLE HANDLER
81              
82             You can also specify multiple states for a single subroutine:
83              
84             on [ 'say', 'yell', 'whisper' ] => run { ... };
85              
86             This has the same behavior as setting the same subroutine for each of these individually.
87              
88             =head3 STATE HANDLER METHODS
89              
90             Each state is also placed as a method within the current package. This method will be prefixed with "_poe_declarative_" to keep it from conflicting with any other methdos you've defined. So, you can define:
91              
92             sub _start { ... }
93              
94             on _start => \&_start;
95              
96             This will then result in an additional method named "C<_poe_declarative__start>" being added to your package. These method names are then passed as state handlers to the L.
97              
98             =head3 MULTIPLE HANDLERS PER STATE
99              
100             You may have multiple handlers for each state with L. If you have two calls to C for the same state name, both of those handlers will be run when that state is entered by L. If you are using L your mixin classes and your main class may all define a handler for a given state and all handlers will be run.
101              
102             package X;
103             use base qw/ POE::Declarative::Mixin /;
104              
105             use POE::Declarative;
106              
107             on foo => run { print "X" };
108              
109             package Y;
110             use base qw/ POE::Declarative::Mixin /;
111              
112             use POE::Declarative;
113              
114             on foo => run { print "Y" };
115              
116             package Z;
117             use POE::Declarative;
118             use X;
119             use Y;
120              
121             on foo => run { print "Z\n" };
122             on _start => run { yield 'foo' };
123              
124             POE::Declarative->setup;
125             POE::Kernel->run;
126              
127             In the example above, the output could be:
128              
129             XYZ
130              
131             The order multiple handlers will run in is not (as of this writing) completely explicit. However, the primary package's handlers will be run last after all mixins have run. Also, the order the handlers is defined within a package will be preserved. Thus, if you define two handlers for the same state within the same package, the one defined first will be run first and the one defined second will be run second.
132              
133             Because of these, the output from the previous example might also be:
134              
135             YXZ
136              
137             If you use L to synchronously activate a state and use the return value. It will be set to the return value of the last handler run in the main package.
138              
139             =cut
140              
141             sub on($$) {
142 54     54 1 93 my $state = shift;
143 54         78 my $code = shift;
144              
145 54 50 33     553 croak qq{"on" expects a code reference as the second argument, }
146             .qq{found $code instead}
147             unless ref $code and reftype $code eq 'CODE';
148              
149 54         290 my $session = POE::Kernel->get_active_session;
150 54         213 my $package;
151              
152             # The kernel is not yet running/not in an active session
153 54 100       439 if ($session->isa('POE::Kernel')) {
154              
155             # Normally, the caller is good enough
156 40         84 $package = caller;
157              
158             # DEEP MAGIC!!! BEWARE OF THE DWIMMERY!!!
159             #
160             # However, if we're in a mixin declaring a state using some sort of
161             # fancy helper subroutine, we need to try and put that declared state
162             # into the calling class not in the mixin where it fouls the mixin's
163             # declaration and doesn't make it into the session configuration
164             # properly. See t/dynamic-late-mixin-states.t for an example of the
165             # kind of situation where this comes up.
166 40         258 my $caller = 1;
167 40   66     532 while (defined $package && $package->isa('POE::Declarative::Mixin')) {
168 9         109 $package = caller($caller++);
169             }
170              
171             # Fallback position in case we get confused
172 40 50       112 $package = caller unless defined $package;
173             }
174              
175             # The POE kernel is running and in a session
176             else {
177             # Try to guess the package from the session if in a POE::Declarative
178             # handler, or fallback to the caller if not, which may be bad. By using
179             # the state's OBJECT, this should magically handle this work in mixins
180             # as well!
181 14   33     29 my $object = get(OBJECT) || caller;
182 14   33     58 $package = ref $object || $object;
183             }
184              
185             # Using on [ qw/ x y z / ] => ... syntax
186 54 100 66     186 if (ref $state and reftype $state eq 'ARRAY') {
187 1         3 for my $individual_state (@$state) {
188 3         11 _declare_method($package, $individual_state, $code);
189             }
190             }
191              
192             # Using on x => ... syntax
193             else {
194 53         129 _declare_method($package, $state, $code);
195             }
196             }
197              
198             sub _declare_method {
199 56     56   123 my $package = shift;
200 56         76 my $state = shift;
201 56         70 my $code = shift;
202              
203 56         120 my $states = _states($package);
204 56         245 my $handlers = _handlers($package);
205              
206 56         133 my $method = '_poe_declarative_' . $state;
207 56         334 $states->{ $state } = $method;
208 56         69 push @{ $handlers->{ $state }{ $package } }, $code;
  56         188  
209              
210             {
211 12     12   86 no strict 'refs';
  12         32  
  12         863  
  56         87  
212 12     12   72 no warnings 'redefine';
  12         23  
  12         8795  
213 56         391 *{ $package . '::' . $method } = sub {
214 172     172   1020173 _args(@_);
215 172         423 _handle_state(@_);
216 56         256 };
217             }
218              
219             # Check if the system is running or not
220 56         195 my $session = POE::Kernel->get_active_session;
221              
222             # We're in an active session, make sure the kernel is updated
223 56 100       445 unless ($session->isa('POE::Kernel')) {
224 14         48 POE::Kernel->state($state, $package, $method);
225             }
226             }
227              
228             sub _handle_state {
229 172     172   247 my $self = $_[OBJECT];
230 172         215 my $state = $_[STATE];
231 172   66     815 my $package = ref $self || $self;
232              
233 172         336 my $all_handlers = _handlers($package);
234 172         342 my $my_handlers = $all_handlers->{ $state };
235              
236 172         238 my ($result, @result);
237             my @handler_packages
238 172 0       656 = sort { $a eq $package ? 1 # put this package at the end
  0 0       0  
239             : $b eq $package ? -1
240             : 0 } keys %$my_handlers;
241 172         1329 for my $handler_package (@handler_packages) {
242 172         391 my $codes = $my_handlers->{ $handler_package };
243              
244 172         291 for my $code (@$codes) {
245 183 100       3633 if (wantarray) {
    100          
246 10         30 @result = $code->(@_);
247             }
248             elsif (defined wantarray) {
249 27         265 $result = $code->(@_);
250             }
251             else {
252 146         573 $code->(@_);
253             }
254             }
255             }
256              
257 172 100       44440 return wantarray ? @result : $result;
258             }
259              
260             =head2 run CODE
261              
262             This is mostly a replacement keyword for C because:
263              
264             on _start => run { ... };
265              
266             reads better than:
267              
268             on _start => sub { ... };
269              
270             =cut
271              
272 54     54 1 6265 sub run(&) { $_[0] }
273              
274             =head1 HELPERS
275              
276             In addition to providing the declarative syntax the system also provides some helpers to shorten up the guts of your POE applications as well.
277              
278             =head2 get INDEX
279              
280             Rather than doing this (which you can still do inside your handlers):
281              
282             my ($kernel, $heap, $session, $flam, $floob, $flib)
283             = @_[KERNEL, HEAP, SESSION, ARG0, ARG1, ARG2];
284              
285             You can use the C subroutine for a short hand, like:
286              
287             my $kernel = get KERNEL;
288             get(HEAP)->{flubber} = 'doo';
289              
290             If you don't like C, don't use it. As I said, the code above will run exactly as you're used to if you're used to writing regular POE applications.
291              
292             =cut
293              
294             sub get($) {
295 142     142 1 20999 my $pos = shift;
296 142         269 my $package = caller;
297 142         248 return _args()->[ $pos ];
298             }
299              
300             =head2 call SESSION, STATE, ARGS
301              
302             This is just a shorthand for L.
303              
304             =cut
305              
306             sub call($$;@) {
307 21     21 1 16334 POE::Kernel->call( @_ );
308             }
309              
310             =head2 delay STATE, SECONDS, ARGS
311              
312             This is just a shorthand for L.
313              
314             =cut
315              
316             sub delay($;$@) {
317 12     12 1 8908 POE::Kernel->delay( @_ );
318             }
319              
320             =head2 post SESSION, STATE, ARGS
321              
322             This is just a shorthand for L.
323              
324             =cut
325              
326             sub post($$;@) {
327 0     0 1 0 POE::Kernel->post( @_ );
328             }
329              
330             =head2 yield STATE, ARGS
331              
332             This is just a shorthand for L.
333              
334             =cut
335              
336             sub yield($;@) {
337 125     125 1 10307 POE::Kernel->yield( @_ );
338             }
339              
340             =head1 SETUP METHODS
341              
342             The setup methods setup your session and such and generally get your session read for the POE kernel to do its thing.
343              
344             =head2 setup [ CLASS [ , HEAP ] ]
345              
346             Typically, this is called via:
347              
348             POE::Declarative->setup;
349              
350             If called within the package defining the session, this should DWIM nicely. However, if you call it from outside the package (for example, you have several session packages that are then each set up from a central loader), you can also run:
351              
352             POE::Declarative->setup('MyPOEApp::Component::FlabbyBo');
353              
354             And finally, the third form is to pass a blessed reference of that class in, which will become the C argument to all your states (rather than it just being the name of the class).
355              
356             my $flabby_bo = MyPOEApp::Component::FlabbyBo->new;
357             POE::Declarative->setup($flabby_bo);
358              
359             You may also specify a second argument that will be used to setup the L heap. If not given, the C argument defaults to an empty hash reference.
360              
361             =cut
362              
363             our $_POE_DECLARATIVE_ARGS;
364             sub _args {
365 314 100   314   1320 $_POE_DECLARATIVE_ARGS = [ @_ ] if scalar(@_) > 0;
366 314   50     11620 return $_POE_DECLARATIVE_ARGS || [];
367             }
368              
369             sub _handlers {
370 233   33 233   713 my $package = shift || caller(1);
371              
372 12     12   77 no strict 'refs';
  12         28  
  12         910  
373 233   100     269 return scalar (${ $package . '::_POE_DECLARATIVE_HANDLERS' } ||= {});
  233         1420  
374             }
375              
376             sub _states {
377 74   33 74   430 my $package = shift || caller(1);
378              
379 12     12   67 no strict 'refs';
  12         24  
  12         4318  
380 74   100     141 return scalar (${ $package . '::_POE_DECLARATIVE_STATES' } ||= {});
  74         742  
381             }
382              
383             sub setup {
384 13     13 1 753 my $class = shift;
385              
386 13 50 33     130 unshift @_, $class if defined $class and $class ne __PACKAGE__;
387              
388 13   66     79 my $package = shift || caller;
389 13   100     106 my $heap = shift || {};
390              
391             # Use object states
392 13 100       70 if (blessed $package) {
393 1         5 POE::Session->create(
394             object_states => [ $package => _states(blessed $package) ],
395             heap => $heap,
396             );
397             }
398              
399             # Use package states
400             else {
401 12         41 POE::Session->create(
402             package_states => [ $package => _states($package) ],
403             heap => $heap,
404             );
405             }
406             }
407              
408             =head1 SEE ALSO
409              
410             L
411              
412             =head1 AUTHORS
413              
414             Andrew Sterling Hanenkamp C<< >>
415              
416             =head1 COPYRIGHT AND LICENSE
417              
418             Copyright 2007 Boomer Consulting, Inc. All Rights Reserved.
419              
420             This program is free software and may be modified and distributed under the same terms as Perl itself.
421              
422             =cut
423              
424             1;