| #!/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(); |