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 Test::More;

use Template::Sandbox;
use Test::Exception;

plan tests => 41;

#  TODO:  Surely there's a Test:: module for this?
#         Test::Trap looks to clash with Test::Exception and not old perls.
sub warns_ok( &$$ )
{
    my ( $test, $like, $desc ) = @_;
    my ( $warning_contents );

    {
        $warning_contents = '';
        local $SIG{ __WARN__ } = sub { $warning_contents .= $_[ 0 ]; };
        $test->();
    }

    like( $warning_contents, $like, $desc );
}
sub doesnt_warn( &$ )
{
    my ( $test, $desc ) = @_;
    my ( $warning_contents );

    {
        $warning_contents = '';
        local $SIG{ __WARN__ } = sub { $warning_contents .= $_[ 0 ]; };
        $test->();
    }

    is( $warning_contents, '', $desc );
}

my ( $template, $pre_template, $post_template, $token, $syntax );

$token = "nonexistingtoken";
$syntax = "<: $token :>";

#
#  1:  Test that the custom token really doesn't exist and causes a fail.
$template = Template::Sandbox->new();
throws_ok { $template->set_template_string( $syntax ) }
    qr/compile error: unrecognised token \(<: nonexistingtoken :>\) at line 1, char 1 of/,
    "verify custom syntax doesn't exist already";

#
#  2-4:  Syntax added during construction.
ok( $template = Template::Sandbox->new(
    template_syntax => [
        $token => {
            compile => sub { [] },
            run     => sub { '[during-construction custom syntax was ere]' },
            },
        ],
    ), 'construct with custom syntax as option' );
lives_ok { $template->set_template_string( $syntax ) }
    'parse with during-construction custom syntax';
is( ${$template->run()}, '[during-construction custom syntax was ere]',
    'run of during-construction custom syntax' );


#
#  5-7:  Syntax added after construction.
$template = Template::Sandbox->new();
lives_ok { $template->register_template_syntax(
    $token => {
        compile => sub { [] },
        run     => sub { '[post-construction custom syntax was ere]' },
        },
    ) } 'post-construct register of custom syntax';
lives_ok { $template->set_template_string( $syntax ) }
    'parse with post-construction registered custom syntax';
is( ${$template->run()}, '[post-construction custom syntax was ere]',
    'run of post-construction registered custom syntax' );

#
#  8-10: Check add_template_syntax synonym.
$template = Template::Sandbox->new();
lives_ok { $template->add_template_syntax(
    $token => {
        compile => sub { [] },
        run     => sub { '[post-construction custom syntax was ere]' },
        },
    ) } 'post-construct add of custom syntax (method synonym)';
lives_ok { $template->set_template_string( $syntax ) }
    'parse with post-construction added custom syntax';
is( ${$template->run()}, '[post-construction custom syntax was ere]',
    'run of post-construction added custom syntax' );

#
#  11-12:  Syntax unregister.
$template = Template::Sandbox->new(
    template_syntax => [
        $token => {
            compile => sub { [] },
            run     => sub { '[during-construction custom syntax was ere]' },
            },
        ],
    );
lives_ok { $template->unregister_template_syntax( $token ) }
    'unregister custom syntax';
throws_ok { $template->set_template_string( $syntax ) }
    qr/compile error: unrecognised token \(<: nonexistingtoken :>\) at line 1, char 1 of/,
    "verify custom syntax no-longer exists";

#
#  13-14:  Syntax delete synonym.
$template = Template::Sandbox->new(
    template_syntax => [
        $token => {
            compile => sub { [] },
            run     => sub { '[during-construction custom syntax was ere]' },
            },
        ],
    );
lives_ok { $template->delete_template_syntax( $token ) }
    'delete custom syntax (method synonym)';
throws_ok { $template->set_template_string( $syntax ) }
    qr/compile error: unrecognised token \(<: nonexistingtoken :>\) at line 1, char 1 of/,
    "verify custom syntax no-longer exists";

#
#  15:  Syntax args.
$template = Template::Sandbox->new(
    template_syntax => [
        $token => {
            compile => sub
                {
                    my ( $template, $token, $pos, $args ) = @_;

                    return( $args );
                },
            run     => sub
                {
                    my ( $template, $token, $args ) = @_;
                    my ( $content );

                    $content = '';

                    $content .= " name=$args->{ name }"
                        if $args and exists( $args->{ name } );

                    $content .= " id=$args->{ id }"
                        if $args and exists( $args->{ id } );

                    $content = substr( $content, 1 ) if $content;
                    return( "[$content]" );
                },
            },
        ],
    );
