File Coverage

blib/lib/IO/Any.pm
Criterion Covered Total %
statement 28 30 93.3
branch n/a
condition n/a
subroutine 10 10 100.0
pod n/a
total 38 40 95.0


line stmt bran cond sub pod time code
1             package IO::Any;
2              
3 2     2   203824 use warnings;
  2         8  
  2         164  
4 2     2   11 use strict;
  2         4  
  2         115  
5              
6             our $VERSION = '0.07';
7              
8 2     2   11 use Carp 'confess';
  2         17  
  2         189  
9 2     2   14 use Scalar::Util 'blessed';
  2         4  
  2         104  
10 2     2   2170 use IO::String;
  2         8617  
  2         74  
11 2     2   1003 use IO::File;
  2         10013  
  2         323  
12 2     2   2095 use IO::AtomicFile;
  2         1762  
  2         99  
13 2     2   14 use File::Spec;
  2         3  
  2         53  
14 2     2   10 use Fcntl qw(:flock);
  2         5  
  2         313  
15 2     2   4576 use List::MoreUtils qw(none any);
  0            
  0            
16              
17             sub new {
18             my $class = shift;
19             my $what = shift;
20             my $how = shift || '<';
21             my $opt = shift || {};
22             confess 'too many arguments'
23             if @_;
24              
25             confess '$what is missing'
26             if not defined $what;
27              
28             confess 'expecting hash ref'
29             if ref $opt ne 'HASH';
30             foreach my $key (keys %$opt) {
31             confess 'unknown option '.$key
32             if (none { $key eq $_ } qw(atomic LOCK_SH LOCK_EX LOCK_NB));
33             }
34              
35             my ($type, $proper_what) = $class->_guess_what($what);
36              
37              
38             if ($type eq 'string') { return IO::String->new($proper_what) }
39             if ($type eq 'file') {
40             my $fh = $opt->{'atomic'} ? IO::AtomicFile->new() : IO::File->new();
41             $fh->open($proper_what, $how)
42             or confess 'error opening file "'.$proper_what.'" - '.$!;
43              
44             # locking if requested
45             if ($opt->{'LOCK_SH'} or $opt->{'LOCK_EX'}) {
46             flock($fh,
47             ($opt->{'LOCK_SH'} ? LOCK_SH : 0)
48             | ($opt->{'LOCK_EX'} ? LOCK_EX : 0)
49             | ($opt->{'LOCK_NB'} ? LOCK_NB : 0)
50             ) or confess 'flock failed - '.$!;
51             }
52              
53             return $fh;
54             }
55             if ($type eq 'iofile') { return $proper_what }
56             if ($type eq 'iostring') { return $proper_what }
57             if ($type eq 'http') { die 'no http support yet :-|' }
58             }
59              
60             sub _guess_what {
61             my $class = shift;
62             my $what = shift;
63              
64             if (!blessed($what)) { } # not blessed, do nothing
65             elsif ($what->isa('Path::Class::File')) { $what = $what->stringify }
66             elsif (any { $what->isa($_) } qw(IO::File IO::AtomicFile IO::Uncompress::Bunzip2)) {
67             confess 'passed unopened IO::File'
68             if not $what->opened;
69             return ('iofile', $what);
70             }
71             elsif ($what->isa('IO::String')) { return ('iostring', $what) }
72             else { confess 'no support for '.blessed($what) };
73              
74             my $ref_what = ref($what);
75             if ($ref_what eq 'ARRAY') { return ('file', File::Spec->catfile(@{$what})) }
76             elsif ($ref_what eq 'SCALAR') { return ('string', $what) }
77             elsif ($ref_what eq '') {} # do nothing here if not reference
78             else { confess 'no support for ref '.(ref $what) }
79              
80             # check for typeglobs
81             if ((ref \$what eq 'GLOB') and (my $fh = *{$what}{IO})) {
82             return ('iofile', $fh);
83             }
84              
85             if ($what =~ m{^file://(.+)$}) { return ('file', $1) } # local file
86             if ($what =~ m{^https?://}) { return ('http', $what) } # http link
87             if ($what =~ m{^<}) { return ('string', $what) } # xml string
88             if ($what =~ m(^{)) { return ('string', $what) } # json string
89             if ($what =~ m{^\[}) { return ('string', $what) } # json string
90             if ($what =~ m{\n[\s\w]}) { return ('string', $what) } # multi-line string
91             if ($what eq '') { return ('string', '') } # empty string
92             { return ('file', $what) } # default is filename
93             }
94              
95             sub read {
96             my $class = shift;
97             my $what = shift;
98             my $opt = shift;
99             confess 'too many arguments'
100             if @_;
101              
102             return $class->new($what, '<', $opt);
103             }
104              
105             sub write {
106             my $class = shift;
107             my $what = shift;
108             my $opt = shift;
109             confess 'too many arguments'
110             if @_;
111              
112             return $class->new($what, '>', $opt);
113             }
114              
115             sub slurp {
116             my $class = shift;
117             my $what = shift;
118             my $opt = shift;
119             confess 'too many arguments'
120             if @_;
121              
122             my $fh = $class->read($what, $opt);
123              
124             # use event loop when AnyEvent is loaded (skip IO::String, doesn't work and makes no sense)
125             # not supported under MSWin32
126             if ($INC{'AnyEvent.pm'} and not $fh->isa('IO::String') and ($^O ne 'MSWin32')) {
127             eval 'use AnyEvent::Handle'
128             if not $INC{'AnyEvent/Handle.pm'};
129             my $eof = AnyEvent->condvar;
130             my $content = '';
131             my $hdl = AnyEvent::Handle->new(
132             fh => $fh,
133             on_read => sub {
134             $content .= delete $_[0]->{'rbuf'};
135             },
136             on_eof => sub {
137             $eof->send;
138             },
139             on_error => sub {
140             my ($hdl, $fatal, $msg) = @_;
141             $hdl->destroy;
142             $eof->croak($msg);
143             }
144             );
145              
146             $eof->recv;
147             $hdl->destroy;
148             close $fh;
149             return $content;
150             }
151              
152             my $content = do { local $/; <$fh> };
153             close $fh;
154             return $content;
155             }
156              
157             sub spew {
158             my $class = shift;
159             my $what = shift;
160             my $data = shift;
161             my $opt = shift;
162             confess 'too many arguments'
163             if @_;
164              
165             # "parade" to allow safe locking
166             my $fh = $class->new($what, '+>>', $opt);
167             $fh->seek(0,0);
168             $fh->truncate(0);
169              
170             # use event loop when AnyEvent is loaded (skip IO::String, doesn't work and makes no sense)
171             if ($INC{'AnyEvent.pm'} and not $fh->isa('IO::String')) {
172             eval 'use AnyEvent::Handle'
173             if not $INC{'AnyEvent/Handle.pm'};
174              
175             my $eof = AnyEvent->condvar;
176             my $hdl = AnyEvent::Handle->new(
177             fh => $fh,
178             on_drain => sub {
179             $eof->send;
180             },
181             on_error => sub {
182             my ($hdl, $fatal, $msg) = @_;
183             $hdl->destroy;
184             $eof->croak($msg);
185             }
186             );
187              
188             $hdl->push_write($data);
189              
190             $eof->recv;
191             $hdl->destroy;
192             close $fh;
193             return;
194             }
195              
196             print $fh $data;
197             $fh->close || confess 'failed to close file - '.$!;
198             return;
199             }
200              
201             1;
202              
203              
204             __END__