#!perl
#   - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
#
#   file: t/templates.t #
#
#   Copyright © 2015 Van de Bugger
#
#   This file is part of perl-Dist-Zilla-Plugin-Templates.
#
#   perl-Dist-Zilla-Plugin-Templates is free software: you can redistribute it and/or modify it
#   under the terms of the GNU General Public License as published by the Free Software Foundation,
#   either version 3 of the License, or (at your option) any later version.
#
#   perl-Dist-Zilla-Plugin-Templates is distributed in the hope that it will be useful, but WITHOUT
#   ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
#   PURPOSE. See the GNU General Public License for more details.
#
#   You should have received a copy of the GNU General Public License along with
#   perl-Dist-Zilla-Plugin-Templates. If not, see <http://www.gnu.org/licenses/>.
#
#   - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

use strict;
use warnings;
use if $ENV{AUTOMATED_TESTING}, 'Test::DiagINC';
use lib 't';

use POSIX qw{ locale_h };
use Test::More;
use Test::Routine::Util;
use Test::Deep qw{ re };

#   `AutoPrereqs` hints:
use Dist::Zilla::Role::TextTemplater v0.8.0 qw{};
    # ^ Error messages changed in v0.8.0. With earlier version the test fails.
use Dist::Zilla::File::OnDisk;  # Implicitly used by the test.

#   Some tests check error messages, which expected to be in English.
setlocale( LC_ALL, 'C' )
    or diag "*** Can't set \"C\" locale, some tests may fail! ***";

my $role  = 'TemplatesTester';
my $abort = 'Dist::Zilla died in construction: Aborting...';
my $E     = qr{^\s*\^\^\^ };

plan tests => 18;

{
    package MY;
    our $name    = "Assa";
    our $package = "Assa";
    our $version = "0.007";

}

my $files = {
    'README' => [
            '{{$MY::name}} is an example of...',
    ],
    'lib/Assa.pm' => [
        'package {{$MY::package}};',
        '# ABSTRACT: Yoohoo',
        '1;',
    ],
    'lib/Assa/Manual.pod' => [
        '=head1 NAME',
        '',
        '{{$name}} - Err... Aahh...',       # Note: `$name`, not `$MY::name`.
        '/* $version */',
        '',
        '=cut',
    ],
    't/assa.t' => '# This is a part of {{$MY::name}}',
};

run_tests 'InstallModules', $role => {
    files => $files,
    options => {
        templates => ':InstallModules',
    },
    expected => {
        messages => [
        ],
        files => {
            'README' => [
                '{{$MY::name}} is an example of...',
                #^^^^^^^^^^^^^ Not replaced because not an install module.
            ],
            'lib/Assa.pm' => [
                'package Assa;',
                #        ^^^^ Replaced.
                '# ABSTRACT: Yoohoo',
                '1;',
            ],
            'lib/Assa/Manual.pod' => [
                '=head1 NAME',
                '',
                ' - Err... Aahh...',
                #^^^ Replaced with empty string: there is no variable `$name`.
                '/* $version */',
                '',
                '=cut',
            ],
            't/assa.t' => '# This is a part of {{$MY::name}}',
                #                              ^^^^^^^^^^^^^ Not replaced — not an install module.
        },
    },
};

{
    local $MY::name = $MY::name;
    run_tests 'AllFiles', $role => {
        files => $files,
        options => {
            'package' => 'MY',
            'prepend' => '$MY::name = "hohoho";',   # Note: We change the variable value
            'templates' => [
                ':AllFiles',
            ],
        },
        expected => {
            messages => [
            ],
            files => {
                'README' => [
                    'hohoho is an example of...',
                    #^^^^^^ Replaced, not `Assa` because of `prepend` effect.
                ],
                'lib/Assa.pm' => [
                    'package Assa;',
                    #        ^^^^ Replaced.
                    '# ABSTRACT: Yoohoo',
                    '1;',
                ],
                'lib/Assa/Manual.pod' => [
                    '=head1 NAME',
                    '',
                    'hohoho - Err... Aahh...',
                    #^^^^^^ Replaced because of `MY` package context (and `prepend` effect).
                    '/* $version */',
                    '',
                    '=cut',
                ],
            },
        },
    };
}

run_tests 'Non-standard delimiters', $role => {
    files => $files,
    options => {
        'delimiters' => '/* */',
        'package'    => 'MY',
        'templates' => [
            ':AllFiles',
        ],
    },
    expected => {
        messages => [
        ],
        files => {
            'README' => [
                '{{$MY::name}} is an example of...',
                #^^^^^^^^^^^^^ Not replaced.
            ],
            'lib/Assa.pm' => [
                'package {{$MY::package}};',
                #        ^^^^^^^^^^^^^^^^ Not replaced.
                '# ABSTRACT: Yoohoo',
                '1;',
            ],
            'lib/Assa/Manual.pod' => [
                '=head1 NAME',
                '',
                '{{$name}} - Err... Aahh...',
                #^^^^^^^^^ Not replaced.
                '0.007',
                #^^^^^ Replaced.
                '',
                '=cut',
            ],
        },
    }
};

