### ----------------------------------------------------
### -- Needle::Compile
### -- Licenses: Artistic-2.0
### -- Authors: Elizabeth Mattijsen
### -- File: t/02-single.rakutest
### ----------------------------------------------------
BEGIN %*ENV<RAKU_TEST_DIE_ON_FAIL> = 1;

use Test;
use Needle::Compile <compile-needle implicit2explicit Type>;

plan 10;

code '.subst("foo", "bar")', "foo", "bar", 'simple .subst';
code '.uc',                  "foo", "FOO", 'simple .uc';
code '$*_ eq $_',            "foo", True,  'check availability of $*_';

code 'is $_, 42', 42, True, 'check loading of Test module', :module<Test>;

run-nameds &contains, "foo", "foo", "bar", "find simple 'foo'";

run-nameds &starts-with, "foo", "foo", "bar", "starts with simple 'foo'";

run-nameds &ends-with, "foo", "foo", "bar", "ends with simple 'foo'";

run-nameds &equal, "foo", "foo", "bar", "equal simple 'foo'";

run-nameds &words, "foo", "foo", "bar", "words simple 'foo'";

run-nameds &regex, "foo", "foo", ":;!", "regex simple 'foo'";

#-------------------------------------------------------------------------------
# Infrastructure

# Filter out the targets that are pure strings
my sub autonots(@targets) {
    @targets.map: { "!$_" if .WHAT =:= Str }
}

# Filter out the targets that can have matches
my sub matches(@targets) {
    @targets.map: {
        my $type := ($_ ~~ Pair ?? $_ !! implicit2explicit($_)).key;
        $_ if $type eq 'contains'
                     | 'starts-with'
                     | 'ends-with'
                     | 'words'
                     | 'regex'
    }
}

# Compile a needle for the given pattern and do basic sanity test
sub needle($_, %_) is test-assertion {
    my $needle := compile-needle($_, |%_);
    isa-ok $needle, Callable, "Testing $_.raku()";
    $needle
}

# Run the given needle specification for all possible named arguments
INIT my @nameds = (), :ignorecase, :ignoremark, :smartcase, :smartmark;
my sub run-nameds(&code, |c) is test-assertion {
    subtest "all named arguments for &code.name()" => {
        plan +@nameds;
        code |c, |$_ for @nameds;
    }
}

#- code ------------------------------------------------------------------------
# All tests for code that aren't tested elsewhere
my sub code($target, $source, $result, $comment, *%_) is test-assertion {
    subtest "code: $comment" => {
        my @targets =
          $target but Type("code"),
          "\{$target}",
          code => $target,
        ;
        @targets.push("*$target") if $target.starts-with(".");

        plan (2 + ?%_) * @targets;

        for @targets {
            my $needle := needle($_, %_);
            is-deeply $needle($source), $result, "'$source' transformed ok";
        }
    }
}

#- contains --------------------------------------------------------------------
# All possible tests for contains
my sub contains($target, $hit, $miss, $comment, *%_) {
    my $named := %_ ?? ", :%_.keys.head()" !! "";

    subtest "contains: $comment$named$named" => {
        my $ascode := ".contains('$target')";

        my @targets =
          $target,
          $target but Type("auto"),
          $target but Type("contains"),
          $target but Type("regex"),
          $ascode but Type("code"),
          "/$target/",
          "\{$ascode}",
          "*$ascode",
          auto     => $target,
          contains => $target,
          regex    => $target,
          code     => $ascode,
        ;

        my @autonots = autonots @targets;
        my @matches  = matches  @targets;

        plan 12 * @targets + 4 * @autonots + 4 * @matches;

        for @targets {
            my $needle := needle($_, %_);
            is-deeply $needle($hit),          True,  "hit '$hit'";
            is-deeply $needle("in{$hit}out"), True,  "hit 'in{$hit}out'";
            is-deeply $needle($miss),         False, "miss '$miss'";

            my $not := needle("not" => $_, %_);
            is-deeply $not($hit),          False, "not hit '$hit'";
            is-deeply $not("in{$hit}out"), False, "not hit 'in{$hit}out'";
            is-deeply $not($miss),         True,  "not miss '$miss'";

            my $and := needle("and" => $_, %_);
            is-deeply $and($hit),          True,  "and hit '$hit'";
            is-deeply $and("in{$hit}out"), True,  "and hit 'in{$hit}out'";
            is-deeply $and($miss),         False, "and miss '$miss'";
        }

        for @autonots {
            my $not := needle($_, %_);
            is-deeply $not($hit),          False, "! hit '$hit'";
            is-deeply $not("in{$hit}out"), False, "! hit 'in{$hit}out'";
            is-deeply $not($miss),         True,  "! miss '$miss'";
        }

        for @matches {
            my $needle := needle($_, %(:matches, |%_));
            is-deeply $needle($hit),          slip($hit,), "returned '$hit'";
            is-deeply $needle("in{$hit}out"), slip($hit,), "returned '$hit'";
            is-deeply $needle($miss),         False,       "miss '$miss'";
        }
    }
}

