The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.
#!perl -T

use strict;
use warnings;
use lib 't';
use Util;

use Test::More tests => 37;

use Carp qw(croak);
use File::Temp;

use App::Ack::Filter::Default;
use App::Ack::ConfigLoader;

delete @ENV{qw( PAGER ACK_PAGER ACK_PAGER_COLOR ACK_OPTIONS )};

my %defaults = (
    'break'                   => undef,
    color                     => undef,
    column                    => undef,
    count                     => undef,
    dont_report_bad_filenames => undef,
    f                         => undef,
    files_from                => undef,
    filters                   => [ App::Ack::Filter::Default->new ],
    flush                     => undef,
    follow                    => undef,
    g                         => undef,
    h                         => undef,
    H                         => undef,
    heading                   => undef,
    i                         => undef,
    l                         => undef,
    L                         => undef,
    m                         => undef,
    n                         => undef,
    output                    => undef,
    pager                     => undef,
    passthru                  => undef,
    print0                    => undef,
    Q                         => undef,
    regex                     => undef,
    show_types                => undef,
    smart_case                => undef,
    sort_files                => undef,
    v                         => undef,
    w                         => undef,
);

test_loader(
    expected_opts    => { %defaults },
    expected_targets => [],
    'empty inputs should result in default outputs'
);

# --after_context, --before_context
for my $option ( qw( after_context before_context ) ) {
    my $long_arg = $option;
    $long_arg =~ s/_/-/ or die;

    test_loader(
        argv             => [ "--$long_arg=15" ],
        expected_opts    => { %defaults, $option => 15 },
        expected_targets => [],
        "--$long_arg=15 should set $option to 15",
    );

    test_loader(
        argv             => [ "--$long_arg=0" ],
        expected_opts    => { %defaults, $option => 0 },
        expected_targets => [],
        "--$long_arg=0 should set $option to 0",
    );

    test_loader(
        argv             => [ "--$long_arg" ],
        expected_opts    => { %defaults, $option => 2 },
        expected_targets => [],
        "--$long_arg without a value should default $option to 2",
    );

    test_loader(
        argv             => [ "--$long_arg=-43" ],
        expected_opts    => { %defaults, $option => 2 },
        expected_targets => [],
        "--$long_arg with a negative value should default $option to 2",
    );

    my $short_arg = '-' . uc substr( $option, 0, 1 );
    test_loader(
        argv             => [ $short_arg, 15 ],
        expected_opts    => { %defaults, $option => 15 },
        expected_targets => [],
        "$short_arg 15 should set $option to 15",
    );

    test_loader(
        argv             => [ $short_arg, 0 ],
        expected_opts    => { %defaults, $option => 0 },
        expected_targets => [],
        "$short_arg 0 should set $option to 0",
    );

    test_loader(
        argv             => [ $short_arg ],
        expected_opts    => { %defaults, $option => 2 },
        expected_targets => [],
        "$short_arg without a value should default $option to 2",
    );

    test_loader(
        argv             => [ $short_arg, '-43' ],
        expected_opts    => { %defaults, $option => 2 },
        expected_targets => [],
        "$short_arg with a negative value should default $option to 2",
    );
}

test_loader(
    argv             => ['-C', 5],
    expected_opts    => { %defaults, after_context => 5, before_context => 5 },
    expected_targets => [],
    '-C sets both before_context and after_context'
);

test_loader(
    argv             => ['-C'],
    expected_opts    => { %defaults, after_context => 2, before_context => 2 },
    expected_targets => [],
    '-C sets both before_context and after_context, with default'
);

test_loader(
    argv             => ['-C', 0],
    expected_opts    => { %defaults, after_context => 0, before_context => 0 },
    expected_targets => [],
    '-C sets both before_context and after_context, with zero overriding default'
);

test_loader(
    argv             => ['-C', -43],
    expected_opts    => { %defaults, after_context => 2, before_context => 2 },
    expected_targets => [],
    '-C with invalid value sets both before_context and after_context to default'
);

test_loader(
    argv             => ['--context=5'],
    expected_opts    => { %defaults, after_context => 5, before_context => 5 },
    expected_targets => [],
    '--context sets both before_context and after_context'
);

test_loader(
    argv             => ['--context'],
    expected_opts    => { %defaults, after_context => 2, before_context => 2 },
    expected_targets => [],
    '--context sets both before_context and after_context, with default'
);

test_loader(
    argv             => ['--context=0'],
    expected_opts    => { %defaults, after_context => 0, before_context => 0 },
    expected_targets => [],
    '--context sets both before_context and after_context, with zero overriding default'
);

