File Coverage

blib/lib/Text/Filter/Cooked.pm
Criterion Covered Total %
statement 78 92 84.7
branch 22 38 57.8
condition 10 17 58.8
subroutine 26 29 89.6
pod 3 15 20.0
total 139 191 72.7


line stmt bran cond sub pod time code
1             #! perl
2              
3             package Text::Filter::Cooked;
4              
5 1     1   23594 use strict;
  1         4  
  1         63  
6             our $VERSION = "0.02";
7 1     1   6 use base q{Text::Filter};
  1         2  
  1         738  
8 1     1   16 use Carp;
  1         3  
  1         1290  
9              
10             # later use Encode;
11              
12             =head1 NAME
13              
14             Text::Filter::Cooked - Cooked reader for input files
15              
16             =head1 SYNOPSIS
17              
18             use Text::Filter::Cooked;
19             my $f = Text::Filter::Cooked->new
20             (input => 'myfile.dat',
21             comment => "#",
22             join_lines => "\\");
23              
24             while ( my $line = $f->readline ) {
25             printf("%3d\t%s\n", $f->lineno, $line);
26             }
27              
28             =head1 DESCRIPTION
29              
30             Text::Filter::Cooked is a generic input reader. It takes care of a
31             number of things that are commonly used when reading data and
32             configuration files.
33              
34             =over 4
35              
36             =item *
37              
38             Excess whitespace (leading and trailing) may be removed automatically.
39             Also, multiple whitespace characters may be replaced by a single blank.
40              
41             =item *
42              
43             Empty lines may be ignored automatically.
44              
45             =item *
46              
47             Lines that end with a custom defined join symbol, ususally a
48             backslash, are joined with the next line.
49              
50             =item *
51              
52             Lines that start with a custom defined comment symbol are ignored.
53              
54             =back
55              
56             =for later
57             On top of this, if the input file starts with a Unicode BOM, the input
58             will be correctly decoded into Perl internal format. It is also
59             possible to change the encoding used in a single file as often as
60             desired. See L.
61              
62             Text::Filter::Cooked is based on Text::Filter, see L.
63              
64             =cut
65              
66             ################ Attribute Controls ################
67              
68             my %_attributes =
69             ( ignore_empty_lines => 1,
70             ignore_leading_whitespace => 1,
71             ignore_trailing_whitespace => 1,
72             compress_whitespace => 1,
73             # later input => \&_diamond,
74             # later input_encoding => undef,
75             input_postread => 'chomp',
76             output_prewrite => 'newline',
77             comment => undef,
78             join_lines => undef,
79             _lineno => undef,
80             _open => 0,
81             );
82              
83             sub _standard_atts {
84 3     3   4 my $self = shift;
85 3         6 my %k;
86 3         15 @k{ $self->SUPER::_standard_atts, keys %_attributes } = (0);
87 3         23 return keys %k;
88             }
89              
90             sub _attr_default {
91 27     27   38 my ($self, $attr) = @_;
92 27 100       98 return $_attributes{$attr} if exists $_attributes{$attr};
93 3         21 return $self->SUPER::_attr_default($attr);
94             }
95              
96             ################ Constructor ################
97              
98             =head1 CONSTRUCTOR
99              
100             The constructor is called new() and takes a hash with attributes as
101             its parameter.
102              
103             The following attributes are recognized and used by the constructor,
104             all others are passed to the base class, Text::Filter.
105              
106             =over 4
107              
108             =item ignore_empty_lines
109              
110             If true, empty lines encountered in the input are ignored.
111              
112             =item ignore_leading_whitespace
113              
114             If true, leading whitespace encountered in the input is ignored.
115              
116             =item ignore_trailing_whitespace
117              
118             If true, trailing whitespace encountered in the input is ignored.
119              
120             =item compress_whitespace
121              
122             If true, multiple adjacent whitespace are compressed to a single space.
123              
124             =item join_lines
125              
126             This must be set to a string. Input lines that end with this string
127             (not taking the final line ending into account) are joined with the
128             next line read from the input.
129              
130             =item comment
131              
132             This must be set to a string. Input lines that start with this string
133             are ignored.
134              
135             =for later
136             (but see L).
137              
138             =begin later item input_encoding
139              
140             Assume the input file to have this encoding.
141              
142             Setting input_encoding will enable automatic and transparant handling
143             of different file encodings, see L.
144              
145             =back
146              
147             =cut
148              
149             # Inherited from base class.
150              
151             ################ Attributes ################
152              
153             =head1 METHODS
154              
155             All attributes have set and get methods, e.g., C and
156             C.
157              
158             Other methods:
159              
160             =over 4
161              
162             =item readline
163              
164             Read a single line of input. If line ignoring is in effect, the
165             operation will be repeated internally until there is data to return.
166              
167             =item lineno
168              
169             Returns the number of the last line that was read from the input.
170              
171             =item is_eof
172              
173             Returns true iff the last record from the input has been read.
174              
175             =back
176              
177             =cut
178              
179             sub set_input {
180 3     3 1 6 my ($self, $input) = @_;
181 3 50   0   39 $input = sub { $self->_diamond } if $input eq \&_diamond;
  0         0  
182 3         16 $self->SUPER::set_input($input);
183             }
184              
185             sub set_ignore_empty_lines {
186 3     3 0 9 $_[0]->{ignore_empty_lines} = $_[1];
187 3         7 return;
188             }
189              
190             sub get_ignore_empty_lines {
191 30     30 0 198 return $_[0]->{ignore_empty_lines};
192             }
193              
194             =begin later
195              
196             sub set_input_encoding {
197             my ($self, $enc) = @_;
198             $self->{input_encoding} = $enc;
199             if ( my $fd = $self->get_filter_input_fd ) {
200             binmode($fd, ':raw');
201             }
202             # warn("Input encoding = $enc\n");
203             return;
204             }
205              
206             sub get_input_encoding {
207             return $_[0]->{input_encoding};
208             }
209              
210             =cut
211              
212             sub set_ignore_trailing_whitespace {
213 3     3 0 9 $_[0]->{ignore_trailing_whitespace} = $_[1];
214 3         8 return;
215             }
216              
217             sub get_ignore_trailing_whitespace {
218 9     9 0 46 return $_[0]->{ignore_trailing_whitespace};
219             }
220              
221             sub _set_lineno {
222 33 100   33   62 if ( @_ == 1 ) {
223 30         50 $_[0]->{_lineno}++
224             }
225             else {
226 3         7 $_[0]->{_lineno} = $_[1];
227             }
228 33         42 return;
229             }
230              
231             sub _get_lineno {
232 18     18   45 return $_[0]->{_lineno};
233             }
234              
235             sub set_comment {
236 3     3 0 5 my ($self, $c) = @_;
237             # This check will probably fail with a custom regexp engine.
238 3 50 33     59 $c = qr/^\Q$c\E(.*)$/ unless !defined($c) || ref($c) eq 'Regexp';
239 3         7 $self->{comment} = $c;
240 3         12 return;
241             }
242              
243             sub get_comment {
244 24     24 0 50 return $_[0]->{comment};
245             }
246              
247             sub set_ignore_leading_whitespace {
248 3     3 0 9 $_[0]->{ignore_leading_whitespace} = $_[1];
249 3         7 return;
250             }
251              
252             sub get_ignore_leading_whitespace {
253 9     9 0 37 return $_[0]->{ignore_leading_whitespace};
254             }
255              
256             sub set_compress_whitespace {
257 3     3 0 10 $_[0]->{compress_whitespace} = $_[1];
258 3         6 return;
259             }
260              
261             sub get_compress_whitespace {
262 9     9 0 75 return $_[0]->{compress_whitespace};
263             }
264              
265             sub set_join_lines {
266 3     3 0 5 my ($self, $v) = @_;
267             # This check will probably fail with a custom regexp engine.
268 3 50 33     63 $v = qr/^(.*)\Q$v\E$/ unless !defined($v) || ref($v) eq 'Regexp';
269 3         7 $self->{join_lines} = $v;
270 3         60 return;
271             }
272              
273             sub get_join_lines {
274 21     21 0 37 return $_[0]->{join_lines};
275             }
276              
277             sub _set_eof {
278 3     3   8 $_[0]->{_eof} = 1;
279 3         4 return;
280             }
281              
282             sub _is_eof {
283 12     12   32 return $_[0]->{_eof};
284             }
285              
286             sub _set_open {
287 3     3   16 $_[0]->{_open} = 1;
288 3         7 return;
289             }
290              
291             sub _is_open {
292 0     0   0 return $_[0]->{_open};
293             }
294              
295             ################ Methods ################
296              
297             sub readline {
298 12     12 1 24 my $self = shift;
299              
300 12 50       24 return if $self->_is_eof;
301              
302             my $post = sub {
303 9     9   20 for ( shift ) {
304              
305             # Whitespace ignore + compress.
306 9 50       17 s/^\s+// if $self->get_ignore_leading_whitespace;
307 9 50       24 s/\s+$// if $self->get_ignore_trailing_whitespace;
308 9 50       19 s/\s+/ /g if $self->get_compress_whitespace;
309              
310 9         69 return $_;
311             }
312 12         44 };
313              
314 12         15 my $line;
315             my $pre;
316              
317 12         57 while ( defined ($line = $self->SUPER::readline) ) {
318              
319             =begin later
320              
321             my $ienc = $self->get_input_encoding;
322             if ( $ienc && ! defined $self->_get_lineno ) {
323             # Detecting BOM...
324             if ( substr($line, 0, 2) eq "\xff\xfe" ) {
325             # Found BOM (BE)
326             $line = substr($line, 2);
327             $self->set_input_encoding($ienc = "utf-16-be");
328             }
329             elsif ( substr($line, 0, 2) eq "\xfe\xff" ) {
330             # Found BOM (LE)
331             $line = substr($line, 2);
332             $self->set_input_encoding($ienc = "utf-16le");
333             }
334             }
335              
336             =cut
337              
338 30         61 $self->_set_lineno;
339 30 100       87 $self->{_start_line} = $self->_get_lineno unless defined $pre;
340              
341             =begin later
342              
343             if ( $ienc ) {
344             $line = decode($ienc, $line, 0);
345             }
346              
347             =cut
348              
349             # Feature: ignore_empty_lines.
350 30 100 66     55 next unless $self->get_ignore_empty_lines && $line =~ /\S/;
351              
352 24         50 my $t = $self->get_comment;
353 24 100 66     165 if ( $t && $line =~ $t ) {
354              
355             =begin later
356             $line = $1;
357             if ( $line =~ /^\s*
358             content-type \s*
359             : \s*
360             text \s* (?: \/ \s* plain \s* )?
361             ; \s* charset \s* = \s*
362             ([^\s;]+)
363             \s* $
364             /mix ) {
365             $self->set_input_encoding($1);
366             }
367              
368             =cut
369              
370 3         12 next;
371             }
372              
373 21         42 $t = $self->get_join_lines;
374 21 100 66     155 if ( $t && $line =~ $t ) {
375 12   100     262 $pre ||= "";
376 12         27 $pre .= $1;
377 12         40 next;
378             }
379              
380 9 100       39 return $post->(defined $pre ? "$pre$line" : $line);
381             }
382 3         8 $self->_set_eof;
383              
384             =for later
385             $self->set_input_encoding($self->{input_encoding});
386              
387             =cut
388              
389 3 50       7 return $post->($pre) if defined $pre;
390 3         23 return;
391             }
392              
393             sub lineno {
394 3     3 1 13 my $self = shift;
395 3         25 return $self->{_start_line};
396             }
397              
398             sub _diamond {
399 0     0     my $self = shift;
400              
401 0           while ( 1 ) {
402 0 0         unless ( $self->_is_open ) {
403 0 0         return unless @ARGV;
404 0           my $argv = shift(@ARGV);
405 0           $self->{_argf} = undef;
406 0 0         open($self->{_argf}, '< :raw', $argv)
407             or die("$argv: $!\n");
408 0           $self->_set_open(1);
409             }
410 0           my $result = $self->{_argf}->readline;
411 0 0         return $result if defined $result;
412 0           close($self->{_argf});
413 0           $self->_set_open(0);
414             }
415             }
416              
417             1;
418              
419             __END__