$syntax = "<: $token :><: $token name=fred :><: $token id=12 :><: $token name=joe id=42 :><: $token name=\"long name\" id=15 :><: $token :>";
$template->set_template_string( $syntax );
is( ${$template->run()},
    '[][name=fred][id=12][name=joe id=42][name=long name id=15][]',
    'custom syntax args' );
#  Restore test string to old one.
$syntax = "<: $token :>";

#
#  16-17:  Are constructor-added instance-syntaxes local to that instance?
$pre_template = Template::Sandbox->new();
$template = Template::Sandbox->new(
    template_syntax => [
        $token => {
            compile => sub { [] },
            run     => sub { '[during-construction custom syntax was ere]' },
            },
        ],
    );
$post_template = Template::Sandbox->new();
throws_ok { $pre_template->set_template_string( $syntax ) }
    qr/compile error: unrecognised token \(<: nonexistingtoken :>\) at line 1, char 1 of/,
    "construct-option instance-syntax doesn't exist in existing instances";
throws_ok { $post_template->set_template_string( $syntax ) }
    qr/compile error: unrecognised token \(<: nonexistingtoken :>\) at line 1, char 1 of/,
    "construct-option instance-syntax doesn't exist in new instances";

#
#  18-19:  Are post-construct instance-syntaxes local to that instance?
$pre_template = Template::Sandbox->new();
$template = Template::Sandbox->new();
$template->register_template_syntax(
    $token => {
        compile => sub { [] },
        run     => sub { '[post-construction custom syntax was ere]' },
        },
    );
$post_template = Template::Sandbox->new();
throws_ok { $pre_template->set_template_string( $syntax ) }
    qr/compile error: unrecognised token \(<: nonexistingtoken :>\) at line 1, char 1 of/,
    "post-construct instance-syntax doesn't exist in existing instances";
throws_ok { $post_template->set_template_string( $syntax ) }
    qr/compile error: unrecognised token \(<: nonexistingtoken :>\) at line 1, char 1 of/,
    "post-construct instance-syntax doesn't exist in new instances";

#
#  20-26:  Class syntax addition.
$pre_template = Template::Sandbox->new();
lives_ok { Template::Sandbox->register_template_syntax(
    $token => {
        compile => sub { [] },
        run     => sub { '[class-method custom syntax was ere]' },
        },
    ) } 'class-method register of custom syntax';
$post_template = Template::Sandbox->new();
lives_ok { $pre_template->set_template_string( $syntax ) }
    'existing template parse with class-method registered custom syntax';
is( ${$pre_template->run()}, '[class-method custom syntax was ere]',
    'existing template run of class-method registered custom syntax' );
lives_ok { $post_template->set_template_string( $syntax ) }
    'new template parse with class-method registered custom syntax';
is( ${$post_template->run()}, '[class-method custom syntax was ere]',
    'new template run of class-method registered custom syntax' );
lives_ok { Template::Sandbox->unregister_template_syntax( $token ) }
    'class-method unregister of custom syntax';
$template = Template::Sandbox->new();
throws_ok { $template->set_template_string( $syntax ) }
    qr/compile error: unrecognised token \(<: nonexistingtoken :>\) at line 1, char 1 of/,
    'verify class-method custom syntax was removed';

#
#  27-28:  warnings on remove of non-existing syntax
$template = Template::Sandbox->new();
{
    local $^W = 1;
    warns_ok { $template->unregister_template_syntax( $token ) }
        qr/Template post-initialization error: Template syntax '$token' does not exist, cannot be removed. at .*60-custom-syntax\.t line/,
        'warn on unregister of non-existing syntax';
}
{
    local $^W = 0;
    doesnt_warn { $template->unregister_template_syntax( $token ) }
        'warning suppression on unregister of non-existing syntax';
}

#
#  29-30:  warnings on add of existing syntax
$template = Template::Sandbox->new();
$template->register_template_syntax(
    $token => {
        compile => sub { [] },
        run     => sub { '[instance-method custom syntax was ere]' },
        },
    );
{
    local $^W = 1;
    warns_ok
        {
            $template->register_template_syntax(
                $token => {
                    compile => sub { [] },
                    run     =>
                        sub { '[instance-method custom syntax was ere]' },
                    },
                );
        }
        qr/Template post-initialization error: Template syntax '$token' exists, overwriting. at .*60-custom-syntax\.t line/,
        'warn on register of existing syntax';
}
{
    local $^W = 0;
    doesnt_warn
        {
            $template->register_template_syntax(
                $token => {
                    compile => sub { [] },
                    run     =>
                        sub { '[instance-method custom syntax was ere]' },
                    },
                );
        }
        'warning suppression on register of existing syntax';
}

