File Coverage

lib/File/Alter.pm
Criterion Covered Total %
statement 74 75 98.6
branch 23 34 67.6
condition 10 11 90.9
subroutine 13 13 100.0
pod 5 5 100.0
total 125 138 90.5


line stmt bran cond sub pod time code
1             package File::Alter;
2              
3 2     2   1831 use strict;
  2         5  
  2         97  
4 2     2   1193 use base 'IO::String';
  2         10  
  2         3484  
5 2     2   20441 use IO::File;
  2         23105  
  2         338  
6 2     2   17 use Carp;
  2         3  
  2         119  
7 2     2   3218 use Params::Check qw[allow];
  2         25055  
  2         167  
8              
9 2     2   21 use vars qw[$VERSION];
  2         3  
  2         99  
10              
11             $VERSION = '0.01';
12              
13             ### readline() vs <> doesn't DWIM
14             ### mailed p5p at July 25, 2005 12:55:41 PM CEST
15             # As it seems, it calls the builtin readline() on <>,
16             # rather than the class' readline():
17             #
18             # BEGIN { *CORE::GLOBAL::readline = sub { 2 }; }
19             # sub X::readline { 1 };
20             # $x = bless {}, 'X';
21             # print "rl ". $x->readline . $/;
22             # print "<> ". <$x> . $/;
23             # rl 1
24             # <> 2
25              
26 2     2   9 use vars qw[$LINENUMBER $LINE];
  2         4  
  2         1530  
