use 5.14.0;
use strict;
use warnings;

use Stenciller::Standard;
use Stenciller::Stencil;
# PODNAME: Stenciller
our $VERSION = '0.1004'; # VERSION
# ABSTRACT: Convert textfiles to different output

class Stenciller using Moose {

    has filepath => (
        is => 'ro',
        isa => File,
        required => 1,
        coerce => 1,
        documentation => 'The textfile to parse.',
    );
    has is_utf8 => (
        is => 'ro',
        isa => Bool,
        default => 1,
        documentation => 'Determines how the stencil file is read.'
    );
    has stencils => (
        is => 'ro',
        isa => ArrayRef[Stencil],
        traits => ['Array'],
        default => sub { [ ] },
        documentation => 'After parsing, this contains all parsed stencils.',
        init_arg => undef,
        handles => {
            add_stencil => 'push',
            all_stencils => 'elements',
            get_stencil => 'get',
            count_stencils => 'count',
        },
    );
    has header_lines => (
        is => 'ro',
        isa => ArrayRef[Str],
        traits => ['Array'],
        default => sub { [] },
        init_arg => undef,
        documentation => 'After parsing, this contains all lines in the header.',
        handles => {
            add_header_line => 'push',
            all_header_lines => 'elements',
        },
    );
    has skip_if_input_empty => (
        is => 'ro',
        isa => Bool,
        default => 1,
        documentation => 'If a stencil has no input content, skip entire stencil.',
    );
    has skip_if_output_empty => (
        is => 'ro',
        isa => Bool,
        default => 1,
        documentation => 'If a stencil has no output content, skip entire stencil.',
    );

    method BUILD {
        $self->parse;
    }

    method render(Str $plugin_name       does doc('Plugin to render contents with.'),
                      @constructor_args  does doc('Constructor arguments for the plugin.')
              --> Str                    does doc('Returns the rendered contents.')
    ) {

        my $plugin_class = "Stenciller::Plugin::$plugin_name";
        eval "use $plugin_class";
        die ("Cant 'use $plugin_class': $@") if $@;
     #   if(!$plugin_class->does('Stenciller::Renderer')) {
     #       croak("[$plugin_name] doesn't do the Stenciller::Renderer role. Quitting.");
     #   }
        return $plugin_class->new(stenciller => $self, @constructor_args)->render;
    }

    method parse {
        my @contents = split /\v/ => $self->is_utf8 ? $self->filepath->slurp_utf8 : $self->filepath->slurp;

        my $stencil_start = qr/^== +stencil +(\{.*\} +)?==$/;
        my $input_start = qr/^--+input--+$/;
        my $input_end = qr/^--+end input--+$/;
        my $output_start = qr/^--+output--+$/;
        my $output_end = qr/^--+end output--+$/;

        my $environment = 'header';
        my $line_count = 0;

        my $stencil = undef;

        LINE:
        foreach my $line (@contents) {
            ++$line_count if $environment ne 'next_stencil'; # because then we are redo-ing the line

            if(any { $environment eq $_ } (qw/header next_stencil/)) {
                $self->add_header_line($line) and next LINE if $line !~ $stencil_start;

                my $settings = $1 ? $self->eval($1) : {};

                $stencil = Stenciller::Stencil->new(
                            name => exists $settings->{'name'} ? delete $settings->{'name'} : $self->filepath->basename . "-$line_count",
                            loop_values => delete $settings->{'loop'},
                            line_number => $line_count,
                      maybe skip  => delete $settings->{'skip'},
                    provided scalar keys %{ $settings }, extra_settings => $settings,
                );
                $environment = 'before_input';
            }
            elsif($environment eq 'before_input') {
                $stencil->add_before_input($line) and next LINE if $line !~ $input_start;
                $environment = 'input';
            }
            elsif($environment eq 'input') {
                $stencil->add_input($line) and next LINE if $line !~ $input_end;
                $environment = 'between';
            }
            elsif($environment eq 'between') {
                $stencil->add_between($line) and next LINE if $line !~ $output_start;
                $environment = 'output';
            }
            elsif($environment eq 'output') {
                $stencil->add_output($line) and next LINE if $line !~ $output_end;
                $environment = 'after_output';
            }
            elsif($environment eq 'after_output') {
                $stencil->add_after_output($line) and next LINE if $line !~ $stencil_start;
                $self->handle_completed_stencil($stencil);
                $environment = 'next_stencil';
                redo LINE;
            }
        }
        $self->handle_completed_stencil($stencil);
    }

