File Coverage

blib/lib/MooX/Async/Console.pm
Criterion Covered Total %
statement 30 40 75.0
branch 0 2 0.0
condition n/a
subroutine 10 12 83.3
pod n/a
total 40 54 74.0


line stmt bran cond sub pod time code
1             package MooX::Async::Console;
2              
3             our $VERSION = '0.105';
4             $VERSION = eval $VERSION;
5              
6             =head1 NAME
7              
8             MooX::Async::Console - Attach a console to an async module
9              
10             =head1 SYNOPSIS
11              
12             my Thing;
13             use MooX::Async;
14             with 'MooX::Async::Console';
15             has _console => is => lazy => builder => sub {
16             $_[0]->_launch_console(TCP => address => '127.0.0.1', port => 4242);
17             };
18             event cmd_foo => sub {
19             my $self = shift;
20             my %args = @_;
21             say "foo";
22             $args{inform}->('this thing is happening');
23             $args{then}->done('finished');
24             };
25              
26             =head1 DESCRIPTION
27              
28             Attaches some machinery to an object which allows it to start
29             listeners such as on a TCP or unix socket which expose a console
30             interface to the object.
31              
32             This module is a role which mixes in the L method
33             and implements a L and L command. Another module such as
34             L is responsible for managing the socket
35             and the framing protocol.
36              
37             =head1 BUGS
38              
39             Certainly.
40              
41             =cut
42              
43 1     1   605 use Modern::Perl '2017';
  1         3  
  1         11  
44 1     1   214 use strictures 2;
  1         10  
  1         56  
45              
46 1     1   300 use Moo::Role;
  1         3  
  1         10  
47 1     1   1390 use Future;
  1         13772  
  1         38  
48 1     1   6 use Module::Runtime qw(compose_module_name use_module);
  1         2  
  1         8  
49 1     1   53 use MooX::Async;
  1         2  
  1         5  
50 1     1   41 use Scalar::Util qw(blessed weaken);
  1         2  
  1         50  
51 1     1   447 use Syntax::Keyword::Try;
  1         602  
  1         5  
52 1     1   46 use namespace::clean;
  1         2  
  1         7  
