File Coverage

blib/lib/C/Utility.pm
Criterion Covered Total %
statement 67 166 40.3
branch 6 56 10.7
condition 2 15 13.3
subroutine 15 30 50.0
pod 20 22 90.9
total 110 289 38.0


line stmt bran cond sub pod time code
1             package C::Utility;
2 3     3   222982 use warnings;
  3         23  
  3         112  
3 3     3   15 use strict;
  3         4  
  3         64  
4 3     3   16 use File::Spec;
  3         5  
  3         99  
5 3     3   16 use Carp;
  3         6  
  3         203  
6 3     3   1367 use File::Versions 'make_backup';
  3         3584  
  3         168  
7 3     3   1318 use File::Slurper qw/read_text write_text/;
  3         52436  
  3         223  
8 3     3   1284 use C::Tokenize qw/$comment_re $include $reserved_re/;
  3         13134  
  3         534  
9 3     3   1577 use Text::LineNumber;
  3         992  
  3         6799  
10              
11             require Exporter;
12              
13             our @ISA = qw(Exporter);
14              
15             our @EXPORT_OK = qw/
16             add_lines
17             brute_force_line
18             c_string
19             c_to_h_name
20             ch_files
21             convert_to_c_string
22             convert_to_c_string_pc
23             escape_string
24             hash_to_c_file
25             line_directive
26             linein
27             lineout
28             print_bottom_h_wrapper
29             print_top_h_wrapper
30             read_includes
31             remove_quotes
32             stamp_file
33             valid_c_variable
34             /;
35              
36             our %EXPORT_TAGS = (
37             'all' => \@EXPORT_OK,
38             );
39              
40             our $VERSION = '0.012';
41              
42             sub convert_to_c_string
43             {
44 2     2 1 94 my ($text) = @_;
45 2 50       9 if (length ($text) == 0) {
46 0         0 return "\"\"";
47             }
48             # Convert backslashes to double backslashes.
49 2         6 $text =~ s/\\/\\\\/g;
50             # Escape double quotes
51 2         6 $text = escape_string ($text);
52             # If there was a backslash before a quote, as in \", the first
53             # regex above converted it to \\", and then escape_string
54             # converted that to \\\".
55 2         20 $text =~ s/\\\\"/\\"/g;
56             # Remove backslashes from before the @ symbol.
57 2         6 $text =~ s/\\\@/@/g;
58             # Turn each line into a string
59 2         12 $text =~ s/(.*)\n/"$1\\n"\n/gm;
60             # Catch a final line without any \n at its end.
61 2 100       9 if ($text !~ /\\n\"$/) {
62 1         11 $text =~ s/(.+)$/"$1"/g;
63             }
64 2         8 return $text;
65             }
66              
67             sub c_string
68             {
69 0     0 1 0 goto & convert_to_c_string;
70             }
71              
72             sub ch_files
73             {
74 0     0 1 0 my ($c_file_name) = @_;
75 0 0       0 if ($c_file_name !~ /\.c/) {
76 0         0 die "$c_file_name is not a C file name";
77             }
78 0         0 my $h_file_name = $c_file_name;
79 0         0 $h_file_name =~ s/\.c$/\.h/;
80 0 0       0 if (-f $c_file_name) {
81 0         0 make_backup ($c_file_name);
82             }
83 0 0       0 if (-f $h_file_name) {
84 0         0 make_backup ($h_file_name);
85             }
86 0         0 return $h_file_name;
87             }
88              
89             sub convert_to_c_string_pc
90             {
91 1     1 1 678 my ($text) = @_;
92 1         6 $text =~ s/%/%%/g;
93 1         4 return convert_to_c_string ($text);
94             }
95              
96             sub escape_string
97             {
98 2     2 1 6 my ($text) = @_;
99 2         7 $text =~ s/\"/\\\"/g;
100 2         5 return $text;
101             }
102              
103             sub c_to_h_name
104             {
105 0     0 1 0 my ($c_file_name) = @_;
106 0 0       0 if ($c_file_name !~ /\.c/) {
107 0         0 die "$c_file_name is not a C file name";
108             }
109 0         0 my $h_file_name = $c_file_name;
110 0         0 $h_file_name =~ s/\.c$/\.h/;
111 0         0 return $h_file_name;
112             }
113              
114             sub valid_c_variable
115             {
116 0     0 1 0 my ($variable_name) = @_;
117 0 0 0     0 if ($variable_name !~ /^[A-Za-z_][A-Za-z_0-9]+$/ ||
118             $variable_name =~ /^(?:$reserved_re)$/) {
119 0         0 return;
120             }
121 0         0 return 1;
122             }
123              
124             # Wrapper name
125             # BKB 2009-10-05 14:09:41
126              
127             sub wrapper_name
128             {
129 0     0 1 0 my ($string) = @_;
130 0         0 $string =~ s/[.-]/_/g;
131 0 0       0 if (! valid_c_variable ($string)) {
132 0         0 croak "Bad string for wrapper '$string'";
133             }
134 0         0 my $wrapper_name = uc $string;
135 0         0 return $wrapper_name;
136             }
137              
138             sub print_top_h_wrapper
139             {
140 0     0 1 0 my ($fh, $file_name) = @_;
141            
142 0         0 my $wrapper_name = wrapper_name ($file_name);
143 0         0 my $wrapper = <
144             #ifndef $wrapper_name
145             #define $wrapper_name
146             EOF
147 0         0 print_out ($fh, $wrapper);
148             }
149              
150             sub print_out
151             {
152 0     0 0 0 my ($fh, $wrapper) = @_;
153 0 0 0     0 if (ref $fh && ref $fh eq 'SCALAR') {
154 0         0 ${$fh} .= $wrapper;
  0         0  
155             }
156             else {
157 0         0 print $fh $wrapper;
158             }
159             }
160              
161             sub print_bottom_h_wrapper
162             {
163 0     0 1 0 my ($fh, $file_name) = @_;
164 0         0 my $wrapper_name = wrapper_name ($file_name);
165 0         0 my $wrapper = <
166             #endif /* $wrapper_name */
167             EOF
168 0         0 print_out ($fh, $wrapper);
169             }
170              
171             sub print_include
172             {
173 0     0 1 0 my ($fh, $h_file_name) = @_;
174 0         0 print $fh <
175             #include "$h_file_name"
176             EOF
177             }
178              
179             sub hash_to_c_file
180             {
181             # $prefix is an optional prefix applied to all variables.
182 0     0 1 0 my ($c_file_name, $hash_ref, $prefix) = @_;
183 0         0 my $h_file_name = ch_files ($c_file_name);
184 0 0       0 die "Not a hash ref" unless ref $hash_ref eq "HASH";
185 0 0       0 $prefix = "" unless $prefix;
186 0 0       0 open my $c_out, ">:utf8", $c_file_name or die $!;
187 0         0 my (undef, undef, $h_file) = File::Spec->splitpath ($h_file_name);
188 0         0 print_include ($c_out, $h_file);
189 0 0       0 open my $h_out, ">:utf8", $h_file_name or die $!;
190 0         0 print_top_h_wrapper ($h_out, $h_file);
191 0         0 for my $variable (sort keys %$hash_ref) {
192 0 0       0 if (! valid_c_variable ($variable)) {
193 0         0 croak "key '$variable' is not a valid C variable";
194             }
195 0         0 my $value = $hash_ref->{$variable};
196 0         0 $value = convert_to_c_string ($value);
197 0         0 print $c_out "const char * $prefix$variable = $value;\n";
198 0         0 print $h_out "extern const char * $prefix$variable; /* $value */\n";
199             }
200 0 0       0 close $c_out or die $!;
201 0         0 print_bottom_h_wrapper ($h_out, $h_file);
202 0 0       0 close $h_out or die $!;
203 0         0 return $h_file_name;
204             }
205              
206             sub line_directive
207             {
208 0     0 1 0 my ($output, $line_number, $file_name) = @_;
209 0 0 0     0 die "$line_number is not a positive integer number"
210             unless $line_number =~ /^[0-9]+$/ && $line_number > 0;
211 0         0 print_out ($output, "#line $line_number \"$file_name\"\n");
212             }
213              
214             sub brute_force_line
215             {
216 0     0 1 0 my ($input_file, $output_file) = @_;
217 0 0       0 open my $input, "<:encoding(utf8)", $input_file or die $!;
218 0 0       0 open my $output, ">:encoding(utf8)", $output_file or die $!;
219 0         0 while (<$input>) {
220 0         0 print $output "#line $. \"$input_file\"\n";
221 0         0 print $output $_;
222             }
223 0 0       0 close $input or die $!;
224 0 0       0 close $output or die $!;
225             }
226              
227             sub add_lines
228             {
229 0     0 1 0 my ($input_file) = @_;
230 0         0 my $full_name = File::Spec->rel2abs ($input_file);
231 0         0 my $text = '';
232 0 0       0 open my $input, "<:encoding(utf8)", $input_file or die $!;
233 0         0 while (<$input>) {
234 0 0       0 if (/^#line/) {
    0          
235 0         0 my $line_no = $. + 1;
236 0         0 $text .= "#line $line_no \"$full_name\"\n";
237             }
238             elsif ($. == 1) {
239 0         0 $text .= "#line 1 \"$full_name\"\n";
240 0         0 $text .= $_;
241             }
242             else {
243 0         0 $text .= $_;
244             }
245             }
246 0         0 return $text;
247             }
248              
249             sub remove_quotes
250             {
251 3     3 1 76 my ($string) = @_;
252 3         21 $string =~ s/^"|"$|"\s*"//g;
253 3         13 return $string;
254             }
255             #use Data::Dumper;
256             sub linedirective
257             {
258 2     2 0 6 my ($intext, $file, $directive) = @_;
259 2 50 33     14 die unless $intext && $file && $directive;
      33        
260 2         4 my %renumbered;
261             # Uniquifier for the lines.
262 2         2 my $count = 0;
263             # This is unlikely to occur.
264 2         18 my $tag = 'ABRACADABRA';
265             # Watch for blue-moon occurences
266 2 50       21 die if $intext =~ /$tag\d+/;
267 2         40 while ($intext =~ s/^\Q$directive/$tag$count$tag/sm) {
268 3         18 $count++;
269             }
270             # Make the line numbering only after the above substitution, or we
271             # get problems due to changed offsets after the substitution
272             # above.
273 2         11 my $tln = Text::LineNumber->new ($intext);
274             # "pos" doesn't work well with s///g, so now we need to match the tags
275             # one by one.
276 2         99 while ($intext =~ /($tag\d+$tag)/g) {
277 3         7 my $key = $1;
278 3         6 my $pos = pos ($intext);
279 3         8 my $line = $tln->off2lnr ($pos);
280             # print "Position $pos in $file = line $line.\n";
281              
282             # The actual line the line directive sends us to is one after
283             # the value the line contains, e.g. on the first line we
284             # should have "#line 2", so we need to add one to the value.
285              
286 3         73 $renumbered{$key} = $line + 1;
287             }
288             # print Dumper (\%renumbered);
289 2         29 $intext =~ s/($tag\d+$tag)/#line $renumbered{$1} "$file"/g;
290             # Check for failures. We already checked this doesn't occur
291             # naturally in the file above.
292 2 50       17 die if $intext =~ /$tag\d+$tag/;
293 2         11 return $intext;
294             }
295              
296             sub linein
297             {
298 1     1 1 688 my ($infile) = @_;
299 1         7 my $intext = read_text ($infile);
300 1         108 $intext = linedirective ($intext, $infile, '#linein');
301 1         4 return $intext;
302             }
303              
304             sub lineout
305             {
306 1     1 1 23 my ($outtext, $outfile) = @_;
307              
308 1         3 $outtext = linedirective ($outtext, $outfile, "#lineout");
309 1         5 write_text ($outfile, $outtext);
310             }
311              
312             sub stamp_file
313             {
314 0     0 1   my ($fh, $name) = @_;
315 0 0         if (! defined $name) {
316 0           $name = "This C file";
317             }
318 0           my $now = scalar localtime ();
319 0           my $stamp =<
320             /*
321             $name was generated by $0 at $now.
322             */
323             EOF
324 0           print_out ($fh, $stamp);
325             }
326              
327             sub read_includes
328             {
329 0     0 1   my ($file) = @_;
330 0           my $text = read_text ($file);
331             # Remove all the comments from the file so that things like
332             # /*#include "something.h"*/ don't create false positives.
333 0           $text =~ s/$comment_re//g;
334 0           my @hfiles;
335 0           while ($text =~ /$include/g) {
336 0           push @hfiles, $1;
337             }
338 0           return \@hfiles;
339             }
340              
341             1;