File Coverage

blib/lib/IO/Slice.pm
Criterion Covered Total %
statement 130 139 93.5
branch 41 60 68.3
condition 6 7 85.7
subroutine 27 29 93.1
pod 15 15 100.0
total 219 250 87.6


line stmt bran cond sub pod time code
1             package IO::Slice;
2             {
3             $IO::Slice::VERSION = '0.2_01';
4             }
5              
6             # ABSTRACT: restrict reads to a range in a file
7              
8 7     7   142340 use strict;
  7         19  
  7         283  
9 7     7   3320 use English qw< -no_match_vars >;
  7         21926  
  7         36  
10 7     7   5640 use Symbol ();
  7         5569  
  7         265  
11 7     7   53 use Fcntl qw< :seek >;
  7         10  
  7         981  
12 7     7   4227 use Log::Log4perl::Tiny qw< :easy :dead_if_first >;
  7         41737  
  7         36  
13              
14              
15             sub new {
16 15     15 1 6498 my $package = shift;
17 15         64 my $efh = Symbol::gensym();
18 15         294 my $self = tie *$efh, $package;
19 15 50       85 $self->open(@_) if @_;
20 12         32 return $efh;
21             }
22              
23             sub TIEHANDLE {
24 15     15   95 DEBUG "TIEHANDLE(@_)";
25 15         404 my $package = shift;
26 15         45 my $self = bless {}, $package;
27 15         39 return $self;
28             }
29              
30             sub DESTROY {
31 15     15   4235 DEBUG "DESTROY(@_)";
32             }
33              
34              
35             sub open {
36 15     15 1 19 my $self = shift;
37 15 100       64 my %args = ref($_[0]) ? %{$_[0]} : @_;
  9         147  
38              
39 15         47 $self->close();
40              
41             # mandatory features
42 15         31 for my $mandatory (qw< offset length >) {
43 29 100       81 LOGCROAK "open(): missing mandatory feature $mandatory"
44             unless defined $args{$mandatory};
45 27         64 $self->{$mandatory} = $args{$mandatory};
46             }
47              
48             # optional/conditional features
49 13 100       56 $self->{filename} = defined($args{filename})
50             ? $args{filename} : '*undefined*';
51              
52             # underlying filehandle
53 13 100       33 if ($args{fh}) {
54 2         5 $self->{fh} = $args{fh};
55             }
56             else {
57 11 100       43 LOGCROAK "open(): either fh or filename MUST be provided"
58             unless exists $args{filename};
59 10 50       444 open my $fh, '<:raw', $args{filename}
60             or LOGCROAK "open('$args{filename}'): $OS_ERROR";
61 10         31 $self->{fh} = $fh;
62             }
63              
64 12         23 $self->{position} = 0;
65              
66 12         29 return $self; # been there, done that
67             }
68              
69              
70             sub close {
71 16     16 1 50 my $self = shift;
72 16         69 %$self = ();
73 16         25 return 1;
74             }
75              
76              
77             sub opened {
78 2     2 1 915 my $self = shift;
79 2         13 return exists $self->{fh};
80             }
81              
82              
83             sub binmode {
84 0     0 1 0 my $self = shift;
85 0         0 return ! scalar @_;
86             }
87              
88              
89             sub getc {
90 154     154 1 43884 my $self = shift;
91 154         139 my $buf;
92 154 100       226 return $buf if $self->read($buf, 1);
93 4         12 return undef;
94             }
95              
96              
97             sub ungetc {
98 3     3 1 511 my $self = shift;
99 3         10 $self->pos($self->{position} - 1);
100 3         7 return 1;
101             }
102              
103              
104             sub eof {
105 4     4 1 871 my $self = shift;
106 4         21 return $self->{position} >= $self->{length};
107             }
108              
109              
110             sub pos {
111 173     173 1 668 my $self = shift;
112 173         181 my $retval = $self->{position};
113 173 100       254 if (@_) {
114 171         131 my $newpos = shift;
115 171   100     298 $newpos ||= 0;
116 171 50       818 $newpos = 0 if $newpos !~ m{\A\d+\z}mxs;
117 171         156 $newpos += 0; # make it a "normal" non-negative integer
118 171 50       271 $newpos = $self->{length} if $newpos > $self->{length};
119 171         221 $self->{position} = $newpos;
120             }
121 173         176 return $retval;
122             }
123              
124              
125             sub seek {
126 3     3 1 2886 my ($self, $offset, $whence) = @_;
127              
128 3 50       11 $whence = '*undefined*' unless defined $whence;
129 3 50       8 if ($whence == SEEK_SET) {
    0          
    0          
130 3         6 $self->pos($offset);
131             }
132             elsif ($whence == SEEK_CUR) {
133 0         0 $self->pos($self->{position} + $offset);
134             }
135             elsif ($whence == SEEK_END) {
136 0         0 $self->pos($self->{length} + $offset);
137             }
138             else {
139 0         0 LOGCROAK "seek(): whence value $whence is not valid";
140             }
141              
142 3         5 return 1;
143             }
144              
145              
146 3     3 1 1112 sub tell { return shift->{position} }
147              
148              
149             sub do_read {
150 7     7 1 8 my ($self, $count) = @_;
151 7         5 my $buf;
152 7 50       11 defined (my $nread = $self->read($buf, $count)) or return;
153 7         16 return $buf;
154             }
155              
156              
157             sub getline {
158 8     8 1 7 my $self = shift;
159 8 100       17 return if $self->{position} >= $self->{length};
160              
161 7 100       14 return $self->do_read($self->{length} - $self->{position})
162             unless defined $INPUT_RECORD_SEPARATOR; # slurp mode
163              
164 6         6 my $chunk_size = 100;
165 6 100       12 if (! length $INPUT_RECORD_SEPARATOR) { # paragraph mode
166             return $self->_conditioned_getstuff(sub {
167 4     4   5 my $idx = CORE::index $_[0], "\n\n";
168 4 50       10 return if $idx < 0;
169 4         2 my $nreturn = ++$idx;
170 4         4 my $buflen = length $_[0];
171 4         5 ++$idx;
172 4   100     23 ++$idx while ($idx < $buflen) && (substr($_[0], $idx, 1) eq "\n");
173 4         7 return ($nreturn, $idx);
174 4         16 });
175             }
176              
177             # look for $INPUT_RECORD_SEPARATOR, precisely
178             return $self->_conditioned_getstuff(sub {
179 2     2   9 my $idx = CORE::index $_[0], $INPUT_RECORD_SEPARATOR;
180 2 50       7 return if $idx < 0;
181 2         2 my $n = $idx + length($INPUT_RECORD_SEPARATOR);
182 2         8 return ($n, $n);
183 2         9 });
184             }
185              
186             sub _conditioned_getstuff {
187 6     6   7 my ($self, $condition, $chunk_size) = @_;
188 6   50     16 $chunk_size ||= 100;
189 6         7 my $initial_position = $self->{position};
190 6         6 my $buffer;
191 6         11 while ($self->{position} < $self->{length}) {
192 6         9 my $chunk = $self->do_read($chunk_size);
193 6 50       12 if (! $chunk) {
194 0         0 $self->{position} = $initial_position;
195 0         0 return;
196             }
197 6 50       8 $buffer = defined($buffer) ? $buffer . $chunk : $chunk;
198 6 50       10 if (my ($nreturn, $ndelete) = $condition->($buffer)) {
199 6         13 $buffer = substr $buffer, 0, $nreturn;
200 6         11 $self->pos($initial_position + $ndelete);
201 6         46 return $buffer;
202             }
203             }
204 0         0 return $buffer;
205             }
206              
207              
208             sub getlines {
209 1 50   1 1 3 LOGCROAK "getlines is only valid in list context"
210             unless wantarray();
211 1         2 my $self = shift;
212 1         1 my ($line, @lines);
213 1         2 push @lines, $line while defined($line = $self->getline());
214 1         7 return @lines;
215             }
216              
217             sub READLINE {
218 4 100   4   112 goto &getlines if wantarray();
219 3         9 goto &getline;
220             }
221              
222              
223             sub read {
224 163     163 1 171 my $self = shift;
225 163         147 my $bufref = \shift;
226 163         114 my $length = shift;
227              
228 163         168 my $position = $self->{position};
229 163         169 my $data_length = $self->{length};
230 163 100       312 return 0 if $position >= $data_length;
231              
232 159         147 my $fh = $self->{fh};
233 159 50       592 CORE::seek $fh, ($self->{offset} + $position), SEEK_SET
234             or return;
235              
236 159         166 my $available = $data_length - $position;
237 159 100       237 $length = $available if $length > $available;
238              
239 159 50       1053 defined (my $nread = read $fh, $$bufref, $length, @_)
240             or return;
241 159         306 $self->pos($position + $nread);
242 159         573 return $nread;
243             }
244              
245             {
246 7     7   10435 no strict 'refs';
  7         12  
  7         217  
247 7     7   29 no warnings 'once';
  7         12  
  7         1704  
248              
249              
250             *sysseek = \&seek;
251             *sysread = \&read;
252              
253              
254 0     0     my $nothing = sub { return };
255             *print = $nothing;
256             *printflush = $nothing;
257             *printf = $nothing;
258             *fileno = $nothing;
259             *error = $nothing;
260             *clearerr = $nothing;
261             *sync = $nothing;
262             *write = $nothing;
263             *setbuf = $nothing;
264             *setvbuf = $nothing;
265             *untaint = $nothing;
266             *autoflush = $nothing;
267             *fcntl = $nothing;
268             *ioctl = $nothing;
269             *input_line_number = $nothing;
270              
271             *GETC = \&getc;
272             *PRINT = $nothing;
273             *PRINTF = $nothing;
274             *READ = \&read;
275             *WRITE = $nothing;
276             *SEEK = \&seek;
277             *TELL = \&tell;
278             *EOF = \&eof;
279             *CLOSE = \&close;
280             *BINMODE = \&binmode;
281             *FILENO = $nothing;
282             }
283              
284             1;
285              
286             __END__