run_tests 'Multiple finders', $role => {
    files => $files,
    options => {
        'templates' => [
            ':InstallModules',      # Multiple file finders work.
            ':TestFiles',
        ],
    },
    expected => {
        files => {
            'lib/Assa.pm'   => [
                'package Assa;',
                #        ^^^^ Replaced because an install module.
                '# ABSTRACT: Yoohoo',
                '1;',
            ],
            't/assa.t'      => '# This is a part of Assa',
            #                                       ^^^^ Replaced because test file.
        },
    },
};

run_tests 'Include + fill_in', $role => {
    files => {
        'lib/Assa.pm' => 'A {{ include( "include.txt" ); }} B',
        'lib/Foo.pm'  => 'A {{ include( "include.txt" )->fill_in; }} B',
        'lib/Bar.pm'  => 'A {{ include( "include.txt" )->fill_in->indent; }} B',
        #                                              ^^       ^^ Chaining.
        'include.txt' => 'As{{ 2 + 2 }}sa',
    },
    options => { 'templates' => ':InstallModules' },
    expected => {
        files => {
            'lib/Assa.pm' => 'A As{{ 2 + 2 }}sa B',
            #                     ^^^^^^^^^^^ `include` does not expand templates.
            'lib/Foo.pm'  => 'A As4sa B',
            #                    ^^^ but `include()->fill_in` does.
            'lib/Bar.pm'  => 'A     As4sa B',
        },
    },
};

run_tests 'fill_in args', $role => {
    files => {
        'lib/Assa.pm' => '{{ include( "include.txt" )->fill_in( { A => "X", B => "Y" } ); }}',
        'include.txt' => '{{$A}}-{{$B}}',
    },
    options => { 'templates' => ':InstallModules' },
    expected => {
        files => {
            'lib/Assa.pm' => 'X-Y',
        },
    },
};

run_tests 'Indent', $role => {
    files => {
        'lib/Assa.pm' => '{{ include( "include.txt" )->indent; }}',
        #                                                   ^^^ Default indent size.
        'lib/Foo.pm'  => '{{ include( "include.txt" )->indent( 2 ); }}',
        #                                                     ^^^ Custom indent size.
        'include.txt' => [
            'line 1',
            '',                         # Empty line should not be indented.
            'line 3',
        ],
    },
    options => { 'templates' => ':InstallModules' },
    expected => {
        files => {
            'lib/Assa.pm' => [
                '    line 1',           # Default indent size is 4 spaces.
                '',                     # Empty line is not indented.
                '    line 3',
            ],
            'lib/Foo.pm' => [
                '  line 1',             # Custom indent size.
                '',                     # Empty line is not indented.
                '  line 3',
            ],
        },
    },
};

run_tests 'Chomp', $role => {
    files => {
        'lib/Assa.pm' => '{{ include( "include.txt" )->chomp; }}',
        'lib/Foo.pm'  => '{{ include( "include.txt" )->chomp( 2 ); }}',
        #                                                    ^^^ Custom chomp count.
        'include.txt' => "line1\n \n\n\n",
    },
    options => { 'templates' => ':InstallModules' },
    expected => {
        files => {
            'lib/Assa.pm' => "line1\n ",    # Should chomp all the trailing newlines.
            'lib/Foo.pm'  => "line1\n \n",  # Should chomp only two trailing newlines.
        },
    },
};

run_tests 'Pod2text', $role => {
    files => {
        'lib/Assa.pm' => '{{ include( "include.pod" )->pod2text->chomp; }}',
        #   On some machines `pod2text` returns 5 lines, on some — 6 lines (including the last
        #   empty line). Let us chomp it to avoid accidental test failure.
        'include.pod' => [
            '=head1 DESCRIPTION',
            '',
            'Text C<code> B<bold> I<italics>.',
            '',
            '    Verbatim text.',
            '    No C<code> B<bold> I<italics>.',
            '',
            '=cut',
        ],
    },
    options => { 'templates' => ':InstallModules' },
    expected => {
        files => {
            'lib/Assa.pm' => [
                'DESCRIPTION',
                '    Text "code" bold *italics*.',
                '',
                '        Verbatim text.',
                '        No C<code> B<bold> I<italics>.',
            ],
        },
    },
};