    method handle_completed_stencil(Stencil $stencil) {
        return if !defined $stencil;
        return if $stencil->skip;
        return if !$stencil->has_input  && $self->skip_if_input_empty;
        return if !$stencil->has_output && $self->skip_if_output_empty;

        if(!$stencil->has_loop_values) {
            $self->add_stencil($stencil);
            return;
        }

        foreach my $loop_value ($stencil->all_loop_values) {
            my $clone = $stencil->clone_with_loop_value($loop_value);
            $self->add_stencil($clone);
        }
    }

    method eval(Str $possible_hash --> HashRef) {
        my $stencil_settings = eval $possible_hash;
        die sprintf "Can't parse stencil start: <%s> in %s: %s", $possible_hash, $self->filepath, $@ if $@;
        return $stencil_settings;
    }
}

__END__

=pod

=encoding UTF-8

=head1 NAME

Stenciller - Convert textfiles to different output

=head1 VERSION

Version 0.1004, released 2015-01-15.

=head1 SYNOPSIS

    use Stenciller;
    my $stenciller = Stenciller->new(filepath => 't/corpus/test-1.stencil');
    my $content = $stenciller->render('ToUnparsedText');

=head1 DESCRIPTION

Stenciller reads a special fileformat and provides a way to convert the content into different types of output. For example, it can be used to create documentation and tests from the same source file.

=head2 File format

    == stencil {} ==

    --input--

    --end input--

    --output--

    --end output--

This is the basic layout. A stencil ends when a new stencil block is discovered (there is no set limit to the number of stencils in a file). The (optional) hash is for settings. Each stencil has five parts: C<before_input>, C<input>, C<between>, C<output> and C<after_output>. In addition to this
there is a header before the first stencil.



=head1 ATTRIBUTES


=head2 filepath

=begin HTML

<table cellpadding="0" cellspacing="0">
<tr><td style="padding-right: 6px; padding-left: 6px; border-right: 1px solid #b8b8b8; white-space: nowrap;"><a href="https://metacpan.org/pod/Types::Path::Tiny#File">File</a>

</td>
<td style="padding-right: 6px; padding-left: 6px; border-right: 1px solid #b8b8b8; white-space: nowrap;">required</td>
<td style="padding-left: 6px; padding-right: 6px; white-space: nowrap;">read-only</td></tr>
</table>

<p>The textfile to parse.</p>

=end HTML

=head2 is_utf8

=begin HTML

<table cellpadding="0" cellspacing="0">
<tr><td style="padding-right: 6px; padding-left: 6px; border-right: 1px solid #b8b8b8; white-space: nowrap;"><a href="https://metacpan.org/pod/Types::Standard#Bool">Bool</a>

</td>
<td style="padding-right: 6px; padding-left: 6px; border-right: 1px solid #b8b8b8; white-space: nowrap;">optional, default: <code>1</code>

</td>
<td style="padding-left: 6px; padding-right: 6px; white-space: nowrap;">read-only</td></tr>
</table>

<p>Determines how the stencil file is read.</p>

=end HTML

=head2 skip_if_input_empty

=begin HTML

