File Coverage

blib/lib/POE/Component/Hailo.pm
Criterion Covered Total %
statement 73 120 60.8
branch 8 22 36.3
condition 2 4 50.0
subroutine 16 26 61.5
pod 3 3 100.0
total 102 175 58.2


line stmt bran cond sub pod time code
1             package POE::Component::Hailo;
2             our $AUTHORITY = 'cpan:HINRIK';
3             $POE::Component::Hailo::VERSION = '0.11';
4 2     2   203261 use 5.010;
  2         17  
5 2     2   10 use strict;
  2         4  
  2         41  
6 2     2   10 use warnings;
  2         4  
  2         58  
7 2     2   10 use Carp 'croak';
  2         3  
  2         107  
8 2     2   1105 use Hailo;
  2         1763113  
  2         122  
9 2     2   1608 use JSON;
  2         18453  
  2         12  
10 2     2   1569 use LWP::UserAgent;
  2         85584  
  2         83  
11 2     2   543 use POE qw(Wheel::Run Filter::JSON);
  2         32912  
  2         20  
12 2     2   126364 use Try::Tiny;
  2         6  
  2         3078  
13              
14             sub spawn {
15 1     1 1 405 my ($package, %args) = @_;
16              
17 1 50       6 croak "Hailo_args parameter missing" if ref $args{Hailo_args} ne 'HASH';
18 1         3 my $options = delete $args{options};
19 1         3 my $self = bless \%args, $package;
20              
21             $self->{response} = {
22 1         9 learn => 'hailo_learned',
23             train => 'hailo_trained',
24             reply => 'hailo_replied',
25             learn_reply => 'hailo_learn_replied',
26             stats => 'hailo_stats',
27             save => 'hailo_saved',
28             };
29              
30             POE::Session->create(
31             object_states => [
32             $self => [qw(
33             _start
34             shutdown
35             _sig_DIE
36             _sig_chld
37             _go_away
38             _child_stderr
39             _child_stdout
40             )],
41             $self => {
42 1 50       5 map { +$_ => '_hailo_method' } keys %{ $self->{response } },
  6         30  
  1         5  
43             }
44             ],
45             (ref $options eq 'HASH' ? (options => $options) : ()),
46             );
47              
48 1         419 return $self;
49             }
50              
51             sub _start {
52 1     1   350 my ($kernel, $session, $self) = @_[KERNEL, SESSION, OBJECT];
53 1         4 $self->{session_id} = $session->ID();
54 1         8 $kernel->sig(DIE => '_sig_DIE');
55              
56 1 50       36 if (defined $self->{alias}) {
57 1         5 $kernel->alias_set($self->{alias});
58             }
59             else {
60 0         0 $kernel->refcount_increment($self->{session_id}, __PACKAGE__);
61             }
62              
63             $self->{wheel} = POE::Wheel::Run->new(
64             Program => \&_main,
65 1 50       44 ProgramArgs => [ %{ $self->{Hailo_args} } ],
  1         12  
66             StdoutEvent => '_child_stdout',
67             StderrEvent => '_child_stderr',
68             StdioFilter => POE::Filter::JSON->new,
69             ( $^O eq 'MSWin32' ? ( CloseOnCall => 0 ) : ( CloseOnCall => 1 ) ),
70             );
71              
72 1         5618 $kernel->sig_child( $self->{wheel}->PID, '_sig_chld' );
73 1         485 return;
74             }
75              
76             sub _sig_DIE {
77 0     0   0 my ($kernel, $self, $ex) = @_[KERNEL, OBJECT, ARG1];
78 0         0 chomp $ex->{error_str};
79 0         0 warn "Error: Event $ex->{event} in $ex->{dest_session} raised exception:\n";
80 0         0 warn " $ex->{error_str}\n";
81 0         0 $kernel->sig_handled();
82 0         0 return;
83             }
84              
85             sub session_id {
86 0     0 1 0 return $_[0]->{session_id};
87             }
88              
89             sub _hailo_method {
90 1     1   1061 my ($kernel, $self, $state, $args, $context)
91             = @_[KERNEL, OBJECT, STATE, ARG0, ARG1];
92 1         17 my $sender = $_[SENDER]->ID();
93              
94 1 50       8 return if $self->{shutdown};
95              
96 1   50     13 $args //= [ ];
97 1   50     16 $context = { %{ $context // { } } };
  1         16  
98             my $request = {
99             args => $args,
100             context => $context,
101             method => $state,
102             sender => $sender,
103 1         51 event => $self->{response}{$state},
104             };
105              
106 1         14 $kernel->refcount_increment($sender, __PACKAGE__);
107 1         75 $self->{wheel}->put($request);
108              
109 1         266 return;
110             }
111              
112             sub _sig_chld {
113 1     1   6041 my ($kernel, $self) = @_[KERNEL, OBJECT];
114 1 50       5 $kernel->yield('shutdown') if !$self->{shutdown};
115 1         6 $kernel->yield('_go_away');
116 1         95 $kernel->sig_handled();
117 1         15 return;
118             }
119              
120             sub _child_stderr {
121 0     0   0 my ($kernel, $self, $input) = @_[KERNEL, OBJECT, ARG0];
122 0         0 warn "$input\n";
123 0         0 return;
124             }
125              
126             sub _child_stdout {
127 1     1   9316456 my ($kernel, $self, $input) = @_[KERNEL, OBJECT, ARG0];
128              
129 1 50       11 warn $input->{error} if $input->{error};
130 1         21 $kernel->post(@$input{qw(sender event result context error)});
131 1         162 $kernel->refcount_decrement($input->{sender}, __PACKAGE__);
132 1         66 return;
133             }
134              
135             sub shutdown {
136 1     1 1 2996 my ($self) = $_[OBJECT];
137 1         9 $self->{shutdown} = 1;
138 1         20 $self->{wheel}->shutdown_stdin;
139 1         294 return;
140             }
141              
142             sub _go_away {
143 1     1   365 my ($kernel, $self) = @_[KERNEL, OBJECT];
144              
145 1         20 delete $self->{wheel};
146 1         362 $kernel->alias_remove($_) for $kernel->alias_list();
147 1 50       123 if (!defined $self->{alias}) {
148 0         0 $kernel->refcount_decrement($self->{session_id}, __PACKAGE__);
149             }
150 1         4 return;
151             }
152              
153             sub _main {
154 0     0     my (%args) = @_;
155              
156 0 0         if ($^O eq 'MSWin32') {
157 0           binmode STDIN;
158 0           binmode STDOUT;
159             }
160              
161 0           my $raw;
162 0           my $size = 4096;
163 0           my $filter = POE::Filter::JSON->new;
164              
165 0           while (sysread STDIN, $raw, $size) {
166 0           my $requests = $filter->get([$raw]);
167 0           for my $req (@$requests) {
168 0 0         if ($args{server_host}) {
169 0           _call_hailo_server(@args{qw(server_host server_port)}, $req);
170             }
171             else {
172 0           _call_hailo_native(\%args, $req);
173             }
174 0           my $response = $filter->put([$req]);
175 0           print @$response;
176             }
177             }
178              
179 0           return;
180             }
181              
182             sub _call_hailo_native {
183 0     0     my ($hailo_args, $request) = @_;
184              
185             try {
186 0     0     my $hailo = Hailo->new(%$hailo_args);
187 0           my $method = $request->{method};
188 0           $request->{result} = [$hailo->$method(@{ $request->{args} })];
  0            
189             }
190             catch {
191 0     0     $request->{error} = $_;
192 0           };
193              
194 0           return;
195             }
196              
197             sub _call_hailo_server {
198 0     0     my ($host, $port, $request) = @_;
199              
200 0           my %method_map = (
201             learn => 'learn',
202             learn_reply => 'learn_and_reply',
203             reply => 'reply',
204             );
205 0           my $rest_method = $method_map{$request->{method}};
206 0 0         return if !$rest_method;
207              
208             try {
209 0     0     my $ua = LWP::UserAgent->new(
210             default_headers => HTTP::Headers->new(
211             Content_Type => 'application/json'
212             ),
213             );
214             my $raw_response = $ua->post(
215             "http://$host:$port/$rest_method",
216 0           Content => encode_json({input => $request->{args}[0]}),
217             );
218              
219 0           my $response = decode_json($raw_response->content);
220 0           $request->{result} = [$response->{reply}];
221             }
222             catch {
223 0     0     $request->{error} = $_;
224 0           };
225              
226 0           return;
227             }
228              
229             1;
230              
231             =encoding utf8
232              
233             =head1 NAME
234              
235             POE::Component::Hailo - A non-blocking wrapper around L<Hailo|Hailo>
236              
237             =head1 SYNOPSIS
238              
239             use strict;
240             use warnings;
241             use POE qw(Component::Hailo);
242              
243             POE::Session->create(
244             package_states => [
245             (__PACKAGE__) => [ qw(_start hailo_learned hailo_replied) ],
246             ],
247             );
248              
249             POE::Kernel->run;
250              
251             sub _start {
252             POE::Component::Hailo->spawn(
253             alias => 'hailo',
254             Hailo_args => {
255             storage_class => 'SQLite',
256             brain_resource => 'hailo.sqlite',
257             },
258             );
259              
260             POE::Kernel->post(hailo => learn =>
261             ['This is a sentence'],
262             );
263             }
264              
265             sub hailo_learned {
266             POE::Kernel->post(hailo => reply => ['This']);
267             }
268              
269             sub hailo_replied {
270             my $reply = $_[ARG0]->[0];
271             die "Didn't get a reply" if !defined $reply;
272             print "Got reply: $reply\n";
273             POE::Kernel->post(hailo => 'shutdown');
274             }
275              
276             =head1 DESCRIPTION
277              
278             POE::Component::Hailo is a L<POE|POE> component that provides a
279             non-blocking wrapper around L<Hailo|Hailo>. It accepts the events listed
280             under L</INPUT> and emits the events listed under L</OUTPUT>.
281              
282             =head1 METHODS
283              
284             =head2 C<spawn>
285              
286             This is the constructor. It takes the following arguments:
287              
288             B<'alias'>, an optional alias for the component's session.
289              
290             B<'Hailo_args'>, a hash reference of arguments to pass to L<Hailo|Hailo>'s
291             constructor.
292              
293             B<'options'>, a hash reference of options to pass to
294             L<POE::Session|POE::Session>'s constructor.
295              
296             =head2 C<session_id>
297              
298             Takes no arguments. Returns the POE Session ID of the component.
299              
300             =head1 INPUT
301              
302             This component reacts to the following POE events:
303              
304             =head2 C<learn>
305              
306             =head2 C<train>
307              
308             =head2 C<reply>
309              
310             =head2 C<learn_reply>
311              
312             =head2 C<stats>
313              
314             =head2 C<save>
315              
316             All these events take two arguments. The first is an array reference of
317             arguments which will be passed to the L<Hailo|Hailo> method of the same
318             name. The second (optional) is a hash reference. You'll get this hash
319             reference back with the corresponding event listed under L</OUTPUT>.
320              
321             =head2 C<shutdown>
322              
323             Takes no arguments. Terminates the component.
324              
325             =head1 OUTPUT
326              
327             The component will post the following event to your session:
328              
329             =head2 C<hailo_learned>
330              
331             =head2 C<hailo_trained>
332              
333             =head2 C<hailo_replied>
334              
335             =head2 C<hailo_learn_replied>
336              
337             =head2 C<hailo_stats>
338              
339             =head2 C<hailo_saved>
340              
341             C<ARG0> is an array reference of arguments returned by the underlying
342             L<Hailo|Hailo> method. C<ARG1> is the context hashref you provided (if any).
343              
344             =head1 AUTHOR
345              
346             Hinrik E<Ouml>rn SigurE<eth>sson, hinrik.sig@gmail.com
347              
348             =head1 LICENSE AND COPYRIGHT
349              
350             Copyright 2010 Hinrik E<Ouml>rn SigurE<eth>sson
351              
352             This program is free software, you can redistribute it and/or modify
353             it under the same terms as Perl itself.
354              
355             =cut