run_tests 'Include error reporting', $role => {
    files => {
        'lib/Assa.pm' => [
            '{{',
            '    include( "include.pod" )->pod2text;',
            '}}{{',
            '    include();',
            '}}{{',
            '    include( $dist );',
            '}}',
        ],
    },
    options => { 'templates' => ':InstallModules' },
    expected => {
        exception => "Aborting...\n",
        messages => [
            re( qr{^Error open .* on 'include.pod': No such file or directory at } ),
            # TODO: This error message is still not very good.
            '    Bad code fragment begins at lib/Assa.pm line 1.',
            'Can\'t include undefined file at lib/Assa.pm line 4.',
            '    Bad code fragment begins at lib/Assa.pm line 3.',
            re( qr{^Can't include object of .* class at lib/Assa\.pm line 6\.} ),
            '    Bad code fragment begins at lib/Assa.pm line 5.',
            'lib/Assa.pm:',
            '    1: {{',
            '       ^^^ Bad code fragment begins at lib/Assa.pm line 1. ^^^',
            '    2:     include( "include.pod" )->pod2text;',
            '    3: }}{{',
            '       ^^^ Bad code fragment begins at lib/Assa.pm line 3. ^^^',
            '    4:     include();',
            '       ^^^ Can\'t include undefined file at lib/Assa.pm line 4. ^^^',
            '    5: }}{{',
            '       ^^^ Bad code fragment begins at lib/Assa.pm line 5. ^^^',
            '    6:     include( $dist );',
            re( qr{${E}Can't include object of .* class at lib/Assa\.pm line 6\.} ),
            '    7: }}',
        ],
    },
};

run_tests 'Pod2text error reporting', $role => {
    files => {
        'lib/Assa.pm' => '{{ include( "include.pod" )->pod2text; }}',
        'include.pod' => [
            '=h ead1 DESCRIPTION',
            #^^ Bad directive.
            '',
            'Text A<code> B<bold> I<italics>.',
            #     ^^^^^^^ Badformatting code.
            '',
            '=cut',
        ],
    },
    options => { 'templates' => ':InstallModules' },
    expected => {
        exception => "Aborting...\n",
        messages => [
            'Unknown directive: =h at include.pod line 1.',
            'Deleting unknown formatting code A<> at include.pod line 3.',
            'POD errata found at include.pod.',
            '    Bad code fragment begins at lib/Assa.pm line 1.',
            'lib/Assa.pm:',
            '    1: {{ include( "include.pod" )->pod2text; }}',
            '       ^^^ Bad code fragment begins at lib/Assa.pm line 1. ^^^',
        ],
    },
};

run_tests 'Expanded once', $role => {
    files => {
        'lib/Assa.pm' => 'A {{ "{{ 2 + 2 }}" }} B',
        # ^ The file falls to both categories: ":InstallModules" and ":AllFiles".
    },
    options => { 'templates' => [ ':InstallModules', ':AllFiles' ] },
    expected => {
        files => {
            'lib/Assa.pm' => 'A {{ 2 + 2 }} B',
            #                   ^^^^^^^^^^^ Expanded only once.
        },
    },
};

{
    #   `include` works in nested templates. Nested templates are evaluated in the same package.
    local @MY::Packages;
    run_tests 'Recursive include', $role => {
        files => {
            'lib/Assa.pm' => 'A {{ push( @MY::Packages, __PACKAGE__ ); include( "outer.txt" )->fill_in; }} B',
            'outer.txt'   => 'Y {{ push( @MY::Packages, __PACKAGE__ ); include( "inner.txt" )->fill_in; }} Z',
            'inner.txt'   => 'P {{ push( @MY::Packages, __PACKAGE__ ); 2 + 2 }} Q',
        },
        options => { 'templates' => ':InstallModules' },
        expected => {
            files => {
                'lib/Assa.pm' => 'A Y P 4 Q Z B',
            },
        },
    };
    is( @MY::Packages + 0, 3 );
    is( $MY::Packages[ 0 ], $MY::Packages[ 1 ], 'package 1' );
    is( $MY::Packages[ 0 ], $MY::Packages[ 2 ], 'package 2' );
}

#   Generated files are first-class citizens: they can be templates and can be included.
run_tests 'Generated files', $role => {
    files => {
        'README' => '{{ include( \'include.txt\' ) }}',
        #                          ^^^^^^^^^^^ Including a generated file.
    },
    plugins => [
        'GatherDir',
        [ 'GenerateFile', 'lib/Assa.pm' => {    # Main module is a generated file.
            filename => 'lib/Assa.pm',          # It is a template.
            content  => $files->{ 'lib/Assa.pm' },
        } ],
        [ 'GenerateFile', 'include.txt' => {    # This generated file will be included.
            filename => 'include.txt',
            content  => 'include body',
        } ],
        [ 'Templates' => {
            templates => ':AllFiles',
        } ],
    ],
    expected => {
        files => {
            'README' => [
                'include body'
                #^^^^^^^^^^^^ Perl code fragment evaluated.
            ],
            'lib/Assa.pm' => [
                'package Assa;',
                #        ^^^^ Perl code fragment evaluated.
                '# ABSTRACT: Yoohoo',
                '1;',
            ],
        },
    },
};

run_tests 'Including a Dist::ZIlla::File object', $role => {
    files => {
        'include.txt' => 'file content',
        'README' =>
            '{{ include( Dist::Zilla::File::OnDisk->new( { name => \'include.txt\' } ) ) }}',
        #                ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Including a file object.
    },
    options => {
        templates => ':AllFiles',
    },
    expected => {
        files => {
            'README' => [
                'file content'
                #^^^^^^^^^^^^ File included.
            ],
        },
    },
};

done_testing;

exit( 0 );

# end of file #
