File Coverage

blib/lib/IO/AIO/Promiser.pm
Criterion Covered Total %
statement 62 64 96.8
branch 12 14 85.7
condition 2 2 100.0
subroutine 17 17 100.0
pod 1 1 100.0
total 94 98 95.9


line stmt bran cond sub pod time code
1             package IO::AIO::Promiser;
2              
3 5     5   370574 use strict;
  5         40  
  5         131  
4 5     5   23 use warnings;
  5         6  
  5         219  
5              
6             our $VERSION = '0.01_01';
7              
8             =encoding utf-8
9              
10             =head1 NAME
11              
12             IO::AIO::Promiser - Promise interface around L
13              
14             =begin html
15              
16             Coverage Status
17              
18             =end html
19              
20             =head1 SYNOPSIS
21              
22             (This example uses L for conciseness; it’s not difficult
23             to adapt it for use with, e.g., L or L.)
24              
25             To slurp with L and L:
26              
27             use AnyEvent::AIO;
28             use Promise::AsyncAwait;
29              
30             use IO::AIO::Promiser ':all';
31              
32             async sub slurp ($abs_path) {
33             my $fh = await aio_open($abs_path, Fcntl::O_RDONLY, 0);
34              
35             my $buf = q<>;
36             1 while await aio_read($fh, undef, 65536, $buf, length $buf);
37              
38             return $buf;
39             }
40              
41             … and now you can:
42              
43             my $cv = AnyEvent->condvar();
44              
45             slurp("/etc/services")->then(
46             sub { $cv->(@_) },
47             sub { $cv->croak(@_) },
48             );
49              
50             my $content = $cv->recv();
51              
52             It’s a bit like L, but with async/await rather than L,
53             and more proactive error-checking.
54              
55             See below for examples of setup with L and L.
56              
57             =head1 DESCRIPTION
58              
59             L is great, but its callback-driven interface is less so.
60             This module wraps IO::AIO so you can easily use promises with it instead.
61              
62             =cut
63              
64             #----------------------------------------------------------------------
65              
66 5     5   23 use Carp ();
  5         7  
  5         64  
67 5     5   2006 use IO::AIO ();
  5         16847  
  5         99  
68              
69 5     5   2163 use Promise::XS ();
  5         13033  
  5         1285  
