File Coverage

blib/lib/Whitespace.pm
Criterion Covered Total %
statement 18 176 10.2
branch 0 82 0.0
condition 0 18 0.0
subroutine 5 21 23.8
pod 0 10 0.0
total 23 307 7.4


line stmt bran cond sub pod time code
1             package Whitespace;
2 1     1   566 use strict;
  1         1  
  1         35  
3              
4             #
5             # $Id: Whitespace.pm,v 1.4 2001/05/23 21:36:50 rv Exp $
6             #
7              
8             BEGIN {
9 1     1   4 use Exporter ();
  1         2  
  1         101  
10 1     1   2 our ($VERSION, @ISA, @EXPORT, @EXPORT_OK, %EXPORT_TAGS);
11              
12 1         6 $VERSION = 1.02;
13 1         15 @ISA = qw(Exporter);
14 1         3 @EXPORT = qw(&new &detect &cleanup &error &status);
15 1         3 @EXPORT_OK = qw(&leadclean &trailclean &indentclean &spacetabclean
16             &eolclean &DESTROY);
17 1         1000 %EXPORT_TAGS = ();
18             }
19              
20             =head1 NAME
21              
22             Whitespace - Cleanup various types of bogus whitespace in source files.
23              
24             =head1 SYNOPSIS
25              
26             use Whitespace;
27              
28             # Instantiate a whitespace object with
29             # both input and output files specified
30             $ws = new Whitespace($infile, $outfile);
31              
32             # Instantiate a whitespace object with
33             # only the input files specified (in-place cleanup)
34             $ws2 = new Whitespace($infile);
35              
36             # Detect the whitespaces
37             $ret = $ws->detect();
38              
39             C returns B if it is unable to operate on the given
40             file.
41              
42             The error that caused the undef can be retrieved using C
43              
44             print $ws->error() . "\n" unless defined $ret;
45              
46             C returns the types of whitespaces detected as a hash which
47             can be retrieved using the method C. The populated hash might
48             look like this, if the file only had leading, trailing and end-of-line
49             spaces (say on 3 lines).
50              
51             %stat = %{$env->status()};
52             print map "$_ => $stat{$_}\n", sort keys %stat;
53              
54             eol => 3
55             indent => 0
56             leading => 1
57             spacetab => 0
58             trailing => 1
59              
60             Cleanup can be achieved for all the whitespaces or for just a given
61             type of whitespace, using the following methods.
62              
63             If a B is given, the cleaned contents are written to this
64             file. If not, the contents are replaced in-place. B is
65             returned if there was an error writing the file.
66              
67             # To cleanup the all the whitespaces
68             $ret = $env->cleanup();
69              
70             # To cleanup leading whitespaces only
71             $leadstat = $env->leadclean();
72              
73             # To cleanup trailing whitespaces only
74             $trailstat = $env->trailclean();
75              
76             # To cleanup indentation whitespaces only
77             $indentstat = $env->indentclean();
78              
79             # To cleanup space-followed-by-tabs only
80             $sftstat = $env->spacetabclean();
81              
82             # To cleanup end-of-line whitespaces only
83             $eolstat = $env->eolclean();
84              
85             =cut
86              
87             #
88             # Exported Functions
89             #
90             sub new {
91 0     0 0   my $package = shift;
92 0           my $env = {
93             'infile' => shift,
94             'outfile' => shift,
95             'cleaned' => 0,
96             };
97 0           bless $env, $package;
98             }
99              
100             sub detect {
101 0     0 0   my $env = shift;
102 0           my $ret = 0;
103 0 0         my $infile = $env->{cleaned} ? $env->{'outfile'} : $env->{'infile'};
104              
105 0 0         unless (defined $infile) {
106 0           $env->{'error'} = "No input file!";
107 0           return undef;
108             }
109 0 0         if (-d $infile) {
110 0           $env->{'error'} = "$infile: Is a directory!";
111 0           return undef;
112             }
113 0 0         if (ref $infile) {
114 0           $env->{'error'} = "$infile: Is not a regular file (a reference)!";
115 0           return undef;
116             }
117 0 0         unless (open FILE, $infile) {
118 0           $env->{'error'} = "$infile: $!";
119 0           return undef;
120             }
121 0 0         if (! -T _) {
122 0           close FILE;
123 0           $env->{'error'} = "$infile: Not a text file!";
124 0           return undef;
125             }
126 0           $env->{'_IFILE'} = *FILE;
127              
128 0           my $first = 1;
129 0           my $leading = 0;
130 0           my $trailing = 0;
131 0           my $indent = 0;
132 0           my $spacetab = 0;
133 0           my $ateol = 0;
134 0           while () {
135 0 0         if (! /^.*\n$/) {
136             # warn "$infile: Line too long\n";
137 0           $env->{'error'} = "$infile: Line too long";
138 0           last;
139             }
140              
141             =head1 DESCRIPTION
142              
143             =item Leading space
144              
145             Empty lines at the top of a file.
146              
147             =cut
148 0 0 0       $leading = 1 if $first && /^[ \t]*$/;
149              
150             =item Trailing space
151              
152             Empty lines at the end of a file.
153              
154             =cut
155 0 0         $trailing = /^[ \t]*$/ ? 1 : 0;
156              
157             =item Indentation space
158              
159             8 or more spaces at the beginning of a line, that should be replaced with
160             TABS.
161              
162             Since this is the most controversial one, here is the rationale:
163             Most terminal drivers and printer drivers have TAB configured or
164             even hardcoded to be 8 spaces. (Some of them allow configuration,
165             but almost always they default to 8.)
166              
167             Changing tab-width to other than 8 and editing will cause your
168             code to look different from within emacs, and say, if you cat it
169             or more it, or even print it.
170              
171             Almost all the popular programming modes let you define an offset
172             (like c-basic-offset or perl-indent-level) to configure the
173             offset, so you should never have to set your tab-width to be other
174             than 8 in all these modes. In fact, with an indent level of say,
175             4, 2 TABS will cause emacs to replace your 8 spaces with one \t
176             (try it). If vi users in your office complain, tell them to use
177             vim, which distinguishes between tabstop and shiftwidth (vi
178             equivalent of our offsets), and also ask them to set smarttab.
179              
180             =cut
181 0 0         $indent = 1 if /^\s* {8,}/;
182              
183             =item Spaces followed by a TAB.
184              
185             Almost always, we never want that.
186              
187             =cut
188 0 0         $spacetab = 1 if / \t/;
189              
190              
191             =item EOL Whitespace
192              
193             Spaces or TABS at the end of a line.
194              
195             =cut
196              
197 0 0         $ateol = 1 if /[ \t]$/;
198 0           $first = 0;
199             }
200 0           close FILE;
201 0           $env->{'_IFILE'} = undef;
202 0 0         return undef if defined $env->{'error'};
203 0 0         ++$ret if $leading;
204 0 0         ++$ret if $indent;
205 0 0         ++$ret if $spacetab;
206 0 0         ++$ret if $ateol;
207 0 0         ++$ret if $trailing;
208              
209 0 0         ++$env->{'status'}->{'leading'} if $leading;
210 0 0         ++$env->{'status'}->{'trailing'} if $trailing;
211 0 0         ++$env->{'status'}->{'indent'} if $indent;
212 0 0         ++$env->{'status'}->{'spacetab'} if $spacetab;
213 0 0         ++$env->{'status'}->{'eol'} if $ateol;
214              
215 0           return $ret;
216             }
217              
218             sub cleanup {
219 0     0 0   my $env = shift;
220 0           my $infile = $env->{'infile'};
221 0           my $outfile = $env->{'outfile'};
222 0           my $cleanup => $env->{'cleanup'};
223              
224 0 0         unless (defined $infile) {
225 0           $env->{'error'} = "No input file!";
226 0           return undef;
227             }
228 0 0         if (-d $infile) {
229 0           $env->{'error'} = "$infile: Is a directory!";
230 0           return undef;
231             }
232 0 0         if (ref $infile) {
233 0           $env->{'error'} = "$infile: Is not a regular file (a reference)!";
234 0           return undef;
235             }
236 0 0         unless (open FILE, $infile) {
237 0           $env->{'error'} = "$infile: $!";
238 0           return undef;
239             }
240 0           $env->{'_IFILE'} = *FILE;
241 0 0         if (defined $outfile) {
242 0 0         unless (open OUTFILE, ">$outfile") {
243 0           $env->{'error'} = "$outfile: $!";
244 0           close FILE;
245 0           return $env->{'_IFILE'} = undef;
246             }
247 0           close OUTFILE;
248             } else {
249 0 0         unless (-w $infile) {
250 0           $env->{'error'} = "$infile: Not writable!";
251 0           return undef;
252             }
253 0           $outfile = $infile;
254 0           $env->{'outfile'} = $env->{'infile'};
255             }
256              
257 0           my @arr = ;
258 0           close FILE;
259 0           $env->{'_IFILE'} = undef;
260             #
261             # Leading/Trailing space cleanup
262             #
263 0 0 0       @arr = _leadtrailclean(@arr)
264             if (!defined $cleanup || $cleanup->{'leading'});
265 0 0 0       @arr = reverse _leadtrailclean(reverse @arr)
266             if (!defined $cleanup || $cleanup->{'trailing'});
267              
268             #
269             # Indentation cleanup
270             #
271 0 0 0       @arr = _indentclean(@arr)
272             if (!defined $cleanup || $cleanup->{'indent'});
273              
274             #
275             # EOL Space cleanup
276             #
277 0 0 0       @arr = _eolclean(@arr)
278             if (!defined $cleanup || $cleanup->{'eol'});
279              
280             #
281             # Space-followed-by-TAB cleanup
282             #
283 0 0 0       @arr = _spctabclean(@arr)
284             if (!defined $cleanup || $cleanup->{'spacetab'});
285              
286 1     1   8 use File::Spec 0.8;
  1         30  
  1         179  
287 0           my ($junk, $tmp);
288 0           ($junk, $junk, $tmp) = File::Spec->splitpath($infile);
289 0           my $tmpdir = File::Spec->tmpdir;
290              
291 0           $tmp = File::Spec->catfile($tmpdir, "$tmp.$$");
292 0 0         unless (open FILE, ">$tmp") {
293 0           $env->{'error'} = "$tmp: $!. $infile not cleaned";
294 0           return undef;
295             }
296 0           $env->{'_TFILE'} = *FILE;
297 0           print FILE @arr;
298 0           close FILE;
299 0           $env->{'_TFILE'} = undef;
300              
301 1     1   996 use File::Copy qw(move);
  1         5855  
  1         925  
302 0           move($tmp, $outfile);
303              
304             #
305             # Test the file once again.
306             #
307 0           $env->{'cleaned'} = 1;
308 0           return $env->detect;
309             }
310              
311             sub leadclean {
312 0     0 0   my $env = shift;
313 0           $env->{'cleanup'}->{'leading'} = 1;
314 0           return $env->cleanup;
315             }
316              
317             sub trailclean {
318 0     0 0   my $env = shift;
319 0           $env->{'cleanup'}->{'trailing'} = 1;
320 0           return $env->cleanup;
321             }
322              
323             sub indentclean {
324 0     0 0   my $env = shift;
325 0           $env->{'cleanup'}->{'indent'} = 1;
326 0           return $env->cleanup;
327             }
328              
329             sub spacetabclean {
330 0     0 0   my $env = shift;
331 0           $env->{'cleanup'}->{'spacetab'} = 1;
332 0           return $env->cleanup;
333             }
334              
335             sub eolclean {
336 0     0 0   my $env = shift;
337 0           $env->{'cleanup'}->{'eol'} = 1;
338 0           return $env->cleanup;
339             }
340              
341             sub error {
342 0     0 0   my $env = shift;
343 0           $env->{'error'};
344             }
345              
346             sub status {
347 0     0 0   my $env = shift;
348 0           $env->{'status'};
349             }
350              
351             sub DESTROY {
352 0     0     my $env = shift;
353 0           my $ifh = $env->{'_IFILE'};
354 0           my $tfh = $env->{'_TFILE'};
355             # warn "destroying whitespace object for $env->{'infile'}\n";
356 0 0         close $ifh if defined $ifh;
357 0 0         close $tfh if defined $tfh;
358             }
359              
360             #
361             # Internal functions
362             #
363             sub _leadtrailclean {
364 0     0     my $first = 1;
365 0           my @ret = ();
366 0           foreach (@_) {
367 0 0         if ($first) {
368 0 0         if (! /^[ \t]*$/) {
369 0           $first = 0;
370 0           push @ret, $_;
371             }
372             } else {
373 0           $first = 0;
374 0           push @ret, $_;
375             }
376             }
377 0           return @ret;
378             }
379              
380             sub _indentclean {
381 0     0     my @ret = ();
382 0           foreach (@_) {
383 0           while (/^\s* {8,}/) {
384 0           $_ =~ s/^(\t*) {8}/$1\t/g;
385             }
386 0           push @ret, $_;
387             }
388 0           return @ret;
389             }
390              
391             sub _eolclean {
392 0     0     my @ret = ();
393 0           foreach (@_) {
394 0           $_ =~ s/[ \t]*$//g;
395 0           push @ret, $_;
396             }
397 0           return @ret;
398             }
399              
400             sub _spctabclean {
401 0     0     my @ret = ();
402 0           foreach (@_) {
403 0           while (/ \t/) {
404 0 0         s/ \t/_brinkoftabstop($`) ? "\t\t" : "\t"/eg;
  0            
405             }
406 0           push @ret, $_;
407             }
408 0           return @ret;
409             }
410              
411             #
412             # This sub ensures that while cleaning space-followed-by-TAB issues,
413             # we don't blindly cleanup at tab boundaries.
414             #
415             # For instance, "1234567 \t" should change to "1234567\t\t" and not to
416             # "1234567\t", which would not look the same as the original.
417             #
418             sub _brinkoftabstop {
419 0     0     my $s = shift;
420 0           $s =~ s/.*\t//;
421 0           return length($s) % 8 == 7;
422             }
423              
424             1;
425              
426             =head1 ACKNOWLEDGMENTS
427              
428             This module is based on the original B program written by
429             Bradley W. White, distributed under the same license as the module
430             itself.
431              
432             =head1 AUTHORS
433              
434             Rajesh Vaidheeswarran Erv@gnu.orgE
435              
436             Bradley W. White
437              
438             =head1 LICENSE
439              
440             Copyright (C) 2000-2001 Rajesh Vaidheeswarran
441              
442             All rights reserved.
443              
444             This program is free software; you can redistribute it and/or modify
445             it under the same terms as Perl itself.
446              
447             =cut