1#!/usr/local/bin/perl
2#
3#  Heuristically converts line endings to the current OS's preferred format
4#
5#  All existing line endings must be identical (e.g. lf's only, or even
6#  the accedental cr.cr.lf sequence.)  If some lines end lf, and others as
7#  cr.lf, the file is presumed binary.  If the cr character appears anywhere
8#  except prefixed to an lf, the file is presumed binary.  If there is no
9#  change in the resulting file size, or the file is binary, the conversion
10#  is discarded.
11#
12#  Todo: Handle NULL stdin characters gracefully.
13#
14
15use IO::File;
16use File::Find;
17
18# The ignore list is '-' seperated, with this leading hyphen and
19# trailing hyphens in ever concatinated list below.
20$ignore = "-";
21
22# Image formats
23$ignore .= "gif-jpg-jpeg-png-ico-bmp-";
24
25# Archive formats
26$ignore .= "tar-gz-z-zip-jar-war-bz2-tgz-";
27
28# Many document formats
29$ignore .= "eps-psd-pdf-chm-ai-";
30
31# Some encodings
32$ignore .= "ucs2-ucs4-";
33
34# Some binary objects
35$ignore .= "class-so-dll-exe-obj-lib-a-o-lo-slo-sl-dylib-";
36
37# Some build env files
38$ignore .= "mcp-xdc-ncb-opt-pdb-ilk-exp-res-pch-idb-sbr-";
39
40$preservedate = 1;
41
42$forceending = 0;
43
44$givenpaths = 0;
45
46$notnative = 0;
47
48while (defined @ARGV[0]) {
49    if (@ARGV[0] eq '--touch') {
50        $preservedate = 0;
51    }
52    elsif (@ARGV[0] eq '--nocr') {
53        $notnative = -1;
54    }
55    elsif (@ARGV[0] eq '--cr') {
56        $notnative = 1;
57    }
58    elsif (@ARGV[0] eq '--force') {
59        $forceending = 1;
60    }
61    elsif (@ARGV[0] eq '--FORCE') {
62        $forceending = 2;
63    }
64    elsif (@ARGV[0] =~ m/^-/) {
65        die "What is " . @ARGV[0] . " supposed to mean?\n\n"
66          . "Syntax:\t$0 [option()s] [path(s)]\n\n" . <<'OUTCH'
67Where:  paths specifies the top level directory to convert (default of '.')
68        options are;
69
70          --cr     keep/add one ^M
71          --nocr   remove ^M's
72          --touch  the datestamp (default: keeps date/attribs)
73          --force  mismatched corrections (unbalanced ^M's)
74          --FORCE  all files regardless of file name!
75
76OUTCH
77    }
78    else {
79        find(\&totxt, @ARGV[0]);
80        print "scanned " . @ARGV[0] . "\n";
81        $givenpaths = 1;
82    }
83    shift @ARGV;
84}
85
86if (!$givenpaths) {
87    find(\&totxt, '.');
88    print "did .\n";
89}
90
91sub totxt {
92        $oname = $_;
93        $tname = '.#' . $_;
94        if (!-f) {
95            return;
96        }
97        @exts = split /\./;
98        if ($forceending < 2) {
99            while ($#exts && ($ext = pop(@exts))) {
100                if ($ignore =~ m|-$ext-|i) {
101                    return;
102                }
103            }
104        }
105        return if ($File::Find::dir =~ m|^(.+/)?.svn(/.+)?$|);
106        @ostat = stat($oname);
107        $srcfl = new IO::File $oname, "r" or die;
108        $dstfl = new IO::File $tname, "w" or die;
109        binmode $srcfl;
110        if ($notnative) {
111            binmode $dstfl;
112        }
113        undef $t;
114        while (<$srcfl>) {
115            if (s/(\r*)\n$/\n/) {
116                $n = length $1;
117                if (!defined $t) {
118                    $t = $n;
119                }
120                if (!$forceending && (($n != $t) || m/\r/)) {
121                    print "mismatch in " .$oname. ":" .$n. " expected " .$t. "\n";
122                    undef $t;
123                    last;
124                }
125                elsif ($notnative > 0) {
126                    s/\n$/\r\n/;
127                }
128            }
129            print $dstfl $_;
130        }
131        if (defined $t && (tell $srcfl == tell $dstfl)) {
132            undef $t;
133        }
134        undef $srcfl;
135        undef $dstfl;
136        if (defined $t) {
137            unlink $oname or die;
138            rename $tname, $oname or die;
139            @anames = ($oname);
140            if ($preservedate) {
141                utime $ostat[9], $ostat[9], @anames;
142            }
143            chmod $ostat[2] & 07777, @anames;
144            chown $ostat[5], $ostat[6], @anames;
145            print "Converted file " . $oname . " to text in " . $File::Find::dir . "\n";
146        }
147        else {
148            unlink $tname or die;
149        }
150}
151