File Coverage

bin/sh2p.pl
Criterion Covered Total %
statement 39 217 17.9
branch 0 90 0.0
condition 0 24 0.0
subroutine 13 18 72.2
pod n/a
total 52 349 14.9


line stmt bran cond sub pod time code
1             #!/usr/bin/perl
2             # Clive Darke 2006
3             # Additional modifications 2008, 2009. See Changes file
4              
5 1     1   3495 use warnings;
  1         2  
  1         30  
6 1     1   5 use strict;
  1         2  
  1         33  
7 1     1   5 use Getopt::Std;
  1         3  
  1         51  
8 1     1   5 use File::Basename;
  1         2  
  1         78  
9 1     1   5 use Config;
  1         2  
  1         38  
10              
11 1     1   6 use App::sh2p::Statement;
  1         1  
  1         20  
12              
13 1     1   11 use App::sh2p::Parser;
  1         2  
  1         18  
14 1     1   5 use App::sh2p::Handlers;
  1         2  
  1         35  
15 1     1   6 use App::sh2p::Builtins;
  1         46  
  1         19  
16 1     1   5 use App::sh2p::Operators;
  1         2  
  1         18  
17 1     1   4 use App::sh2p::Compound;
  1         2  
  1         22  
18 1     1   6 use App::sh2p::Here;
  1         1  
  1         19  
19 1     1   5 use App::sh2p::Utils;
  1         2  
  1         4580  
