File Coverage

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