The London Perl and Raku Workshop takes place on 26th Oct 2024. If your company depends on Perl, please consider sponsoring and/or attending.
package Inline::TT;
use strict; use warnings; 

use Carp;

use Storable qw( store retrieve );
# Storable is used to store/retrieve the compiled template on disk

use Template::Parser;
use Template::Document;
use Template::Context;
use Template::Stash;

our $TRIM_LEADING_SPACE  = 'TRIM_LEADING_SPACE';
our $TRIM_TRAILING_SPACE = 'TRIM_TRAILING_SPACE';

our $VERSION    = '0.07';
our @ISA        = qw( Inline );
our %default_inline_tt_option_for = (
        $TRIM_LEADING_SPACE  => 1,
        $TRIM_TRAILING_SPACE => 1,
);

# To understand the methods here it helps to read the Inline API docs:
# http://search.cpan.org/~ingy/Inline-0.44/Inline-API.pod

sub register {
    return {
        language        => 'TT',
        aliases	        => [ qw( tt template ) ],
        type	        => 'interpreted',
        suffix	        => 'tt2',
    };
}

# TRIM_LEADING_SPACES and TRIM_TRAILING_SPACES are valid options.  They
# both default to 1.  All other options are passed directly to TT.
sub validate {
    my $o          = shift;
    my %option_for = @_;

    foreach my $option ( keys %default_inline_tt_option_for ) {
        if ( defined $option_for{$option} ) {

            $o->{ILSM}{Inline_TT_options}{$option} = $option_for{$option};

            delete $option_for{$option};

        }
        else {
            $o->{ILSM}{Inline_TT_options}{$option}
            = $default_inline_tt_option_for{$option};
        }
    }

    $o->{ILSM}{TT_options} = {};

    foreach my $option ( keys %option_for ) {
        $o->{ILSM}{TT_options}{$option} = $option_for{$option};
    }
}

# To provide any useful information, we must rehydrate the stored object.
# This is not really a problem, since this method is not likely called in
# production.
#
# XXX This does not work for the first invocation.  That's bad, since
# that's the most likely one to request info.
sub info {
    my $o       = shift;
    my $obj     = $o->{API}{location};
    my $retval;

    eval {
        # retrieve is exported from Storable
        my $tt_code     = retrieve( $obj );
        my $document    = Template::Document->new( $tt_code );
        my $blocks      = join( "\n  ", sort keys %{$document->{_DEFBLOCKS}} );

        $retval	        = "The following tt2 blocks have been bound as subs:"
            . "\n  $blocks\n";
    };

    # If the _Inline directory is not yet built, this error will occur.
    if ( $@ ) {
        $retval = "Rerun, without deleting _Inline, to see INFO.\n";
    }

    return $retval;
}

sub working_info { return "no useful info, sorry\n"; }

# This build receives $code from the template object $o, parses it,
# makes a path for it and stores it.  This handles all the BLOCKS at once.
# Inline will only call this if the md5sum of the input template does not
# match one that is available in the _Inline directory (or its moral
# equivalent).  Otherwise, it will only call load below.
sub build {
    my $o       = shift;
    my $code    = $o->{API}{code};

    my $parser  = Template::Parser->new( $o->{ILSM}{TT_options} );
    my $content = $parser->parse( $code );

    my $path    = "$o->{API}{install_lib}/auto/$o->{API}{modpname}";
    my $obj     = $o->{API}{location};

    $o->mkpath( $path ) unless -d $path;

    store( $content, $obj );  # from Storable
}

# This routine rehydrates the parsed Template object which was originally
# generated by build.  This happens each time the program runs.  After the
# the parsed object it reconstituted, load turns it into a document.
# Each key in that document's _DEFBLOCKS list is made a sub in the caller's
# package.  No, we really shouldn't be peeking inside to the _DEFBLOCKS
# level, but I couldn't find the relavent API.
sub load {
    my $o        = shift;
    my $obj      = $o->{API}{location};

    my $tt_code  = retrieve( $obj );  # from Storable
    my $document = Template::Document->new( $tt_code );

    foreach my $sub ( keys %{$document->{_DEFBLOCKS}} ) {

        no strict 'refs';

        *{"$o->{API}{pkg}\::$sub"} = _make_block_sub( $sub, $document, $o );
    }

    croak "Unable to load TT module $obj:\n$@" if $@;
}

