#!/usr/bin/perl
# $Id: /mirror/youri/soft/submit/trunk/bin/youri-submit.in 2331 2007-03-22T21:21:14.565358Z guillomovitch  $

=head1 NAME

youri-submit - package submission tool

=head1 VERSION

Version 2.0

=head1 SYNOPSIS

youri-submit [options] <target> <files>

youri-submit --list <category> [target]

youri-submit --help [category] [item]

Options:

    --config <file>        use file <file> as config file
    --skip-test <test>   skip test <test>
    --skip-action <action> skip action <action>
    --define <key>=<value> pass additional values
    --clean                delete package after success
    --verbose              verbose run
    --test                 test run
    --list <category>      list items from given category
    --help [category]      display contextual help

=head1 DESCRIPTION

B<youri-submit> allows to submit packages to a repository.

All packages given on command lines are passed to a list of test plugins,
depending on given upload target. If none of them fails, all packages are
passed to a list of action plugins, depending also on given upload target.

=head1 OPTIONS

=over

=item B<--config> I<file>

Use given file as configuration, instead of normal one.

=item B<--skip-test> I<id>

Skip test plugin with given identity.

=item B<--skip-action> I<id>

Skip action plugin with given identity.

=item B<--define> <key>=<value>

Define additional parameters, to be used by plugins.

=item B<--clean>

Delete submited packages upon successfull submission.

=item B<--verbose>

Produce more verbose output (can be used more than once)

=item B<--test>

Don't perform any modification.

=item B<--list> I<category>

List available items from given category and exits. Category must be either
B<targets>, B<actions> or B<tests>. A target is needed for the two last ones.

=item B<--help> I<category>

Display help for given category and exits. Category must be either
B<repository>, B<action> or B<test>. An item is needed for the two last ones.
If no category given, display standard help.

=back

=head1 CONFIGURATION

Configuration is read from the first file found among:

=over

=item * the one specified by B<--config> option on command-line

=item * $HOME/.youri/submit.conf

=item * /etc/youri/submit.conf

=back

The configuration file should be a YAML-format files, with the following
mandatory top-level directives:

=over

=item B<repository>

The definition of repository plugin to be used.

=item B<targets>

The list of available submission targets, each one being composed from the
following keys:

=over

=item B<tests>

The list of test plugins to use for this target.

=item B<actions>

The list of action plugins to use for this target.

=back

=item B<tests>

The list of test plugin definitions, indexed by their identity.

=item B<actions>

The list of action plugin definitions, indexed by their identity.

=back

=head1 SEE ALSO

Youri::Config, for additional details about configuration file format.

Each used plugin man page, for available options.

=head1 COPYRIGHT AND LICENSE

Copyright (C) 2002-2006, YOURI project

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

=cut

use strict;
use warnings;
use lib '/usr/share/youri/lib';

use Youri::Config;
use Youri::Utils 0.002;
use Pod::Usage;

my $config = Youri::Config->new(
    args => {
        'skip-test'  => '=s@',
        'skip-action' => '=s@',
        'define'      => '=s%',
        'timestamp'   => '!',
        'clean'       => '!',
        'test'        => '|t!',
        'list'        => '|l!'
    },
    directories => [ "$ENV{HOME}/.youri", '/etc/youri' ],
    file => 'submit.conf',
);

if ($config->get_arg('list')) {
    my $category = $ARGV[0];
    pod2usage(-verbose => 0, -message => "No category specified, aborting\n")
        unless $category;
    if ($category eq 'targets') { 
        print join(' ', keys %{$config->get_param('targets')});
    } elsif ($category eq 'tests' || $category eq 'actions') {
        my $target = $ARGV[1];
        pod2usage(-verbose => 0, -message => "No target specified, aborting\n")
            unless $target;
        if ($category eq 'tests') {
            my $tests = $config->get_param('targets')->{$target}->{tests};
            print join(' ', @{$tests}) if $tests;
        } else {
            my $actions = $config->get_param('targets')->{$target}->{actions};
            print join(' ', @{$actions}) if $actions;
        }
    } else {
        pod2usage(-verbose => 0, -message => "Invalid category $category, aborting\n")
    }
    print "\n";
    exit 0;
}

