1#!/usr/bin/perl
2
3use strict;
4use File::Find;
5use File::Temp qw/ tempfile tempdir /;
6use Getopt::Std;
7use Pod::Usage;
8use Text::Tabs;
9
10=head1 NAME
11
12B<sed-sources> -- Performs multiple sed commands on files with the ability to expand or unexpand tabs.
13
14=head1 SYNOPSIS
15
16B<sed-sources> [options] [file dir ...]
17
18=head1 DESCRIPTION
19
20Performs multiple sed commands (modify builtin %seds hash) on source files
21or any sources in directories. If no arguments are given, STDIN will be used
22as the source. If source files or directories are specified as arguments,
23all files will be transformed and overwritten with new versions. Use the B<-p>
24option to preview changes to STDOUT, or use the B<-b> option to make a backup
25or the original files.
26
27=head1 OPTIONS
28
29=over
30
31=item B<-b>
32
33Backup original source file by appending ".bak" before overwriting with the
34newly transformed file.
35
36=item B<-g>
37
38Display verbose debug logging.
39
40=item B<-e>
41
42Expand tabs to spaces (in addition to doing sed substitutions).
43
44=item B<-u>
45
46Unexpand spaces to tabs (in addition to doing sed substitutions).
47
48=item B<-p>
49
50Preview changes to STDOUT without modifying original source files.
51
52=item B<-r>
53
54Skip variants when doing multiple files (no _profile or _debug variants).
55
56=item B<-t N>
57
58Set the number of spaces per tab (default is 4) to use when expanding or
59unexpanding.
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
84our $opt_b = 0;	# Backup original file?
85our $opt_g = 0;	# Verbose debug output?
86our $opt_e = 0;	# Expand tabs to spaces?
87our $opt_h = 0; # Show help?
88our $opt_m = 0;	# Show help manpage style?
89our $opt_p = 0;	# Preview changes to STDOUT?
90our $opt_t = 4;	# Number of spaces per tab?
91our $opt_u = 0;	# Unexpand spaces to tabs?
92getopts('eghmpt:u');
93
94$opt_m and show_manpage();
95$opt_h and help();
96
97our %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
103sub show_manpage { exit pod2usage( verbose => 2 ); };
104sub help { exit pod2usage( verbose => 3, noperldoc => 1 ); };
105
106
107#----------------------------------------------------------------------
108# process_opened_file_handle
109#----------------------------------------------------------------------
110sub 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#----------------------------------------------------------------------
138sub 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
193our @valid_extensions = ( "h", "cpp", "c", "m", "mm" );
194
195#----------------------------------------------------------------------
196# find_callback
197#----------------------------------------------------------------------
198sub 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#----------------------------------------------------------------------
220sub 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
251main();