|  | #!/usr/bin/perl | 
|  |  | 
|  | use strict; | 
|  | use File::Find; | 
|  | use File::Temp qw/ tempfile tempdir /; | 
|  | use Getopt::Std; | 
|  | use Pod::Usage; | 
|  | use Text::Tabs; | 
|  |  | 
|  | =head1 NAME | 
|  |  | 
|  | B<sed-sources> -- Performs multiple sed commands on files with the ability to expand or unexpand tabs. | 
|  |  | 
|  | =head1 SYNOPSIS | 
|  |  | 
|  | B<sed-sources> [options] [file dir ...] | 
|  |  | 
|  | =head1 DESCRIPTION | 
|  |  | 
|  | Performs multiple sed commands (modify builtin %seds hash) on source files | 
|  | or any sources in directories. If no arguments are given, STDIN will be used | 
|  | as the source. If source files or directories are specified as arguments, | 
|  | all files will be transformed and overwritten with new versions. Use the B<-p> | 
|  | option to preview changes to STDOUT, or use the B<-b> option to make a backup | 
|  | or the original files. | 
|  |  | 
|  | =head1 OPTIONS | 
|  |  | 
|  | =over | 
|  |  | 
|  | =item B<-b> | 
|  |  | 
|  | Backup original source file by appending ".bak" before overwriting with the | 
|  | newly transformed file. | 
|  |  | 
|  | =item B<-g> | 
|  |  | 
|  | Display verbose debug logging. | 
|  |  | 
|  | =item B<-e> | 
|  |  | 
|  | Expand tabs to spaces (in addition to doing sed substitutions). | 
|  |  | 
|  | =item B<-u> | 
|  |  | 
|  | Unexpand spaces to tabs (in addition to doing sed substitutions). | 
|  |  | 
|  | =item B<-p> | 
|  |  | 
|  | Preview changes to STDOUT without modifying original source files. | 
|  |  | 
|  | =item B<-r> | 
|  |  | 
|  | Skip variants when doing multiple files (no _profile or _debug variants). | 
|  |  | 
|  | =item B<-t N> | 
|  |  | 
|  | Set the number of spaces per tab (default is 4) to use when expanding or | 
|  | unexpanding. | 
|  |  | 
|  | =back | 
|  |  | 
|  | =head1 EXAMPLES | 
|  |  | 
|  | # Recursively process all source files in the current working directory | 
|  | # and subdirectories and also expand tabs to spaces. All source files | 
|  | # will be overwritten with the newly transformed source files. | 
|  |  | 
|  | % sed-sources -e $cwd | 
|  |  | 
|  | # Recursively process all source files in the current working directory | 
|  | # and subdirectories and also unexpand spaces to tabs and preview the | 
|  | # results to STDOUT | 
|  |  | 
|  | % sed-sources -p -u $cwd | 
|  |  | 
|  | # Same as above except use 8 spaces per tab. | 
|  |  | 
|  | % sed-sources -p -u -t8 $cwd | 
|  |  | 
|  | =cut | 
|  |  | 
|  |  | 
|  | our $opt_b = 0;	# Backup original file? | 
|  | our $opt_g = 0;	# Verbose debug output? | 
|  | our $opt_e = 0;	# Expand tabs to spaces? | 
|  | our $opt_h = 0; # Show help? | 
|  | our $opt_m = 0;	# Show help manpage style? | 
|  | our $opt_p = 0;	# Preview changes to STDOUT? | 
|  | our $opt_t = 4;	# Number of spaces per tab? | 
|  | our $opt_u = 0;	# Unexpand spaces to tabs? | 
|  | getopts('eghmpt:u'); | 
|  |  | 
|  | $opt_m and show_manpage(); | 
|  | $opt_h and help(); | 
|  |  | 
|  | our %seds = ( | 
|  | '\s+$' => "\n",		# Get rid of spaces at the end of a line | 
|  | '^\s+$' => "\n",	# Get rid spaces on lines that are all spaces | 
|  | ); | 
|  |  | 
|  |  | 
|  | sub show_manpage { exit pod2usage( verbose => 2 ); }; | 
|  | sub help { exit pod2usage( verbose => 3, noperldoc => 1 ); }; | 
|  |  | 
|  |  | 
|  | #---------------------------------------------------------------------- | 
|  | # process_opened_file_handle | 
|  | #---------------------------------------------------------------------- | 
|  | sub process_opened_file_handle | 
|  | { | 
|  | my $in_fh = shift; | 
|  | my $out_fh = shift; | 
|  |  | 
|  | # Set the number of spaces per tab for expand/unexpand | 
|  | $tabstop = $opt_t; | 
|  |  | 
|  | while (my $line = <$in_fh>) | 
|  | { | 
|  | foreach my $key (keys %seds) | 
|  | { | 
|  | my $value = $seds{"$key"}; | 
|  | $line =~ s/$key/$value/g; | 
|  | } | 
|  | if ($opt_e) { | 
|  | print $out_fh expand $line; | 
|  | } elsif ($opt_u) { | 
|  | print $out_fh unexpand $line; | 
|  | } else { | 
|  | print $out_fh $line; | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | #---------------------------------------------------------------------- | 
|  | # process_file | 
|  | #---------------------------------------------------------------------- | 
|  | sub process_file | 
|  | { | 
|  | my $in_path = shift; | 
|  | if (-T $in_path) | 
|  | { | 
|  | my $out_fh; | 
|  | my $out_path; | 
|  | if ($opt_p) | 
|  | { | 
|  | # Preview to STDOUT | 
|  | $out_fh = *STDOUT; | 
|  | print "#---------------------------------------------------------------------- \n"; | 
|  | print "# BEGIN: '$in_path'\n"; | 
|  | print "#---------------------------------------------------------------------- \n"; | 
|  | } | 
|  | else | 
|  | { | 
|  | ($out_fh, $out_path) = tempfile(); | 
|  | $opt_g and print "temporary for '$in_path' is '$out_path'\n"; | 
|  | } | 
|  | open (IN, "<$in_path") or die "error: can't open '$in_path' for reading: $!"; | 
|  | process_opened_file_handle (*IN, $out_fh); | 
|  |  | 
|  |  | 
|  | # Close our input file | 
|  | close (IN); | 
|  |  | 
|  | if ($opt_p) | 
|  | { | 
|  | print "#---------------------------------------------------------------------- \n"; | 
|  | print "# END: '$in_path'\n"; | 
|  | print "#---------------------------------------------------------------------- \n"; | 
|  | print "\n\n"; | 
|  | } | 
|  | else | 
|  | { | 
|  | # Close the output file if it wasn't STDOUT | 
|  | close ($out_fh); | 
|  |  | 
|  | # Backup file if requested | 
|  | if ($opt_b) | 
|  | { | 
|  | my $backup_command = "cp '$in_path' '$in_path.bak'"; | 
|  | $opt_g and print "\% $backup_command\n"; | 
|  | system ($backup_command); | 
|  | } | 
|  |  | 
|  | # Copy temp file over original | 
|  | my $copy_command = "cp '$out_path' '$in_path'"; | 
|  | $opt_g and print "\% $copy_command\n"; | 
|  | system ($copy_command); | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | our @valid_extensions = ( "h", "cpp", "c", "m", "mm" ); | 
|  |  | 
|  | #---------------------------------------------------------------------- | 
|  | # find_callback | 
|  | #---------------------------------------------------------------------- | 
|  | sub find_callback | 
|  | { | 
|  | my $file = $_; | 
|  | my $fullpath = $File::Find::name; | 
|  |  | 
|  | foreach my $ext (@valid_extensions) | 
|  | { | 
|  | my $ext_regex = "\\.$ext\$"; | 
|  | if ($fullpath =~ /$ext_regex/i) | 
|  | { | 
|  | print "processing: '$fullpath'\n"; | 
|  | process_file ($fullpath); | 
|  | return; | 
|  | } | 
|  | } | 
|  | print "  ignoring: '$fullpath'\n"; | 
|  | } | 
|  |  | 
|  |  | 
|  | #---------------------------------------------------------------------- | 
|  | # main | 
|  | #---------------------------------------------------------------------- | 
|  | sub main | 
|  | { | 
|  | if (@ARGV == 0) | 
|  | { | 
|  | # no args, take from STDIN and put to STDOUT | 
|  | process_opened_file_handle (*STDIN, *STDOUT); | 
|  | } | 
|  | else | 
|  | { | 
|  | # Got args, any files we run into parse them, any directories | 
|  | # we run into, search them for files | 
|  | my $path; | 
|  | foreach $path (@ARGV) | 
|  | { | 
|  | if (-f $path) | 
|  | { | 
|  | print "processing: '$path'\n"; | 
|  | process_file ($path); | 
|  | } | 
|  | else | 
|  | { | 
|  | print " searching: '$path'\n"; | 
|  | find(\&find_callback, $path); | 
|  | } | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  |  | 
|  |  | 
|  | # call the main function | 
|  | main(); |