#
#  31: Does local instance-function mask class-function?
Template::Sandbox->register_template_syntax(
    $token => {
        compile => sub { [] },
        run     => sub { '[class-method custom syntax was ere]' },
        },
    );
$template = Template::Sandbox->new();
$template->register_template_syntax(
    $token => {
        compile => sub { [] },
        run     => sub { '[instance-method custom syntax was ere]' },
        },
    );
$template->set_template_string( $syntax );
is( ${$template->run()}, '[instance-method custom syntax was ere]',
    'instance registration masks class registration' );
Template::Sandbox->unregister_template_syntax( $token );

#
#  32:  undef args from compile skips instruction.
$template = Template::Sandbox->new();
$template->register_template_syntax(
    $token => {
        compile => sub { undef },
        run     => sub { '[undef args custom syntax never appens]' },
        },
    );
$template->set_template_string( $syntax );
is( ${$template->run()}, '',
    'undef args custom syntax compile eliminates instruction from program' );

#
#  33:  undef return from run skips content.
$template = Template::Sandbox->new();
$template->register_template_syntax(
    $token => {
        compile => sub { {} },
        run     => sub { undef },
        },
    );
$template->set_template_string( $syntax );
is( ${$template->run()}, '',
    'undef custom syntax run provides no content' );

#
#  34-37: bad syntax definition args to constructor.
throws_ok
    {
        $template = Template::Sandbox->new(
            template_syntax => [
                $token => 'expecting a hashref? tough luck.',
                ],
            );
    }
    qr/Template initialization error: Bad template syntax '$token' to register_template_syntax\(\), expected hash ref, got: 'expecting a hashref\? tough luck\.' at .*Template.*Sandbox\.pm line/,
    'error on bad definition (string) for construct-option custom syntax';
throws_ok
    {
        $template = Template::Sandbox->new(
            template_syntax => [
                $token => [ 'expecting a hashref? tough luck.' ],
                ],
            );
    }
    qr/Template initialization error: Bad template syntax '$token' to register_template_syntax\(\), expected hash ref, got: ARRAY at .*Template.*Sandbox\.pm line/,
    'error on bad definition (arrayref) for construct-option custom syntax';
throws_ok
    {
        $template = Template::Sandbox->new(
            template_syntax => [
                $token => {
                    run     =>
                        sub { '[during-construction custom syntax was ere]' },
                },
                ],
            );
    }
    qr/Template initialization error: Missing compile callback for syntax nonexistingtoken at .*Template.*Sandbox\.pm line/,
    'error on missing compile callback for construct-option custom syntax';
throws_ok
    {
        $template = Template::Sandbox->new(
            template_syntax => [
                $token => {
                    compile => sub { [] },
                },
                ],
            );
    }
    qr/Template initialization error: Missing run callback for syntax nonexistingtoken at .*Template.*Sandbox\.pm line/,
    'error on missing run callback for construct-option custom syntax';

#
#  38-41: bad syntax definition args to constructor.
$template->new();
throws_ok
    {
        $template->register_template_syntax(
            $token => 'expecting a hashref? tough luck.',
            );
    }
    qr/Template error: Bad template syntax '$token' to register_template_syntax\(\), expected hash ref, got: 'expecting a hashref\? tough luck\.' at .*Template.*Sandbox\.pm line/,
    'error on bad definition (string) for post-construct custom syntax';
throws_ok
    {
        $template->register_template_syntax(
            $token => [ 'expecting a hashref? tough luck.' ],
            );
    }
    qr/Template error: Bad template syntax '$token' to register_template_syntax\(\), expected hash ref, got: ARRAY at .*Template.*Sandbox\.pm line/,
    'error on bad definition (arrayref) for post-construct custom syntax';
throws_ok
    {
        $template->register_template_syntax(
            $token => {
                run     =>
                    sub { '[post-construction custom syntax was ere]' },
                },
            );
    }
    qr/Template error: Missing compile callback for syntax nonexistingtoken at .*Template.*Sandbox\.pm line/,
    'error on missing compile callback for post-construct custom syntax';
throws_ok
    {
        $template->register_template_syntax(
            $token => {
                compile => sub { [] },
                },
            );
    }
    qr/Template error: Missing run callback for syntax nonexistingtoken at .*Template.*Sandbox\.pm line/,
    'error on missing run callback for post-construct custom syntax';


#  TODO: multiple custom syntaxes as single constructor param
#  TODO: multiple custom syntaxes as multiple constructor param