27              
28             =head1 NAME
29              
30             File::Alter
31              
32             =head2 SYNOPSIS
33              
34             use File::Alter;
35            
36             $fh = File::Alter->new( "filename.txt" );
37            
38             $fh->insert( 3 => "new text\n" ); # insert text on line 3
39              
40             $fh->remove( 7 ); # remove line 7
41             $fh->remove( '$LINE =~ /foo/' ); # remove the line if
42             # it matches 'foo'
43              
44             $fh->alter( qr/2/, 'TWO' ); # replace all occurrences of
45             # 2 by TWO
46             $fh->alter( a => 'b', '$e == 4'); # replace all a by b if
47             # $e equals 4
48              
49             $str = $fh->as_string; # returns the buffer as string
50              
51             ### global variables you can use in conditions
52             $File::Alter::LINE # contents of the current line
53             $File::Alter::LINENUMBER # line number of the current line
54              
55             =head2 DESCRIPTION
56              
57             C allows in memory manipulation of a file's contents.
58             The modified buffer will B be written back to the file at any
59             point! This is useful if you want to massage read-only files, or files
60             you do not wish to alter, before they are read or used by an application.
61              
62             C inherits directly from C adding it's own
63             methods. This means that any method that is supported by C
64             is supported by C.
65              
66             =head1 METHODS
67              
68             =head2 $fh = File::Alter->new( FILENAME );
69              
70             Creates a new C filehandle object. The arguments get passed
71             straight to C, so even more complicated strings are
72             accepted. Please note though that opening a file for writing makes no
73             sense, as you're only able to modify the files contents in memory, without
74             writing it to disk.
75              
76             =cut
77              
78              
79             sub new {
80 5     5 1 4033 my $class = shift;
81 5 50       20 my @args = @_ or return;
82              
83 5 50       31 my $fh = IO::File->new( @args ) or (
84             carp( "Could not create new filehandle from args '@args': $!" ),
85             return
86             );
87              
88 5         461 my $self = __PACKAGE__->SUPER::new( do { local $/; <$fh> } );
  5         19  
  5         204  
89            
90 5         238 return $self;
91             }
92              
93             =head2 $string = $fh->as_string;
94              
95             Returns the stringified version of the internal buffer
96              
97             =cut
98              
99             sub as_string {
100 8     8 1 2871 my $self = shift;
101 8         29 my $pos = $self->pos;
102            
103 8         52 $self->setpos(0);
104 8         66 my $str = do { local $/; <$self> };
  8         22  
  8         28  
105            
106 8         130 $self->setpos( $pos );
107            
108 8         89 return $str;
109             }
110              
111             =head2 $bool = $fh->insert( $line => $text );
112              
113             Inserts the given text at linenumber C<$line>. This text can be multiline
114             if desired, as it's a plain insert. That means that if you want this
115             text to be on it's own line, you should add a newline to it.
116              
117             =cut
118              
119             sub insert {
120 3 50   3 1 1072 my $self = shift or return;
121              
122 3         9 $self->_edit( insert => @_ );
123             }
124              
125             =head2 $bool = $fh->alter( $find => $replace, [$condition] );
126              
127             Looks on a per-line basis for the string specified by C<$find> and tries
128             to replace that with C<$replace>. Note that C<$find> can be a C
129             object if you so desire.
130              
131             If you specify a condition, the substitute will only be attempted if the
132             condition evaluates to C. You can use some of C's
133             global variables to make conditions based on line numbers and contents;
134             see the C section for details.
135              
136             =cut
137              
138             sub alter {
139 3 50   3 1 855 my $self = shift or return;
140              
141 3         8 $self->_edit( alter => @_ );
142             }
143              
144             =head2 $bool = $fh->remove( $line | $condition );
145              
146             Removes a line based on either line number or condition.
147              
148             If you specify a condition, the remove will only be done if the
149             condition evaluates to C. You can use some of C's
150             global variables to make conditions based on line numbers and contents;
151             see the C section for details.
152              
153             =cut
154              
155             sub remove {
156 3 50   3 1 897 my $self = shift or return;
157            
158 3         8 $self->_edit( remove => @_ );
159             }
160              
161             sub _edit {
162 9     9   13 my $self = shift;
163 9         10 my $type = shift;
164            
165 9 50       36 unless( allow( $type, [qw|alter insert remove|] ) ) {
166 0         0 carp( "Unknown type '$type' -- can not comply" ),
167             return
168             };
169            
170            
171             ### first, reset the position to 0
172 9         220 $self->setpos(0);
173              
174             ### $. is actually not the line number, but the amount of times
175             ### you've read a line from a filehandle
176 9         98 local $LINENUMBER;
177            
178 9         8 my ($buf);
179              
180 9 100       31 if( $type eq 'alter' ) {
    100          
    50          
181 3 50       9 my $find = shift or return;
182 3 50       6 my $replace = shift or return;
183 3   100     11 my $cond = shift || 1;
184            
185 3         12 while( $LINE = <$self> ) {
186 18 100       829 eval { $LINE =~ s/$find/$replace/ } if eval $cond;
  13         46  
187 18         62 $buf .= $LINE;
188             }
189            
190             } elsif ( $type eq 'insert' ) {
191 3 50       11 my $line = shift or return;
192 3 50       4 my $text = shift; return unless defined $text;
  3         8  
193              
194              
195 3         12 while( $LINE = <$self> ) {
196 18 100       362 $buf .= $text if ++$LINENUMBER eq $line;
197 18         51 $buf .= $LINE;
198             }
199             } elsif ( $type eq 'remove' ) {
200 3         5 my $line; my $cond;
201            
202 3 100       13 $_[0] !~ /\D/ ? $line = $_[0] : $cond = $_[0];
203              
204 3         8 while( $LINE = <$self> ) {
205 17         278 ++$LINENUMBER;
206            
207 17 100 100     316 if( ($line and $line eq $LINENUMBER) or
      100        
      66        
208             ($cond and eval $cond )
209             ) {
210 3         11 next;
211             }
212            
213 14         40 $buf .= $LINE;
214             }
215             }
216            
217             ### we changed stuff from the FH... we need to truncate it to 0
218             ### and reprint the buffer to make sure there's no trailing garbage
219 9         111 $self->truncate(0);
220              
221             ### set to 0, so to print at the beginning
222 9         87 $self->setpos(0);
223 9         90 $self->print( $buf );
224            
225 9         143 $self->setpos(0);
226              
227 9         95 return 1;
228             }
229              
230             =head1 GLOBAL VARIABLES
231              
232             =head2 $File::Alter::LINE
233              
234             Contains the contents of the current line being read. You can use this
235             in a condition if you wish to only have it apply relative to a certain
236             line number. For example:
237              
238             $fh->remove( '$LINE =~ /foo/ or $LINE =~ /bar/' );
239            
240             To remove all lines that contain C or C.
241              
242             =head2 $File::Alter::LINENUMBER
243              
244             Containts the current line number of the file being read. You can use
245             this in a condition if you wish to only have it apply relative to a certain
246             line number. For example:
247              
248             $fh->remove( '$LINENUMBER > 20 and $LINENUMBER < 30' );
249            
250             To remove all lines between 20 and 30.
251              
252             =head1 CAVEATS
253              
254             =head2 Filehandle position always reset to C<0> after modification
255              
256             As we're modifying the filehandle on every C, C and
257             C, we can not be certain that the position the last C
258             was from is still correct (especially since the position is in bytes),
259             nor can we be sure it's desirable.
260              
261             So, after every alteration of the in memory string using above mentioned
262             methods, the file's position is set to C<0>, so any read will start again
263             at the beginning of the file
264              
265             =head2 use $File::Alter::LINENUMBER rather than $.
266              
267             C<$.> isn't actually C
268             filehandle> but the amount of times a line has been read from the last
269             active filehandle.
270              
271             This is a subtle but important difference, seeing when you loop over a
272             file as a whole, and then read the first line again, C<$.> would hold
273             C rather than C<1>.
274              
275             C<$File::Alter::LINENUMBER> does what you expect here and would hold C<1>.
276              
277             =head1 AUTHOR
278              
279             This module by
280             Jos Boumans Ekane@cpan.orgE.
281              
282             =head1 COPYRIGHT
283              
284             This module is
285             copyright (c) 2005 Jos Boumans Ekane@cpan.orgE.
286             All rights reserved.
287              
288             This library is free software;
289             you may redistribute and/or modify it under the same
290             terms as Perl itself.
291              
292             =cut
293              
294              
295             1;