File Coverage

File:lib/Makefile/Update.pm
Coverage:90.0%

linestmtbrancondsubcode
1package Makefile::Update;
2
3# ABSTRACT: Update make files.
4
5
23
23
23
use strict;
6
23
23
23
use warnings;
7
23
23
23
use autodie;
8
9
23
23
23
use Exporter qw(import);
10
11our @EXPORT = qw(read_files_list upmake);
12
13# VERSION
14
15 - 21
=head1 SYNOPSIS

    use Makefile::Update;
    my $vars = read_files_list('files.lst');
    upmake('foo.vcxproj', $vars->{sources}, $vars->{headers});

=cut
22
23=func read_files_list
24
25Reads the file containing the file lists definitions and returns a hash ref
26with variable names as keys and refs to arrays of the file names as values.
27
28Takes an (open) file handle as argument.
29
30The file contents is supposed to have the following very simple format:
31
32    # Comments are allowed and ignored.
33    #
34    # The variable definitions must always be in the format shown below,
35    # i.e. whitespace is significant and there should always be a single
36    # file per line.
37    sources =
38        file1.cpp
39        file2.cpp
40
41    headers =
42        file1.h
43        file2.h
44
45    # It is also possible to define variables in terms of other variables
46    # defined before it in the file (no forward references):
47    everything =
48        $sources
49        $headers
50=cut
51
52sub read_files_list
53{
54
3
    my ($fh) = @_;
55
56
3
    my ($var, %vars);
57
3
    while (<$fh>) {
58
42
        chomp;
59
42
        s/#.*$//;
60
42
        s/^\s+//;
61
42
        s/\s+$//;
62
42
        next if !$_;
63
64
27
        if (/^(\w+)\s*=$/) {
65
9
            $var = $1;
66        } else {
67
18
            die "Unexpected contents outside variable definition at line $.\n"
68                unless defined $var;
69
18
            if (/^\$(\w+)$/) {
70
6
                my $name = $1;
71                die qq{Reference to undefined variable "$name" in the } .
72                    qq{assignment to "$var" at line $.\n}
73
6
                    unless exists $vars{$name};
74
6
                my $value = $vars{$name};
75
6
12
                push @{$vars{$var}}, $_ for @$value;
76            } else {
77
12
12
                push @{$vars{$var}}, $_;
78            }
79        }
80    }
81
82
3
    return \%vars;
83}
84
85=func upmake
86
87Update a file in place using the specified function and passing it the rest of
88the arguments.
89
90The first parameter is either just the file path or a hash reference which may
91contain the following keys:
92
93 - 119
=over

=item C<file>

The path to the file to be updated, required.

=item C<verbose>

If true, give more messages about what is being done.

=item C<quiet>

If true, don't output any non-error messages.

=item C<dryrun>

If true, don't really update the file but just output whether it would have
been updated or not. If C<verbose> is also true, also output the diff of the
changes that would have been done.

=back

This is meant to be used with C<update_xxx()> defined in different
Makefile::Update::Xxx modules.

Returns 1 if the file was changed or 0 otherwise.
=cut
120
121sub upmake
122{
123
31
    my $file_or_options = shift;
124
31
    my ($updater, @args) = @_;
125
126
31
    my ($fname, $verbose, $quiet, $dryrun);
127
31
    if (ref $file_or_options eq 'HASH') {
128
25
        $fname = $file_or_options->{file};
129
25
        $verbose = $file_or_options->{verbose};
130
25
        $quiet = $file_or_options->{quiet};
131
25
        $dryrun = $file_or_options->{dryrun};
132    } else {
133
6
        $fname = $file_or_options;
134
6
        $verbose =
135        $quiet =
136        $dryrun = 0;
137    }
138
139
31
    if ($dryrun) {
140
6
        my $old = do {
141
6
            local $/;
142
6
            open my $f, '<', $fname;
143            <$f>
144
6
        };
145
6
        my $new = '';
146
147
6
        open my $in, '<', \$old;
148
6
        open my $out, '>', \$new;
149
150
6
        if ($updater->($in, $out, @args)) {
151
3
            print qq{Would update "$fname"};
152
153
3
            if ($verbose) {
154
0
0
                if (eval { require Text::Diff; }) {
155
0
                    print " with the following changes:\n";
156
157
0
                    print Text::Diff::diff(\$old, \$new, {
158                                FILENAME_A => $fname,
159                                FILENAME_B => "$fname.new"
160                            });
161                } else {
162
0
                    print ".\n";
163
164
0
                    warn qq{Can't display diff of the changes, please install Text::Diff module.\n};
165                }
166            } else {
167
3
                print ".\n";
168            }
169        } else {
170
3
            print qq{Wouldn't change the file "$fname".\n};
171        }
172
173
6
        return 0;
174    }
175
176
25
    my $fname_new = "$fname.upmake.new"; # TODO make it more unique
177
178
25
    open my $in, '<', $fname;
179
25
    open my $out, '>', $fname_new;
180
181
25
    my $changed = $updater->($in, $out, @args);
182
183
25
    close $in;
184
25
    close $out;
185
186
25
    if ($changed) {
187
14
        rename $fname_new, $fname;
188    } else {
189
11
        unlink $fname_new;
190    }
191
192
25
    if ($changed) {
193
14
        print qq{File "$fname" successfully updated.\n} unless $quiet;
194
14
        return 1;
195    } else {
196
11
        print qq{No changes in the file "$fname".\n} if $verbose;
197
11
        return 0;
198    }
199}
200
2011;