#- starts-with -----------------------------------------------------------------
# All possible tests for starts-with
my sub starts-with($target, $hit, $miss, $comment, *%_) is test-assertion {
    my $named := %_ ?? ", :%_.keys.head()" !! "";

    subtest "starts-with: $comment$named" => {
        my $auto   := "^$target";
        my $ascode := ".starts-with('$target')";

        my @targets =
          $target but Type("starts-with"),
          $auto,
          $auto   but Type("auto"),
          $auto   but Type("regex"),
          $ascode but Type("code"),
          "/^ $target/",
          "\{$ascode}",
          "*$ascode",
          starts-with => $target,
          auto        => $auto,
          regex       => $auto,
          code        => $ascode,
        ;

        my @autonots = autonots @targets;
        my @matches  = matches  @targets;

        plan 12 * @targets + 4 * @autonots + 4 * @matches;

        for @targets {
            my $needle := needle($_, %_);
            is-deeply $needle($hit),    True,  "hit '$hit'";
            is-deeply $needle(" $hit"), False, "miss ' $hit'";
            is-deeply $needle($miss),   False, "miss '$miss'";

            my $not := needle("not" => $_, %_);
            is-deeply $not($hit),    False, "not hit '$hit'";
            is-deeply $not(" $hit"), True,  "not miss ' $hit'";
            is-deeply $not($miss),   True,  "not miss '$miss'";

            my $and := needle("and" => $_, %_);
            is-deeply $and($hit),    True,  "and hit '$hit'";
            is-deeply $and(" $hit"), False, "and miss ' $hit'";
            is-deeply $and($miss),   False, "and miss '$miss'";
        }

        for @autonots {
            my $not := needle($_, %_);
            is-deeply $not($hit),    False, "! hit '$hit'";
            is-deeply $not(" $hit"), True,  "! miss ' $hit'";
            is-deeply $not($miss),   True,  "! miss '$miss'";
        }

        for @matches {
            my $needle := needle($_, %(:matches, |%_));
            is-deeply $needle($hit),    slip($hit,), "returned '$hit'";
            is-deeply $needle(" $hit"), False,       "miss '$hit'";
            is-deeply $needle($miss),   False,       "miss '$miss'";
        }
    }
}

