File Coverage

blib/lib/IO/Any.pm
Criterion Covered Total %
statement 117 150 78.0
branch 56 82 68.2
condition 11 18 61.1
subroutine 19 24 79.1
pod 5 5 100.0
total 208 279 74.5


line stmt bran cond sub pod time code
1             package IO::Any;
2              
3 2     2   94105 use warnings;
  2         4  
  2         68  
4 2     2   8 use strict;
  2         2  
  2         43  
5 2     2   1019 use utf8;
  2         21  
  2         6  
6              
7             our $VERSION = '0.09';
8              
9 2     2   102 use Carp 'confess';
  2         3  
  2         121  
10 2     2   9 use Scalar::Util 'blessed';
  2         3  
  2         72  
11 2     2   871 use IO::String;
  2         5125  
  2         57  
12 2     2   448 use IO::File;
  2         6105  
  2         235  
13 2     2   1016 use IO::AtomicFile;
  2         1021  
  2         82  
14 2     2   14 use File::Spec;
  2         2  
  2         51  
15 2     2   17 use Fcntl qw(:flock);
  2         2  
  2         285  
16 2     2   955 use List::MoreUtils qw(none any);
  2         1820  
  2         2543  
17              
18             sub new {
19 23     23 1 3162 my $class = shift;
20 23         33 my $what = shift;
21 23   50     99 my $how = shift || '<';
22 23   100     83 my $opt = shift || {};
23 23 50       48 confess 'too many arguments'
24             if @_;
25              
26 23 100       71 confess '$what is missing'
27             if not defined $what;
28              
29 21 50       57 confess 'expecting hash ref'
30             if ref $opt ne 'HASH';
31 21         78 foreach my $key (keys %$opt) {
32             confess 'unknown option '.$key
33 17 100   51   86 if (none { $key eq $_ } qw(atomic LOCK_SH LOCK_EX LOCK_NB));
  51         155  
34             }
35              
36 19         61 my ($type, $proper_what) = $class->_guess_what($what);
37              
38              
39 19 100       60 if ($type eq 'string') { return IO::String->new($proper_what) }
  3         18  
40 16 100       37 if ($type eq 'file') {
41 15 100       89 my $fh = $opt->{'atomic'} ? IO::AtomicFile->new() : IO::File->new();
42 15 50       508 $fh->open($proper_what, $how)
43             or confess 'error opening file "'.$proper_what.'" - '.$!;
44              
45             # locking if requested
46 15 100 100     1157 if ($opt->{'LOCK_SH'} or $opt->{'LOCK_EX'}) {
47 8 100       137 flock($fh,
    100          
    100          
    100          
48             ($opt->{'LOCK_SH'} ? LOCK_SH : 0)
49             | ($opt->{'LOCK_EX'} ? LOCK_EX : 0)
50             | ($opt->{'LOCK_NB'} ? LOCK_NB : 0)
51             ) or confess 'flock failed - '.$!;
52             }
53              
54 11         56 return $fh;
55             }
56 1 50       3 if ($type eq 'iofile') { return $proper_what }
  1         4  
57 0 0       0 if ($type eq 'iostring') { return $proper_what }
  0         0  
58 0 0       0 if ($type eq 'http') { die 'no http support yet :-|' }
  0         0  
59             }
60              
61             sub _guess_what {
62 31     31   7196 my $class = shift;
63 31         45 my $what = shift;
64              
65 31 100       130 if (!blessed($what)) { } # not blessed, do nothing
    50          
    50          
    0          
66 0         0 elsif ($what->isa('Path::Class::File')) { $what = $what->stringify }
67 1     1   7 elsif (any { $what->isa($_) } qw(IO::File IO::AtomicFile IO::Uncompress::Bunzip2)) {
68 1 50       32 confess 'passed unopened IO::File'
69             if not $what->opened;
70 1         12 return ('iofile', $what);
71             }
72 0         0 elsif ($what->isa('IO::String')) { return ('iostring', $what) }
73 0         0 else { confess 'no support for '.blessed($what) };
74              
75 30         46 my $ref_what = ref($what);
76 30 100       100 if ($ref_what eq 'ARRAY') { return ('file', File::Spec->catfile(@{$what})) }
  16 100       17  
  16 50       243  
77 1         4 elsif ($ref_what eq 'SCALAR') { return ('string', $what) }
78             elsif ($ref_what eq '') {} # do nothing here if not reference
79 0         0 else { confess 'no support for ref '.(ref $what) }
80              
81             # check for typeglobs
82 13 100 100     56 if ((ref \$what eq 'GLOB') and (my $fh = *{$what}{IO})) {
  1         11  
83 1         3 return ('iofile', $fh);
84             }
85              
86 12 100       35 if ($what =~ m{^file://(.+)$}) { return ('file', $1) } # local file
  1         8  
87 11 100       31 if ($what =~ m{^https?://}) { return ('http', $what) } # http link
  2         7  
88 9 100       23 if ($what =~ m{^<}) { return ('string', $what) } # xml string
  1         4  
89 8 100       24 if ($what =~ m(^{)) { return ('string', $what) } # json string
  2         8  
90 6 100       15 if ($what =~ m{^\[}) { return ('string', $what) } # json string
  1         5  
91 5 100       14 if ($what =~ m{\n[\s\w]}) { return ('string', $what) } # multi-line string
  1         4  
92 4 100       10 if ($what eq '') { return ('string', '') } # empty string
  2         8  
93 2         3 { return ('file', $what) } # default is filename
  2         8  
94             }
95              
96             sub read {
97 10     10 1 4484 my $class = shift;
98 10         17 my $what = shift;
99 10         11 my $opt = shift;
100 10 50       26 confess 'too many arguments'
101             if @_;
102              
103 10         26 return $class->new($what, '<', $opt);
104             }
105              
106             sub write {
107 2     2 1 1035 my $class = shift;
108 2         3 my $what = shift;
109 2         3 my $opt = shift;
110 2 50       7 confess 'too many arguments'
111             if @_;
112              
113 2         6 return $class->new($what, '>', $opt);
114             }
115              
116             sub slurp {
117 4     4 1 3056 my $class = shift;
118 4         8 my $what = shift;
119 4         5 my $opt = shift;
120 4 50       18 confess 'too many arguments'
121             if @_;
122              
123 4         13 my $fh = $class->read($what, $opt);
124              
125             # use event loop when AnyEvent is loaded (skip IO::String, doesn't work and makes no sense)
126             # not supported under MSWin32
127 4 0 33     15 if ($INC{'AnyEvent.pm'} and not $fh->isa('IO::String') and ($^O ne 'MSWin32')) {
      33        
128 0 0       0 eval 'use AnyEvent::Handle'
129             if not $INC{'AnyEvent/Handle.pm'};
130 0         0 my $eof = AnyEvent->condvar;
131 0         0 my $content = '';
132             my $hdl = AnyEvent::Handle->new(
133             fh => $fh,
134             on_read => sub {
135 0     0   0 $content .= delete $_[0]->{'rbuf'};
136             },
137             on_eof => sub {
138 0     0   0 $eof->send;
139             },
140             on_error => sub {
141 0     0   0 my ($hdl, $fatal, $msg) = @_;
142 0         0 $hdl->destroy;
143 0         0 $eof->croak($msg);
144             }
145 0         0 );
146              
147 0         0 $eof->recv;
148 0         0 $hdl->destroy;
149 0         0 close $fh;
150 0         0 return $content;
151             }
152              
153 4         5 my $content = do { local $/; <$fh> };
  4         16  
  4         92  
154 4         32 close $fh;
155 4         36 return $content;
156             }
157              
158             sub spew {
159 5     5 1 2785 my $class = shift;
160 5         8 my $what = shift;
161 5         6 my $data = shift;
162 5         6 my $opt = shift;
163 5 50       15 confess 'too many arguments'
164             if @_;
165              
166             # "parade" to allow safe locking
167 5         12 my $fh = $class->new($what, '+>>', $opt);
168 4         68 $fh->seek(0,0);
169 4         56 $fh->truncate(0);
170              
171             # use event loop when AnyEvent is loaded (skip IO::String, doesn't work and makes no sense)
172 4 50 33     105 if ($INC{'AnyEvent.pm'} and not $fh->isa('IO::String')) {
173 0 0       0 eval 'use AnyEvent::Handle'
174             if not $INC{'AnyEvent/Handle.pm'};
175              
176 0         0 my $eof = AnyEvent->condvar;
177             my $hdl = AnyEvent::Handle->new(
178             fh => $fh,
179             on_drain => sub {
180 0     0   0 $eof->send;
181             },
182             on_error => sub {
183 0     0   0 my ($hdl, $fatal, $msg) = @_;
184 0         0 $hdl->destroy;
185 0         0 $eof->croak($msg);
186             }
187 0         0 );
188              
189 0         0 $hdl->push_write($data);
190              
191 0         0 $eof->recv;
192 0         0 $hdl->destroy;
193 0         0 close $fh;
194 0         0 return;
195             }
196              
197 4         28 print $fh $data;
198 4 50       43 $fh->close || confess 'failed to close file - '.$!;
199 4         239 return;
200             }
201              
202             1;
203              
204              
205             __END__
206              
207             =encoding utf8
208              
209             =head1 NAME
210              
211             IO::Any - open anything
212              
213             =head1 SYNOPSIS
214              
215             # NOTE commented out lines doesn't work (yet)
216             use IO::Any;
217              
218             $fh = IO::Any->read('filename');
219             $fh = IO::Any->read('file://var/log/syslog');
220             #$fh = IO::Any->read('http://search.cpan.org/');
221             #$fh = IO::Any->read('-');
222             $fh = IO::Any->read(['folder', 'other-folder', 'filename']);
223             $fh = IO::Any->read('folder');
224             $fh = IO::Any->read("some text\nwith more lines\n");
225             $fh = IO::Any->read(\"some text\nwith more lines\n");
226             $fh = IO::Any->read('{"123":[1,2,3]}');
227             $fh = IO::Any->read('<root><element>abc</element></root>');
228             $fh = IO::Any->read(*DATA);
229             $fh = IO::Any->read(IO::String->new("cba"));
230             #$fh = IO::Any->read($object_with_toString_method);
231              
232             $fh = IO::Any->write('filename');
233             $fh = IO::Any->write('file://var/log/syslog');
234             #$fh = IO::Any->write('-');
235             $fh = IO::Any->write(['folder', 'filename']);
236             #$fh = IO::Any->write('=');
237             my $string;
238             $fh = IO::Any->write(\$string);
239              
240             my $content = IO::Any->slurp(['folder', 'filename']);
241             IO::Any->spew(['folder2', 'filename'], $content);
242              
243             perl -MIO::Any -le 'print IO::Any->slurp("/etc/passwd")'
244             perl -MIO::Any -le 'IO::Any->spew("/tmp/timetick", time())'
245              
246             =head1 DESCRIPTION
247              
248             The aim is to provide read/write anything. The module tries to guess
249             C<$what> the "anything" is based on some rules. See L</new> method Pod for
250             examples and L</new> and L</_guess_what> code for the implementation.
251              
252             There are two methods L</slurp> and L</spew> to read/write whole C<$what>.
253              
254             =head1 MOTIVATION
255              
256             The purpose is to be able to use L<IO::Any> in other modules that needs
257             to read or write data. The description for an argument could be - pass
258             anything that L<IO::Any> accepts as argument - GLOBs, L<IO::File>,
259             L<Path::Class::File>, L<IO::AtomicFile>, L<IO::String>, pointers to scalar
260             and pointer to array (array elements are passed to L<File::Spec/catfile>
261             as portable file addressing).
262              
263             First time I've used L<IO::Any> for L<JSON::Util> where for the functions
264             to encode and decode needs to read/write data.
265              
266             =head1 METHODS
267              
268             =head2 new($what, $how, $options)
269              
270             Open C<$what> in C<$how> mode.
271              
272             C<$what> can be:
273              
274             'filename' => [ 'file' => 'filename' ],
275             'folder/filename' => [ 'file' => 'folder/filename' ],
276             'file:///folder/filename' => [ 'file' => '/folder/filename' ],
277             [ 'folder', 'filename' ] => [ 'file' => File::Spec->catfile('folder', 'filename') ],
278             'http://a/b/c' => [ 'http' => 'http://a/b/c' ],
279             'https://a/b/c' => [ 'http' => 'https://a/b/c' ],
280             '{"123":[1,2,3]}' => [ 'string' => '{"123":[1,2,3]}' ],
281             '[1,2,3]' => [ 'string' => '[1,2,3]' ],
282             '<xml></xml>' => [ 'string' => '<xml></xml>' ],
283             "a\nb\nc\n" => [ 'string' => "a\nb\nc\n" ],
284             *DATA => [ 'file' => *{DATA}{IO} ],
285              
286             Returns filehandle. L<IO::String> for 'string', L<IO::File> for 'file'.
287             'http' not implemented yet.
288              
289             Here are available C<%$options> options:
290              
291             atomic true/false if the file operations should be done using L<IO::AtomicFile> or L<IO::File>
292             LOCK_SH lock file for shared access
293             LOCK_EX lock file for exclusive
294             LOCK_NB lock file non blocking (will throw an excpetion if file is
295             already locked, instead of blocking the process)
296              
297             =head2 _guess_what
298              
299             Returns ($type, $what). $type can be:
300              
301             file
302             string
303             http
304             iostring
305             iofile
306              
307             C<$what> is normalized path that can be used for IO::*.
308              
309             =head2 read($what)
310              
311             Same as C<< IO::Any->new($what, '<'); >> or C<< IO::Any->new($what); >>.
312              
313             =head2 write($what)
314              
315             Same as C<< IO::Any->new($what, '>'); >>
316              
317             =head2 slurp($what)
318              
319             Returns content of C<$what>.
320              
321             If L<AnyEvent> is loaded then uses event loop to read the content.
322              
323             =head2 spew($what, $data, $opt)
324              
325             Writes C<$data> to C<$what>.
326              
327             If L<AnyEvent> is loaded then uses event loop to write the content.
328              
329             =head1 SEE ALSO
330              
331             L<IO::All>, L<File::Spec>, L<Path::Class>
332              
333             =head1 AUTHOR
334              
335             Jozef Kutej, C<< <jkutej at cpan.org> >>
336              
337             =head1 CONTRIBUTORS
338              
339             The following people have contributed to the Sys::Path by committing their
340             code, sending patches, reporting bugs, asking questions, suggesting useful
341             advice, nitpicking, chatting on IRC or commenting on my blog (in no particular
342             order):
343              
344             SREZIC [...] cpan.org
345             Alexandr Ciornii
346             Gabor Szabo
347             Przemek WesoÅ‚ek
348             Slaven Rezić
349              
350             =head1 BUGS
351              
352             Please report any bugs or feature requests to C<bug-io-any at rt.cpan.org>, or through
353             the web interface at L<http://rt.cpan.org/NoAuth/ReportBug.html?Queue=IO-Any>. I will be notified, and then you'll
354             automatically be notified of progress on your bug as I make changes.
355              
356              
357              
358              
359             =head1 SUPPORT
360              
361             You can find documentation for this module with the perldoc command.
362              
363             perldoc IO::Any
364              
365              
366             You can also look for information at:
367              
368             =over 4
369              
370             =item * GitHub: issues
371              
372             L<http://github.com/jozef/IO-Any/issues>
373              
374             =item * RT: CPAN's request tracker
375              
376             L<http://rt.cpan.org/NoAuth/Bugs.html?Dist=IO-Any>
377              
378             =item * AnnoCPAN: Annotated CPAN documentation
379              
380             L<http://annocpan.org/dist/IO-Any>
381              
382             =item * CPAN Ratings
383              
384             L<http://cpanratings.perl.org/d/IO-Any>
385              
386             =item * Search CPAN
387              
388             L<http://search.cpan.org/dist/IO-Any>
389              
390             =back
391              
392              
393             =head1 COPYRIGHT & LICENSE
394              
395             Copyright 2009 Jozef Kutej, all rights reserved.
396              
397             This program is free software; you can redistribute it and/or modify it
398             under the same terms as Perl itself.
399              
400              
401             =cut
402              
403             1; # End of IO::Any