<table cellpadding="0" cellspacing="0">
<tr><td style="padding-right: 6px; padding-left: 6px; border-right: 1px solid #b8b8b8; white-space: nowrap;"><a href="https://metacpan.org/pod/Types::Standard#Bool">Bool</a>

</td>
<td style="padding-right: 6px; padding-left: 6px; border-right: 1px solid #b8b8b8; white-space: nowrap;">optional, default: <code>1</code>

</td>
<td style="padding-left: 6px; padding-right: 6px; white-space: nowrap;">read-only</td></tr>
</table>

<p>If a stencil has no input content, skip entire stencil.</p>

=end HTML

=head2 skip_if_output_empty

=begin HTML

<table cellpadding="0" cellspacing="0">
<tr><td style="padding-right: 6px; padding-left: 6px; border-right: 1px solid #b8b8b8; white-space: nowrap;"><a href="https://metacpan.org/pod/Types::Standard#Bool">Bool</a>

</td>
<td style="padding-right: 6px; padding-left: 6px; border-right: 1px solid #b8b8b8; white-space: nowrap;">optional, default: <code>1</code>

</td>
<td style="padding-left: 6px; padding-right: 6px; white-space: nowrap;">read-only</td></tr>
</table>

<p>If a stencil has no output content, skip entire stencil.</p>

=end HTML

=head2 header_lines

=begin HTML

<table cellpadding="0" cellspacing="0">
<tr><td style="padding-right: 6px; padding-left: 6px; border-right: 1px solid #b8b8b8; white-space: nowrap;"><a href="https://metacpan.org/pod/Types::Standard#ArrayRef">ArrayRef</a> [ <a href="https://metacpan.org/pod/Types::Standard#Str">Str</a> ]</td>
<td style="padding-right: 6px; padding-left: 6px; border-right: 1px solid #b8b8b8; white-space: nowrap;">not in constructor</td>
<td style="padding-left: 6px; padding-right: 6px; white-space: nowrap;">read-only</td></tr>
</table>

<p>After parsing, this contains all lines in the header.</p>

=end HTML

=head2 stencils

=begin HTML

<table cellpadding="0" cellspacing="0">
<tr><td style="padding-right: 6px; padding-left: 6px; border-right: 1px solid #b8b8b8; white-space: nowrap;"><a href="https://metacpan.org/pod/Types::Standard#ArrayRef">ArrayRef</a> [ <a href="https://metacpan.org/pod/Types::Standard#Stencil">Stencil</a> ]</td>
<td style="padding-right: 6px; padding-left: 6px; border-right: 1px solid #b8b8b8; white-space: nowrap;">not in constructor</td>
<td style="padding-left: 6px; padding-right: 6px; white-space: nowrap;">read-only</td></tr>
</table>

<p>After parsing, this contains all parsed stencils.</p>

=end HTML

=head1 METHODS


=head2 render

=begin HTML

<p></p>

<table style="margin-bottom: 10px; margin-left: 10px; border-collapse: bollapse;" cellpadding="0" cellspacing="0">
<tr style="vertical-align: top;"><td style="text-align: left; color: #444; padding-left: 5px; font-weight: bold; background-color: #eee8e8;">Positional parameters</td><td style="text-align: left; color: #444; padding-left: 5px; font-weight: bold; background-color: #eee8e8;">&#160;</td><td style="text-align: left; color: #444; padding-left: 5px; font-weight: bold; background-color: #eee8e8;">&#160;</td><td style="text-align: left; color: #444; padding-left: 5px; font-weight: bold; background-color: #eee8e8;">&#160;</td><td style="text-align: left; color: #444; padding-left: 5px; font-weight: bold; background-color: #eee8e8;">&#160;</td></tr>
<tr style="vertical-align: top;">
<td style="vertical-align: top; border-right: 1px solid #eee; white-space: nowrap;  padding: 3px 6px; border-bottom: 1px solid #eee;"><code>$plugin_name</code>

