File Coverage

blib/lib/Makefile/Update/VCProj.pm
Criterion Covered Total %
statement 72 73 98.6
branch 38 42 90.4
condition 4 6 66.6
subroutine 5 5 100.0
pod 1 1 100.0
total 120 127 94.4


line stmt bran cond sub pod time code
1             package Makefile::Update::VCProj;
2             # ABSTRACT: Update list of sources and headers in Visual C++ projects.
3              
4 2     2   81139 use Exporter qw(import);
  2         16  
  2         88  
5             our @EXPORT = qw(update_vcproj);
6              
7 2     2   11 use strict;
  2         5  
  2         42  
8 2     2   8 use warnings;
  2         3  
  2         1878  
9              
10             our $VERSION = '0.4'; # VERSION
11              
12              
13              
14             sub update_vcproj
15             {
16 6     6 1 7425 my ($in, $out, $sources, $headers, $filter_cb) = @_;
17              
18             # Use standard/default classifier for the files if none is explicitly
19             # specified.
20 6 50       15 if (!defined $filter_cb) {
21             $filter_cb = sub {
22 11     11   17 my ($file) = @_;
23              
24 11 100       43 return 'Source Files' if $file =~ q{\.c(c|pp|xx|\+\+)?$};
25 4 100       25 return 'Header Files' if $file =~ q{\.h(h|pp|xx|\+\+)?$};
26              
27 1         9 warn qq{No filter defined for the file "$file".\n};
28              
29             undef
30 1         43 }
31 6         21 }
32              
33             # Hash mapping the filter to all the files using it (whether sources or
34             # headers).
35 6         9 my %files_by_filter;
36 6         14 foreach my $file (@$sources, @$headers) {
37 11         30 my $filter = $filter_cb->($file);
38 11 100       21 if (defined $filter) {
39 10         11 push @{$files_by_filter{$filter}}, $file
  10         35  
40             }
41             }
42              
43             # Name of the current filter, if any.
44 6         10 my $filter;
45              
46             # Hash containing 0 or 1 for each file using the current filter.
47             my %seen;
48              
49             # Indicates whether the closing angle bracket of "" tags is on its
50             # own line (which is how MSVS 2005 and 2008 format their files) or on the
51             # same line as "RelativePath" attribute (which is how MSVS 2003 does it).
52 6         7 my $angle_bracket_on_same_line = 0;
53              
54             # Set to 1 if we made any changes.
55 6         7 my $changed = 0;
56              
57 6         18 while (defined (my $line_with_eol = <$in>)) {
58 133         380 (my $line = $line_with_eol) =~ s/\r?\n$//;
59              
60 133 100       281 if ($line =~ /^\s*
    100          
61 8 100       16 if (defined($filter)) {
62 1         7 warn qq{Nested tag at line $. while parsing filter } .
63             qq{"$filter" is not supported.\n};
64 1         39 next;
65             }
66              
67 7         10 print $out $line_with_eol;
68 7         10 $line_with_eol = <$in>;
69 7 100 66     33 if (defined $line_with_eol &&
70             $line_with_eol =~ /^\s*Name="(.*)"\r?\n$/) {
71 6         13 $filter = $1;
72 6 100       13 if (!exists $files_by_filter{$filter}) {
73             # If we don't have any files for this filter, don't remove
74             # all the files from it, just skip it entirely instead.
75 1         1 undef $filter;
76             } else {
77 5         7 %seen = map { $_ => 0 } @{$files_by_filter{$filter}};
  9         22  
  5         9  
78             }
79             } else {
80 1         16 warn qq{Unrecognized format for tag at line $..\n};
81             }
82             } elsif (defined $filter) {
83 33 100       111 if ($line =~ /^\s*
    100          
84 11         14 my $line_file_start = $line_with_eol;
85              
86 11         12 $line_with_eol = <$in>;
87 11 100 66     46 if (defined $line_with_eol &&
88             $line_with_eol =~ /^\s*RelativePath="(.*)"(>?)\r?\n$/) {
89 10         20 $angle_bracket_on_same_line = $2 eq '>';
90              
91             # Normalize path separators to Unix and remove the leading
92             # dot which MSVC likes to use for some reason.
93 10         15 (my $file = $1) =~ s@\\@/@g;
94 10         12 $file =~ s@^\./@@;
95              
96             # Special hack for resource files that sometimes occur in
97             # the "Source Files" section of MSVC projects too: don't
98             # remove them, even if they don't appear in the master
99             # files list, because they are never going to appear in it.
100 10 100       19 if ($file !~ /\.rc$/) {
101 9 100       23 if (!exists $seen{$file}) {
102             # This file is not in the master file list any
103             # more, delete it from the project file as well by
104             # not copying the lines corresponding to it to the
105             # output.
106 3         4 $changed = 1;
107              
108             # Skip the next line unless we had already seen
109             # the angle bracket.
110 3 100       6 if (!$angle_bracket_on_same_line) {
111 1 50       4 if (<$in> !~ /^\s*>\r?\n$/) {
112 0         0 warn qq{Expected closing '>' on the line $.\n}
113             }
114             }
115              
116             # And skip everything up to and including the
117             # closing tag in any case.
118 3         6 while (<$in>) {
119 3 50       11 last if qr{^\s*\r?\n$}
120             }
121              
122 3         8 next;
123             }
124              
125             # This file is still in the files list, mark it as seen.
126 6 100       12 if ($seen{$file}) {
127 1         9 warn qq{Duplicate file "$file" in the project at line $.\n};
128             } else {
129 5         8 $seen{$file} = 1;
130             }
131             }
132             } else {
133 1         6 warn qq{Unrecognized format for tag inside filter } .
134             qq{"$filter" at line $..\n};
135             }
136              
137             # Don't lose the original line, it won't be printed at the
138             # end of the loop any more.
139 8         176 print $out $line_file_start;
140             } elsif ($line =~ qr{^\s*$}) {
141 5 100       12 my $angle_bracket = $angle_bracket_on_same_line
142             ? '>'
143             : "\n\t\t\t\t>";
144              
145             # Add new files, if any.
146             #
147             # TODO Insert them in alphabetical order.
148 5         14 while (my ($file, $seen) = each(%seen)) {
149 9 100       22 if (!$seen) {
150             # Convert path separator to the one used by MSVC.
151 4         13 $file =~ s@/@\\@g;
152              
153             # And use path even for the files in this directory.
154 4 50       13 $file = ".\\$file" if $file !~ /\\/;
155              
156 4         12 print $out <
157             \t\t\t
158             \t\t\t\tRelativePath="$file"$angle_bracket
159             \t\t\t
160             END
161             ;
162              
163 4         12 $changed = 1;
164             }
165             }
166              
167 5         11 undef $filter;
168             }
169             }
170              
171 129         377 print $out $line_with_eol;
172             }
173              
174             $changed
175 6         52 }
176              
177             __END__