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;