</td>
<td style="vertical-align: top; border-right: 1px solid #eee; white-space: nowrap;  padding: 3px 6px; border-bottom: 1px solid #eee;"><a href="https://metacpan.org/pod/Types::Standard#Str">Str</a>

</td>
<td style="vertical-align: top; border-right: 1px solid #eee; white-space: nowrap;  padding: 3px 6px; border-bottom: 1px solid #eee;">required</td>
<td style="vertical-align: top; border-right: 1px solid #eee; white-space: nowrap;  border-bottom: 1px solid #eee;"></td>
<td style="padding: 3px 6px; vertical-align: top;  border-bottom: 1px solid #eee;">Plugin to render contents with.<br /></td>
</tr>
<tr style="vertical-align: top;">
<td style="vertical-align: top; border-right: 1px solid #eee; white-space: nowrap;  padding: 3px 6px; border-bottom: 1px solid #eee;"><code>@constructor_args</code>

</td>
<td style="vertical-align: top; border-right: 1px solid #eee; white-space: nowrap;  padding: 3px 6px; border-bottom: 1px solid #eee;"></td>
<td style="vertical-align: top; border-right: 1px solid #eee; white-space: nowrap;  padding: 3px 6px; border-bottom: 1px solid #eee;">required</td>
<td style="vertical-align: top; border-right: 1px solid #eee; white-space: nowrap;  padding: 3px 6px; border-bottom: 1px solid #eee;">slurpy</td>
<td style="padding: 3px 6px; vertical-align: top;  border-bottom: 1px solid #eee;">Constructor arguments for the plugin.<br /></td>
</tr>
<tr style="vertical-align: top;"><td style="text-align: left; color: #444; padding-left: 5px; font-weight: bold; background-color: #e8e8ee;">Returns</td><td style="text-align: left; color: #444; padding-left: 5px; font-weight: bold; background-color: #e8e8ee;">&#160;</td><td style="text-align: left; color: #444; padding-left: 5px; font-weight: bold; background-color: #e8e8ee;">&#160;</td><td style="text-align: left; color: #444; padding-left: 5px; font-weight: bold; background-color: #e8e8ee;">&#160;</td><td style="text-align: left; color: #444; padding-left: 5px; font-weight: bold; background-color: #e8e8ee;">&#160;</td></tr>
<tr style="vertical-align: top;">
<td style="vertical-align: top; border-right: 1px solid #eee;  padding: 3px 6px; border-bottom: 1px solid #eee;"><a href="https://metacpan.org/pod/Types::Standard#Str">Str</a>

</td><td style="vertical-align: top; border-right: 1px solid #eee;  padding: 3px 6px; border-bottom: 1px solid #eee;">&#160;</td><td style="vertical-align: top; border-right: 1px solid #eee;  padding: 3px 6px; border-bottom: 1px solid #eee;">&#160;</td><td style="vertical-align: top; border-right: 1px solid #eee;  padding: 3px 6px; border-bottom: 1px solid #eee;">&#160;</td>
<td style="padding: 3px 6px; vertical-align: top;  border-bottom: 1px solid #eee;">Returns the rendered contents.</td>
</tr>
</table>

=end HTML

=head1 PLUGINS

The actual rendering is done by plugins. There are two plugins bundled in this distribution:

=over 4

=item *

L<Stenciller::Plugin::ToUnparsedText>

=item *

L<Stenciller::Plugin::ToHtmlPreBlock>

=back

Custom plugins should be in the L<Stenciller::Plugin> namespace and consume the L<Stenciller::Renderer> role.

=head1 SOURCE

L<https://github.com/Csson/p5-Stenciller>

=head1 HOMEPAGE

L<https://metacpan.org/release/Stenciller>

=head1 AUTHOR

Erik Carlsson <info@code301.com>

=head1 COPYRIGHT AND LICENSE

This software is copyright (c) 2015 by Erik Carlsson <info@code301.com>.

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

=cut