20              
21             sub process_script (\@);
22              
23             my %g_block_commands = ('while' => 'done',
24             'until' => 'done',
25             'for' => 'done',
26             'select' => 'done',
27             'if' => 'fi',
28             'case' => 'esac'
29             );
30              
31             # Runtime options
32             my $g_integer = 1;
33             my $g_clobber = 0;
34             my $g_display = 0;
35              
36             our $VERSION = 0.06;
37             our $DEBUG = 0;
38              
39             ###########################################################
40              
41             sub outer
42             {
43 0     0     my $num_of_files = 0;
44 0           my @files;
45            
46 0 0         if (-d $_[-1]) {
    0          
47 0           my $dir = pop;
48            
49 0           for my $file (@_) {
50            
51 0           my $outfile = basename $file;
52            
53             # There might not be an extension
54 0           $outfile =~ s/\..*$//;
55 0           $outfile = "$dir/$outfile.pl";
56            
57 0 0 0       if (-f $outfile && !$g_clobber) {
58 0           print STDERR "$outfile already exists. Overwrite(Y/N)?: ";
59 0           my $reply = ;
60 0 0         if ( uc(substr($reply,0,1)) eq 'N' ) {
61 0           print STDERR "$file ignored\n";
62 0           next;
63             }
64             }
65 0           push @files,[ ($file, $outfile) ];
66             }
67             }
68             elsif (@_ == 2) {
69 0           @files = [ @_[0,1] ];
70             }
71             else {
72 0           usage();
73             }
74              
75 0           for my $ref (@files)
76             {
77 0           my $script_h;
78            
79 0 0         if ($ref->[0] eq '-') {
80 0           $script_h = *STDIN;
81             }
82             else {
83 0 0         open ($script_h, '<', $ref->[0]) || die "$ref->[0]: $!\n";
84             }
85            
86             # Pass the file name and the permissions
87 0           open_out_file ($ref->[1], (stat $script_h)[2] & 07777);
88            
89 0           $num_of_files++;
90              
91 0 0         if ( $DEBUG ) {
92 0           print STDERR "Processing $ref->[0] -> $ref->[1]\n";
93             }
94            
95 0           my @the_script = <$script_h>;
96 0           close $script_h;
97            
98 0           reset_globals();
99 0           @the_script = pre_process (@the_script);
100            
101             # Test in case the file is empty
102 0 0         if (@the_script) {
103 0           process_script (@the_script);
104             }
105             else {
106 0           error_out "Nothing found to process";
107             }
108            
109 0           close_out_file();
110             }
111            
112 0           return $num_of_files;
113              
114             } # outer
115              
116             ###########################################################
117             # Join together wrapped lines
118             # Currently only deals with one delimiter
119             sub pre_process
120             {
121 0     0     my @in_lines = @_;
122 0           my @out_lines;
123            
124             # Braces are not here because of functions
125 0           my %delimiters = ('[' => ']',
126             '`' => '`',
127             '(' => ')',
128             "'" => "'",
129             '"' => '"');
130            
131 0           my $open_delimiters = '['.join('',keys(%delimiters)).']';
132              
133             # Inspect the first line for the shell
134 0 0         if ($in_lines[0] =~ /^#!\s*.*\/(\w+)/) {
135 0           set_shell ($1);
136             }
137              
138 0           for (my $i = 0; $i < @in_lines; $i++) {
139 0           my $line = $in_lines[$i];
140              
141             # Do not test inside a comment
142 0           my $temp = $line;
143 0           $temp =~ s/#.*$//;
144            
145 0 0         if ($temp =~ /($open_delimiters)/) {
146            
147 0           my $delim = $1;
148 0           my $pattern = "\\$delim\[^\\$delimiters{$delim}\]*\\$delimiters{$delim}";
149            
150 0 0         if ($line !~ /$pattern/) {
151            
152 0           my $comments = '';
153 0           my $line_pos = @out_lines; # Remember the start of this block
154            
155 0           while ($i < $#in_lines) {
156 0           chomp $line;
157            
158             # Remove trailing comments
159 0 0         if ($line =~ s/\s+(#.*)$//) {
160 0           $comments .= " $1";
161             }
162            
163             # February 2009 substitute line continuation with "\n"
164 0           $line =~ s/\\$/\n/;
165            
166 0           $i++;
167            
168             # Remove leading whitespace if not quoted
169 0 0 0       $in_lines[$i] =~ s/^\s+// if ($delim ne "'" && $delim ne '"');
170            
171 0           $line .= " $in_lines[$i]";
172            
173 0 0         last if index($in_lines[$i],$delimiters{$delim}) > -1;
174             }
175            
176             # February 2009 (if)
177 0 0         if ( $comments ) {
178 0           splice (@out_lines, $line_pos, 0, $comments);
179             }
180             }
181             }
182              
183 0           push @out_lines, $line;
184             #{local $" = '|'; print STDERR "out_lines: <@out_lines>\n\n";}
185              
186             }
187              
188 0           return @out_lines;
189             }
190              
191             ###########################################################
192              
193             sub process_script (\@)
194             {
195 0     0     my ($ref) = @_;
196 0           my $index = 0;
197 0           my $limit = @$ref;
198 0           my $line = '';
199 0           my $delimiter = ';';
200 0           my $here_label;
201             my $here;
202 0           my $redirection_file;
203 0           my $statement = undef;
204            
205             # Maybe make this optional?
206 0 0         if ( $ref->[0] =~ /^#!/ ) {
207 0 0         if ($ref->[0] =~ /^#!.*(t?csh|perl|awk|sed)/) {
208 0           warn "This file appears to be a $1 script - abandoned\n";
209 0           return;
210             }
211 0           $index = 1;
212             }
213            
214             # use . (not "") in case path contains weird chars
215 0           out '#!'.$Config{'perlpath'}."\n\n";
216 0           out "# Generated by $0 on ".localtime()."\n\n";
217 0           out "use warnings;\n";
218 0           out "use strict;\n";
219            
220 0 0         out "use integer;\n" if $g_integer;
221 0           out "\n";
222 0           flush_out ();
223            
224             # A foreach loop would be too simplistic
225             OUTER:
226 0           while ($index < $limit) {
227            
228 0           my @tokens;
229              
230 0           $line .= $ref->[$index];
231 0           $index++;
232              
233             # shortcut for blank lines
234 0 0         if ($line =~ /^\s*$/) {
235 0           out $line;
236             next
237 0           }
238            
239             # Remove leading & trailing whitespace
240             # Also allows for Windows line endings (Cygwin)
241 0           $line =~ s/^\s+//;
242 0           $line =~ s/\s+$//;
243            
244 0 0         if ( substr($line,-1) eq '\\' ) {
245             # Continuation character
246 0           substr($line,-1) = "\n";
247 0           next;
248             }
249            
250 0 0         if ($line) {
251            
252 0           App::sh2p::Utils::mark_new_line();
253            
254 0 0         if ( $DEBUG ) {
255 0           print STDERR "\nProcessing <$line>\n";
256             }
257            
258             # Option -t (testing) - ignore comment lines
259 0 0 0       if ($g_display && $line !~ /^\s*#/) {
260             # convert newlines - for line continuation (January 2009)
261 0           my $out_line = $line;
262 0           $out_line =~ s/\n/\n#< /g;
263 0           out ("#< $out_line\n");
264             }
265            
266 0 0         if (! defined $statement) {
267 0           $statement = new App::sh2p::Statement();
268             }
269            
270             # Hack for here-docs
271 0 0         if ( defined $here_label ) {
272              
273 0 0         if ($here_label eq $line) {
274 0           $here_label = undef;
275 0           $here->close();
276             }
277             else {
278             # push line into here doc
279 0           $here->write($line);
280             }
281 0           $line = '';
282             next
283 0           }
284            
285             # Hack for Bourne shell function syntax
286             # Change it to ksh syntax (cheat)
287 0 0         if ($line =~ /^(\w+)\s*\(\)(.*)/) {
288 0           my $name = $1;
289 0           my $rest = $2;
290 0           $line = "function $name $rest"
291             }
292            
293 0           @tokens = App::sh2p::Parser::tokenise ($line);
294            
295             # Look for statement delimiters
296 0           for (my $i = 0; $i < @tokens; $i++) {
297            
298 0           my $tok = $tokens[$i];
299             #print STDERR "Processing token: <$tok>\n";
300            
301             # This check is to 'read-ahead' looking for redirection
302             # $delimiter test added January 2009 for nested conditionals
303 0 0 0       if (exists $g_block_commands{$tok} && $delimiter eq ';') {
304             # print STDERR "Delimiter switched from <$delimiter> to <$g_block_commands{$tok}>\n";
305 0           $delimiter = $g_block_commands{$tok};
306             }
307            
308             # Look ahead to check for redirection
309             # Currently only 'here' documents
310            
311             # We need the code here twice, since redirections
312             # also occur AFTER a while loop, if statement, or function
313             # This one handles redirection on the statement
314 0 0         if ($tok eq '<<') {
315 0           $i += 1;
316 0 0         if ( !defined $tokens[$i] ) {
317 0           die "*** Malformed here document (no label) line ",$index + 1,"\n";
318             }
319 0           $here_label = $tokens[$i];
320 0           $here_label =~ s/^\s+//;
321 0           $here = App::sh2p::Here->open($here_label, '>');
322             }
323             # This is no good for built-ins and externals
324             #elsif ($tok eq '<' || $tok eq '>' || $tok eq '>>') {
325             # $i += 1;
326             # if ( !defined $tokens[$i] ) {
327             # die "*** Malformed redirection (no file) line ",$index + 1,"\n";
328             # }
329             # $redirection_file = $tokens[$i];
330             # $redirection_file =~ s/^\s+//;
331             # App::sh2p::Handlers::Handle_open_redirection ($tok, $redirection_file);
332             # next;
333             #}
334              
335 0 0         if ($tok eq $delimiter) {
    0          
336             # We need the code here twice, since redirections
337             # also occur AFTER a while loop, if statement, or function
338             # This one handles redirection AFTER the statement
339 0 0 0       if ( defined $tokens[$i+1] && $tokens[$i+1] eq '<<' ) {
    0 0        
    0 0        
340            
341 0           $statement->add_token ($tok);
342            
343 0           $i += 2;
344 0 0         if ( !defined $tokens[$i] ) {
345 0           die "*** Malformed here document (no label) line ",$index + 1,"\n";
346             }
347 0           $here_label = $tokens[$i];
348 0           $here_label =~ s/^\s+//;
349 0           $here = App::sh2p::Here->open($here_label, '>');
350             }
351             elsif (defined $tokens[$i+1] && # ADDED 11/11/2008
352             ($tokens[$i+1] eq '<' ||
353             $tokens[$i+1] eq '>' ||
354             $tokens[$i+1] eq '>>')) {
355            
356 0           $statement->add_token ($tok);
357            
358 0           my $access = $tokens[$i+1];
359 0           $i += 2;
360 0 0         if ( !defined $tokens[$i] ) {
361 0           die "*** Malformed redirection (no file) line ",$index + 1,"\n";
362             }
363 0           $redirection_file = $tokens[$i];
364 0           $redirection_file =~ s/^\s+//;
365 0           App::sh2p::Handlers::Handle_open_redirection ($access, $redirection_file);
366 0           $statement->add_token (\&App::sh2p::Handlers::Handle_close_redirection);
367             }
368             #elsif ($tok ne ';' && $tok ne BREAK) {
369             elsif ($tok ne ';') {
370 0           $statement->add_token ($tok);
371             }
372            
373             # Process statements
374 0 0         if (defined $statement) {
375 0           $statement->identify_tokens(0);
376 0           $statement->convert_tokens();
377            
378 0           undef $statement;
379             }
380            
381 0           $delimiter = ';';
382             }
383             elsif (defined $statement) {
384             # Inside a while, until, for, if, or case
385             # print STDERR "statement <$tok> added\n";
386 0           $statement->add_token ($tok);
387             }
388             else {
389             # statements after ;
390 0           $statement = new App::sh2p::Statement();
391 0           $statement->add_token ($tok);
392             }
393             }
394            
395             # 0.05
396 0 0 0       if (defined $statement && $delimiter eq ';' ) {
    0          
    0          
397 0           $statement->identify_tokens(0);
398 0           $statement->convert_tokens();
399 0           $statement = undef;
400             }
401             elsif ($delimiter eq 'esac') {
402 0           $statement->push_case();
403 0           $statement = undef;
404             }
405             elsif (defined $statement) {
406 0           $statement->add_break ();
407             }
408            
409             }
410            
411             # At end
412              
413 0           flush_out ();
414            
415 0           $line = '';
416             }
417            
418 0           App::sh2p::Handlers::write_subs();
419 0           App::sh2p::Here::write_here_subs();
420 0           flush_out ();
421            
422             } # process_script
423            
424             ###########################################################
425              
426             sub usage {
427 0     0     print STDERR "Usage: sh2p.pl [-i] [-t] [-f] input-file output-file | input-files... out-directory\n";
428 0           exit 1;
429             }
430              
431             ###########################################################
432             # main
433             # done this way to aid testing
434             # see "Perl Testing, A Developer's Notebook" by Ian Langworth & chromatic (O'Reilly)
435              
436             main(@ARGV) unless caller();
437              
438             sub main
439             {
440 0     0     my %args;
441              
442 0           getopts ('ift', \%args);
443 0 0         $g_integer = 0 if exists $args{'i'};
444 0 0         $g_clobber = 1 if exists $args{'f'};
445 0 0         $g_display = 1 if exists $args{'t'};
446              
447 0 0         if ( @ARGV < 2 ) {
448 0           usage();
449             }
450            
451 0           outer(@ARGV);
452             }
453              
454             __END__