Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

Create template cache when render_file method is used.

Because analyzing syntax when using a render_file method
each time has much futility.
  • Loading branch information...
commit 04603d1d4110c7d80816be1c2bb2e11b55487cb9 1 parent 43b439d
rightgo09 rightgo09 authored
10 Makefile.PL
View
@@ -35,9 +35,13 @@ WriteMakefile(
),
PREREQ_PM => {
- 'IO::File' => '0',
- 'Scalar::Util' => '0',
- 'Encode' => '0',
+ 'IO::File' => '0',
+ 'Scalar::Util' => '0',
+ 'Encode' => '0',
+ 'Carp' => '0',
+ 'File::Spec' => '0',
+ 'File::Basename' => '0',
+ 'URI::Escape' => '0',
},
test => {TESTS => 't/*.t t/*/*.t t/*/*/*.t t/*/*/*/*.t'}
);
191 lib/Text/Haml.pm
View
@@ -6,11 +6,26 @@ use warnings;
use IO::File;
use Scalar::Util qw/weaken/;
use Encode qw/decode/;
+use Carp ();
+use File::Spec;
+use File::Basename ();
+use URI::Escape ();
our $VERSION = '0.990105';
use constant CHUNK_SIZE => 4096;
+BEGIN {
+ my $cache_dir = '.text_haml_cache';
+ for my $dir ($ENV{HOME}, File::Spec->tmpdir) {
+ if (defined($dir) && -d $dir && -w _) {
+ $cache_dir = File::Spec->catdir($dir, '.text_haml_cache');
+ last;
+ }
+ }
+ *_DEFAULT_CACHE_DIR = sub () { $cache_dir };
+}
+
my $ESCAPE = {
'\"' => "\x22",
"\'" => "\x27",
@@ -68,7 +83,10 @@ sub new {
$attrs->{prepend} = '';
$attrs->{append} = '';
$attrs->{namespace} = '';
- $attrs->{vars} = {};
+ $attrs->{path} = ['.'];
+ $attrs->{cache} = 1; # 0: not cached, 1: checks mtime, 2: always cached
+ $attrs->{cache_dir} = _DEFAULT_CACHE_DIR;
+
$attrs->{escape} = <<'EOF';
my $s = shift;
$s =~ s/&/&amp;/g;
@@ -95,6 +113,12 @@ EOF
my $self = {%$attrs, @_};
bless $self, $class;
+ # Convert to template fullpath
+ $self->path([
+ map File::Spec->rel2abs($_),
+ ref($self->path) eq 'ARRAY' ? @{$self->path} : $self->path
+ ]);
+
$self->{helpers_arg} ||= $self;
weaken $self->{helpers_arg};
@@ -114,15 +138,25 @@ sub escape_html {
? $_[0]->{escape_html} = $_[1]
: $_[0]->{escape_html};
}
-sub code { @_ > 1 ? $_[0]->{code} = $_[1] : $_[0]->{code} }
-sub compiled { @_ > 1 ? $_[0]->{compiled} = $_[1] : $_[0]->{compiled} }
-sub helpers { @_ > 1 ? $_[0]->{helpers} = $_[1] : $_[0]->{helpers} }
-sub filters { @_ > 1 ? $_[0]->{filters} = $_[1] : $_[0]->{filters} }
-sub prepend { @_ > 1 ? $_[0]->{prepend} = $_[1] : $_[0]->{prepend} }
-sub append { @_ > 1 ? $_[0]->{append} = $_[1] : $_[0]->{append} }
-sub escape { @_ > 1 ? $_[0]->{escape} = $_[1] : $_[0]->{escape} }
-sub tape { @_ > 1 ? $_[0]->{tape} = $_[1] : $_[0]->{tape} }
-sub vars { @_ > 1 ? $_[0]->{vars} = $_[1] : $_[0]->{vars} }
+sub code { @_ > 1 ? $_[0]->{code} = $_[1] : $_[0]->{code} }
+sub compiled { @_ > 1 ? $_[0]->{compiled} = $_[1] : $_[0]->{compiled} }
+sub helpers { @_ > 1 ? $_[0]->{helpers} = $_[1] : $_[0]->{helpers} }
+sub filters { @_ > 1 ? $_[0]->{filters} = $_[1] : $_[0]->{filters} }
+sub prepend { @_ > 1 ? $_[0]->{prepend} = $_[1] : $_[0]->{prepend} }
+sub append { @_ > 1 ? $_[0]->{append} = $_[1] : $_[0]->{append} }
+sub escape { @_ > 1 ? $_[0]->{escape} = $_[1] : $_[0]->{escape} }
+sub tape { @_ > 1 ? $_[0]->{tape} = $_[1] : $_[0]->{tape} }
+sub path { @_ > 1 ? $_[0]->{path} = $_[1] : $_[0]->{path} }
+sub cache { @_ > 1 ? $_[0]->{cache} = $_[1] : $_[0]->{cache} }
+sub fullpath {
+ @_ > 1 ? $_[0]->{fullpath} = $_[1] : $_[0]->{fullpath};
+}
+sub cache_dir {
+ @_ > 1 ? $_[0]->{cache_dir} = $_[1] : $_[0]->{cache_dir};
+}
+sub cache_path {
+ @_ > 1 ? $_[0]->{cache_path} = $_[1] : $_[0]->{cache_path};
+}
sub helpers_arg {
if (@_ > 1) {
@@ -516,6 +550,8 @@ EOF
$code .= qq/my \$self = shift;/;
+ $code .= qq/my \%____vars = \@_;/;
+
$code .= qq/no strict 'refs'; no warnings 'redefine';/;
# Install helpers
@@ -533,10 +569,10 @@ EOF
if ($self->vars_as_subs) {
next if $self->helpers->{$var};
$code
- .= qq/sub $var() : lvalue; *$var = sub () : lvalue {\$self->vars->{'$var'}};/;
+ .= qq/sub $var() : lvalue; *$var = sub () : lvalue {\$____vars{'$var'}};/;
}
else {
- $code .= qq/my \$$var = \$self->vars->{'$var'};/;
+ $code .= qq/my \$$var = \$____vars{'$var'};/;
}
}
@@ -831,6 +867,7 @@ EOF
$code .= q/return $_H; };/;
$self->code($code);
+
return $self;
}
@@ -914,14 +951,9 @@ sub compile {
sub interpret {
my $self = shift;
- $self->vars({@_});
-
my $compiled = $self->compiled;
- my $output = eval { $compiled->($self) };
-
- # Destroy variables refs to avoid memory leaks
- $self->vars({});
+ my $output = eval { $compiled->($self, @_) };
if ($@) {
$self->error($@);
@@ -952,9 +984,24 @@ sub render_file {
my $self = shift;
my $path = shift;
+ # Set file fullpath
+ $self->_fullpath($path);
+
+ if ($self->cache >= 1) {
+ # Make cache directory
+ my $cache_dir = $self->_cache_dir;
+ # Set cache path
+ $self->_cache_path($path, $cache_dir);
+
+ # Exists same cache file ?
+ if (-e $self->cache_path && $self->_eq_mtime) {
+ return $self->_interpret_cached(@_);
+ }
+ }
+
# Open file
my $file = IO::File->new;
- $file->open("< $path") or die "Can't open template '$path': $!";
+ $file->open($self->fullpath, 'r') or die "Can't open template '$path': $!";
binmode $file, ':utf8';
# Slurp file
@@ -962,12 +1009,92 @@ sub render_file {
while ($file->sysread(my $buffer, CHUNK_SIZE, 0)) {
$tmpl .= $buffer;
}
+ $file->close;
# Encoding
$tmpl = decode($self->encoding, $tmpl) if $self->encoding;
# Render
- return $self->render($tmpl, @_);
+ my $output;
+ if ($output = $self->render($tmpl, @_)) {
+ if ($self->cache >= 1) {
+ # Create cache
+ if ($file->open($self->cache_path, 'w')) {
+ binmode $file, ':utf8';
+ my $mtime = (stat($self->fullpath))[9];
+ print $file '#'.$mtime."\n".$self->code; # Write with file mtime
+ $file->close;
+ }
+ }
+ }
+
+ return $output;
+}
+
+sub _fullpath {
+ my $self = shift;
+ my $path = shift;
+
+ for my $p (@{$self->path}) {
+ my $fullpath = File::Spec->catfile($p, $path);
+ if (-r $fullpath) { # is readable ?
+ $self->fullpath($fullpath);
+ return;
+ }
+ }
+
+ Carp::croak("Can't find template '$path'");
+}
+
+sub _cache_dir {
+ my $self = shift;
+
+ my $cache_dir = File::Spec->catdir(
+ $self->cache_dir,
+ URI::Escape::uri_escape(
+ File::Basename::dirname($self->fullpath)
+ ),
+ );
+ if (not -e $cache_dir) {
+ require File::Path;
+ eval { File::Path::mkpath($cache_dir) };
+ Carp::carp("Can't mkpath '$cache_dir': $@") if $@;
+ }
+
+ return $cache_dir;
+}
+
+sub _cache_path {
+ my $self = shift;
+ my $path = shift;
+ my $cache_dir = shift;
+
+ $self->cache_path(File::Spec->catfile(
+ $cache_dir,
+ File::Basename::basename($path).'.pl',
+ ));
+}
+
+sub _eq_mtime {
+ my $self = shift;
+
+ return 1 if $self->cache == 2;
+ return 0 if $self->cache == 0;
+
+ my $file = IO::File->new;
+ $file->open($self->cache_path, 'r') or return;
+ $file->sysread(my $cache_mtime, length('#xxxxxxxxxx'));
+ $file->close;
+ my $orig_mtime = '#'.(stat($self->fullpath))[9];
+ return $cache_mtime eq $orig_mtime;
+}
+
+sub _interpret_cached {
+ my $self = shift;
+
+ my $compiled = require $self->cache_path;
+ $self->compiled($compiled);
+ return $self->interpret(@_);
}
sub _doctype {
@@ -1053,6 +1180,9 @@ Text::Haml - Haml Perl implementation
$html = $haml->render('= user', user => 'friend'); # <div>friend</div>
+ # Use Haml file
+ $html = $haml->render_file('tmpl/index.haml', user => 'friend');
+
=head1 DESCRIPTION
L<Text::Haml> implements Haml
@@ -1182,6 +1312,24 @@ Holds the last error.
Holds parsed haml elements.
+=head2 C<path>
+
+Holds path of Haml templates. Current directory is a default.
+If you want to set several paths, arrayref can also be set up.
+This way is the same as L<Text::Xslate>.
+
+=head2 C<cache>
+
+Holds cache level of Haml templates. 1 is a default.
+0 means "Not cached", 1 means "Checked template mtime" and 2 means "Used always cached".
+This way is the same as L<Text::Xslate>.
+
+=head2 C<cache_dir>
+
+Holds cache directory of Haml templates. $ENV{HOME}/.text_haml_cache is a default.
+Unless $ENV{HOME}, File::Spec->tempdir was used.
+This way is the same as L<Text::Xslate>.
+
=head1 METHODS
=head2 C<new>
@@ -1234,9 +1382,10 @@ Renders Haml string. Returns undef on error. See error attribute.
=head2 C<render_file>
- my $text = $haml->render_file('foo.haml');
+ my $text = $haml->render_file('foo.haml', var => 'hello');
A helper method that loads a file and passes it to the render method.
+Since "%____vars" is used internally, you cannot use this as parameter name.
=head1 PERL SPECIFIC IMPLEMENTATION ISSUES
38 t/render1.haml
View
@@ -0,0 +1,38 @@
+!!!
+%html
+ %head
+ %title= $title
+ %body
+ #main
+ .note
+ %h2 Quick Notes
+ %ul
+ %li
+ Text::Haml is usually indented with two spaces,
+ although more than two is allowed.
+ You have to be consistent, though.
+ %li
+ The first character of any line is called
+ the "control character" - it says "make a tag"
+ or "run Ruby code" or all sorts of things.
+ %li
+ Text::Haml takes care of nicely indenting your HTML.
+ %li
+ Text::Haml allows Ruby code and blocks.
+ But not in this example.
+ We turned it off for security.
+
+ .note
+ You can get more information by reading the
+ %a{:href => "/docs/yardoc/HAML_REFERENCE.md.html"}
+ Official Text::Haml Reference
+
+ .note
+ %p
+ This example doesn't allow Ruby to be executed,
+ but real Text::Haml does.
+ %p
+ Ruby code is included by using = at the
+ beginning of a line.
+ %p
+ Read the tutorial for more information.
52 t/render1.html
View
@@ -0,0 +1,52 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+<html>
+ <head>
+ <title>RENDER_FILE_TEST</title>
+ </head>
+ <body>
+ <div id='main'>
+ <div class='note'>
+ <h2>Quick Notes</h2>
+ <ul>
+ <li>
+ Text::Haml is usually indented with two spaces,
+ although more than two is allowed.
+ You have to be consistent, though.
+ </li>
+ <li>
+ The first character of any line is called
+ the &quot;control character&quot; - it says &quot;make a tag&quot;
+ or &quot;run Ruby code&quot; or all sorts of things.
+ </li>
+ <li>
+ Text::Haml takes care of nicely indenting your HTML.
+ </li>
+ <li>
+ Text::Haml allows Ruby code and blocks.
+ But not in this example.
+ We turned it off for security.
+ </li>
+ </ul>
+ </div>
+ <div class='note'>
+ You can get more information by reading the
+ <a href='/docs/yardoc/HAML_REFERENCE.md.html'>
+ Official Text::Haml Reference
+ </a>
+ </div>
+ <div class='note'>
+ <p>
+ This example doesn&apos;t allow Ruby to be executed,
+ but real Text::Haml does.
+ </p>
+ <p>
+ Ruby code is included by using = at the
+ beginning of a line.
+ </p>
+ <p>
+ Read the tutorial for more information.
+ </p>
+ </div>
+ </div>
+ </body>
+</html>
83 t/render_file.t
View
@@ -0,0 +1,83 @@
+#!/usr/bin/env perl
+
+use strict;
+use warnings;
+
+use Text::Haml;
+
+use Test::More tests => 6;
+
+use IO::File;
+use URI::Escape ();
+use File::Spec;
+use File::Temp qw/ tempdir /;
+
+my $file = IO::File->new;
+# cache_dir
+my $tempdir = tempdir(CLEANUP => 1);
+# haml template
+$file->open(File::Spec->catfile('t', 'render1.haml'), 'r') or die $!;
+my $haml1_content = do { local $/; <$file> };
+$file->close;
+# create haml file for test
+my $render_haml = File::Spec->catfile($tempdir, 'render.haml');
+$file->open($render_haml, 'w') or die $!;
+print $file $haml1_content;
+$file->close;
+
+my $haml = Text::Haml->new(
+ path => $tempdir,
+ cache_dir => $tempdir,
+ cache => 1,
+);
+
+my $output = $haml->render_file('render.haml', title => 'RENDER_FILE_TEST');
+$file->open(File::Spec->catfile('t', 'render1.html'), 'r') or die $!;
+my $expected1 = do { local $/; <$file> };
+$file->close;
+is($output, $expected1);
+
+my $uri_escaped = URI::Escape::uri_escape($tempdir);
+my $cache_dir = File::Spec->catdir($tempdir, $uri_escaped);
+note($cache_dir);
+ok(-d $cache_dir);
+my $cache_path = File::Spec->catfile($cache_dir, 'render.haml.pl');
+ok(-f $cache_path);
+cmp_ok(-s $cache_path, '>', 1);
+
+# overwrite haml file for test
+$file->open($render_haml, 'w') or die $!;
+print $file <<'EOF';
+%p.title= $title
+%p
+ TEST
+EOF
+$file->close;
+
+# change mtime
+my $mtime = time;
+$mtime += 1000;
+utime $mtime, $mtime, $render_haml;
+
+$haml = Text::Haml->new(
+ path => [$tempdir],
+ cache_dir => $tempdir,
+ cache => 2, # using already exists cache
+);
+$output = $haml->render_file('render.haml', title => 'RENDER_FILE_TEST');
+# same output test 1
+is($output, $expected1);
+
+$haml = Text::Haml->new(
+ path => $tempdir,
+ cache_dir => $tempdir,
+ cache => 1,
+);
+$output = $haml->render_file('render.haml', title => 'RENDER_FILE_TEST');
+is($output, <<'EOF');
+<p class='title'>RENDER_FILE_TEST</p>
+<p>
+ TEST
+</p>
+EOF
+
Please sign in to comment.
Something went wrong with that request. Please try again.