#!/usr/bin/env perl

# PODNAME: upmake
#
# ABSTRACT: Simple script to update list of files in a make or project file.
#
our $VERSION = '0.2'; # VERSION


use strict;
use warnings;
use autodie;

use Getopt::Long;

use Makefile::Update;
use Makefile::Update::Bakefile0;
use Makefile::Update::Makefile;
use Makefile::Update::MSBuild;
use Makefile::Update::VCProj;

my $verbose = 0;
my $quiet = 0;
my $dry_run = 0;
my (@sources_vars, @headers_vars);

GetOptions(
        'version'        => sub { print "$0 version $Makefile::Update::VERSION.\n"; exit },
        'verbose|v'      => \$verbose,
        'quiet|q'        => \$quiet,
        'dry-run|n'      => \$dry_run,
        'sources|s=s'    => \@sources_vars,
        'headers|h=s'    => \@headers_vars,
    ) and (@ARGV > 0) or die <<EOF
Usage: $0 [--version] [--verbose] [--quiet] [--dry-run|-n] <file-to-update...>

Update the sources and headers files used in the specified make/project
file(s) from the list of files in "files" file in the current directory.

If --sources and/or --headers options are given, use the names of the
variables specified by them in the files list file instead of the default
"sources" and "headers" when updating the files which use only a single
variable for the files of each kind (e.g. project files). Both options can
be specified multiple times to combine the values of several variables.

If --dry-run option is specified, the files are not really updated, but the
script just indicates where they would be changed and, if --verbose is also
specified, outputs the diff.
EOF
;

# Helper calling upmake() to actually update the file and showing the result.
sub log_upmake
{
    my ($fname, $updater, @args) = @_;

    if ($dry_run) {
        my $old = do {
            local $/;
            open my $f, '<', $fname;
            <$f>
        };
        my $new = '';

        open my $in, '<', \$old;
        open my $out, '>', \$new;

        if ($updater->($in, $out, @args)) {
            print qq{Would update "$fname"};

            if ($verbose) {
                if (eval { require Text::Diff; }) {
                    print " with the following changes:\n";

                    print Text::Diff::diff(\$old, \$new, {
                                FILENAME_A => $fname,
                                FILENAME_B => "$fname.new"
                            });
                } else {
                    print ".\n";

                    warn qq{Can't display diff of the changes, please install Text::Diff module.\n};
                }
            } else {
                print ".\n";
            }
        } else {
            print qq{Wouldn't change the file "$fname".\n};
        }

        return 0;
    }

    if (upmake($fname, $updater, @args)) {
        print qq{File "$fname" successfully updated.\n} unless $quiet;
        return 1;
    } else {
        print qq{No changes in the file "$fname".\n} if $verbose;
        return 0;
    }
}

my $files_list = 'files';

open my $files, '<', $files_list;
my $vars = read_files_list($files);

sub get_sources_and_headers
{
    my @sources;
    for (@sources_vars) {
        if (!defined $vars->{$_}) {
            die qq{Invalid --sources option: variable "$_" is not } .
                qq{defined in the "$files_list" file.\n}
        }

        push @sources, @{$vars->{$_}};
    }

    my @headers;
    for (@headers_vars) {
        if (!defined $vars->{$_}) {
            die qq{Invalid --headers option: variable "$_" is not } .
                qq{defined in the "$files_list" file.\n}
        }

        push @headers, @{$vars->{$_}};
    }

    my $sources = @sources ? \@sources : $vars->{sources};
    my $headers = @headers ? \@headers : $vars->{headers};
    if (!defined $sources || !@$sources) {
        die qq{No sources specified, define "sources" variable in the } .
            qq{"$files_list" file or use --sources option.\n}
    }

    return ($sources, $headers)
}

foreach my $fname (@ARGV) {
    # What kind of file is it?
    if ($fname =~ /\.bkl$/) {
        if (log_upmake($fname, \&update_bakefile_0, $vars)) {
            print qq{Don't forget to run bakefile or bakefile_gen now.} if $verbose;
        }
    } elsif ($fname =~ /\.vcxproj$/) {
        my ($sources, $headers) = get_sources_and_headers();
        log_upmake($fname, \&update_msbuild, $sources, $headers);
        log_upmake("$fname.filters", \&update_msbuild_filters, $sources, $headers);
    } elsif ($fname =~ /\.vcproj$/) {
        log_upmake($fname, \&update_vcproj, get_sources_and_headers());
    } elsif ($fname =~ /^[Mm]akefile/ || $fname =~ /\.make?$/) {
        log_upmake($fname, \&update_makefile, $vars);
    } else {
        die qq{File "$fname" is of unknown type, can't update.\n}
    }
}

__END__

=pod

=encoding UTF-8

=head1 NAME

upmake - Simple script to update list of files in a make or project file.

=head1 VERSION

version 0.2

=head1 SYNOPSIS

upmake <file-to-update> [<file-to-update>...]

For example:

  upmake GNUmakefile myproject.vcxproj

to update the source/header files listed in the makefile and project file
from the master file list in the "files" file.

=head1 DESCRIPTION

This script can be used to update the list of source and possible header files
in make and/or project files. It is useful for projects that can be built by
different tools as it allows to have a single master list of the files used in
the project and update all the rest automatically.

The master list of files is assumed to be in the file named just C<"files"> in
the current working directory.

=head1 OPTIONS

C<--quiet> and C<--verbose> options control the script output in the expected
way. The latter modifies the behaviour of C<--dry-run> option which on its own
would just output if the files would be updated or not, to show a unified diff
with the changes that would be done to them.

The C<--sources> and C<--headers> options are mostly useful when updating a
project file, to specify the variables which contain the sources and headers
to use in them. They can be repeated several times to combine the values of
several variables. Notice that by using Makefile::Update module directly, it
is possible to manipulate the files list in more advanced ways, e.g. adjust
the file paths, the command line script covers only the most common case.

=head1 SEE ALSO

L<Makefile::Update>

=head1 AUTHOR

Vadim Zeitlin <vz-cpan@zeitlins.org>

=head1 COPYRIGHT AND LICENSE

This software is copyright (c) 2014 by Vadim Zeitlin.

This is free software; you can redistribute it and/or modify it under
the same terms as the Perl 5 programming language system itself.

=cut