#- ends-with -------------------------------------------------------------------
# All possible tests for ends-with
my sub ends-with($target, $hit, $miss, $comment, *%_) is test-assertion {
    my $named := %_ ?? ", :%_.keys.head()" !! "";

    subtest "ends-with: $comment$named" => {
        my $auto   := $target ~ '$';
        my $ascode := ".ends-with('$target')";

        my @targets =
          $target but Type("ends-with"),
          $auto,
          $auto   but Type("auto"),
          $auto   but Type("regex"),
          $ascode but Type("code"),
          "/$target \$/",
          "\{$ascode}",
          "*$ascode",
          ends-with => $target,
          auto      => $auto,
          code      => $ascode,
          regex     => $auto,
        ;

        my @autonots = autonots @targets;
        my @matches  = matches  @targets;

        plan 12 * @targets + 4 * @autonots + 4 * @matches;

        for @targets {
            my $needle := needle($_, %_);
            is-deeply $needle($hit),    True,  "hit '$hit'";
            is-deeply $needle("$hit "), False, "miss '$hit '";
            is-deeply $needle($miss),   False, "miss '$miss'";

            my $not := needle("not" => $_, %_);
            is-deeply $not($hit),    False, "not hit '$hit'";
            is-deeply $not("$hit "), True,  "not miss '$hit '";
            is-deeply $not($miss),   True,  "not miss '$miss'";

            my $and := needle("and" => $_, %_);
            is-deeply $and($hit),    True,  "and hit '$hit'";
            is-deeply $and("$hit "), False, "and miss '$hit '";
            is-deeply $and($miss),   False, "and miss '$miss'";
        }

        for @autonots {
            my $not := needle($_, %_);
            is-deeply $not($hit),    False, "! hit '$hit'";
            is-deeply $not("$hit "), True,  "! miss '$hit '";
            is-deeply $not($miss),   True,  "! miss '$miss'";
        }

        for @matches {
            my $needle := needle($_, %(:matches, |%_));
            is-deeply $needle($hit),    slip($hit,), "returned '$hit'";
            is-deeply $needle("$hit "), False,       "miss '$hit'";
            is-deeply $needle($miss),   False,       "miss '$miss'";
        }
    }
}

#- equal -----------------------------------------------------------------------
# All possible tests for equal
my sub equal($target, $hit, $miss, $comment, *%_) is test-assertion {
    my $named := %_ ?? ", :%_.keys.head()" !! "";

    subtest "equal: $comment$named" => {
        my $auto   := "^$target\$";
        my $ascode := "'$target' eq \$_";

        my @targets =
          $target but Type("equal"),
          $auto,
          $auto   but Type("auto"),
          $ascode but Type("code"),
          "/^ $target \$/",
          "\{$ascode}",
          auto  => $auto,
          regex => $auto,
          code  => $ascode,
        ;

        my @autonots = autonots @targets;
        my @matches  = matches  @targets;

        plan 12 * @targets + 4 * @autonots + 4 * @matches;

        for @targets {
            my $needle := needle($_, %_);
            is-deeply $needle($hit),     True,  "hit '$hit'";
            is-deeply $needle(" $hit "), False, "miss ' $hit '";
            is-deeply $needle($miss),    False, "miss '$miss'";

            my $not := needle("not" => $_, %_);
            is-deeply $not($hit),     False, "not hit '$hit'";
            is-deeply $not(" $hit "), True,  "not miss ' $hit '";
            is-deeply $not($miss),    True,  "not miss '$miss'";

            my $and := needle("and" => $_, %_);
            is-deeply $and($hit),     True,  "and hit '$hit'";
            is-deeply $and(" $hit "), False, "and miss ' $hit '";
            is-deeply $and($miss),    False, "and miss '$miss'";
        }

        for @autonots {
            my $not := needle($_, %_);
            is-deeply $not($hit),     False, "! hit '$hit'";
            is-deeply $not(" $hit "), True,  "! miss ' $hit '";
            is-deeply $not($miss),    True,  "! miss '$miss'";
        }

        for @matches {
            my $needle := needle($_, %(:matches, |%_));
            is-deeply $needle($hit),     slip($hit,), "returned '$hit'";
            is-deeply $needle(" $hit "), False,       "miss '$hit'";
            is-deeply $needle($miss),    False,       "miss '$miss'";
        }
    }
}