53              
54             with 'MooX::Role::Logger';
55              
56             =head1 METHODS
57              
58             =over
59              
60             =item _launch_console($module, [@args]) => $console_instance
61              
62             Loads C and creates a new instance of
63             it (with C<@args>) who's C event will invoke commands on
64             C<$self>.
65              
66             If C<$module> begins with C<::> it is removed and the remainder used
67             as-is.
68              
69             $self->_launch_console('My::Console::Socket', argu => 'meant');
70              
71             At present this distribution includes one socket layer module,
72             L.
73              
74             An C event handler is unconditionally appended to the
75             arguments passed to the console's constructor.
76              
77             Its interface is desribed in L in L.
78              
79             =cut
80              
81             sub _launch_console {
82 0     0     my $self = shift;
83 0           my $module = compose_module_name(__PACKAGE__, shift);
84 0           weaken $self;
85             my $executive = sub {
86 0 0   0     if (not $self) {
87 0           warn "MooX::Async::Console went away with events pending";
88 0           return;
89             }
90 0           unshift @_, $self;
91 0           goto \&__execute;
92 0           };
93 0           use_module($module)->new(@_, on_command => $executive);
94             }
95              
96             =back
97              
98             =head1 COMMANDS
99              
100             The command handler will be invoked with the arguments decoded by the
101             socket layer implementation launched by L. Usually
102             these will come in the form of key/value pairs but need not. 4 items
103             will be appended to the argument list, which therefore constitute two
104             entries which will be included in the command handlers' args if it
105             treats C<@_> like a hash.
106              
107             =over
108              
109             =item inform => $subref
110              
111             A subref the command handler can use to send messages to the connected
112             client. What is suitable for the socket layer to receive is not
113             defined. L can accept 0 or more scalars
114             which it concatenates, so probably at least that.
115              
116             =item then => $future
117              
118             A L which the command handler can complete or fail with the
119             result of executing the command.
120              
121             =back
122              
123             The return value from the command handler is usually ignored.
124              
125             If a L is returned from the command then that is what's used
126             to determine when the command has completed instead of L which
127             was given to the command handler. The command handler is free then to
128             do with the L as it wishes; it is no longer used by this
129             module.
130              
131             sub cmd_generic {
132             my $self = shift;
133             my %args = @_;
134             # If you're not sure about the arguments: my %args = @_[-4..-1];
135             # or: my ($then, undef, $inform, undef) = map pop, 1..4;
136             ...;
137             $args{inform}->(...);
138             $args{inform}->(...);
139             ...;
140             $args{then}->done(...);
141             }
142              
143             =over
144              
145             =item ping [$nonce]
146              
147             Respond with C and <$nonce>, if given. Other arguments are
148             ignored.
149              
150             =cut
151              
152             event cmd_ping => sub {
153             my $ping = @_ > 5 ? $_[1] : undef;
154             $_[0]->_logger->tracef('ping %s', $ping);
155             $_[-1]->done(pong => $ping // ());
156             };
157              
158             =item quit
159              
160             Disconnect this client session. Arguments are accepted and ignored.
161              
162             =cut
163              
164             event cmd_quit => sub {
165             my $self = shift;
166             my %args = @_;
167             $self->_logger->tracef('Client quit');
168             $args{then}->fail(('quit')x2);
169             };
170              
171             =back
172              
173             =head1 EVENTS
174              
175             =over
176              
177             =item on_command
178              
179             Understanding this is not necessary to link a console into your own
180             code, it describes the interface used when implementing one's own
181             socket layer.
182              
183             The event handler invoked when a client executes a command. The
184             definition of the command and objects for communication between the
185             client and the console-using object are provided in C<%args>:
186              
187             =over
188              
189             =item command
190              
191             A scalar containing the name of the command to be executed. This will
192             be transformed into the C> event.
193              
194             =item args
195              
196             An optional arrayref containing the arguments to the command handler.
197              
198             =item then
199              
200             A L which will be completed when the command handler has
201             finished. This is not the L which the command handler is
202             invoked with. If the command dies or was not found then this L
203             will be failed with the exception.
204              
205             Future->fail($message, console =>
206             ex => $@, # If the command threw one
207             command => $command,
208             args => \%args)
209              
210             Another L is created which is passed onto the command itself,
211             if the command returns without dying then L is completed with
212             the L the command handler was given.
213              
214             The command handler is free to return a different L to this
215             event handler. If it does so then it is that L which L
216             is completed with. Anything else returned from the command handler is
217             ignored.
218              
219             =item inform
220              
221             An optional subref which the command handler may invoke to respond to
222             the client before execution has completed.
223              
224             Although C isn't a required argument its default value is
225             C which will die if the command handler attempts to use
226             it.
227              
228             =back
229              
230             =cut
231              
232 1     1   617 use namespace::clean '__execute';
  1         2  
  1         6  
233             sub __execute {
234             my $self = shift;
235             my $client_connection = shift;
236             my %args = @_;
237             my $then = delete $args{then} or die 'no future';
238             my $command = delete $args{command} or die 'no command';
239             my $informer= delete $args{inform} || sub { ... };
240             # $real_informer = sub { ...; $informer->(); ... };
241             $self->_logger->debugf('Invoking command %s %s', $command, $args{args});
242             my $next = $self->loop->new_future;
243             try {
244             my $r = $self->invoke_event("cmd_$command" => @{ $args{args} // [] },
245             inform => $informer, then => $next);
246             $next = $r if blessed $r and $r->DOES('Future');
247             return $then->done($next);
248              
249             } catch {
250             # TODO: See what's happened to $next
251             return $then->fail("Unknown command: $command",
252             console => command => $command, args => \%args)
253             if $@ =~ /cannot handle cmd_\Q$command\E event/;
254             chomp $@ unless ref $@;
255             return $then->fail("Unhandled exception running $command: $@",
256             console => ex => $@, command => $command, args => \%args);
257             }
258             }
259              
260             1;
261              
262             =back
263              
264             =head1 HISTORY
265              
266             =over
267              
268             =item MooX::Async::Console 0.103
269              
270             =over
271              
272             =item on_command/cmd_* interface
273              
274             =item line-based TCP implementation
275              
276             =back
277              
278             =back
279              
280             =head1 SEE ALSO
281              
282             L
283              
284             =head1 AUTHOR
285              
286             Matthew King
287              
288             =cut