if ($config->get_arg('help')) {
    my $category = $ARGV[0];
    my ($item, $section);
    if ($category eq 'repository') { 
        $section = $config->get_param('repository');
        pod2usage(
            -verbose => 0,
            -message => "No repository defined, aborting\n"
        ) unless $section;
    } elsif ($category eq 'test' || $category eq 'action') {
        $item = $ARGV[1];
        pod2usage(
            -verbose => 0,
            -message => "No item specified, aborting\n"
        ) unless $item;
        if ($category eq 'test') {
            $section = $config->get_param('tests')->{$item};
            pod2usage(
                -verbose => 0,
                -message => "No such test $item defined, aborting\n"
            ) unless $section;
        } else  {
            $section = $config->get_param('actions')->{$item};
            pod2usage(
                -verbose => 0,
                -message => "No such action $item defined, aborting\n"
            ) unless $section;
        }
    } else {
        pod2usage(-verbose => 0, -message => "Invalid category $category, aborting\n")
    }
    my $file = $section->{class} . '.pm';
    $file =~ s/::/\//g;
    pod2usage(
        -verbose  => 99,
        -sections => 'NAME|DESCRIPTION',
        -input    => $file,
        -pathlist => \@INC
    );
}


pod2usage(-verbose => 0, -message => "No target specified, aborting\n")
    unless @ARGV > 0;
pod2usage(-verbose => 0, -message => "No packages specified, aborting\n")
    unless @ARGV > 1;

# convenient global flags
my $test    = $config->get_arg('test');
my $verbose = $config->get_arg('verbose');
my $timestamp = $config->get_arg('timestamp');

# test target
my $target = shift @ARGV;
my $target_conf = $config->get_param('targets')->{$target};

# create repository
my $repository;
my $repository_conf = $config->get_param('repository');
die "No repository declared\n" unless $repository_conf;
log_message("Creating repository", $timestamp) if $verbose;
eval {
    $repository = create_instance(
        'Youri::Repository',
        $repository_conf,
        {
            test    => $test,
            verbose => $verbose > 0 ? $verbose - 1 : 0,
            targets => [ keys %{$config->get_param('targets')} ],
        }
    );
};
die "Failed to create repository: $@\n" if $@;

# create packages
my @packages;
foreach my $file (@ARGV) {
    push(
        @packages,
        create_instance(
            'Youri::Package',
            {
                class => $repository->get_package_class(),
            },
            {
                file => $file
            }
        )
    );
}

# check all packages pass all tests
my %errors;
my $skip_test = $config->get_arg('skip-test');
my %skip_test = $skip_test ?  map { $_ => 1 } @{$skip_test} : ();
eval {
    foreach my $id (@{$target_conf->{tests}}) {
        next if $skip_test{$id};
        my $test_conf = $config->get_param('tests')->{$id};

        die "Invalid configuration, test $id is not defined\n"
            unless $test_conf;

        log_message("Creating test $id", $timestamp) if $verbose;
        my $test;
        $test = create_instance(
            'Youri::Submit::Test',
            $test_conf,
            {
                id       => $id,
                test     => $test,
                verbose  => $verbose > 0 ? $verbose - 1 : 0,
            }
        );

        foreach my $package (@packages) {
            log_message("running test $id on package $package", $timestamp)
                if $verbose;
            my @errors = $test->run(
                $package,
                $repository,
                $target,
                $config->get_arg('define')
            );
            push(@{$errors{$package}}, @errors) if @errors;
        }
    }
};
if ($@) {
    die "Error during tests execution: $@\n";
}
if (%errors) {
    print "Submission errors, aborting:\n";
    foreach my $package (keys %errors) {
        print "- $package:\n";
        foreach my $error (@{$errors{$package}}) {
            print " - $error\n";
        }
    }
    exit(1);
}

# proceed further
my $skip_action = $config->get_arg('skip-action');
my %skip_action = $skip_action ?  map { $_ => 1 } @{$skip_action} : ();

eval {
    foreach my $id (@{$target_conf->{actions}}) {
        next if $skip_action{$id};

        my $action_conf = $config->get_param('actions')->{$id};

        die "Invalid configuration, action $id is not defined\n"
            unless $action_conf;

        log_message("creating action $id", $timestamp) if $verbose;
        my $action = create_instance(
            'Youri::Submit::Action',
             $action_conf,
            {
                id       => $id,
                test     => $test,
                verbose  => $verbose > 0 ? $verbose - 1 : 0,
            }
        );

        foreach my $package (@packages) {
            log_message("running action $id on package $package", $timestamp)
                if $verbose;
            $action->run(
                $package,
                $repository,
                $target,
                $config->get_arg('define')
            );
        }
    }
};
if ($@) {
    die "Error during actions execution: $@\n";
}

if ($config->get_arg('clean')) {
    foreach my $package (@packages) {
        log_message("cleaning file $package", $timestamp) if $verbose;
        unlink $package->as_file();
    }
}