#- words -----------------------------------------------------------------------
# All possible tests for words
my sub words($target, $hit, $miss, $comment, *%_) is test-assertion {
    my $named := %_ ?? ", :%_.keys.head()" !! "";

    subtest "words: $comment$named" => {
        my $auto := "§$target";

        my @targets =
          $target but Type("words"),
          $auto,
          $auto   but Type("auto"),
          words => $target,
          auto  => $auto,
        ;

        my @autonots = autonots @targets;
        my @matches  = matches  @targets;

        plan 18 * @targets + 6 * @autonots + 6 * @matches;

        for @targets {
            my $needle := needle($_, %_);
            is-deeply $needle($hit),         True,  "hit '$hit'";
            is-deeply $needle(" $hit "),     True,  "hit ' $hit '";
            is-deeply $needle(":$hit:"),     True,  "hit ':$hit:'";
            is-deeply $needle("oo{$hit}ff"), False, "miss 'oo{$hit}ff'";
            is-deeply $needle($miss),        False, "miss '$miss'";

            my $not := needle("not" => $_, %_ );
            is-deeply $not($hit),         False, "not hit '$hit'";
            is-deeply $not(" $hit "),     False, "not hit ' $hit '";
            is-deeply $not(":$hit:"),     False, "not hit ':$hit:'";
            is-deeply $not("oo{$hit}ff"), True,  "not miss 'oo{$hit}ff'";
            is-deeply $not($miss),        True,  "not miss '$miss'";

            my $and := needle("and" => $_, %_);
            is-deeply $and($hit),         True,  "and hit '$hit'";
            is-deeply $and(" $hit "),     True,  "and hit ' $hit '";
            is-deeply $and(":$hit:"),     True,  "and hit ':$hit:'";
            is-deeply $and("oo{$hit}ff"), False, "and miss 'oo{$hit}ff'";
            is-deeply $and($miss),        False, "and miss '$miss'";
        }

        for @autonots {
            my $not := needle($_, %_);
            is-deeply $not($hit),         False, "! hit '$hit'";
            is-deeply $not(" $hit "),     False, "! hit ' $hit '";
            is-deeply $not(":$hit:"),     False, "! hit ':$hit:'";
            is-deeply $not("oo{$hit}ff"), True,  "! miss 'oo{$hit}ff'";
            is-deeply $not($miss),        True,  "! miss '$miss'";
        }

        for @matches {
            my $needle := needle($_, %(:matches, |%_));
            is-deeply $needle($hit),         slip($hit,), "returned '$hit'";
            is-deeply $needle(" $hit "),     slip($hit,), "returned ' $hit '";
            is-deeply $needle(":$hit:"),     slip($hit,), "returned ':$hit:'";
            is-deeply $needle("oo{$hit}ff"), False,       "miss 'oo{$hit}ff'";
            is-deeply $needle($miss),        False,       "miss '$miss'";
        }
    }
}

#- regex -----------------------------------------------------------------------
# All possible tests for regex
my sub regex($target, $hit, $miss, $comment, *%_) is test-assertion {
    my $named := %_ ?? ", :%_.keys.head()" !! "";

    subtest "regex: $comment$named" => {
        my $auto := "/ $target /";

        my @targets =
          $target but Type("regex"),
          $auto,
          $auto   but Type("auto"),
          regex => $target,
          auto  => $auto,
        ;

        my @autonots = autonots @targets;
        my @matches  = matches  @targets;

        plan 12 * @targets + 4 * @autonots + 4 * @matches;

        for @targets {
            my $needle := needle($_, %_);
            is-deeply $needle($hit),     True,  "hit '$hit'";
            is-deeply $needle(" $hit "), True,  "hit ' $hit '";
            is-deeply $needle($miss),    False, "miss '$miss'";

            my $not := needle("not" => $_, %_ );
            is-deeply $not($hit),     False, "not hit '$hit'";
            is-deeply $not(" $hit "), False, "not hit ' $hit '";
            is-deeply $not($miss),    True,  "not miss '$miss'";

            my $and := needle("and" => $_, %_);
            is-deeply $and($hit),     True,  "and hit '$hit'";
            is-deeply $and(" $hit "), True,  "and hit ' $hit '";
            is-deeply $and($miss),    False, "and miss '$miss'";
        }

        for @autonots {
            my $not := needle($_, %_);
            is-deeply $not($hit),     False, "! hit '$hit'";
            is-deeply $not(" $hit "), False, "! hit ' $hit '";
            is-deeply $not($miss),    True,  "! miss '$miss'";
        }

        for @matches {
            my $needle := needle($_, %(:matches, |%_));
            is-deeply $needle($hit),     slip($hit,), "returned '$hit'";
            is-deeply $needle(" $hit "), slip($hit,), "returned '$hit'";
            is-deeply $needle($miss),    False,       "miss '$miss'";
        }
    }
}

# vim: expandtab shiftwidth=4