test_loader(
    argv             => ['--context=-43'],
    expected_opts    => { %defaults, after_context => 2, before_context => 2 },
    expected_targets => [],
    '--context with invalid value sets both before_context and after_context to default'
);

# XXX These tests should all be replicated to work off of the ack command line
#     tools instead of its internal APIs!
do {
    local $ENV{'ACK_PAGER'} = './test-pager --skip=2';

    test_loader(
        argv             => [],
        expected_opts    => { %defaults, pager => './test-pager --skip=2' },
        expected_targets => [],
        'ACK_PAGER should set the default pager',
    );

    test_loader(
        argv             => ['--pager=./test-pager'],
        expected_opts    => { %defaults, pager => './test-pager' },
        expected_targets => [],
        '--pager should override ACK_PAGER',
    );

    test_loader(
        argv             => ['--nopager'],
        expected_opts    => { %defaults },
        expected_targets => [],
        '--nopager should suppress ACK_PAGER',
    );
};

do {
    local $ENV{'ACK_PAGER_COLOR'} = './test-pager --skip=2';

    test_loader(
        argv             => [],
        expected_opts    => { %defaults, pager => './test-pager --skip=2' },
        expected_targets => [],
        'ACK_PAGER_COLOR should set the default pager',
    );

    test_loader(
        argv             => ['--pager=./test-pager'],
        expected_opts    => { %defaults, pager => './test-pager' },
        expected_targets => [],
        '--pager should override ACK_PAGER_COLOR',
    );

    test_loader(
        argv             => ['--nopager'],
        expected_opts    => { %defaults },
        expected_targets => [],
        '--nopager should suppress ACK_PAGER_COLOR',
    );

    local $ENV{'ACK_PAGER'} = './test-pager --skip=3';

    test_loader(
        argv             => [],
        expected_opts    => { %defaults, pager => './test-pager --skip=2' },
        expected_targets => [],
        'ACK_PAGER_COLOR should override ACK_PAGER',
    );

    test_loader(
        argv             => ['--pager=./test-pager'],
        expected_opts    => { %defaults, pager => './test-pager' },
        expected_targets => [],
        '--pager should override ACK_PAGER_COLOR and ACK_PAGER',
    );

    test_loader(
        argv             => ['--nopager'],
        expected_opts    => { %defaults },
        expected_targets => [],
        '--nopager should suppress ACK_PAGER_COLOR and ACK_PAGER',
    );
};

do {
    local $ENV{'PAGER'} = './test-pager';

    test_loader(
        argv             => [],
        expected_opts    => { %defaults },
        expected_targets => [],
        q{PAGER doesn't affect ack by default},
    );

    test_loader(
        argv             => ['--pager'],
        expected_opts    => { %defaults, pager => './test-pager' },
        expected_targets => [],
        'PAGER is used if --pager is specified with no argument',
    );

    test_loader(
        argv             => ['--pager=./test-pager --skip=2'],
        expected_opts    => { %defaults, pager => './test-pager --skip=2' },
        expected_targets => [],
        'PAGER is not used if --pager is specified with an argument',
    );

    # XXX what if --pager is specified but PAGER isn't set?
};

done_testing;


sub test_loader {
    local $Test::Builder::Level = $Test::Builder::Level + 1;

    die 'Must pass key/value pairs, plus a message at the end' unless @_ % 2 == 1;

    my $msg  = pop;
    my %opts = @_;

    return subtest "test_loader( $msg )" => sub {
        plan tests => 2;

        my ( $env, $argv, $expected_opts, $expected_targets ) =
            delete @opts{qw/env argv expected_opts expected_targets/};

        $env  = '' unless defined $env;
        $argv = [] unless defined $argv;

        my @files = map {
            $opts{$_}
        } sort {
            my ( $a_end ) = $a =~ /(\d+)/;
            my ( $b_end ) = $b =~ /(\d+)/;

            $a_end <=> $b_end;
        } grep { /^file\d+/ } keys %opts;
        foreach my $contents (@files) {
            my $file = File::Temp->new;
            print {$file} $contents;
            close $file or die $!;
        }

        my ( $got_opts, $got_targets );

        do {
            local $ENV{'ACK_OPTIONS'} = $env;
            local @ARGV;

            my @arg_sources = (
                { name => 'ARGV', contents => $argv },
                map { +{ name => $_, contents => scalar read_file($_) } } @files,
            );

            $got_opts    = App::Ack::ConfigLoader::process_args( @arg_sources );
            $got_targets = [ @ARGV ];
        };

        is_deeply( $got_opts, $expected_opts, 'Options match' );
        is_deeply( $got_targets, $expected_targets, 'Targets match' );
    };
}