70              
71             #defined below
72             my %METADATA;
73              
74             #----------------------------------------------------------------------
75              
76             =head1 FUNCTIONS
77              
78             This module doesn’t (yet?) cover everything IO::AIO can do.
79             If there’s functionality you’d like to have, create a feature request.
80              
81             The following are like their L counterparts, but the final
82             callback argument is omitted, and a promise is returned instead.
83             That promise’s resolution is the callback’s success-case return, and
84             the rejection is the callback’s failure-case return.
85              
86             =over
87              
88             =item * C and C
89              
90             =item * C (NB: The resolution is an oddity: a filehandle that is
91             B a GLOB reference.)
92              
93             =item * C
94              
95             =item * C
96              
97             =item * C and C
98              
99             =item * C and C
100              
101             =item * C and C
102              
103             =item * C and C
104              
105             =item * C and C
106              
107             =item * C
108              
109             =item * C
110              
111             =item * C, C, and C
112              
113             =item * C and C
114              
115             =back
116              
117             The following are a bit different—but with good reason!—from the
118             corresponding IO::AIO interface:
119              
120             =over
121              
122             =item * C - The promise resolves to the file content,
123             so you don’t need to initialize a separate C<$data> scalar.
124              
125             =cut
126              
127             sub slurp {
128 2     2 1 447 my $d = Promise::XS::deferred();
129              
130 2         6 my $data;
131              
132             &IO::AIO::aio_slurp( @_[0 .. 2], $data, sub {
133 2 100   2   14 if ($_[0] >= 0) {
134 1         10 $d->resolve($data);
135             }
136             else {
137 1         9 $d->reject($!);
138             }
139 2         72 } );
140              
141 2         24 $d->promise();
142             }
143              
144             =back
145              
146             =cut
147              
148             #----------------------------------------------------------------------
149              
150             =head1 EXPORT INTERFACE
151              
152             Since it’s clunky to have to type C, if you
153             pass C<:all> to this module on import you’ll get C aliases
154             exported into your namespace, so you can call, e.g., C instead.
155             This matches IO::AIO’s own calling convention.
156              
157             =cut
158              
159             sub _create_func {
160 115     115   151 my $fn = shift;
161              
162 115 50       192 my $metadata_ar = $METADATA{$fn} or do {
163 0         0 Carp::confess sprintf("Bad function: %s::%s", __PACKAGE__, $fn);
164             };
165              
166 115         159 my ($limit, $resolver_cr) = @$metadata_ar;
167 115   100     308 $resolver_cr ||= \&_create_negative_resolver;
168              
169 115 50       452 my $ioaio_cr = IO::AIO->can("aio_$fn") or do {
170 0         0 Carp::confess "IO::AIO::aio_$fn is missing!";
171             };
172              
173             my $new_cr = sub {
174 40     40   8659 my $d = Promise::XS::deferred();
175              
176 40         136 $ioaio_cr->( @_[0 .. $limit], $resolver_cr->($d) );
177              
178 40         442 $d->promise();
179 115         417 };
180              
181             {
182 5     5   42 no strict 'refs';
  5         10  
  5         1395  
  115         162  
183 115         324 *$fn = $new_cr;
184             }
185              
186 115         684 return $new_cr;
187             }
188              
189             #----------------------------------------------------------------------
190              
191             sub _create_defined_resolver {
192 9     9   19 my $d = $_[0];
193              
194             return sub {
195 9 100   9   49 if (defined $_[0]) {
196 3         23 $d->resolve($_[0]);
197             }
198             else {
199 6         72 $d->reject($!);
200             }
201 9         545 };
202             }
203              
204             sub _create_negative_resolver {
205 31     31   49 my $d = $_[0];
206              
207             return sub {
208 31 100   31   123 if ($_[0] >= 0) {
209 14         71 $d->resolve($_[0]);
210             }
211             else {
212 17         137 $d->reject($!);
213             }
214 31         989 };
215             }
216              
217             BEGIN {
218 5     5   158 %METADATA = (
219             wd => [0, \&_create_defined_resolver],
220             realpath => [0, \&_create_defined_resolver],
221              
222             open => [2, \&_create_defined_resolver],
223             close => [0, \&_create_defined_resolver],
224             seek => [2],
225             read => [4],
226             readdir => [0, \&_create_defined_resolver],
227             readdirx => [1, \&_create_defined_resolver],
228             write => [4],
229             stat => [0],
230             lstat => [0],
231             utime => [2],
232             chown => [2],
233             chmod => [1],
234             unlink => [0],
235             mkdir => [1],
236             rmdir => [0],
237             link => [1],
238             rename => [1],
239             rename2 => [2],
240             symlink => [1],
241             readlink => [0, \&_create_defined_resolver],
242             truncate => [1],
243             );
244              
245 5         45 _create_func($_) for keys %METADATA;
246             }
247              
248             sub import {
249 5     5   88 my $ns = (caller 1)[0];
250 5         12 my $opt = $_[1];
251              
252 5 100       19 if ($opt) {
253 2         2 my @to_import;
254              
255 2 100       7 if ($opt eq ':all') {
256 1         13 @to_import = keys %METADATA;
257             }
258             else {
259 1         287 Carp::confess sprintf "%s: Bad import parameter: %s", __PACKAGE__, $opt;
260             }
261              
262 5     5   36 no strict 'refs';
  5         8  
  5         443  
263 1         11 *{"${ns}::aio_$_"} = __PACKAGE__->can($_) for @to_import;
  23         109  
264             }
265              
266 4         8159 return;
267             }
268              
269             1;
270              
271             #----------------------------------------------------------------------
272              
273             =head1 AUTHOR & COPYRIGHT
274              
275             Copyright 2021 Gasper Software Consulting
276              
277             =head1 LICENSE
278              
279             This library is licensed under the same license as Perl.
280              
281             =cut