# _make_block_sub takes the name of a block and the document containing it
# and returns a closure for the block.  The closure expects a hash of values
# for template interpolation.  It always processes its block and only its
# block.  A lot of trimming happens.  Typically, no space is left for
# template directives.  Further, no leading or trailing spaces survive.
sub _make_block_sub {
    my $name    = shift;
    my $doc     = shift;
    my $o       = shift;

    return sub {
        my $args    = shift;

        my $stash   = Template::Stash->new( $args );
        my $context = Template::Context->new(
            {
                STASH  => $stash,
                TRIM   => 1,
                BLOCKS => $doc->{_DEFBLOCKS},
            }
        );

        my $retval  = $doc->{_DEFBLOCKS}{$name}( $context );

        if ( $o->{ILSM}{Inline_TT_options}{$TRIM_LEADING_SPACE} ) {
            $retval =~ s/^\s+//;
        }
        if ( $o->{ILSM}{Inline_TT_options}{$TRIM_TRAILING_SPACE} ) {
            $retval =~ s/\s+$//;
        }

        return $retval;
    }
}

1;
__END__

=head1 NAME

Inline::TT - Provides inline support for template toolkit 2.x

=head1 ABSTRACT

Inline::TT provides Inline support for Template Toolkit versions 2.x.

=head1 SYNOPSIS

    use Inline TT => 'DATA';

    print hello( { name => 'Rob' } ), "\n";

    print goodbye( { name => 'Rob' } ), "\n";

    __DATA__
    __TT__
    [% BLOCK hello %]
    <H1> Hello [% name %], how are you? </H1>
    [% END %]
    [% BLOCK goodbye %]
    <H1> Goodbye [% name %], have a nice day. </H1>
    [% END %]

Alternatively:

    use Inline TT => << EO_TEMPLATE
    [% BLOCK hello %]
    <H1> Hello [% name %], how are you? </H1>
    [% END %]
    EO_TEMPLATE

    print hello( { name => 'Rob' } ), "\n";

See the Inline perldoc for even more ways to reference the inline source.

=head1 DESCRIPTION

C<Inline::TT> provides Inline access to version 2.0 and higher of the
Template Toolkit.  This allows you to house your templates inside the
source code file, while retaining the ability to cache compiled templates
on the disk (via Inline's caching mechanism).

The names of the blocks in the template are exported as functions.  Call
these with a reference to a hash of values, which will be used for
interpolation in the templates.  This hash reference is the same as the
second argument to the C<process> method of any Template object.  The output
from template toolkit will be returned to you as a single string.  Note
that leading and trailing spaces are trimmed, further the template toolkit
options PRE_CHOMP and POST_CHOMP are set.  Currently, there is no way to
change these behaviors.

=head1 OPTIONS

You can pass options to Inline::TT.  Two of the options are specific
to Inline::TT:

=over 4

=item TRIM_LEADING_SPACES

Set this to one to remove all leading spaces from the text generated
by TT.  You should also consider using PRE_CHOMP, if you use PROCESS
or INCLUDE directives.

=item TRIM_TRAILING_SPACES

Set this to one to remove all trailing spaces from the text generated
by TT.  You should also consider using POST_CHOMP, if you use PROCESS
or INCLUDE directives.

=back

These two options work on the final and complete string.  They don't
remove ANY interior spaces, only the ones at the beginning and end of
the whole string.

The rest of the options are passed without comment or validation directly
to TT.

Note passing options can be done at compile time or at run time.
To do it at run time, use Inline->bind.  See t/06_options for examples
of passing options at run time and at compile time.

=head1 RATIONALE

Some people (including me at times) prefer to separate formatting from
code, but not by much.  For example, we don't like to have xml embedded
inside Perl, but we don't want to have to hunt on the disk for a template
whose name is likely hidden in a config file (a config file whose name we
may not even remember).  By using Inline::TT, we can store the templates
in the program, but away from the code that supplies their data.  Yet,
because of the wonders of Inline, we still benefit from disk based caching
of templates.  (For details about caching see the Inline documentation,
search for _Inline.)

=head2 EXPORT

The names you used for the BLOCKs in the template, are exported into
your calling package as sub names.

=head1 LIMITATIONS and KNOWN BUGS

Only things in BLOCKs are accessible through Inline::TT.

=head1 SEE ALSO

This module is one of many in the Inline:: family.  You must have Inline
version 0.42 or higher to use this module.  See the Inline documenation
for how to control the location of files it caches.

=head1 AUTHOR

Phil Crow, E<lt>phil@localdomainE<gt>

=head1 COPYRIGHT AND LICENSE

Copyright (C) 2005 by Phil Crow

This library is free software; you can redistribute it and/or modify
it under the same terms as Perl itself, either Perl version 5.8.5 or,
at your option, any later version of Perl 5 you may have available.

=cut