package Dancer2::Template::Implementation::ForkedTiny;
$Dancer2::Template::Implementation::ForkedTiny::VERSION = '0.200001';
# ABSTRACT: Dancer2 own implementation of Template::Tiny

use 5.00503;
use strict;
no warnings;

# Evaluatable expression
my $EXPR = qr/ [a-z_][\w.]* /xs;

sub new {
    my $self = bless {
        start_tag => '[%',
        end_tag   => '%]',
        @_[ 1 .. $#_ ]
      },
      $_[0];

# Opening tag including whitespace chomping rules
    my $LEFT = $self->{LEFT} = qr/
    (?:
        (?: (?:^|\n) [ \t]* )? \Q$self->{start_tag}\E\-
        |
        \Q$self->{start_tag}\E \+?
    ) \s*
/xs;

# Closing %] tag including whitespace chomping rules
    my $RIGHT = $self->{RIGHT} = qr/
    \s* (?:
        \+? \Q$self->{end_tag}\E
        |
        \-\Q$self->{end_tag}\E (?: [ \t]* \n )?
    )
/xs;

# Preparsing run for nesting tags
    $self->{PREPARSE} = qr/
    $LEFT ( IF | UNLESS | FOREACH ) \s+
        (
            (?: \S+ \s+ IN \s+ )?
        \S+ )
    $RIGHT
    (?!
        .*?
        $LEFT (?: IF | UNLESS | FOREACH ) \b
    )
    ( .*? )
    (?:
        $LEFT ELSE $RIGHT
        (?!
            .*?
            $LEFT (?: IF | UNLESS | FOREACH ) \b
        )
        ( .+? )
    )?
    $LEFT END $RIGHT
/xs;

    $self->{CONDITION} = qr/
    \Q$self->{start_tag}\E\s
        ( ([IUF])\d+ ) \s+
        (?:
            ([a-z]\w*) \s+ IN \s+
        )?
        ( $EXPR )
    \s\Q$self->{end_tag}\E
    ( .*? )
    (?:
        \Q$self->{start_tag}\E\s \1 \s\Q$self->{end_tag}\E
        ( .+? )
    )?
    \Q$self->{start_tag}\E\s \1 \s\Q$self->{end_tag}\E
/xs;

    $self;
}

# Copy and modify
sub preprocess {
    my $self = shift;
    my $text = shift;
    $self->_preprocess( \$text );
    return $text;
}

sub process {
    my $self  = shift;
    my $copy  = ${ shift() };
    my $stash = shift || {};

    local $@  = '';
    local $^W = 0;

    # Preprocess to establish unique matching tag sets
    $self->_preprocess( \$copy );

    # Process down the nested tree of conditions
    my $result = $self->_process( $stash, $copy );
    if (@_) {
        ${ $_[0] } = $result;
    }
    elsif ( defined wantarray ) {
        require Carp;
        Carp::carp(
            'Returning of template results is deprecated in Template::Tiny 0.11'
        );
        return $result;
    }
    else {
        print $result;
    }
}


######################################################################
# Support Methods

# The only reason this is a standalone is so we can
# do more in-depth testing.
sub _preprocess {
    my $self = shift;
    my $copy = shift;

    # Preprocess to establish unique matching tag sets
    my $id = 0;
    1 while $$copy =~ s/
        $self->{PREPARSE}
    /
        my $tag = substr($1, 0, 1) . ++$id;
        "\[\% $tag $2 \%\]$3\[\% $tag \%\]"
        . (defined($4) ? "$4\[\% $tag \%\]" : '');
    /sex;
}

sub _process {
    my ( $self, $stash, $text ) = @_;

    $text =~ s/
        $self->{CONDITION}
    /
        ($2 eq 'F')
            ? $self->_foreach($stash, $3, $4, $5)
            : eval {
                $2 eq 'U'
                xor
                !! # Force boolification
                $self->_expression($stash, $4)
            }
                ? $self->_process($stash, $5)
                : $self->_process($stash, $6)
    /gsex;

    # Resolve expressions
    $text =~ s/
        $self->{LEFT} ( $EXPR ) $self->{RIGHT}
    /
        eval {
            $self->_expression($stash, $1)
            . '' # Force stringification
        }
    /gsex;

    # Trim the document
    $text =~ s/^\s*(.+?)\s*\z/$1/s if $self->{TRIM};

    return $text;
}

# Special handling for foreach
sub _foreach {
    my ( $self, $stash, $term, $expr, $text ) = @_;

    # Resolve the expression
    my $list = $self->_expression( $stash, $expr );
    if ( ref $list ne 'ARRAY' ) {
        return '';
    }

    # Iterate
    return join '',
      map { $self->_process( { %$stash, $term => $_ }, $text ) } @$list;
}

# Evaluates a stash expression
sub _expression {
    my $cursor = $_[1];
    my @path = split /\./, $_[2];
    foreach (@path) {

        # Support for private keys
        return if substr( $_, 0, 1 ) eq '_';

        # Split by data type
        my $type = ref $cursor;
        if ( $type eq 'ARRAY' ) {
            return '' unless /^(?:0|[0-9]\d*)\z/;
            $cursor = $cursor->[$_];
        }
        elsif ( $type eq 'HASH' ) {
            $cursor = $cursor->{$_};
        }
        elsif ($type) {
            $cursor = $cursor->$_();
        }
        else {
            return '';
        }
    }

    # If the last expression is a CodeRef, execute it
    ref($cursor) eq 'CODE'
      and $cursor = $cursor->();
    return $cursor;
}

1;

__END__

=pod

=encoding UTF-8

=head1 NAME

Dancer2::Template::Implementation::ForkedTiny - Dancer2 own implementation of Template::Tiny

=head1 VERSION

version 0.200001

=head1 SYNOPSIS

  my $template = Dancer2::Template::Implementation::ForkedTiny->new(
      TRIM => 1,
  );

  # Print the template results to STDOUT
  $template->process( <<'END_TEMPLATE', { foo => 'World' } );
  Hello [% foo %]!
  END_TEMPLATE

=head1 DESCRIPTION

B<Dancer2::Template::Implementation::ForkedTiny> is a reimplementation of a subset of the functionality from
L<Template> Toolkit in as few lines of code as possible.

It is intended for use in light-usage, low-memory, or low-cpu templating
situations, where you may need to upgrade to the full feature set in the
future, or if you want the retain the familiarity of TT-style templates.

For the subset of functionality it implements, it has fully-compatible template
and stash API. All templates used with B<Dancer2::Template::Implementation::ForkedTiny> should be able to be
transparently upgraded to full Template Toolkit.

Unlike Template Toolkit, B<Dancer2::Template::Implementation::ForkedTiny> will process templates without a
compile phase (but despite this is still quicker, owing to heavy use of
the Perl regular expression engine.

=head2 SUPPORTED USAGE

By default, the C<[% %]> tag style is used. You can change the start tag and
end tag by specifying them at object creation :

  my $template = Dancer2::Template::Implementation::ForkedTiny->new(
      start_tag => '<%',
      end_tag => '%>,
  );

In the rest of the documentation, C<[% %]> will be used, but it can be of
course your specified start / end tags.

Both the C<[%+ +%]> style explicit whitespace and the C<[%- -%]> style
explicit chomp B<are> support, although the C<[%+ +%]> version is unneeded
in practice as B<Dancer2::Template::Implementation::ForkedTiny> does not support default-enabled C<PRE_CHOMP>
or C<POST_CHOMP>.

Variable expressions in the form C<[% foo.bar.baz %]> B<are> supported.

Appropriate simple behaviours for C<ARRAY> references, C<HASH> references and
objects are supported. "VMethods" such as [% array.length %] are B<not>
supported at this time.

If the resulting expression is a CodeRef, it'll be evaluated.

C<IF>, C<ELSE> and C<UNLESS> conditional blocks B<are> supported, but only with
simple C<[% foo.bar.baz %]> conditions.

Support for looping (or rather iteration) is available in simple
C<[% FOREACH item IN list %]> form B<is> supported. Other loop structures are
B<not> supported. Because support for arbitrary or infinite looping is not
available, B<Dancer2::Template::Implementation::ForkedTiny> templates are not turing complete. This is
intentional.

All of the four supported control structures C<IF>/C<ELSE>/C<UNLESS>/C<FOREACH>
can be nested to arbitrary depth.

The treatment of C<_private> hash and method keys is compatible with
L<Template> Toolkit, returning null or false rather than the actual content
of the hash key or method.

Anything beyond the above is currently out of scope.

=head1 NAME

Dancer2::Template::Implementation::ForkedTiny - Template Toolkit reimplemented in as little code as possible, forked from Template::Tiny

=head1 METHODS

=head2 new

  my $template = Dancer2::Template::Implementation::ForkedTiny->new(
      TRIM => 1,
  );

The C<new> constructor is provided for compatibility with Template Toolkit.

The only parameter it currently supports is C<TRIM> (which removes leading
and trailing whitespace from processed templates).

Additional parameters can be provided without error, but will be ignored.

=head2 process

  # DEPRECATED: Return template results (emits a warning)
  my $text = $template->process( \$input, $vars );

  # Print template results to STDOUT
  $template->process( \$input, $vars );

  # Generate template results into a variable
  my $output = '';
  $template->process( \$input, $vars, \$output );

The C<process> method is called to process a template.

The first parameter is a reference to a text string containing the template
text. A reference to a hash may be passed as the second parameter containing
definitions of template variables.

If a third parameter is provided, it must be a scalar reference to be
populated with the output of the template.

For a limited amount of time, the old deprecated interface will continue to
be supported. If C<process> is called without a third parameter, and in
scalar or list contest, the template results will be returned to the caller.

If C<process> is called without a third parameter, and in void context, the
template results will be C<print()>ed to the currently selected file handle
(probably C<STDOUT>) for compatibility with L<Template>.

=head1 SUPPORT

Bugs should be reported via the CPAN bug tracker at

L<http://rt.cpan.org/NoAuth/ReportBug.html?Queue=Template-Tiny>

For other issues, or commercial enhancement or support, contact the author.

=head1 AUTHOR

Adam Kennedy E<lt>adamk@cpan.orgE<gt>

Forked and improved by Damien Krotkine E<lt>dams@cpan.orgE<gt>

=head1 SEE ALSO

L<Config::Tiny>, L<CSS::Tiny>, L<YAML::Tiny>

=head1 COPYRIGHT

Copyright 2009 - 2011 Adam Kennedy.
Copyright 2012 Damien Krotkine.

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

The full text of the license can be found in the
LICENSE file included with this module.

=head1 AUTHOR

Dancer Core Developers

=head1 COPYRIGHT AND LICENSE

This software is copyright (c) 2016 by Alexis Sukrieh.

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
