1 #!/usr/bin/perl 2 3 use strict; 4 use File::Find; 5 use File::Temp qw/ tempfile tempdir /; 6 use Getopt::Std; 7 use Pod::Usage; 8 use Text::Tabs; 9 10 =head1 NAME 11 12 B<sed-sources> -- Performs multiple sed commands on files with the ability to expand or unexpand tabs. 13 14 =head1 SYNOPSIS 15 16 B<sed-sources> [options] [file dir ...] 17 18 =head1 DESCRIPTION 19 20 Performs multiple sed commands (modify builtin %seds hash) on source files 21 or any sources in directories. If no arguments are given, STDIN will be used 22 as the source. If source files or directories are specified as arguments, 23 all files will be transformed and overwritten with new versions. Use the B<-p> 24 option to preview changes to STDOUT, or use the B<-b> option to make a backup 25 or the original files. 26 27 =head1 OPTIONS 28 29 =over 30 31 =item B<-b> 32 33 Backup original source file by appending ".bak" before overwriting with the 34 newly transformed file. 35 36 =item B<-g> 37 38 Display verbose debug logging. 39 40 =item B<-e> 41 42 Expand tabs to spaces (in addition to doing sed substitutions). 43 44 =item B<-u> 45 46 Unexpand spaces to tabs (in addition to doing sed substitutions). 47 48 =item B<-p> 49 50 Preview changes to STDOUT without modifying original source files. 51 52 =item B<-r> 53 54 Skip variants when doing multiple files (no _profile or _debug variants). 55 56 =item B<-t N> 57 58 Set the number of spaces per tab (default is 4) to use when expanding or 59 unexpanding. 60 61 =back 62 63 =head1 EXAMPLES 64 65 # Recursively process all source files in the current working directory 66 # and and subdirectories and also expand tabs to spaces. All source files 67 # will be overwritten with the newly transformed source files. 68 69 % sed-sources -e $cwd 70 71 # Recursively process all source files in the current working directory 72 # and and subdirectories and also unexpand spaces to tabs and preview the 73 # results to STDOUT 74 75 % sed-sources -p -u $cwd 76 77 # Same as above except use 8 spaces per tab. 78 79 % sed-sources -p -u -t8 $cwd 80 81 =cut 82 83 84 our $opt_b = 0; # Backup original file? 85 our $opt_g = 0; # Verbose debug output? 86 our $opt_e = 0; # Expand tabs to spaces? 87 our $opt_h = 0; # Show help? 88 our $opt_m = 0; # Show help manpage style? 89 our $opt_p = 0; # Preview changes to STDOUT? 90 our $opt_t = 4; # Number of spaces per tab? 91 our $opt_u = 0; # Unexpand spaces to tabs? 92 getopts('eghmpt:u'); 93 94 $opt_m and show_manpage(); 95 $opt_h and help(); 96 97 our %seds = ( 98 '\s+$' => "\n", # Get rid of spaces at the end of a line 99 '^\s+$' => "\n", # Get rid spaces on lines that are all spaces 100 ); 101 102 103 sub show_manpage { exit pod2usage( verbose => 2 ); }; 104 sub help { exit pod2usage( verbose => 3, noperldoc => 1 ); }; 105 106 107 #---------------------------------------------------------------------- 108 # process_opened_file_handle 109 #---------------------------------------------------------------------- 110 sub process_opened_file_handle 111 { 112 my $in_fh = shift; 113 my $out_fh = shift; 114 115 # Set the number of spaces per tab for expand/unexpand 116 $tabstop = $opt_t; 117 118 while (my $line = <$in_fh>) 119 { 120 foreach my $key (keys %seds) 121 { 122 my $value = $seds{"$key"}; 123 $line =~ s/$key/$value/g; 124 } 125 if ($opt_e) { 126 print $out_fh expand $line; 127 } elsif ($opt_u) { 128 print $out_fh unexpand $line; 129 } else { 130 print $out_fh $line; 131 } 132 } 133 } 134 135 #---------------------------------------------------------------------- 136 # process_file 137 #---------------------------------------------------------------------- 138 sub process_file 139 { 140 my $in_path = shift; 141 if (-T $in_path) 142 { 143 my $out_fh; 144 my $out_path; 145 if ($opt_p) 146 { 147 # Preview to STDOUT 148 $out_fh = *STDOUT; 149 print "#---------------------------------------------------------------------- \n"; 150 print "# BEGIN: '$in_path'\n"; 151 print "#---------------------------------------------------------------------- \n"; 152 } 153 else 154 { 155 ($out_fh, $out_path) = tempfile(); 156 $opt_g and print "temporary for '$in_path' is '$out_path'\n"; 157 } 158 open (IN, "<$in_path") or die "error: can't open '$in_path' for reading: $!"; 159 process_opened_file_handle (*IN, $out_fh); 160 161 162 # Close our input file 163 close (IN); 164 165 if ($opt_p) 166 { 167 print "#---------------------------------------------------------------------- \n"; 168 print "# END: '$in_path'\n"; 169 print "#---------------------------------------------------------------------- \n"; 170 print "\n\n"; 171 } 172 else 173 { 174 # Close the output file if it wasn't STDOUT 175 close ($out_fh); 176 177 # Backup file if requested 178 if ($opt_b) 179 { 180 my $backup_command = "cp '$in_path' '$in_path.bak'"; 181 $opt_g and print "\% $backup_command\n"; 182 system ($backup_command); 183 } 184 185 # Copy temp file over original 186 my $copy_command = "cp '$out_path' '$in_path'"; 187 $opt_g and print "\% $copy_command\n"; 188 system ($copy_command); 189 } 190 } 191 } 192 193 our @valid_extensions = ( "h", "cpp", "c", "m", "mm" ); 194 195 #---------------------------------------------------------------------- 196 # find_callback 197 #---------------------------------------------------------------------- 198 sub find_callback 199 { 200 my $file = $_; 201 my $fullpath = $File::Find::name; 202 203 foreach my $ext (@valid_extensions) 204 { 205 my $ext_regex = "\\.$ext\$"; 206 if ($fullpath =~ /$ext_regex/i) 207 { 208 print "processing: '$fullpath'\n"; 209 process_file ($fullpath); 210 return; 211 } 212 } 213 print " ignoring: '$fullpath'\n"; 214 } 215 216 217 #---------------------------------------------------------------------- 218 # main 219 #---------------------------------------------------------------------- 220 sub main 221 { 222 if (@ARGV == 0) 223 { 224 # no args, take from STDIN and put to STDOUT 225 process_opened_file_handle (*STDIN, *STDOUT); 226 } 227 else 228 { 229 # Got args, any files we run into parse them, any directories 230 # we run into, search them for files 231 my $path; 232 foreach $path (@ARGV) 233 { 234 if (-f $path) 235 { 236 print "processing: '$path'\n"; 237 process_file ($path); 238 } 239 else 240 { 241 print " searching: '$path'\n"; 242 find(\&find_callback, $path); 243 } 244 } 245 } 246 } 247 248 249 250 # call the main function 251 main();