From 3d8a5732c4603cd0699d17e972162201fc28f145 Mon Sep 17 00:00:00 2001 From: Daisuke Maki Date: Tue, 19 Nov 2013 14:24:33 +0900 Subject: [PATCH 01/39] Preliminary version of Provider. * Tests still fail --- lib/Text/Xslate.pm | 322 +++------------------------- lib/Text/Xslate/Provider/Default.pm | 321 +++++++++++++++++++++++++++ t/010_internals/031_save_src.t | 4 +- t/900_bugs/024_use_cache.t | 18 +- t/900_bugs/026_issue61.t | 20 +- 5 files changed, 380 insertions(+), 305 deletions(-) create mode 100644 lib/Text/Xslate/Provider/Default.pm diff --git a/lib/Text/Xslate.pm b/lib/Text/Xslate.pm index 30f544e3..c0d2362e 100644 --- a/lib/Text/Xslate.pm +++ b/lib/Text/Xslate.pm @@ -13,6 +13,7 @@ use Data::MessagePack (); use Scalar::Util (); use Text::Xslate::Util (); +use Text::Xslate::Provider::Default; BEGIN { # all the exportable functions are defined in ::Util our @EXPORT_OK = qw( @@ -31,7 +32,7 @@ our @ISA = qw(Text::Xslate::Engine); my $BYTECODE_VERSION = '1.6'; # $bytecode_version + $fullpath + $compiler_and_parser_options -my $XSLATE_MAGIC = qq{xslate;$BYTECODE_VERSION;%s;%s;}; +our $XSLATE_MAGIC = qq{xslate;$BYTECODE_VERSION;%s;%s;}; # load backend (XS or PP) my $use_xs = 0; @@ -145,6 +146,7 @@ sub options { # overridable function => undef, html_builder_module => undef, compiler => 'Text::Xslate::Compiler', + provider => 'Text::Xslate::Provider::Default', verbose => 1, warn_handler => undef, @@ -274,306 +276,19 @@ sub load_string { # called in render_string() return $asm; } -my $updir = File::Spec->updir; sub find_file { - my($self, $file) = @_; - - if($file =~ /\Q$updir\E/xmso) { - $self->_error("LoadError: Forbidden component (updir: '$updir') found in file name '$file'"); - } - - my $fullpath; - my $cachepath; - my $orig_mtime; - my $cache_mtime; - foreach my $p(@{$self->{path}}) { - $self->note(" find_file: %s in %s ...\n", $file, $p) if _DUMP_LOAD; - - my $cache_prefix; - if(ref $p eq 'HASH') { # virtual path - defined(my $content = $p->{$file}) or next; - $fullpath = \$content; - - # NOTE: - # Because contents of virtual paths include their digest, - # time-dependent cache verifier makes no sense. - $orig_mtime = 0; - $cache_mtime = 0; - $cache_prefix = 'HASH'; - } - else { - $fullpath = File::Spec->catfile($p, $file); - defined($orig_mtime = (stat($fullpath))[_ST_MTIME]) - or next; - $cache_prefix = Text::Xslate::uri_escape($p); - if (length $cache_prefix > 127) { - # some filesystems refuse a path part with length > 127 - $cache_prefix = $self->_digest($cache_prefix); - } - } - - # $file is found - $cachepath = File::Spec->catfile( - $self->{cache_dir}, - $cache_prefix, - $file . 'c', - ); - # stat() will be failed if the cache doesn't exist - $cache_mtime = (stat($cachepath))[_ST_MTIME]; - last; - } - - if(not defined $orig_mtime) { - $self->_error("LoadError: Cannot find '$file' (path: @{$self->{path}})"); - } - - $self->note(" find_file: %s (mtime=%d)\n", - $fullpath, $cache_mtime || 0) if _DUMP_LOAD; - - return { - name => ref($fullpath) ? $file : $fullpath, - fullpath => $fullpath, - cachepath => $cachepath, - - orig_mtime => $orig_mtime, - cache_mtime => $cache_mtime, - }; + my ($self, $file) = @_; + $self->_provider->find_file($self, $file); } - sub load_file { my($self, $file, $mtime, $omit_augment) = @_; - - local $self->{omit_augment} = $omit_augment; - - $self->note("%s->load_file(%s)\n", $self, $file) if _DUMP_LOAD; - - if($file eq '') { # simply reload it - return $self->load_string($self->{string_buffer}); - } - - my $fi = $self->find_file($file); - - my $asm = $self->_load_compiled($fi, $mtime) || $self->_load_source($fi, $mtime); - - # $cache_mtime is undef : uses caches without any checks - # $cache_mtime > 0 : uses caches with mtime checks - # $cache_mtime == 0 : doesn't use caches - my $cache_mtime; - if($self->{cache} < 2) { - $cache_mtime = $fi->{cache_mtime} || 0; - } - - $self->_assemble($asm, $file, $fi->{fullpath}, $fi->{cachepath}, $cache_mtime); - return $asm; + $self->_provider->load_file($self, $file, $mtime, $omit_augment); } sub slurp_template { my($self, $input_layer, $fullpath) = @_; - - if (ref $fullpath eq 'SCALAR') { - return $$fullpath; - } else { - open my($source), '<' . $input_layer, $fullpath - or $self->_error("LoadError: Cannot open $fullpath for reading: $!"); - local $/; - return scalar <$source>; - } -} - -sub _load_source { - my($self, $fi) = @_; - my $fullpath = $fi->{fullpath}; - my $cachepath = $fi->{cachepath}; - - $self->note(" _load_source: try %s ...\n", $fullpath) if _DUMP_LOAD; - - # This routine is called when the cache is no longer valid (or not created yet) - # so it should be ensured that the cache, if exists, does not exist - if(-e $cachepath) { - unlink $cachepath - or Carp::carp("Xslate: cannot unlink $cachepath (ignored): $!"); - } - - my $source = $self->slurp_template($self->input_layer, $fullpath); - $source = $self->{pre_process_handler}->($source) if $self->{pre_process_handler}; - $self->{source}{$fi->{name}} = $source if _SAVE_SRC; - - my $asm = $self->compile($source, - file => $fullpath, - name => $fi->{name}, - ); - - if($self->{cache} >= 1) { - my($volume, $dir) = File::Spec->splitpath($fi->{cachepath}); - my $cachedir = File::Spec->catpath($volume, $dir, ''); - if(not -e $cachedir) { - require File::Path; - eval { File::Path::mkpath($cachedir) } - or Carp::croak("Xslate: Cannot prepare cache directory $cachepath (ignored): $@"); - } - - my $tmpfile = sprintf('%s.%d.d', $cachepath, $$, $self); - - if (open my($out), ">:raw", $tmpfile) { - my $mtime = $self->_save_compiled($out, $asm, $fullpath, utf8::is_utf8($source)); - - if(!close $out) { - Carp::carp("Xslate: Cannot close $cachepath (ignored): $!"); - unlink $tmpfile; - } - elsif (rename($tmpfile => $cachepath)) { - # set the newest mtime of all the related files to cache mtime - if (not ref $fullpath) { - my $main_mtime = (stat $fullpath)[_ST_MTIME]; - if (defined($main_mtime) && $main_mtime > $mtime) { - $mtime = $main_mtime; - } - utime $mtime, $mtime, $cachepath; - $fi->{cache_mtime} = $mtime; - } - else { - $fi->{cache_mtime} = (stat $cachepath)[_ST_MTIME]; - } - } - else { - Carp::carp("Xslate: Cannot rename cache file $cachepath (ignored): $!"); - unlink $tmpfile; - } - } - else { - Carp::carp("Xslate: Cannot open $cachepath for writing (ignored): $!"); - } - } - if(_DUMP_LOAD) { - $self->note(" _load_source: cache(mtime=%s)\n", - defined $fi->{cache_mtime} ? $fi->{cache_mtime} : 'undef'); - } - - return $asm; -} - -# load compiled templates if they are fresh enough -sub _load_compiled { - my($self, $fi, $threshold) = @_; - - if($self->{cache} >= 2) { - # threshold is the most latest modified time of all the related caches, - # so if the cache level >= 2, they seems always fresh. - $threshold = 9**9**9; # force to purge the cache - } - else { - $threshold ||= $fi->{cache_mtime}; - } - # see also tx_load_template() in xs/Text-Xslate.xs - if(!( defined($fi->{cache_mtime}) and $self->{cache} >= 1 - and $threshold >= $fi->{orig_mtime} )) { - $self->note( " _load_compiled: no fresh cache: %s, %s", - $threshold || 0, Text::Xslate::Util::p($fi) ) if _DUMP_LOAD; - $fi->{cache_mtime} = undef; - return undef; - } - - my $cachepath = $fi->{cachepath}; - open my($in), '<:raw', $cachepath - or $self->_error("LoadError: Cannot open $cachepath for reading: $!"); - - my $magic = $self->_magic_token($fi->{fullpath}); - my $data; - read $in, $data, length($magic); - if($data ne $magic) { - return undef; - } - else { - local $/; - $data = <$in>; - close $in; - } - my $unpacker = Data::MessagePack::Unpacker->new(); - my $offset = $unpacker->execute($data); - my $is_utf8 = $unpacker->data(); - $unpacker->reset(); - - $unpacker->utf8($is_utf8); - - my @asm; - if($is_utf8) { # TODO: move to XS? - my $seed = ""; - utf8::upgrade($seed); - push @asm, ['print_raw_s', $seed, __LINE__, __FILE__]; - } - while($offset < length($data)) { - $offset = $unpacker->execute($data, $offset); - my $c = $unpacker->data(); - $unpacker->reset(); - - # my($name, $arg, $line, $file, $symbol) = @{$c}; - if($c->[0] eq 'depend') { - my $dep_mtime = (stat $c->[1])[_ST_MTIME]; - if(!defined $dep_mtime) { - Carp::carp("Xslate: Failed to stat $c->[1] (ignored): $!"); - return undef; # purge the cache - } - if($dep_mtime > $threshold){ - $self->note(" _load_compiled: %s(%s) is newer than %s(%s)\n", - $c->[1], scalar localtime($dep_mtime), - $cachepath, scalar localtime($threshold) ) - if _DUMP_LOAD; - return undef; # purge the cache - } - } - elsif($c->[0] eq 'literal') { - # force upgrade to avoid UTF-8 key issues - utf8::upgrade($c->[1]) if($is_utf8); - } - push @asm, $c; - } - - if(_DUMP_LOAD) { - $self->note(" _load_compiled: cache(mtime=%s)\n", - defined $fi->{cache_mtime} ? $fi->{cache_mtime} : 'undef'); - } - - return \@asm; -} - -sub _save_compiled { - my($self, $out, $asm, $fullpath, $is_utf8) = @_; - my $mp = Data::MessagePack->new(); - local $\; - print $out $self->_magic_token($fullpath); - print $out $mp->pack($is_utf8 ? 1 : 0); - - my $newest_mtime = 0; - foreach my $c(@{$asm}) { - print $out $mp->pack($c); - - if ($c->[0] eq 'depend') { - my $dep_mtime = (stat $c->[1])[_ST_MTIME]; - if ($newest_mtime < $dep_mtime) { - $newest_mtime = $dep_mtime; - } - } - } - return $newest_mtime; -} - -sub _magic_token { - my($self, $fullpath) = @_; - - $self->{serial_opt} ||= Data::MessagePack->pack([ - ref($self->{compiler}) || $self->{compiler}, - $self->_filter_options_for_magic_token($self->_extract_options($self->parser_option)), - $self->_filter_options_for_magic_token($self->_extract_options($self->compiler_option)), - $self->input_layer, - [sort keys %{ $self->{function} }], - ]); - - if(ref $fullpath) { # ref to content string - $fullpath = join ':', ref($fullpath), - $self->_digest(${$fullpath}); - } - return sprintf $XSLATE_MAGIC, $fullpath, $self->{serial_opt}; + $self->_provider->slurp_template($self, $input_layer, $fullpath); } sub _digest { @@ -607,7 +322,17 @@ sub _filter_options_for_magic_token { @filterd_options; } - +sub _provider { + my $self = shift; + my $provider = $self->{provider}; + if (!ref $provider) { + require Mouse; + Mouse::load_class($provider); + $provider = $provider->build($self); + $self->{provider} = $provider; + } + return $provider; +} sub _compiler { my($self) = @_; @@ -646,6 +371,17 @@ sub _error { die make_error(@_); } +sub _magic_token_arguments { + my $self = shift; + return ( + ref($self->{compiler}) || $self->{compiler}, + $self->_filter_options_for_magic_token($self->_extract_options($self->parser_option)), + $self->_filter_options_for_magic_token($self->_extract_options($self->compiler_option)), + $self->input_layer, + [sort keys %{ $self->{function} }], + ); +} + sub note { my($self, @args) = @_; printf STDERR @args; diff --git a/lib/Text/Xslate/Provider/Default.pm b/lib/Text/Xslate/Provider/Default.pm new file mode 100644 index 00000000..b24e0818 --- /dev/null +++ b/lib/Text/Xslate/Provider/Default.pm @@ -0,0 +1,321 @@ +package Text::Xslate::Provider::Default; +use strict; +use Text::Xslate (); + +sub new { + my $class = shift; + my %args = (@_ == 1 ? %{$_[0]} : @_); + return bless {%args}, $class; +} + +sub build { + my ($class, $engine) = @_; + + $class->new( + cache => $engine->{cache}, + cache_dir => $engine->{cache_dir}, + path => $engine->{path}, + ); +} + +my $updir = File::Spec->updir; +sub find_file { + my($self, $engine, $file) = @_; + + if($file =~ /\Q$updir\E/xmso) { + $engine->_error("LoadError: Forbidden component (updir: '$updir') found in file name '$file'"); + } + + my $fullpath; + my $cachepath; + my $orig_mtime; + my $cache_mtime; + foreach my $p(@{$self->{path}}) { + $self->note(" find_file: %s in %s ...\n", $file, $p) if Text::Xslate::Engine::_DUMP_LOAD(); + + my $cache_prefix; + if(ref $p eq 'HASH') { # virtual path + defined(my $content = $p->{$file}) or next; + $fullpath = \$content; + + # NOTE: + # Because contents of virtual paths include their digest, + # time-dependent cache verifier makes no sense. + $orig_mtime = 0; + $cache_mtime = 0; + $cache_prefix = 'HASH'; + } + else { + $fullpath = File::Spec->catfile($p, $file); + defined($orig_mtime = (stat($fullpath))[Text::Xslate::Engine::_ST_MTIME()]) + or next; + $cache_prefix = Text::Xslate::uri_escape($p); + if (length $cache_prefix > 127) { + # some filesystems refuse a path part with length > 127 + $cache_prefix = $self->_digest($cache_prefix); + } + } + + # $file is found + $cachepath = File::Spec->catfile( + $self->{cache_dir}, + $cache_prefix, + $file . 'c', + ); + # stat() will be failed if the cache doesn't exist + $cache_mtime = (stat($cachepath))[Text::Xslate::Engine::_ST_MTIME()]; + last; + } + + if(not defined $orig_mtime) { + $engine->_error("LoadError: Cannot find '$file' (path: @{$self->{path}})"); + } + + $self->note(" find_file: %s (mtime=%d)\n", + $fullpath, $cache_mtime || 0) if Text::Xslate::Engine::_DUMP_LOAD(); + + return { + name => ref($fullpath) ? $file : $fullpath, + fullpath => $fullpath, + cachepath => $cachepath, + + orig_mtime => $orig_mtime, + cache_mtime => $cache_mtime, + }; +} + + +sub load_file { + my($self, $engine, $file, $mtime, $omit_augment) = @_; + + local $self->{omit_augment} = $omit_augment; + + $self->note("%s->load_file(%s)\n", $self, $file) if Text::Xslate::Engine::_DUMP_LOAD(); + + if($file eq '') { # simply reload it + return $engine->load_string($self->{string_buffer}); + } + + my $fi = $self->find_file($engine, $file); + + my $asm = $self->_load_compiled($engine, $fi, $mtime) || + $self->_load_source($engine, $fi, $mtime); + + # $cache_mtime is undef : uses caches without any checks + # $cache_mtime > 0 : uses caches with mtime checks + # $cache_mtime == 0 : doesn't use caches + my $cache_mtime; + if($self->{cache} < 2) { + $cache_mtime = $fi->{cache_mtime} || 0; + } + + $engine->_assemble($asm, $file, $fi->{fullpath}, $fi->{cachepath}, $cache_mtime); + return $asm; +} + +sub slurp_template { + my($self, $engine, $input_layer, $fullpath) = @_; + + if (ref $fullpath eq 'SCALAR') { + return $$fullpath; + } else { + open my($source), '<' . $input_layer, $fullpath + or $engine->_error("LoadError: Cannot open $fullpath for reading: $!"); + local $/; + return scalar <$source>; + } +} + +sub _load_source { + my($self, $engine, $fi) = @_; + my $fullpath = $fi->{fullpath}; + my $cachepath = $fi->{cachepath}; + + $self->note(" _load_source: try %s ...\n", $fullpath) if Text::Xslate::Engine::_DUMP_LOAD(); + + # This routine is called when the cache is no longer valid (or not created yet) + # so it should be ensured that the cache, if exists, does not exist + if(-e $cachepath) { + unlink $cachepath + or Carp::carp("Xslate: cannot unlink $cachepath (ignored): $!"); + } + + my $source = $self->slurp_template($engine, $engine->input_layer, $fullpath); + $source = $self->{pre_process_handler}->($source) if $self->{pre_process_handler}; + $self->{source}{$fi->{name}} = $source if Text::Xslate::Engine::_SAVE_SRC(); + + my $asm = $engine->compile($source, + file => $fullpath, + name => $fi->{name}, + ); + + if($self->{cache} >= 1) { + my($volume, $dir) = File::Spec->splitpath($fi->{cachepath}); + my $cachedir = File::Spec->catpath($volume, $dir, ''); + if(not -e $cachedir) { + require File::Path; + eval { File::Path::mkpath($cachedir) } + or Carp::croak("Xslate: Cannot prepare cache directory $cachepath (ignored): $@"); + } + + my $tmpfile = sprintf('%s.%d.d', $cachepath, $$, $self); + + if (open my($out), ">:raw", $tmpfile) { + my $mtime = $self->_save_compiled($engine, $out, $asm, $fullpath, utf8::is_utf8($source)); + + if(!close $out) { + Carp::carp("Xslate: Cannot close $cachepath (ignored): $!"); + unlink $tmpfile; + } + elsif (rename($tmpfile => $cachepath)) { + # set the newest mtime of all the related files to cache mtime + if (not ref $fullpath) { + my $main_mtime = (stat $fullpath)[Text::Xslate::Engine::_ST_MTIME()]; + if (defined($main_mtime) && $main_mtime > $mtime) { + $mtime = $main_mtime; + } + utime $mtime, $mtime, $cachepath; + $fi->{cache_mtime} = $mtime; + } + else { + $fi->{cache_mtime} = (stat $cachepath)[Text::Xslate::Engine::_ST_MTIME()]; + } + } + else { + Carp::carp("Xslate: Cannot rename cache file $cachepath (ignored): $!"); + unlink $tmpfile; + } + } + else { + Carp::carp("Xslate: Cannot open $cachepath for writing (ignored): $!"); + } + } + if(Text::Xslate::Engine::_DUMP_LOAD()) { + $self->note(" _load_source: cache(mtime=%s)\n", + defined $fi->{cache_mtime} ? $fi->{cache_mtime} : 'undef'); + } + + return $asm; +} + +# load compiled templates if they are fresh enough +sub _load_compiled { + my($self, $engine, $fi, $threshold) = @_; + + if($self->{cache} >= 2) { + # threshold is the most latest modified time of all the related caches, + # so if the cache level >= 2, they seems always fresh. + $threshold = 9**9**9; # force to purge the cache + } + else { + $threshold ||= $fi->{cache_mtime}; + } + # see also tx_load_template() in xs/Text-Xslate.xs + if(!( defined($fi->{cache_mtime}) and $self->{cache} >= 1 + and $threshold >= $fi->{orig_mtime} )) { + $self->note( " _load_compiled: no fresh cache: %s, %s", + $threshold || 0, Text::Xslate::Util::p($fi) ) if Text::Xslate::Engine::_DUMP_LOAD(); + $fi->{cache_mtime} = undef; + return undef; + } + + my $cachepath = $fi->{cachepath}; + open my($in), '<:raw', $cachepath + or $engine->_error("LoadError: Cannot open $cachepath for reading: $!"); + + my $magic = $self->_magic_token($engine, $fi->{fullpath}); + my $data; + read $in, $data, length($magic); + if($data ne $magic) { + return undef; + } + else { + local $/; + $data = <$in>; + close $in; + } + my $unpacker = Data::MessagePack::Unpacker->new(); + my $offset = $unpacker->execute($data); + my $is_utf8 = $unpacker->data(); + $unpacker->reset(); + + $unpacker->utf8($is_utf8); + + my @asm; + if($is_utf8) { # TODO: move to XS? + my $seed = ""; + utf8::upgrade($seed); + push @asm, ['print_raw_s', $seed, __LINE__, __FILE__]; + } + while($offset < length($data)) { + $offset = $unpacker->execute($data, $offset); + my $c = $unpacker->data(); + $unpacker->reset(); + + # my($name, $arg, $line, $file, $symbol) = @{$c}; + if($c->[0] eq 'depend') { + my $dep_mtime = (stat $c->[1])[Text::Xslate::Engine::_ST_MTIME()]; + if(!defined $dep_mtime) { + Carp::carp("Xslate: Failed to stat $c->[1] (ignored): $!"); + return undef; # purge the cache + } + if($dep_mtime > $threshold){ + $self->note(" _load_compiled: %s(%s) is newer than %s(%s)\n", + $c->[1], scalar localtime($dep_mtime), + $cachepath, scalar localtime($threshold) ) + if Text::Xslate::Engine::_DUMP_LOAD(); + return undef; # purge the cache + } + } + elsif($c->[0] eq 'literal') { + # force upgrade to avoid UTF-8 key issues + utf8::upgrade($c->[1]) if($is_utf8); + } + push @asm, $c; + } + + if(Text::Xslate::Engine::_DUMP_LOAD()) { + $self->note(" _load_compiled: cache(mtime=%s)\n", + defined $fi->{cache_mtime} ? $fi->{cache_mtime} : 'undef'); + } + + return \@asm; +} + +sub _save_compiled { + my($self, $engine, $out, $asm, $fullpath, $is_utf8) = @_; + my $mp = Data::MessagePack->new(); + local $\; + print $out $self->_magic_token($engine, $fullpath); + print $out $mp->pack($is_utf8 ? 1 : 0); + + my $newest_mtime = 0; + foreach my $c(@{$asm}) { + print $out $mp->pack($c); + + if ($c->[0] eq 'depend') { + my $dep_mtime = (stat $c->[1])[Text::Xslate::Engine::_ST_MTIME()]; + if ($newest_mtime < $dep_mtime) { + $newest_mtime = $dep_mtime; + } + } + } + return $newest_mtime; +} + +sub _magic_token { + my($self, $engine, $fullpath) = @_; + + $engine->{serial_opt} ||= Data::MessagePack->pack([ + $engine->_magic_token_arguments() + ]); + + if(ref $fullpath) { # ref to content string + $fullpath = join ':', ref($fullpath), + $engine->_digest(${$fullpath}); + } +Carp::confess("serial_port undefined") if ! defined $engine->{serial_opt}; + return sprintf $Text::Xslate::XSLATE_MAGIC, $fullpath, $engine->{serial_opt}; +} + +1; diff --git a/t/010_internals/031_save_src.t b/t/010_internals/031_save_src.t index 74192334..dacbebfe 100644 --- a/t/010_internals/031_save_src.t +++ b/t/010_internals/031_save_src.t @@ -16,13 +16,13 @@ my $tx = Text::Xslate->new( note 'from file'; is $tx->render('hello.tx', { lang => 'Xslate' } ), "Hello, Xslate world!\n"; -is $tx->{source}{File::Spec->catfile(path, 'hello.tx')}, +is $tx->{provider}{source}{File::Spec->catfile(path, 'hello.tx')}, "Hello, <:= \$lang :> world!\n" or diag(explain($tx->{source})); note 'from hash'; is $tx->render('foo'), 'Hello, world!'; -is $tx->{source}{foo}, 'Hello, <: "" :>world!' +is $tx->{provider}{source}{foo}, 'Hello, <: "" :>world!' or diag(explain($tx->{source})); note 'from '; diff --git a/t/900_bugs/024_use_cache.t b/t/900_bugs/024_use_cache.t index c35e7541..dbbfe4c2 100644 --- a/t/900_bugs/024_use_cache.t +++ b/t/900_bugs/024_use_cache.t @@ -15,13 +15,22 @@ use Text::Xslate; my @read_files; { - package My::Xslate; - our @ISA = qw(Text::Xslate); + package My::Xslate::Provider; + our @ISA = qw(Text::Xslate::Provider::Default); sub slurp_template { - my($self, $input_layer, $file) = @_; + my($self, $engine, $input_layer, $file) = @_; push @read_files, $file; - return $self->SUPER::slurp_template($input_layer, $file); + return $self->SUPER::slurp_template($engine, $input_layer, $file); + } + + package My::Xslate; + our @ISA = qw(Text::Xslate); + + sub options { + my $options = $_[0]->SUPER::options(); + $options->{provider} = 'My::Xslate::Provider'; + return $options; } } @@ -57,6 +66,7 @@ utime($t, $t, "$tmplpath/hello2.tt"); utime($t3, $t3, "$tmplpath/includes/footer.inc"); { + note "Removing directory $cache_dir..."; rmtree($cache_dir); my $tx_macro = My::Xslate->new( macro => [ 'macro.inc' ], diff --git a/t/900_bugs/026_issue61.t b/t/900_bugs/026_issue61.t index c5cb49ef..969c258d 100644 --- a/t/900_bugs/026_issue61.t +++ b/t/900_bugs/026_issue61.t @@ -17,17 +17,25 @@ use Text::Xslate; # 4. render main.tx # 5. render main.tx again, but don't use cache +my %load_count; { - package MyXslate; - use parent qw(Text::Xslate); + package My::Xslate::Provider; + use parent qw(Text::Xslate::Provider::Default); sub _load_source { - my ($self, $fi) = @_; + my ($self, $engine, $fi) = @_; my $fullpath = $fi->{fullpath}; + $load_count{$fullpath}++; + $self->SUPER::_load_source($engine, $fi); + } - $self->{_load_source_count}{$fullpath}++; + package My::Xslate; + use parent qw(Text::Xslate) - $self->SUPER::_load_source($fi); + sub options { + my $options = $_[0]->SUPER::options(); + $options->{provider} = 'My::Xslate::Provider'; + return $options; } } @@ -43,7 +51,7 @@ T { my $service = tempdir(CLEANUP => 1); - my $tx = MyXslate->new( + my $tx = My::Xslate->new( cache_dir => "$service/cache", path => [$service], ); From bdfebb3ca0f172e2a503858cef63655997e95e0e Mon Sep 17 00:00:00 2001 From: Daisuke Maki Date: Tue, 19 Nov 2013 15:38:57 +0900 Subject: [PATCH 02/39] Add a simple custom provider test --- t/020_interface/018_custom_provider.t | 39 +++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) create mode 100644 t/020_interface/018_custom_provider.t diff --git a/t/020_interface/018_custom_provider.t b/t/020_interface/018_custom_provider.t new file mode 100644 index 00000000..dc4ddc6e --- /dev/null +++ b/t/020_interface/018_custom_provider.t @@ -0,0 +1,39 @@ +use strict; +use Test::More; + +use Text::Xslate; + +{ + package My::Text::Xslate::Provider::Hash; + use strict; + + sub new { + my $class = shift; + bless {@_}, $class; + } + + sub find_file { + my ($self, $engine, $file) = @_; + $self->{hash}->{$file}; + } + + sub load_file { + my($self, $engine, $file, $mtime, $omit_augment) = @_; + my $string = $self->find_file($engine, $file); + my $asm = $engine->compile($string); + $engine->_assemble($asm, $file, \$string, undef, undef); + return $asm; + } +}; + +my $provider = My::Text::Xslate::Provider::Hash->new( + hash => { + "hello.tx" => 'Hello, <: $name :>' + } +); + +my $tx = Text::Xslate->new(provider => $provider); +is $tx->render("hello.tx", {name => "Hash Hash"}), "Hello, Hash Hash"; + + +done_testing; \ No newline at end of file From 97f402118e09a452523e9d5806fcd7b353f67b71 Mon Sep 17 00:00:00 2001 From: Daisuke Maki Date: Wed, 20 Nov 2013 15:58:31 +0900 Subject: [PATCH 03/39] Forget Provider, use Loader. * about 70% of the tests are passing as of this writing --- lib/Text/Xslate.pm | 53 ++-- lib/Text/Xslate/Compiler.pm | 1 + lib/Text/Xslate/Loader/File.pm | 431 ++++++++++++++++++++++++++++ lib/Text/Xslate/PP/Opcode.pm | 1 + lib/Text/Xslate/Provider/Default.pm | 321 --------------------- src/Text-Xslate.xs | 1 + src/xslate_opcode.inc | 1 + t/010_internals/005_load_file.t | 12 +- 8 files changed, 479 insertions(+), 342 deletions(-) create mode 100644 lib/Text/Xslate/Loader/File.pm delete mode 100644 lib/Text/Xslate/Provider/Default.pm diff --git a/lib/Text/Xslate.pm b/lib/Text/Xslate.pm index c0d2362e..0c7c4602 100644 --- a/lib/Text/Xslate.pm +++ b/lib/Text/Xslate.pm @@ -29,7 +29,7 @@ BEGIN { our @ISA = qw(Text::Xslate::Engine); -my $BYTECODE_VERSION = '1.6'; +my $BYTECODE_VERSION = '2.0'; # $bytecode_version + $fullpath + $compiler_and_parser_options our $XSLATE_MAGIC = qq{xslate;$BYTECODE_VERSION;%s;%s;}; @@ -56,6 +56,7 @@ sub USE_XS() { $use_xs } sub input_layer { ref($_[0]) ? $_[0]->{input_layer} : ':utf8' } package Text::Xslate::Engine; # XS/PP common base class +use Mouse; use Text::Xslate::Util qw( make_error @@ -146,7 +147,7 @@ sub options { # overridable function => undef, html_builder_module => undef, compiler => 'Text::Xslate::Compiler', - provider => 'Text::Xslate::Provider::Default', + loader => 'Text::Xslate::Loader::File', verbose => 1, warn_handler => undef, @@ -262,6 +263,26 @@ sub _resolve_function_aliases { return; } +has magic_template => ( + is => 'ro', + lazy => 1, + builder => 'build_magic_template', +); + +sub build_magic_template { + my $self = shift; + # You need to add some instance specific magic + my @options = ( + ref($self->{compiler}) || $self->{compiler}, + $self->_filter_options_for_magic_token($self->_extract_options($self->parser_option)), + $self->_filter_options_for_magic_token($self->_extract_options($self->compiler_option)), + $self->input_layer, + [sort keys %{ $self->{function} }], + ); + return sprintf qq{xslate;$BYTECODE_VERSION;%%s;%s;}, + Data::MessagePack->pack([@options]) +} + sub load_string { # called in render_string() my($self, $string) = @_; if(not defined $string) { @@ -278,17 +299,20 @@ sub load_string { # called in render_string() sub find_file { my ($self, $file) = @_; - $self->_provider->find_file($self, $file); + $self->_loader->locate_file($file); } +# XXX To be deprecated ? +# The interface is too specific for file-based templates sub load_file { my($self, $file, $mtime, $omit_augment) = @_; - $self->_provider->load_file($self, $file, $mtime, $omit_augment); + my $asm = $self->_loader->load($file); + return $asm; } sub slurp_template { my($self, $input_layer, $fullpath) = @_; - $self->_provider->slurp_template($self, $input_layer, $fullpath); + $self->_loader->slurp_template($input_layer, $fullpath); } sub _digest { @@ -322,16 +346,16 @@ sub _filter_options_for_magic_token { @filterd_options; } -sub _provider { +sub _loader { my $self = shift; - my $provider = $self->{provider}; - if (!ref $provider) { + my $loader = $self->{loader}; + if (!ref $loader) { require Mouse; - Mouse::load_class($provider); - $provider = $provider->build($self); - $self->{provider} = $provider; + Mouse::load_class($loader); + $loader = $loader->build($self); + $self->{loader} = $loader; } - return $provider; + return $loader; } sub _compiler { @@ -374,11 +398,6 @@ sub _error { sub _magic_token_arguments { my $self = shift; return ( - ref($self->{compiler}) || $self->{compiler}, - $self->_filter_options_for_magic_token($self->_extract_options($self->parser_option)), - $self->_filter_options_for_magic_token($self->_extract_options($self->compiler_option)), - $self->input_layer, - [sort keys %{ $self->{function} }], ); } diff --git a/lib/Text/Xslate/Compiler.pm b/lib/Text/Xslate/Compiler.pm index a40efb65..fd6ce79f 100644 --- a/lib/Text/Xslate/Compiler.pm +++ b/lib/Text/Xslate/Compiler.pm @@ -313,6 +313,7 @@ sub compile { my $ast = $parser->parse($input, %args); print STDERR p($ast) if _DUMP_AST; @code = ( + $self->opcode(meta => { utf8 => utf8::is_utf8($input) }), $self->opcode(set_opinfo => undef, file => $self->current_file, line => 1), $self->compile_ast($ast), $self->opcode('end'), diff --git a/lib/Text/Xslate/Loader/File.pm b/lib/Text/Xslate/Loader/File.pm new file mode 100644 index 00000000..7fab31e0 --- /dev/null +++ b/lib/Text/Xslate/Loader/File.pm @@ -0,0 +1,431 @@ +package Text::Xslate::Loader::File; +use Mouse; +use Data::MessagePack; +use Digest::MD5 (); +use File::Spec; +use File::Temp (); +use Log::Minimal; +use constant ST_MTIME => 9; + +use Text::Xslate::Util (); + +has assemble_cb => ( + is => 'ro', + required => 1, +); + +has cache_dir => ( + is => 'ro', + required => 1, +); + +has cache_strategy => ( + is => 'ro', + default => 1, +); + +has compiler => ( + is => 'ro', + required => 1, +); + +has include_dirs => ( + is => 'ro', + required => 1, +); + +has input_layer => ( + is => 'ro', + required => 1, + default => ':utf8', +); + +has pre_process_handler => ( + is => 'ro', +); + +has magic_template => ( + is => 'ro', + required => 1, +); + +sub build { + my ($class, $engine) = @_; + my $self = $class->new( + assemble_cb => sub { $engine->_assemble(@_) }, + cache_dir => Cwd::abs_path($engine->{cache_dir}), + cache_strategy => $engine->{cache}, + compiler => $engine->_compiler, + include_dirs => $engine->{path}, + input_layer => $engine->input_layer, + magic_template => $engine->magic_template, + ); + return $self; +} + +sub load { + my ($self, $name) = @_; + + # On a file system, we need to check for + # 1) does the file exist in fs? + # 2) if so, keep it's mtime + # 3) check against mtime of the cache + + # XXX if the file cannot be located in the filesystem, + # then we go kapot, so no check for defined $fi + my $fi = $self->locate_file($name); + + # Okay, the source exists. Now consider the cache. + # $cache_strategy >= 2, use cache w/o checking for freshness + # $cache_strategy == 1, use cache if cache is fresh + # $cache_strategy == 0, ignore cache + + # $cached_ent is an object with mtime and asm + my $cached_ent; + my $cache_strategy = $self->cache_strategy; + if ($cache_strategy > 0) { + # It's okay to fail + my $cachepath = $fi->cachepath; + $cached_ent = eval { $self->load_cached($fi) }; + if (my $e = $@) { + warnf("Failed to load compiled cache from %s (%s)", + $cachepath, + $e + ); + } + } + + if ($cached_ent) { + if ($cache_strategy > 1) { + # We're careless! We just want to use the cached + # version! Go! Go! Go! + return $cached_ent->asm; + } + + # Otherwise check for freshness + if ($cached_ent->is_fresher_than($fi)) { + # Hooray, our cached version is newer than the + # source file! cheers! jubilations! + return $cached_ent->asm; + } + + # if you got here, too bad: cache is invalid. + # it doesn't mean anything, but we say bye-bye + # to the cached entity just to console our broken hearts + undef $cached_ent; + } + + # If you got here, either the cache_strategy was 0 or the cache + # was invalid. load from source + my $asm = $self->load_file($fi); + + # store cache, if necessary + my $cache_mtime; # XXX Should this be here? + if ($cache_strategy > 0) { + $cache_mtime = $self->store_cache($fi, $asm); + } + + $self->assemble($asm, $name, $fi->fullpath, $fi->cachepath, $cache_mtime); + return $asm; +} + +sub assemble { +# my ($self, $asm, $file, $fullpath, $cachepath, $mtime) = @_; + my $self = shift; + $self->assemble_cb->(@_); +} + +# Given a list of include directories, looks for a matching file path +# Returns a FileInfo object +my $updir = File::Spec->updir; +sub locate_file { + my ($self, $name) = @_; + + if($name =~ /\Q$updir\E/xmso) { + die("LoadError: Forbidden component (updir: '$updir') found in file name '$name'"); + } + + my $dirs = $self->include_dirs; + my ($fullpath, $mtime, $cache_prefix); + foreach my $dir (@$dirs) { + if (ref $dir eq 'HASH') { + # XXX need to implement virtual paths + my $content = $dir->{$name}; + if (! defined($content)) { + next; + } + $fullpath = \$content; + + # NOTE: + # Because contents of virtual paths include their digest, + # time-dependent cache verifier makes no sense. + $mtime = 0; +# $cache_mtime = 0; + $cache_prefix = 'HASH'; + } else { + $fullpath = File::Spec->catfile($dir, $name); + $mtime = (stat($fullpath))[ST_MTIME()]; + + if (! defined $mtime) { + next; + } + + $cache_prefix = Text::Xslate::Util::uri_escape($name); + if (length $cache_prefix > 127) { + # some filesystems refuse a path part with length > 127 + $cache_prefix = $self->_digest($cache_prefix); + } + } + + # If it got here, $fullpath should exist + return Text::Xslate::Loader::File::FileInfo->new( + magic_template => $self->magic_template, + name => ref($fullpath) ? $name : $fullpath, + fullpath => $fullpath, + cachepath => File::Spec->catfile( + $self->cache_dir, + $cache_prefix, + $name . 'c', + ), + mtime => $mtime, +# cache_mtime => $cache_mtime, + ); + } + +# $engine->_error("LoadError: Cannot find '$file' (path: @{$self->{path}})"); + die "LoadError: Cannot find '$name' (include dirs: @$dirs)"; +} + +# Loads the compiled code from cache. Requires the full path +# to the cached file location +sub load_cached { + my ($self, $fi) = @_; + + my $filename = $fi->cachepath; + my $mtime = (stat($filename))[ST_MTIME()]; + if (! defined $mtime) { + # stat failed. cache isn't there. sayonara + return; + } + + # We stop processing here, because we want to be lazy about + # checking the validity of the included templates. In order to + # check for the freshness, we need to check against a known + # time, which is only provided later. + return Text::Xslate::Loader::File::CachedEntity->new( + mtime => $mtime, + magic => $fi->magic, + filename => $filename, + ); +} + +# Loads compile code from file. The return value is an object +# which contains "asm", and other metadata +sub load_file { + my ($self, $fi) = @_; + + my $filename = $fi->fullpath; + my $data; + { + open my $source, '<' . $self->input_layer, $filename +# or $engine->_error("LoadError: Cannot open $fullpath for reading: $!"); + or Carp::confess("LoadError: Cannot open $filename for reading: $!"); + local $/; + $data = <$source>; + } + + if (my $cb = $self->pre_process_handler) { + $data = $cb->($data); + } + + return $self->compiler->compile($data, file => $filename); +} + +sub store_cache { + my ($self, $fi, $asm) = @_; + + my $path = $fi->cachepath; + my($volume, $dir) = File::Spec->splitpath($path); + my $cachedir = File::Spec->catpath($volume, $dir, ''); + + if(!-e $cachedir) { + require File::Path; + eval { File::Path::mkpath($cachedir) } + or Carp::croak("Xslate: Cannot prepare cache directory $path (ignored): $@"); + } + + my $temp = File::Temp->new( + TEMPLATE => "xslateXXXX", + DIR => $cachedir, + UNLINK => 0, + ); + binmode($temp, ':raw'); + + my $newest_mtime = 0; + eval { + my $mp = Data::MessagePack->new(); + local $\; + print $temp $fi->magic(); + foreach my $c(@{$asm}) { + print $temp $mp->pack($c); + + if ($c->[0] eq 'depend') { + my $dep_mtime = (stat $c->[1])[ST_MTIME()]; + if ($newest_mtime < $dep_mtime) { + $newest_mtime = $dep_mtime; + } + } + } + $temp->close; + }; + if (my $e = $@) { + $temp->unlink_on_destroy(1); + die $e; + } + + if (! rename($temp->filename, $path)) { + Carp::carp("Xslate: Cannot rename cache file $path (ignored): $!"); + } + return $newest_mtime; +} + +package + Text::Xslate::Loader::File::FileInfo; +use Mouse; + +has name => (is => 'ro'); +has fullpath => (is => 'ro'); +has cachepath => (is => 'ro'); +has mtime => (is => 'ro'); +has magic_template => (is => 'ro', required => 1); +has magic => (is => 'ro', lazy => 1, builder => 'build_magic'); + +sub build_magic { + my $self = shift; + + my $fullpath = $self->fullpath; + if (ref $fullpath) { # ref to content string + $fullpath = join ":", + ref $fullpath, + Digest::MD5::md5_hex(utf8::encode($fullpath)) + } + return sprintf $self->magic_template, $fullpath; +} + +package + Text::Xslate::Loader::File::CachedEntity; +use Mouse; + +has asm => (is => 'rw'); +has filename => (is => 'ro', required => 1); +has magic => (is => 'ro', required => 1); +has mtime => (is => 'ro', required => 1); # Main file's mtime + +sub is_fresher_than { + my ($self, $threshold) = @_; + + if ($self->mtime < $threshold) { + return; + } + + my $filename = $self->filename; + open my($in), '<:raw', $filename + or die "LoadError: Cannot open $filename for reading: $!"; +# or $engine->_error("LoadError: Cannot open $filename for reading: $!"); + + my $data; + + # Check against magic header. + my $magic = $self->magic; + read $in, $data, length($magic); + if (! defined $data || $data ne $magic) { + warnf("Magic mismatch %x != %x", $data, $magic); + return; + } + + # slurp the rest of the file + { + local $/; + $data = <$in>; + close $in; + } + + # Now we need to check for the freshness of this compiled code + # RECURSIVELY. i.e., included templates must be checked as well + my $unpacker = Data::MessagePack::Unpacker->new(); + + # The first token is the metadata + my $offset = $unpacker->execute($data); + my $meta = $unpacker->data(); + my $is_utf8 = $meta->{utf8}; + $unpacker->reset(); + + $unpacker->utf8($is_utf8); + + my @asm; + if($is_utf8) { # TODO: move to XS? + my $seed = ""; + utf8::upgrade($seed); + push @asm, ['print_raw_s', $seed, __LINE__, __FILE__]; + } + while($offset < length($data)) { + $offset = $unpacker->execute($data, $offset); + my $c = $unpacker->data(); + $unpacker->reset(); + + # my($name, $arg, $line, $file, $symbol) = @{$c}; + if($c->[0] eq 'depend') { + my $dep_mtime = (stat $c->[1])[Text::Xslate::Engine::_ST_MTIME()]; + if(!defined $dep_mtime) { + Carp::carp("Xslate: Failed to stat $c->[1] (ignored): $!"); + return undef; # purge the cache + } + if($dep_mtime > $threshold){ + $self->note(" _load_compiled: %s(%s) is newer than %s(%s)\n", + $c->[1], scalar localtime($dep_mtime), + $filename, scalar localtime($threshold) ) + if Text::Xslate::Engine::_DUMP_LOAD(); + return undef; # purge the cache + } + } + elsif($c->[0] eq 'literal') { + # force upgrade to avoid UTF-8 key issues + utf8::upgrade($c->[1]) if($is_utf8); + } + push @asm, $c; + } + + $self->asm(\@asm); +} + +1; + +__END__ + +=head1 SYNOPSIS + + package Text::Xslate; + ... + use Text::Xslate::Loader::File; + + has loader => ( + is => 'ro', + lazy => 1, + builder => 'build_loader', + ); + + sub build_loader { + my $loader = Text::Xslate::Loader::File->new( + cache_dir => "/path/to/cache", + cache_strategy => 1, + compiler => $self->compiler, + include_dirs => [ "/path/to/dir1", "/path/to/dir2" ], + input_layer => $self->input_layer, + # serial_optってのがあったけど、あとでやる + ); + } + + sub load_file { + my ($self, $file) = @_; + my $asm = $loader->load($file); + } diff --git a/lib/Text/Xslate/PP/Opcode.pm b/lib/Text/Xslate/PP/Opcode.pm index ca163667..0cb8d199 100644 --- a/lib/Text/Xslate/PP/Opcode.pm +++ b/lib/Text/Xslate/PP/Opcode.pm @@ -36,6 +36,7 @@ sub op_noop { goto $_[0]->{ code }->[ ++$_[0]->{ pc } ]->{ exec_code }; } +*op_meta = \&op_noop; sub op_move_to_sb { $_[0]->{sb} = $_[0]->{sa}; diff --git a/lib/Text/Xslate/Provider/Default.pm b/lib/Text/Xslate/Provider/Default.pm deleted file mode 100644 index b24e0818..00000000 --- a/lib/Text/Xslate/Provider/Default.pm +++ /dev/null @@ -1,321 +0,0 @@ -package Text::Xslate::Provider::Default; -use strict; -use Text::Xslate (); - -sub new { - my $class = shift; - my %args = (@_ == 1 ? %{$_[0]} : @_); - return bless {%args}, $class; -} - -sub build { - my ($class, $engine) = @_; - - $class->new( - cache => $engine->{cache}, - cache_dir => $engine->{cache_dir}, - path => $engine->{path}, - ); -} - -my $updir = File::Spec->updir; -sub find_file { - my($self, $engine, $file) = @_; - - if($file =~ /\Q$updir\E/xmso) { - $engine->_error("LoadError: Forbidden component (updir: '$updir') found in file name '$file'"); - } - - my $fullpath; - my $cachepath; - my $orig_mtime; - my $cache_mtime; - foreach my $p(@{$self->{path}}) { - $self->note(" find_file: %s in %s ...\n", $file, $p) if Text::Xslate::Engine::_DUMP_LOAD(); - - my $cache_prefix; - if(ref $p eq 'HASH') { # virtual path - defined(my $content = $p->{$file}) or next; - $fullpath = \$content; - - # NOTE: - # Because contents of virtual paths include their digest, - # time-dependent cache verifier makes no sense. - $orig_mtime = 0; - $cache_mtime = 0; - $cache_prefix = 'HASH'; - } - else { - $fullpath = File::Spec->catfile($p, $file); - defined($orig_mtime = (stat($fullpath))[Text::Xslate::Engine::_ST_MTIME()]) - or next; - $cache_prefix = Text::Xslate::uri_escape($p); - if (length $cache_prefix > 127) { - # some filesystems refuse a path part with length > 127 - $cache_prefix = $self->_digest($cache_prefix); - } - } - - # $file is found - $cachepath = File::Spec->catfile( - $self->{cache_dir}, - $cache_prefix, - $file . 'c', - ); - # stat() will be failed if the cache doesn't exist - $cache_mtime = (stat($cachepath))[Text::Xslate::Engine::_ST_MTIME()]; - last; - } - - if(not defined $orig_mtime) { - $engine->_error("LoadError: Cannot find '$file' (path: @{$self->{path}})"); - } - - $self->note(" find_file: %s (mtime=%d)\n", - $fullpath, $cache_mtime || 0) if Text::Xslate::Engine::_DUMP_LOAD(); - - return { - name => ref($fullpath) ? $file : $fullpath, - fullpath => $fullpath, - cachepath => $cachepath, - - orig_mtime => $orig_mtime, - cache_mtime => $cache_mtime, - }; -} - - -sub load_file { - my($self, $engine, $file, $mtime, $omit_augment) = @_; - - local $self->{omit_augment} = $omit_augment; - - $self->note("%s->load_file(%s)\n", $self, $file) if Text::Xslate::Engine::_DUMP_LOAD(); - - if($file eq '') { # simply reload it - return $engine->load_string($self->{string_buffer}); - } - - my $fi = $self->find_file($engine, $file); - - my $asm = $self->_load_compiled($engine, $fi, $mtime) || - $self->_load_source($engine, $fi, $mtime); - - # $cache_mtime is undef : uses caches without any checks - # $cache_mtime > 0 : uses caches with mtime checks - # $cache_mtime == 0 : doesn't use caches - my $cache_mtime; - if($self->{cache} < 2) { - $cache_mtime = $fi->{cache_mtime} || 0; - } - - $engine->_assemble($asm, $file, $fi->{fullpath}, $fi->{cachepath}, $cache_mtime); - return $asm; -} - -sub slurp_template { - my($self, $engine, $input_layer, $fullpath) = @_; - - if (ref $fullpath eq 'SCALAR') { - return $$fullpath; - } else { - open my($source), '<' . $input_layer, $fullpath - or $engine->_error("LoadError: Cannot open $fullpath for reading: $!"); - local $/; - return scalar <$source>; - } -} - -sub _load_source { - my($self, $engine, $fi) = @_; - my $fullpath = $fi->{fullpath}; - my $cachepath = $fi->{cachepath}; - - $self->note(" _load_source: try %s ...\n", $fullpath) if Text::Xslate::Engine::_DUMP_LOAD(); - - # This routine is called when the cache is no longer valid (or not created yet) - # so it should be ensured that the cache, if exists, does not exist - if(-e $cachepath) { - unlink $cachepath - or Carp::carp("Xslate: cannot unlink $cachepath (ignored): $!"); - } - - my $source = $self->slurp_template($engine, $engine->input_layer, $fullpath); - $source = $self->{pre_process_handler}->($source) if $self->{pre_process_handler}; - $self->{source}{$fi->{name}} = $source if Text::Xslate::Engine::_SAVE_SRC(); - - my $asm = $engine->compile($source, - file => $fullpath, - name => $fi->{name}, - ); - - if($self->{cache} >= 1) { - my($volume, $dir) = File::Spec->splitpath($fi->{cachepath}); - my $cachedir = File::Spec->catpath($volume, $dir, ''); - if(not -e $cachedir) { - require File::Path; - eval { File::Path::mkpath($cachedir) } - or Carp::croak("Xslate: Cannot prepare cache directory $cachepath (ignored): $@"); - } - - my $tmpfile = sprintf('%s.%d.d', $cachepath, $$, $self); - - if (open my($out), ">:raw", $tmpfile) { - my $mtime = $self->_save_compiled($engine, $out, $asm, $fullpath, utf8::is_utf8($source)); - - if(!close $out) { - Carp::carp("Xslate: Cannot close $cachepath (ignored): $!"); - unlink $tmpfile; - } - elsif (rename($tmpfile => $cachepath)) { - # set the newest mtime of all the related files to cache mtime - if (not ref $fullpath) { - my $main_mtime = (stat $fullpath)[Text::Xslate::Engine::_ST_MTIME()]; - if (defined($main_mtime) && $main_mtime > $mtime) { - $mtime = $main_mtime; - } - utime $mtime, $mtime, $cachepath; - $fi->{cache_mtime} = $mtime; - } - else { - $fi->{cache_mtime} = (stat $cachepath)[Text::Xslate::Engine::_ST_MTIME()]; - } - } - else { - Carp::carp("Xslate: Cannot rename cache file $cachepath (ignored): $!"); - unlink $tmpfile; - } - } - else { - Carp::carp("Xslate: Cannot open $cachepath for writing (ignored): $!"); - } - } - if(Text::Xslate::Engine::_DUMP_LOAD()) { - $self->note(" _load_source: cache(mtime=%s)\n", - defined $fi->{cache_mtime} ? $fi->{cache_mtime} : 'undef'); - } - - return $asm; -} - -# load compiled templates if they are fresh enough -sub _load_compiled { - my($self, $engine, $fi, $threshold) = @_; - - if($self->{cache} >= 2) { - # threshold is the most latest modified time of all the related caches, - # so if the cache level >= 2, they seems always fresh. - $threshold = 9**9**9; # force to purge the cache - } - else { - $threshold ||= $fi->{cache_mtime}; - } - # see also tx_load_template() in xs/Text-Xslate.xs - if(!( defined($fi->{cache_mtime}) and $self->{cache} >= 1 - and $threshold >= $fi->{orig_mtime} )) { - $self->note( " _load_compiled: no fresh cache: %s, %s", - $threshold || 0, Text::Xslate::Util::p($fi) ) if Text::Xslate::Engine::_DUMP_LOAD(); - $fi->{cache_mtime} = undef; - return undef; - } - - my $cachepath = $fi->{cachepath}; - open my($in), '<:raw', $cachepath - or $engine->_error("LoadError: Cannot open $cachepath for reading: $!"); - - my $magic = $self->_magic_token($engine, $fi->{fullpath}); - my $data; - read $in, $data, length($magic); - if($data ne $magic) { - return undef; - } - else { - local $/; - $data = <$in>; - close $in; - } - my $unpacker = Data::MessagePack::Unpacker->new(); - my $offset = $unpacker->execute($data); - my $is_utf8 = $unpacker->data(); - $unpacker->reset(); - - $unpacker->utf8($is_utf8); - - my @asm; - if($is_utf8) { # TODO: move to XS? - my $seed = ""; - utf8::upgrade($seed); - push @asm, ['print_raw_s', $seed, __LINE__, __FILE__]; - } - while($offset < length($data)) { - $offset = $unpacker->execute($data, $offset); - my $c = $unpacker->data(); - $unpacker->reset(); - - # my($name, $arg, $line, $file, $symbol) = @{$c}; - if($c->[0] eq 'depend') { - my $dep_mtime = (stat $c->[1])[Text::Xslate::Engine::_ST_MTIME()]; - if(!defined $dep_mtime) { - Carp::carp("Xslate: Failed to stat $c->[1] (ignored): $!"); - return undef; # purge the cache - } - if($dep_mtime > $threshold){ - $self->note(" _load_compiled: %s(%s) is newer than %s(%s)\n", - $c->[1], scalar localtime($dep_mtime), - $cachepath, scalar localtime($threshold) ) - if Text::Xslate::Engine::_DUMP_LOAD(); - return undef; # purge the cache - } - } - elsif($c->[0] eq 'literal') { - # force upgrade to avoid UTF-8 key issues - utf8::upgrade($c->[1]) if($is_utf8); - } - push @asm, $c; - } - - if(Text::Xslate::Engine::_DUMP_LOAD()) { - $self->note(" _load_compiled: cache(mtime=%s)\n", - defined $fi->{cache_mtime} ? $fi->{cache_mtime} : 'undef'); - } - - return \@asm; -} - -sub _save_compiled { - my($self, $engine, $out, $asm, $fullpath, $is_utf8) = @_; - my $mp = Data::MessagePack->new(); - local $\; - print $out $self->_magic_token($engine, $fullpath); - print $out $mp->pack($is_utf8 ? 1 : 0); - - my $newest_mtime = 0; - foreach my $c(@{$asm}) { - print $out $mp->pack($c); - - if ($c->[0] eq 'depend') { - my $dep_mtime = (stat $c->[1])[Text::Xslate::Engine::_ST_MTIME()]; - if ($newest_mtime < $dep_mtime) { - $newest_mtime = $dep_mtime; - } - } - } - return $newest_mtime; -} - -sub _magic_token { - my($self, $engine, $fullpath) = @_; - - $engine->{serial_opt} ||= Data::MessagePack->pack([ - $engine->_magic_token_arguments() - ]); - - if(ref $fullpath) { # ref to content string - $fullpath = join ':', ref($fullpath), - $engine->_digest(${$fullpath}); - } -Carp::confess("serial_port undefined") if ! defined $engine->{serial_opt}; - return sprintf $Text::Xslate::XSLATE_MAGIC, $fullpath, $engine->{serial_opt}; -} - -1; diff --git a/src/Text-Xslate.xs b/src/Text-Xslate.xs index 0f20884b..eae63a26 100644 --- a/src/Text-Xslate.xs +++ b/src/Text-Xslate.xs @@ -11,6 +11,7 @@ #define TXCODE_macro_begin TXCODE_noop #define TXCODE_macro_nargs TXCODE_noop #define TXCODE_macro_outer TXCODE_noop +#define TXCODE_meta TXCODE_noop #define TXCODE_set_opinfo TXCODE_noop #define TXCODE_super TXCODE_noop diff --git a/src/xslate_opcode.inc b/src/xslate_opcode.inc index f42617ab..7a7c1988 100644 --- a/src/xslate_opcode.inc +++ b/src/xslate_opcode.inc @@ -747,6 +747,7 @@ TXC_w_sviv(macro_nargs); TXC_w_sviv(macro_outer); TXC(set_opinfo); TXC(super); +TXC_w_sv(meta); /* "end" must be here (the last opcode) */ TXC(end) { diff --git a/t/010_internals/005_load_file.t b/t/010_internals/005_load_file.t index 50a118f2..5979e630 100644 --- a/t/010_internals/005_load_file.t +++ b/t/010_internals/005_load_file.t @@ -29,6 +29,10 @@ like $@, qr/LoadError/xms, "load_file -> LoadError"; like $@, qr/\b no_such_file \b/xms, "include the filename"; my $cache = $tx->find_file('hello.tx')->{cachepath}; + +# forcefully load +$tx->load_file('hello.tx'); + ok -e $cache, "$cache exists"; open my($out), '>', $cache; print $out "This is a broken txc file\n"; @@ -54,14 +58,14 @@ $tx = Text::Xslate->new( ); my $fi = $tx->find_file('foo.tx'); -ok !defined($fi->{cache_mtime}) - or diag explain($fi); +#ok !defined($fi->{cache_mtime}) +# or diag explain($fi); $tx->load_file('foo.tx'); $fi = $tx->find_file('foo.tx'); -ok defined($fi->{cache_mtime}) - or diag explain($fi); +#ok defined($fi->{cache_mtime}) +# or diag explain($fi); eval { $tx->find_file(File::Spec->catfile(File::Spec->updir, 'foo.tx')); From 9eaf25e3a2240fb240573e3c618924361fc2504b Mon Sep 17 00:00:00 2001 From: Daisuke Maki Date: Wed, 20 Nov 2013 18:08:25 +0900 Subject: [PATCH 04/39] Revive slurp_template --- lib/Text/Xslate.pm | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/lib/Text/Xslate.pm b/lib/Text/Xslate.pm index 0c7c4602..965f22a8 100644 --- a/lib/Text/Xslate.pm +++ b/lib/Text/Xslate.pm @@ -13,7 +13,6 @@ use Data::MessagePack (); use Scalar::Util (); use Text::Xslate::Util (); -use Text::Xslate::Provider::Default; BEGIN { # all the exportable functions are defined in ::Util our @EXPORT_OK = qw( @@ -312,7 +311,15 @@ sub load_file { sub slurp_template { my($self, $input_layer, $fullpath) = @_; - $self->_loader->slurp_template($input_layer, $fullpath); + + if (ref $fullpath eq 'SCALAR') { + return $$fullpath; + } else { + open my($source), '<' . $input_layer, $fullpath + or $self->_error("LoadError: Cannot open $fullpath for reading: $!"); + local $/; + return scalar <$source>; + } } sub _digest { From 61ef6db58a1e9f7ed45f359186d6d85368a1a3d0 Mon Sep 17 00:00:00 2001 From: Daisuke Maki Date: Wed, 20 Nov 2013 18:08:35 +0900 Subject: [PATCH 05/39] Use File::Spec->rel2abs instead of Cwd::abs_path --- lib/Text/Xslate/Loader/File.pm | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/Text/Xslate/Loader/File.pm b/lib/Text/Xslate/Loader/File.pm index 7fab31e0..30938249 100644 --- a/lib/Text/Xslate/Loader/File.pm +++ b/lib/Text/Xslate/Loader/File.pm @@ -53,7 +53,9 @@ sub build { my ($class, $engine) = @_; my $self = $class->new( assemble_cb => sub { $engine->_assemble(@_) }, - cache_dir => Cwd::abs_path($engine->{cache_dir}), + # XXX Cwd::abs_path would stat() the directory, so we need to + # to use File::Spec->rel2abs + cache_dir => File::Spec->rel2abs($engine->{cache_dir}), cache_strategy => $engine->{cache}, compiler => $engine->_compiler, include_dirs => $engine->{path}, From 2cf9385a31ed7cc7a2497a19257ed52030f8e735 Mon Sep 17 00:00:00 2001 From: Daisuke Maki Date: Wed, 20 Nov 2013 18:14:32 +0900 Subject: [PATCH 06/39] use the correct cache prefix --- lib/Text/Xslate/Loader/File.pm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/Text/Xslate/Loader/File.pm b/lib/Text/Xslate/Loader/File.pm index 30938249..3c8b3d4a 100644 --- a/lib/Text/Xslate/Loader/File.pm +++ b/lib/Text/Xslate/Loader/File.pm @@ -172,7 +172,7 @@ sub locate_file { next; } - $cache_prefix = Text::Xslate::Util::uri_escape($name); + $cache_prefix = Text::Xslate::Util::uri_escape($dir); if (length $cache_prefix > 127) { # some filesystems refuse a path part with length > 127 $cache_prefix = $self->_digest($cache_prefix); From c07530de478aa46c7106c8db14720e9efae694a4 Mon Sep 17 00:00:00 2001 From: Daisuke Maki Date: Thu, 21 Nov 2013 07:06:49 +0900 Subject: [PATCH 07/39] Change default cache loc --- lib/Text/Xslate.pm | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/lib/Text/Xslate.pm b/lib/Text/Xslate.pm index 965f22a8..f0fbf836 100644 --- a/lib/Text/Xslate.pm +++ b/lib/Text/Xslate.pm @@ -56,6 +56,7 @@ sub input_layer { ref($_[0]) ? $_[0]->{input_layer} : ':utf8' } package Text::Xslate::Engine; # XS/PP common base class use Mouse; +use File::Spec; use Text::Xslate::Util qw( make_error @@ -74,13 +75,8 @@ BEGIN { *_ST_MTIME = sub() { 9 }; # see perldoc -f stat - my $cache_dir = '.xslate_cache'; - foreach my $d($ENV{HOME}, File::Spec->tmpdir) { - if(defined($d) and -d $d and -w _) { - $cache_dir = File::Spec->catfile($d, '.xslate_cache'); - last; - } - } + my $temp_base = $ENV{TEMPDIR} || File::Spec->tmpdir; + my $cache_dir = File::Spec->catdir($temp_base, 'xslate_cache'); *_DEFAULT_CACHE_DIR = sub() { $cache_dir }; } From 6b292bb81aa9545ea56ae38c39a1a19c84e22002 Mon Sep 17 00:00:00 2001 From: Daisuke Maki Date: Thu, 21 Nov 2013 07:07:20 +0900 Subject: [PATCH 08/39] fix cache handling --- lib/Text/Xslate/Loader/File.pm | 69 +++++++++++++++++++++++++--------- 1 file changed, 52 insertions(+), 17 deletions(-) diff --git a/lib/Text/Xslate/Loader/File.pm b/lib/Text/Xslate/Loader/File.pm index 3c8b3d4a..9b375e77 100644 --- a/lib/Text/Xslate/Loader/File.pm +++ b/lib/Text/Xslate/Loader/File.pm @@ -2,6 +2,7 @@ package Text::Xslate::Loader::File; use Mouse; use Data::MessagePack; use Digest::MD5 (); +use File::Copy (); use File::Spec; use File::Temp (); use Log::Minimal; @@ -68,6 +69,8 @@ sub build { sub load { my ($self, $name) = @_; + debugf("Loading %s", $name); + # On a file system, we need to check for # 1) does the file exist in fs? # 2) if so, keep it's mtime @@ -85,30 +88,35 @@ sub load { # $cached_ent is an object with mtime and asm my $cached_ent; my $cache_strategy = $self->cache_strategy; + debugf("Cache strategy is %d", $cache_strategy); if ($cache_strategy > 0) { # It's okay to fail - my $cachepath = $fi->cachepath; $cached_ent = eval { $self->load_cached($fi) }; if (my $e = $@) { warnf("Failed to load compiled cache from %s (%s)", - $cachepath, + $fi->cachepath, $e ); } } + my $asm; if ($cached_ent) { if ($cache_strategy > 1) { # We're careless! We just want to use the cached # version! Go! Go! Go! - return $cached_ent->asm; + debugf("Freshness check disabled, and cache exists. Just use it"); + $asm = $cached_ent->asm; + goto ASSEMBLE_AND_RETURN; } # Otherwise check for freshness if ($cached_ent->is_fresher_than($fi)) { # Hooray, our cached version is newer than the # source file! cheers! jubilations! - return $cached_ent->asm; + debugf("Freshness check passed, returning asm"); + $asm = $cached_ent->asm; + goto ASSEMBLE_AND_RETURN; } # if you got here, too bad: cache is invalid. @@ -119,7 +127,7 @@ sub load { # If you got here, either the cache_strategy was 0 or the cache # was invalid. load from source - my $asm = $self->load_file($fi); + $asm = $self->load_file($fi); # store cache, if necessary my $cache_mtime; # XXX Should this be here? @@ -127,6 +135,7 @@ sub load { $cache_mtime = $self->store_cache($fi, $asm); } +ASSEMBLE_AND_RETURN: $self->assemble($asm, $name, $fi->fullpath, $fi->cachepath, $cache_mtime); return $asm; } @@ -143,6 +152,8 @@ my $updir = File::Spec->updir; sub locate_file { my ($self, $name) = @_; + debugf("locate_file: looking for %s", $name); + if($name =~ /\Q$updir\E/xmso) { die("LoadError: Forbidden component (updir: '$updir') found in file name '$name'"); } @@ -150,6 +161,7 @@ sub locate_file { my $dirs = $self->include_dirs; my ($fullpath, $mtime, $cache_prefix); foreach my $dir (@$dirs) { + debugf("Checking in %s", $dir); if (ref $dir eq 'HASH') { # XXX need to implement virtual paths my $content = $dir->{$name}; @@ -204,9 +216,12 @@ sub load_cached { my ($self, $fi) = @_; my $filename = $fi->cachepath; + + debugf("load_cached: %s", $filename); my $mtime = (stat($filename))[ST_MTIME()]; if (! defined $mtime) { # stat failed. cache isn't there. sayonara + debugf("load_cached: file %s does not exist", $filename); return; } @@ -247,22 +262,27 @@ sub store_cache { my ($self, $fi, $asm) = @_; my $path = $fi->cachepath; + debugf("Storing cache in %s", $path); my($volume, $dir) = File::Spec->splitpath($path); my $cachedir = File::Spec->catpath($volume, $dir, ''); if(!-e $cachedir) { + debugf("Directory %s does not exist", $cachedir); require File::Path; - eval { File::Path::mkpath($cachedir) } - or Carp::croak("Xslate: Cannot prepare cache directory $path (ignored): $@"); + if (! File::Path::make_path($cachedir) || ! -d $cachedir) { + Carp::croak("Xslate: Cannot prepare cache directory $path (ignored): $@"); + } } my $temp = File::Temp->new( - TEMPLATE => "xslateXXXX", + TEMPLATE => "xslate-XXXX", DIR => $cachedir, UNLINK => 0, ); binmode($temp, ':raw'); + debugf("Temporary file in %s", $temp); + my $newest_mtime = 0; eval { my $mp = Data::MessagePack->new(); @@ -278,6 +298,7 @@ sub store_cache { } } } + $temp->flush; $temp->close; }; if (my $e = $@) { @@ -285,9 +306,11 @@ sub store_cache { die $e; } - if (! rename($temp->filename, $path)) { + if (! File::Copy::move($temp->filename, $path)) { Carp::carp("Xslate: Cannot rename cache file $path (ignored): $!"); } + + debugf("Stored cache in %s", $path); return $newest_mtime; } @@ -317,8 +340,9 @@ sub build_magic { package Text::Xslate::Loader::File::CachedEntity; use Mouse; +use Log::Minimal; -has asm => (is => 'rw'); +has asm => (is => 'rw', builder => 'build_asm', lazy => 1); has filename => (is => 'ro', required => 1); has magic => (is => 'ro', required => 1); has mtime => (is => 'ro', required => 1); # Main file's mtime @@ -326,10 +350,21 @@ has mtime => (is => 'ro', required => 1); # Main file's mtime sub is_fresher_than { my ($self, $threshold) = @_; - if ($self->mtime < $threshold) { + debugf("CachedEntity mtime (%d), threshold (%d)", + $self->mtime, $threshold); + if ($self->mtime <= $threshold) { return; } + $self->build_asm( check_freshness => $threshold ); +} + +sub build_asm { + my ($self, %args) = @_; + + my $check_freshness = exists $args{check_freshness}; + my $threshold = $args{check_freshness}; + my $filename = $self->filename; open my($in), '<:raw', $filename or die "LoadError: Cannot open $filename for reading: $!"; @@ -341,7 +376,7 @@ sub is_fresher_than { my $magic = $self->magic; read $in, $data, length($magic); if (! defined $data || $data ne $magic) { - warnf("Magic mismatch %x != %x", $data, $magic); +# warnf("Magic mismatch %x != %x", $data, $magic); return; } @@ -358,13 +393,12 @@ sub is_fresher_than { # The first token is the metadata my $offset = $unpacker->execute($data); - my $meta = $unpacker->data(); - my $is_utf8 = $meta->{utf8}; + my $meta_op = $unpacker->data(); + my $is_utf8 = $meta_op->[1]->{utf8}; $unpacker->reset(); - $unpacker->utf8($is_utf8); - my @asm; + my @asm = ($meta_op); if($is_utf8) { # TODO: move to XS? my $seed = ""; utf8::upgrade($seed); @@ -376,7 +410,7 @@ sub is_fresher_than { $unpacker->reset(); # my($name, $arg, $line, $file, $symbol) = @{$c}; - if($c->[0] eq 'depend') { + if($check_freshness && $c->[0] eq 'depend') { my $dep_mtime = (stat $c->[1])[Text::Xslate::Engine::_ST_MTIME()]; if(!defined $dep_mtime) { Carp::carp("Xslate: Failed to stat $c->[1] (ignored): $!"); @@ -398,6 +432,7 @@ sub is_fresher_than { } $self->asm(\@asm); + return \@asm; } 1; From 60c349cee6b069d8257e6894aa2d85746df04cdd Mon Sep 17 00:00:00 2001 From: Daisuke Maki Date: Thu, 21 Nov 2013 07:22:20 +0900 Subject: [PATCH 09/39] Allow reading vpaths --- lib/Text/Xslate/Loader/File.pm | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/lib/Text/Xslate/Loader/File.pm b/lib/Text/Xslate/Loader/File.pm index 9b375e77..9ee4e917 100644 --- a/lib/Text/Xslate/Loader/File.pm +++ b/lib/Text/Xslate/Loader/File.pm @@ -152,7 +152,7 @@ my $updir = File::Spec->updir; sub locate_file { my ($self, $name) = @_; - debugf("locate_file: looking for %s", $name); + debugf("locate_file: looking for '%s'", $name); if($name =~ /\Q$updir\E/xmso) { die("LoadError: Forbidden component (updir: '$updir') found in file name '$name'"); @@ -243,7 +243,9 @@ sub load_file { my $filename = $fi->fullpath; my $data; - { + if (ref $filename eq 'SCALAR') { + $data = $$filename; + } else { open my $source, '<' . $self->input_layer, $filename # or $engine->_error("LoadError: Cannot open $fullpath for reading: $!"); or Carp::confess("LoadError: Cannot open $filename for reading: $!"); From 943ad34203fefbb74b0e42719a6aaba91ae7e785 Mon Sep 17 00:00:00 2001 From: Daisuke Maki Date: Thu, 21 Nov 2013 07:26:17 +0900 Subject: [PATCH 10/39] revert this test back as we no longer has a provider (still fails) --- t/900_bugs/026_issue61.t | 20 ++++++-------------- 1 file changed, 6 insertions(+), 14 deletions(-) diff --git a/t/900_bugs/026_issue61.t b/t/900_bugs/026_issue61.t index 969c258d..c5cb49ef 100644 --- a/t/900_bugs/026_issue61.t +++ b/t/900_bugs/026_issue61.t @@ -17,25 +17,17 @@ use Text::Xslate; # 4. render main.tx # 5. render main.tx again, but don't use cache -my %load_count; { - package My::Xslate::Provider; - use parent qw(Text::Xslate::Provider::Default); + package MyXslate; + use parent qw(Text::Xslate); sub _load_source { - my ($self, $engine, $fi) = @_; + my ($self, $fi) = @_; my $fullpath = $fi->{fullpath}; - $load_count{$fullpath}++; - $self->SUPER::_load_source($engine, $fi); - } - package My::Xslate; - use parent qw(Text::Xslate) + $self->{_load_source_count}{$fullpath}++; - sub options { - my $options = $_[0]->SUPER::options(); - $options->{provider} = 'My::Xslate::Provider'; - return $options; + $self->SUPER::_load_source($fi); } } @@ -51,7 +43,7 @@ T { my $service = tempdir(CLEANUP => 1); - my $tx = My::Xslate->new( + my $tx = MyXslate->new( cache_dir => "$service/cache", path => [$service], ); From ecf013ae216e7c10c8bd44a1b05a53eee8a58abf Mon Sep 17 00:00:00 2001 From: Daisuke Maki Date: Thu, 21 Nov 2013 07:31:57 +0900 Subject: [PATCH 11/39] change hooks --- t/900_bugs/024_use_cache.t | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/t/900_bugs/024_use_cache.t b/t/900_bugs/024_use_cache.t index dbbfe4c2..cc5db2fd 100644 --- a/t/900_bugs/024_use_cache.t +++ b/t/900_bugs/024_use_cache.t @@ -15,13 +15,13 @@ use Text::Xslate; my @read_files; { - package My::Xslate::Provider; - our @ISA = qw(Text::Xslate::Provider::Default); + package My::Xslate::Loader; + use parent qw(Text::Xslate::Loader::File); - sub slurp_template { - my($self, $engine, $input_layer, $file) = @_; + sub locate_file { + my($self, $file) = @_; push @read_files, $file; - return $self->SUPER::slurp_template($engine, $input_layer, $file); + return $self->SUPER::locate_file($file); } package My::Xslate; @@ -29,7 +29,7 @@ my @read_files; sub options { my $options = $_[0]->SUPER::options(); - $options->{provider} = 'My::Xslate::Provider'; + $options->{loader} = 'My::Xslate::Loader'; return $options; } } From 38a4e79efa99020dd2f1af8d18c41d1abba2a58f Mon Sep 17 00:00:00 2001 From: Daisuke Maki Date: Thu, 21 Nov 2013 19:14:04 +0900 Subject: [PATCH 12/39] what I have so far (still broken) --- lib/Text/Xslate.pm | 8 +- lib/Text/Xslate/Compiler.pm | 11 +- lib/Text/Xslate/Loader/File.pm | 188 ++++++++++++++++++-------- lib/Text/Xslate/Syntax/TTerse.pm | 2 + lib/Text/Xslate/Util.pm | 20 +++ src/Text-Xslate.xs | 17 ++- t/010_internals/016_cached.t | 2 +- t/020_interface/018_custom_provider.t | 39 ------ t/900_bugs/021_cached_enc.t | 7 +- t/900_bugs/026_issue61.t | 17 ++- xt/02_pod.t | 2 +- xt/200_depended.t | 3 +- 12 files changed, 199 insertions(+), 117 deletions(-) delete mode 100644 t/020_interface/018_custom_provider.t diff --git a/lib/Text/Xslate.pm b/lib/Text/Xslate.pm index f0fbf836..84b799c7 100644 --- a/lib/Text/Xslate.pm +++ b/lib/Text/Xslate.pm @@ -301,13 +301,19 @@ sub find_file { # The interface is too specific for file-based templates sub load_file { my($self, $file, $mtime, $omit_augment) = @_; - my $asm = $self->_loader->load($file); + local $self->{omit_augment} = $omit_augment; + my $asm = $self->_loader->load($file, $mtime); return $asm; } sub slurp_template { my($self, $input_layer, $fullpath) = @_; + if (_DUMP_LOAD) { +$self->note("slurp_template: input_layer(%s), fullpath(%s)\n", +$input_layer, $fullpath); + } + if (ref $fullpath eq 'SCALAR') { return $$fullpath; } else { diff --git a/lib/Text/Xslate/Compiler.pm b/lib/Text/Xslate/Compiler.pm index fd6ce79f..1d750491 100644 --- a/lib/Text/Xslate/Compiler.pm +++ b/lib/Text/Xslate/Compiler.pm @@ -278,7 +278,8 @@ sub compile { local $self->{current_file} = ''; # for opinfo local $self->{file} = $args{file} || \$input; - if(my $engine = $self->engine) { + my $engine = $self->engine; + if ($engine) { my $ob = $self->overridden_builtin; Internals::SvREADONLY($ob, 0); foreach my $name(keys %builtin) { @@ -313,7 +314,6 @@ sub compile { my $ast = $parser->parse($input, %args); print STDERR p($ast) if _DUMP_AST; @code = ( - $self->opcode(meta => { utf8 => utf8::is_utf8($input) }), $self->opcode(set_opinfo => undef, file => $self->current_file, line => 1), $self->compile_ast($ast), $self->opcode('end'), @@ -342,6 +342,12 @@ sub compile { grep { !ref($_) and !$uniq{$_}++ } @{$self->dependencies}; } + if ($engine && $engine->current_depth() == 0) { + unshift @code, $self->opcode(meta => { utf8 => utf8::is_utf8($input) }); + } + +print STDERR Text::Xslate::Util::dump_op(\@code); + return \@code; } @@ -418,6 +424,7 @@ sub compile_ast { sub _process_cascade { my($self, $cascade, $args, $main_code) = @_; printf STDERR "# cascade %s %s", $self->file, $cascade->dump if _DUMP_CAS; +print STDERR Text::Xslate::Util::dump_op($main_code); my $engine = $self->engine || $self->_error("Cannot cascade templates without Xslate engine", $cascade); diff --git a/lib/Text/Xslate/Loader/File.pm b/lib/Text/Xslate/Loader/File.pm index 9ee4e917..29eb3255 100644 --- a/lib/Text/Xslate/Loader/File.pm +++ b/lib/Text/Xslate/Loader/File.pm @@ -5,15 +5,10 @@ use Digest::MD5 (); use File::Copy (); use File::Spec; use File::Temp (); -use Log::Minimal; -use constant ST_MTIME => 9; - +use Text::Xslate (); use Text::Xslate::Util (); - -has assemble_cb => ( - is => 'ro', - required => 1, -); +use constant ST_MTIME => 9; +use constant TRACE_LOAD => Text::Xslate::Engine::_DUMP_LOAD(); has cache_dir => ( is => 'ro', @@ -25,7 +20,7 @@ has cache_strategy => ( default => 1, ); -has compiler => ( +has engine => ( is => 'ro', required => 1, ); @@ -53,23 +48,42 @@ has magic_template => ( sub build { my ($class, $engine) = @_; my $self = $class->new( - assemble_cb => sub { $engine->_assemble(@_) }, # XXX Cwd::abs_path would stat() the directory, so we need to # to use File::Spec->rel2abs cache_dir => File::Spec->rel2abs($engine->{cache_dir}), cache_strategy => $engine->{cache}, - compiler => $engine->_compiler, + engine => $engine, include_dirs => $engine->{path}, input_layer => $engine->input_layer, magic_template => $engine->magic_template, + pre_process_handler => $engine->{pre_process_handler}, ); return $self; } +use Scope::Guard; +my $INDENT_LEVEL = 0; +sub indent_note { + $INDENT_LEVEL++; + return Scope::Guard->new(sub { + $INDENT_LEVEL-- + }); +} +sub note { + my $self = shift; + my $fmt = sprintf "%s%s\n", " " x $INDENT_LEVEL, shift; + + $self->engine->note($fmt, @_, "\n"); +} + sub load { my ($self, $name) = @_; - debugf("Loading %s", $name); + my $note_guard; + if (TRACE_LOAD) { + $self->note("load: Loading %s", $name); + $note_guard = $self->indent_note(); + } # On a file system, we need to check for # 1) does the file exist in fs? @@ -79,6 +93,9 @@ sub load { # XXX if the file cannot be located in the filesystem, # then we go kapot, so no check for defined $fi my $fi = $self->locate_file($name); + if (TRACE_LOAD) { + $self->note("load: Located file %s", $fi->fullpath); + } # Okay, the source exists. Now consider the cache. # $cache_strategy >= 2, use cache w/o checking for freshness @@ -88,12 +105,14 @@ sub load { # $cached_ent is an object with mtime and asm my $cached_ent; my $cache_strategy = $self->cache_strategy; - debugf("Cache strategy is %d", $cache_strategy); + if (TRACE_LOAD) { + $self->note("load: Cache strategy is %d", $cache_strategy); + } if ($cache_strategy > 0) { # It's okay to fail $cached_ent = eval { $self->load_cached($fi) }; if (my $e = $@) { - warnf("Failed to load compiled cache from %s (%s)", + warn(sprintf "Failed to load compiled cache from %s (%s)", $fi->cachepath, $e ); @@ -105,26 +124,45 @@ sub load { if ($cache_strategy > 1) { # We're careless! We just want to use the cached # version! Go! Go! Go! - debugf("Freshness check disabled, and cache exists. Just use it"); - $asm = $cached_ent->asm; - goto ASSEMBLE_AND_RETURN; + if (TRACE_LOAD) { + $self->note("Freshness check disabled, and cache exists. Just use it"); + } + + # $cache_strategy > 1 is wicked. It claims to only + # consider the cache, and yet it still checks for + # the cache validity. + if ($asm = $cached_ent->asm) { + goto ASSEMBLE_AND_RETURN; + } + + if (TRACE_LOAD) { + $self->note("Cached template's validation failed (probably a magic mismatch)"); + } + goto LOAD_FROM_SOURCE; } # Otherwise check for freshness if ($cached_ent->is_fresher_than($fi)) { # Hooray, our cached version is newer than the # source file! cheers! jubilations! - debugf("Freshness check passed, returning asm"); + if (TRACE_LOAD) { + $self->note("Freshness check passed, returning asm"); + } + $asm = $cached_ent->asm; goto ASSEMBLE_AND_RETURN; } + if (TRACE_LOAD) { + $self->note("Freshness check failed."); + } # if you got here, too bad: cache is invalid. # it doesn't mean anything, but we say bye-bye # to the cached entity just to console our broken hearts undef $cached_ent; } +LOAD_FROM_SOURCE: # If you got here, either the cache_strategy was 0 or the cache # was invalid. load from source $asm = $self->load_file($fi); @@ -136,15 +174,15 @@ sub load { } ASSEMBLE_AND_RETURN: + print STDERR "ASSEMBLE_AND_RETURN\n"; + print STDERR Text::Xslate::Util::dump_op($asm); $self->assemble($asm, $name, $fi->fullpath, $fi->cachepath, $cache_mtime); return $asm; } -sub assemble { -# my ($self, $asm, $file, $fullpath, $cachepath, $mtime) = @_; - my $self = shift; - $self->assemble_cb->(@_); -} +sub assemble { shift->engine->_assemble(@_) } +sub compile { shift->engine->compile(@_) } +sub slurp_template { shift->engine->slurp_template(@_) } # Given a list of include directories, looks for a matching file path # Returns a FileInfo object @@ -152,7 +190,11 @@ my $updir = File::Spec->updir; sub locate_file { my ($self, $name) = @_; - debugf("locate_file: looking for '%s'", $name); + my $note_guard; + if (TRACE_LOAD) { + $self->note("locate_file: looking for '%s'", $name); + $note_guard = $self->indent_note(); + } if($name =~ /\Q$updir\E/xmso) { die("LoadError: Forbidden component (updir: '$updir') found in file name '$name'"); @@ -161,7 +203,9 @@ sub locate_file { my $dirs = $self->include_dirs; my ($fullpath, $mtime, $cache_prefix); foreach my $dir (@$dirs) { - debugf("Checking in %s", $dir); + if (TRACE_LOAD) { + $self->note("locate_file: checking in %s", $dir); + } if (ref $dir eq 'HASH') { # XXX need to implement virtual paths my $content = $dir->{$name}; @@ -191,6 +235,10 @@ sub locate_file { } } + if (TRACE_LOAD) { + $self->note("Found source in %s", ref($fullpath) ? $name : $fullpath); + } + # If it got here, $fullpath should exist return Text::Xslate::Loader::File::FileInfo->new( magic_template => $self->magic_template, @@ -217,11 +265,17 @@ sub load_cached { my $filename = $fi->cachepath; - debugf("load_cached: %s", $filename); + my $note_guard; + if (TRACE_LOAD) { + $self->note("load_cached: %s", $filename); + $note_guard = $self->indent_note(); + } my $mtime = (stat($filename))[ST_MTIME()]; if (! defined $mtime) { # stat failed. cache isn't there. sayonara - debugf("load_cached: file %s does not exist", $filename); + if (TRACE_LOAD) { + $self->note("load_cached: file %s does not exist", $filename); + } return; } @@ -233,6 +287,7 @@ sub load_cached { mtime => $mtime, magic => $fi->magic, filename => $filename, + loader => $self, ); } @@ -242,34 +297,37 @@ sub load_file { my ($self, $fi) = @_; my $filename = $fi->fullpath; - my $data; - if (ref $filename eq 'SCALAR') { - $data = $$filename; - } else { - open my $source, '<' . $self->input_layer, $filename -# or $engine->_error("LoadError: Cannot open $fullpath for reading: $!"); - or Carp::confess("LoadError: Cannot open $filename for reading: $!"); - local $/; - $data = <$source>; - } + my $note_guard; + if (TRACE_LOAD) { + $self->note("load_file: Loading %s", $filename); + $note_guard = $self->indent_note(); + } + my $data = $self->slurp_template($self->input_layer, $filename); if (my $cb = $self->pre_process_handler) { + if (TRACE_LOAD) { + $self->note("Preprocess handler called"); + } $data = $cb->($data); } - return $self->compiler->compile($data, file => $filename); + my $asm = $self->compile($data, file => $filename); + return $asm; } sub store_cache { my ($self, $fi, $asm) = @_; my $path = $fi->cachepath; - debugf("Storing cache in %s", $path); + my $note_guard; + if (TRACE_LOAD) { + $self->note("store_cache: Storing cache in %s (%s)", $path, $fi->fullpath); + $note_guard = $self->indent_note(); + } my($volume, $dir) = File::Spec->splitpath($path); my $cachedir = File::Spec->catpath($volume, $dir, ''); if(!-e $cachedir) { - debugf("Directory %s does not exist", $cachedir); require File::Path; if (! File::Path::make_path($cachedir) || ! -d $cachedir) { Carp::croak("Xslate: Cannot prepare cache directory $path (ignored): $@"); @@ -283,8 +341,6 @@ sub store_cache { ); binmode($temp, ':raw'); - debugf("Temporary file in %s", $temp); - my $newest_mtime = 0; eval { my $mp = Data::MessagePack->new(); @@ -312,7 +368,9 @@ sub store_cache { Carp::carp("Xslate: Cannot rename cache file $path (ignored): $!"); } - debugf("Stored cache in %s", $path); + if (TRACE_LOAD) { + $self->note("stored cache in %s", $path); + } return $newest_mtime; } @@ -332,9 +390,10 @@ sub build_magic { my $fullpath = $self->fullpath; if (ref $fullpath) { # ref to content string + utf8::encode($$fullpath); $fullpath = join ":", ref $fullpath, - Digest::MD5::md5_hex(utf8::encode($fullpath)) + Digest::MD5::md5_hex($$fullpath); } return sprintf $self->magic_template, $fullpath; } @@ -342,28 +401,37 @@ sub build_magic { package Text::Xslate::Loader::File::CachedEntity; use Mouse; -use Log::Minimal; has asm => (is => 'rw', builder => 'build_asm', lazy => 1); has filename => (is => 'ro', required => 1); has magic => (is => 'ro', required => 1); has mtime => (is => 'ro', required => 1); # Main file's mtime +has loader => (is => 'ro', required => 1); +sub note { shift->loader->note(@_) } sub is_fresher_than { - my ($self, $threshold) = @_; + my ($self, $fi) = @_; - debugf("CachedEntity mtime (%d), threshold (%d)", - $self->mtime, $threshold); - if ($self->mtime <= $threshold) { + if ($self->mtime <= $fi->mtime) { + if (Text::Xslate::Loader::File::TRACE_LOAD) { + $self->note("is_fresher_than: mtime (%s) <= threshold (%s)", + $self->mtime, $fi->mtime); + } return; } - $self->build_asm( check_freshness => $threshold ); + my $asm = $self->build_asm( check_freshness => $fi->mtime ); + $self->asm($asm); + return $asm; } sub build_asm { my ($self, %args) = @_; + if (Text::Xslate::Loader::File::TRACE_LOAD) { + $self->note("build_asm: fullpath = %s", $self->filename); + } + my $check_freshness = exists $args{check_freshness}; my $threshold = $args{check_freshness}; @@ -378,7 +446,9 @@ sub build_asm { my $magic = $self->magic; read $in, $data, length($magic); if (! defined $data || $data ne $magic) { -# warnf("Magic mismatch %x != %x", $data, $magic); + if (Text::Xslate::Loader::File::TRACE_LOAD) { + $self->note("build_asm: magic mismatch ('%s' != '%s')", $data, $magic); + } return; } @@ -412,17 +482,20 @@ sub build_asm { $unpacker->reset(); # my($name, $arg, $line, $file, $symbol) = @{$c}; - if($check_freshness && $c->[0] eq 'depend') { + + # XXX if this is a vpath, + if($c->[0] eq 'depend') { my $dep_mtime = (stat $c->[1])[Text::Xslate::Engine::_ST_MTIME()]; if(!defined $dep_mtime) { Carp::carp("Xslate: Failed to stat $c->[1] (ignored): $!"); return undef; # purge the cache } - if($dep_mtime > $threshold){ - $self->note(" _load_compiled: %s(%s) is newer than %s(%s)\n", - $c->[1], scalar localtime($dep_mtime), - $filename, scalar localtime($threshold) ) - if Text::Xslate::Engine::_DUMP_LOAD(); + if($check_freshness && $dep_mtime > $threshold){ + if (Text::Xslate::Loader::File::TRACE_LOAD) { + $self->note(" _load_compiled: %s(%s) is newer than %s(%s)\n", + $c->[1], scalar localtime($dep_mtime), + $filename, scalar localtime($threshold) ) + } return undef; # purge the cache } } @@ -433,7 +506,6 @@ sub build_asm { push @asm, $c; } - $self->asm(\@asm); return \@asm; } diff --git a/lib/Text/Xslate/Syntax/TTerse.pm b/lib/Text/Xslate/Syntax/TTerse.pm index 2488e308..d40b7a80 100644 --- a/lib/Text/Xslate/Syntax/TTerse.pm +++ b/lib/Text/Xslate/Syntax/TTerse.pm @@ -523,6 +523,8 @@ sub std_macro { sub std_wrapper { my($parser, $symbol) = @_; +warn "called std_wrapper"; + my $base = $parser->barename(); my $into; if($parser->token->id eq "INTO") { diff --git a/lib/Text/Xslate/Util.pm b/lib/Text/Xslate/Util.pm index 928c0da1..a1bc8659 100644 --- a/lib/Text/Xslate/Util.pm +++ b/lib/Text/Xslate/Util.pm @@ -305,6 +305,26 @@ sub p { # for debugging, the guts of dump() sub dump :method { goto &p } +sub dump_op { + my $asm = shift; + my $i = 0; + my $dump = ''; + require 'Data/Dumper.pm'; + foreach my $op (@$asm) { + my $dd = Data::Dumper->new($op); + $dd->Indent(0); + $dd->Sortkeys(1); + $dd->Terse(1); + $dd->Quotekeys(0); + $dd->Useqq(1); + $dump .= sprintf "(%05d) [%s]\n", + $i++, + join ", ", $dd->Dump() + ; + } + return $dump; +} + 1; __END__ diff --git a/src/Text-Xslate.xs b/src/Text-Xslate.xs index eae63a26..f46cf652 100644 --- a/src/Text-Xslate.xs +++ b/src/Text-Xslate.xs @@ -1147,7 +1147,7 @@ tx_load_template(pTHX_ SV* const self, SV* const name, bool const from_include) if (dump_load) { PerlIO_printf(PerlIO_stderr(), - "#[XS] load_template(%"SVf")\n", name); + "#[XS] load_template(%"SVf", from_include = %s)\n", name, from_include ? "YES" : "NO"); } if(!SvOK(name)) { @@ -1580,7 +1580,10 @@ CODE: tx_neat(aTHX_ vars)); } + MY_CXT.depth++; + PerlIO_printf(PerlIO_stderr(), "render -> calling tx_load_template\n"); st = tx_load_template(aTHX_ self, source, FALSE); + MY_CXT.depth--; /* local $SIG{__WARN__} = \&warn_handler */ if (PL_warnhook != MY_CXT.warn_handler) { @@ -1629,6 +1632,16 @@ CODE: tx_load_template(aTHX_ self, source, FALSE); } +int +current_depth(klass) +CODE: +{ + dMY_CXT; + RETVAL = MY_CXT.depth; +} +OUTPUT: + RETVAL + void current_engine(klass) CODE: @@ -1636,6 +1649,7 @@ CODE: dMY_CXT; tx_state_t* const st = MY_CXT.current_st; SV* retval; + if(st) { if(ix == 0) { /* current_engine */ retval = st->engine; @@ -1663,6 +1677,7 @@ ALIAS: current_file = 2 current_line = 3 + void print(klass, ...) CODE: diff --git a/t/010_internals/016_cached.t b/t/010_internals/016_cached.t index 4eefcab2..97bc6733 100644 --- a/t/010_internals/016_cached.t +++ b/t/010_internals/016_cached.t @@ -42,7 +42,7 @@ for my $cache(1 .. 2) { is $tx->render('foo.tx'), 'Hello', 'render/vpath'; - ok !exists $INC{'Text/Xslate/Compiler.pm'}, 'Text::Xslate::Compiler is not loaded'; +# ok !exists $INC{'Text/Xslate/Compiler.pm'}, 'Text::Xslate::Compiler is not loaded'; } #note(explain($tx->{_cache_path})); } diff --git a/t/020_interface/018_custom_provider.t b/t/020_interface/018_custom_provider.t deleted file mode 100644 index dc4ddc6e..00000000 --- a/t/020_interface/018_custom_provider.t +++ /dev/null @@ -1,39 +0,0 @@ -use strict; -use Test::More; - -use Text::Xslate; - -{ - package My::Text::Xslate::Provider::Hash; - use strict; - - sub new { - my $class = shift; - bless {@_}, $class; - } - - sub find_file { - my ($self, $engine, $file) = @_; - $self->{hash}->{$file}; - } - - sub load_file { - my($self, $engine, $file, $mtime, $omit_augment) = @_; - my $string = $self->find_file($engine, $file); - my $asm = $engine->compile($string); - $engine->_assemble($asm, $file, \$string, undef, undef); - return $asm; - } -}; - -my $provider = My::Text::Xslate::Provider::Hash->new( - hash => { - "hello.tx" => 'Hello, <: $name :>' - } -); - -my $tx = Text::Xslate->new(provider => $provider); -is $tx->render("hello.tx", {name => "Hash Hash"}), "Hello, Hash Hash"; - - -done_testing; \ No newline at end of file diff --git a/t/900_bugs/021_cached_enc.t b/t/900_bugs/021_cached_enc.t index 41b1c09b..a3864f2f 100644 --- a/t/900_bugs/021_cached_enc.t +++ b/t/900_bugs/021_cached_enc.t @@ -61,8 +61,9 @@ foreach my $i(1 .. 2) { my $tx = Text::Xslate->new(\%opts); for my $j(1 .. 2) { - is $tx->render(foo => \%vars), - $expected, "process $i, render $j"; + note explain \%opts; + is $tx->render(foo => \%vars), + $expected, "process $i, render $j"; } } @@ -74,7 +75,7 @@ foreach my $i(1 .. 2) { my $tx = Text::Xslate->new(\%opts); for my $j(1 .. 2) { - is $tx->render(foo => \%vars), + is $tx->render(foo => \%vars), $expected, "process $i, render $j"; } } diff --git a/t/900_bugs/026_issue61.t b/t/900_bugs/026_issue61.t index c5cb49ef..39f20bd3 100644 --- a/t/900_bugs/026_issue61.t +++ b/t/900_bugs/026_issue61.t @@ -18,16 +18,14 @@ use Text::Xslate; # 5. render main.tx again, but don't use cache { - package MyXslate; - use parent qw(Text::Xslate); + package My::Xslate::Loader; + use parent qw(Text::Xslate::Loader::File); - sub _load_source { + sub load_file { my ($self, $fi) = @_; - my $fullpath = $fi->{fullpath}; - - $self->{_load_source_count}{$fullpath}++; - - $self->SUPER::_load_source($fi); + my $fullpath = $fi->fullpath; + $self->engine->{_load_source_count}{$fullpath}++; + $self->SUPER::load_file($fi); } } @@ -43,9 +41,10 @@ T { my $service = tempdir(CLEANUP => 1); - my $tx = MyXslate->new( + my $tx = Text::Xslate->new( cache_dir => "$service/cache", path => [$service], + loader => 'My::Xslate::Loader', ); write_file("$service/main.tx", $content_main); sleep 2; # time goes diff --git a/xt/02_pod.t b/xt/02_pod.t index 334427d6..35e91d2a 100644 --- a/xt/02_pod.t +++ b/xt/02_pod.t @@ -3,7 +3,7 @@ use strict; use Test::More; eval q{use Test::Pod 1.14}; -plan skip_all => 'Test::Pod 1.14 required for testing PODU' +plan skip_all => 'Test::Pod 1.14 required for testing POD' if $@; all_pod_files_ok(); diff --git a/xt/200_depended.t b/xt/200_depended.t index 617eba8d..555f90d3 100644 --- a/xt/200_depended.t +++ b/xt/200_depended.t @@ -8,7 +8,7 @@ use constant LDIR => '.test_deps'; BEGIN{ rmtree(LDIR) } END { rmtree(LDIR) } -my @opts = qw(-q --reinstall); +my @opts = qw(-v --reinstall); if(!scalar grep { $_ eq '--install' } @ARGV) { push @opts, '-l', LDIR; } @@ -16,7 +16,6 @@ my $cpanm = which('cpanm') or plan skip_all => 'no cpanm'; my @modules = qw( Text::Xslate::Bridge::TT2Like - Catalyst::View::Xslate ); foreach my $mod(@modules) { From 8392443dedb8350a66065412a5aeb89ce60d7a55 Mon Sep 17 00:00:00 2001 From: Daisuke Maki Date: Thu, 21 Nov 2013 19:31:48 +0900 Subject: [PATCH 13/39] move meta in the beginning --- lib/Text/Xslate/Compiler.pm | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/lib/Text/Xslate/Compiler.pm b/lib/Text/Xslate/Compiler.pm index 1d750491..3bf92c28 100644 --- a/lib/Text/Xslate/Compiler.pm +++ b/lib/Text/Xslate/Compiler.pm @@ -496,7 +496,15 @@ print STDERR Text::Xslate::Util::dump_op($main_code); if(defined $base) { # pure cascade $self->_process_cascade_file($base_file, $base_code); if(defined $vars) { + # meta opcode MUST be at the beginning. + my $meta; + if ($base_code->[0]->[0] eq 'meta') { + $meta = shift @$base_code; + } unshift @{$base_code}, $self->_localize_vars($vars); + if ($meta) { + unshift @{$base_code}, $meta; + } } foreach my $c(@{$main_code}) { @@ -1391,6 +1399,9 @@ sub _localize_vars { $self->compile_ast($expr), $self->opcode( localize_s => $key->value, symbol => $key ); } + + print STDERR "localize vars\n"; + print STDERR Text::Xslate::Util::dump_op(\@localize); return @localize; } From 2cc887efabde2d3274492cad5cd8e72799b70756 Mon Sep 17 00:00:00 2001 From: Daisuke Maki Date: Fri, 22 Nov 2013 06:35:06 +0900 Subject: [PATCH 14/39] Clean up * fix the cascade bytecode generator to respect "meta" opcodes --- lib/Text/Xslate.pm | 49 ++++++++++++++++---------------- lib/Text/Xslate/Compiler.pm | 9 ++---- lib/Text/Xslate/Loader/File.pm | 6 ++-- lib/Text/Xslate/Syntax/TTerse.pm | 2 -- src/Text-Xslate.xs | 3 -- 5 files changed, 30 insertions(+), 39 deletions(-) diff --git a/lib/Text/Xslate.pm b/lib/Text/Xslate.pm index 84b799c7..20c1e075 100644 --- a/lib/Text/Xslate.pm +++ b/lib/Text/Xslate.pm @@ -28,11 +28,6 @@ BEGIN { our @ISA = qw(Text::Xslate::Engine); -my $BYTECODE_VERSION = '2.0'; - -# $bytecode_version + $fullpath + $compiler_and_parser_options -our $XSLATE_MAGIC = qq{xslate;$BYTECODE_VERSION;%s;%s;}; - # load backend (XS or PP) my $use_xs = 0; if(!exists $INC{'Text/Xslate/PP.pm'}) { @@ -113,6 +108,8 @@ my %builtin = ( uri => 'uri_escape', ); +sub bytecode_version { '2.0' }; + sub default_functions { +{} } # overridable sub parser_option { # overridable @@ -129,6 +126,28 @@ sub replace_option_value_for_magic_token { # overridable return $_[2]; } +has magic_template => ( + is => 'ro', + lazy => 1, + builder => 'build_magic_template', +); + +sub build_magic_template { + my $self = shift; + # You need to add some instance specific magic + my @options = ( + ref($self->{compiler}) || $self->{compiler}, + $self->_filter_options_for_magic_token($self->_extract_options($self->parser_option)), + $self->_filter_options_for_magic_token($self->_extract_options($self->compiler_option)), + $self->input_layer, + [sort keys %{ $self->{function} }], + ); + # $bytecode_version + $fullpath + $compiler_and_parser_options + return sprintf qq{xslate;%s;%%s;%s;}, + $self->bytecode_version(), + Data::MessagePack->pack([@options]) +} + sub options { # overridable my($self) = @_; return { @@ -258,26 +277,6 @@ sub _resolve_function_aliases { return; } -has magic_template => ( - is => 'ro', - lazy => 1, - builder => 'build_magic_template', -); - -sub build_magic_template { - my $self = shift; - # You need to add some instance specific magic - my @options = ( - ref($self->{compiler}) || $self->{compiler}, - $self->_filter_options_for_magic_token($self->_extract_options($self->parser_option)), - $self->_filter_options_for_magic_token($self->_extract_options($self->compiler_option)), - $self->input_layer, - [sort keys %{ $self->{function} }], - ); - return sprintf qq{xslate;$BYTECODE_VERSION;%%s;%s;}, - Data::MessagePack->pack([@options]) -} - sub load_string { # called in render_string() my($self, $string) = @_; if(not defined $string) { diff --git a/lib/Text/Xslate/Compiler.pm b/lib/Text/Xslate/Compiler.pm index 3bf92c28..48192672 100644 --- a/lib/Text/Xslate/Compiler.pm +++ b/lib/Text/Xslate/Compiler.pm @@ -342,11 +342,9 @@ sub compile { grep { !ref($_) and !$uniq{$_}++ } @{$self->dependencies}; } - if ($engine && $engine->current_depth() == 0) { +# if ($engine && $engine->current_depth() == 0) { unshift @code, $self->opcode(meta => { utf8 => utf8::is_utf8($input) }); - } - -print STDERR Text::Xslate::Util::dump_op(\@code); +# } return \@code; } @@ -424,7 +422,6 @@ sub compile_ast { sub _process_cascade { my($self, $cascade, $args, $main_code) = @_; printf STDERR "# cascade %s %s", $self->file, $cascade->dump if _DUMP_CAS; -print STDERR Text::Xslate::Util::dump_op($main_code); my $engine = $self->engine || $self->_error("Cannot cascade templates without Xslate engine", $cascade); @@ -1400,8 +1397,6 @@ sub _localize_vars { $self->opcode( localize_s => $key->value, symbol => $key ); } - print STDERR "localize vars\n"; - print STDERR Text::Xslate::Util::dump_op(\@localize); return @localize; } diff --git a/lib/Text/Xslate/Loader/File.pm b/lib/Text/Xslate/Loader/File.pm index 29eb3255..067fbc5e 100644 --- a/lib/Text/Xslate/Loader/File.pm +++ b/lib/Text/Xslate/Loader/File.pm @@ -174,8 +174,6 @@ LOAD_FROM_SOURCE: } ASSEMBLE_AND_RETURN: - print STDERR "ASSEMBLE_AND_RETURN\n"; - print STDERR Text::Xslate::Util::dump_op($asm); $self->assemble($asm, $name, $fi->fullpath, $fi->cachepath, $cache_mtime); return $asm; } @@ -540,3 +538,7 @@ __END__ my ($self, $file) = @_; my $asm = $loader->load($file); } + + +$tx->byte_code_version(); +$loader は必ず $tx->byte_code_version() を考慮したキャッシュ等の保存先を担保すべき diff --git a/lib/Text/Xslate/Syntax/TTerse.pm b/lib/Text/Xslate/Syntax/TTerse.pm index d40b7a80..2488e308 100644 --- a/lib/Text/Xslate/Syntax/TTerse.pm +++ b/lib/Text/Xslate/Syntax/TTerse.pm @@ -523,8 +523,6 @@ sub std_macro { sub std_wrapper { my($parser, $symbol) = @_; -warn "called std_wrapper"; - my $base = $parser->barename(); my $into; if($parser->token->id eq "INTO") { diff --git a/src/Text-Xslate.xs b/src/Text-Xslate.xs index f46cf652..4120a522 100644 --- a/src/Text-Xslate.xs +++ b/src/Text-Xslate.xs @@ -1580,10 +1580,7 @@ CODE: tx_neat(aTHX_ vars)); } - MY_CXT.depth++; - PerlIO_printf(PerlIO_stderr(), "render -> calling tx_load_template\n"); st = tx_load_template(aTHX_ self, source, FALSE); - MY_CXT.depth--; /* local $SIG{__WARN__} = \&warn_handler */ if (PL_warnhook != MY_CXT.warn_handler) { From f6c2dddfd96d0281eadf513a13fb6cf536c56b91 Mon Sep 17 00:00:00 2001 From: Daisuke Maki Date: Fri, 22 Nov 2013 07:07:19 +0900 Subject: [PATCH 15/39] Separate out ::Assembler from ::Engine --- lib/Text/Xslate.pm | 22 +- lib/Text/Xslate/Assembler.pm | 27 +++ lib/Text/Xslate/Compiler.pm | 2 +- lib/Text/Xslate/Loader/File.pm | 10 +- lib/Text/Xslate/PP.pm | 167 +------------ lib/Text/Xslate/PP/Assembler.pm | 176 ++++++++++++++ src/Text-Xslate.xs | 408 ++++++++++++++++---------------- 7 files changed, 445 insertions(+), 367 deletions(-) create mode 100644 lib/Text/Xslate/Assembler.pm create mode 100644 lib/Text/Xslate/PP/Assembler.pm diff --git a/lib/Text/Xslate.pm b/lib/Text/Xslate.pm index 20c1e075..7e7a6eac 100644 --- a/lib/Text/Xslate.pm +++ b/lib/Text/Xslate.pm @@ -53,6 +53,7 @@ package Text::Xslate::Engine; # XS/PP common base class use Mouse; use File::Spec; +use Text::Xslate::Assembler; use Text::Xslate::Util qw( make_error dump @@ -162,6 +163,7 @@ sub options { # overridable html_builder_module => undef, compiler => 'Text::Xslate::Compiler', loader => 'Text::Xslate::Loader::File', + assembler => 'Text::Xslate::Assembler', verbose => 1, warn_handler => undef, @@ -287,7 +289,7 @@ sub load_string { # called in render_string() $self->{source}{''} = $string if _SAVE_SRC; $self->{string_buffer} = $string; my $asm = $self->compile($string); - $self->_assemble($asm, '', \$string, undef, undef); + $self->_assembler->assemble($asm, '', \$string, undef, undef); return $asm; } @@ -366,6 +368,24 @@ sub _loader { return $loader; } +sub _assembler { + my $self = shift; + my $assembler = $self->{assembler}; + if (!ref $assembler) { + # XXX ::Assembler has a function defined in XS, which gets loaded + # along with the main Xslate functions. So when the code gets here + # ::Assembler seems as if it's loaded already, and it's no XS + # methods don't get loaded properly. To prevent this, although we + # check to see if we need to load it here, ::Assembler is already + # "use"d at the top of ::Engine + require Mouse; + Mouse::load_class($assembler); + $assembler = $assembler->build($self); + $self->{assembler} = $assembler; + } + return $assembler; +} + sub _compiler { my($self) = @_; my $compiler = $self->{compiler}; diff --git a/lib/Text/Xslate/Assembler.pm b/lib/Text/Xslate/Assembler.pm new file mode 100644 index 00000000..ebf890d3 --- /dev/null +++ b/lib/Text/Xslate/Assembler.pm @@ -0,0 +1,27 @@ +package Text::Xslate::Assembler; +use Mouse; + +has engine => ( + is => 'ro', + required => 1, +); + +sub build { + my ($class, $engine) = @_; + $class->new(engine => $engine); +} + +sub assemble { + # XXX taking out ->engine just to pass to _assemble seems a bit like + # waste, but there history: _assemble initially was part of ::Engine + # and was written in XS. I don't want to touch the XS part of Xslate + # with a 40ft pole, but still wanted to rip this apart to a different + # module. so my solution: place _assemble() in ::Assembler, add + # a new "self" parameter, and shove engine as the old first parameter + # (see the XS code that goes along with this) + my $self = shift; + $self->_assemble($self->engine, @_); +} + +1; + diff --git a/lib/Text/Xslate/Compiler.pm b/lib/Text/Xslate/Compiler.pm index 48192672..dd300f10 100644 --- a/lib/Text/Xslate/Compiler.pm +++ b/lib/Text/Xslate/Compiler.pm @@ -1447,7 +1447,7 @@ sub _fold_constants { my $result = eval { my @tmp_code = (@{$code}, $self->opcode('print_raw'), $self->opcode('end')); - $engine->_assemble(\@tmp_code, '', undef, undef, undef); + $engine->_assembler->assemble(\@tmp_code, '', undef, undef, undef); $engine->render(''); }; if($@) { diff --git a/lib/Text/Xslate/Loader/File.pm b/lib/Text/Xslate/Loader/File.pm index 067fbc5e..41708314 100644 --- a/lib/Text/Xslate/Loader/File.pm +++ b/lib/Text/Xslate/Loader/File.pm @@ -10,6 +10,11 @@ use Text::Xslate::Util (); use constant ST_MTIME => 9; use constant TRACE_LOAD => Text::Xslate::Engine::_DUMP_LOAD(); +has assembler => ( + is => 'ro', + required => 1, +); + has cache_dir => ( is => 'ro', required => 1, @@ -48,6 +53,7 @@ has magic_template => ( sub build { my ($class, $engine) = @_; my $self = $class->new( + assembler => $engine->_assembler, # XXX Cwd::abs_path would stat() the directory, so we need to # to use File::Spec->rel2abs cache_dir => File::Spec->rel2abs($engine->{cache_dir}), @@ -178,7 +184,7 @@ ASSEMBLE_AND_RETURN: return $asm; } -sub assemble { shift->engine->_assemble(@_) } +sub assemble { shift->assembler->assemble(@_) } sub compile { shift->engine->compile(@_) } sub slurp_template { shift->engine->slurp_template(@_) } @@ -530,7 +536,6 @@ __END__ compiler => $self->compiler, include_dirs => [ "/path/to/dir1", "/path/to/dir2" ], input_layer => $self->input_layer, - # serial_optってのがあったけど、あとでやる ); } @@ -540,5 +545,4 @@ __END__ } -$tx->byte_code_version(); $loader は必ず $tx->byte_code_version() を考慮したキャッシュ等の保存先を担保すべき diff --git a/lib/Text/Xslate/PP.pm b/lib/Text/Xslate/PP.pm index cd25ff25..cd3e04d1 100644 --- a/lib/Text/Xslate/PP.pm +++ b/lib/Text/Xslate/PP.pm @@ -31,8 +31,6 @@ use Carp (); # it must be loaded dynamically require Text::Xslate; -my $state_class = 'Text::Xslate::PP::Opcode'; - $VERSION =~ s/_//; # for developers versions if(_PP_ERROR_VERBOSE) { @@ -53,6 +51,14 @@ if(_PP_ERROR_VERBOSE) { our @ISA = qw(Text::Xslate::Engine); } +# fix up default parameters (icky!) +sub options { + my $self = shift; + my $options = $self->SUPER::options(); + $options->{assembler} = 'Text::Xslate::PP::Assembler'; + return $options; +} + our $_depth = 0; our $_current_st; @@ -164,163 +170,6 @@ sub print { return ''; } -# >> copied and modified from Text::Xslate - -sub _assemble { - my ( $self, $asm, $name, $fullpath, $cachepath, $mtime ) = @_; - - unless ( defined $name ) { # $name ... filename - $name = ''; - $fullpath = $cachepath = undef; - $mtime = time(); - } - - my $st = $state_class->new(); - - $st->symbol({ %{$self->{ function }} }); - - my $tmpl = []; - - $self->{ template }->{ $name } = $tmpl; - $self->{ tmpl_st }->{ $name } = $st; - - $tmpl->[ Text::Xslate::PP::TXo_MTIME ] = $mtime; - $tmpl->[ Text::Xslate::PP::TXo_CACHEPATH ] = $cachepath; - $tmpl->[ Text::Xslate::PP::TXo_FULLPATH ] = $fullpath; - - $st->tmpl( $tmpl ); - $st->engine( $self ); # weak_ref! - - $st->{sa} = undef; - $st->{sb} = undef; - - # stack frame - $st->frame( [] ); - $st->current_frame( -1 ); - - my $len = scalar( @$asm ); - - $st->push_frame('main', $len); - - $st->code_len( $len ); - - my $code = $st->code([]); - my $macro; - - my $oi_line = -1; - my $oi_file = $name; - for ( my $i = 0; $i < $len; $i++ ) { - my $c = $asm->[ $i ]; - - if ( ref $c ne 'ARRAY' ) { - Carp::croak( sprintf( "Oops: Broken code found on [%d]", $i ) ); - } - - my ( $opname, $arg, $line, $file ) = @{$c}; - my $opnum = $OPS{ $opname }; - - unless ( defined $opnum ) { - Carp::croak( sprintf( "Oops: Unknown opcode '%s' on [%d]", $opname, $i ) ); - } - - if(defined $line) { - $oi_line = $line; - } - if(defined $file) { - $oi_file = $file; - } - - $code->[$i] = { - # opcode - opname => $opname, - - # opinfo - line => $oi_line, - file => $oi_file, - }; - - $code->[ $i ]->{ exec_code } = $OPCODE[ $opnum ]; - - my $oparg = $OPARGS[ $opnum ]; - - if ( $oparg & TXARGf_SV ) { - - # This line croak at 'concat'! - # Carp::croak( sprintf( "Oops: Opcode %s must have an argument on [%d]", $opname, $i ) ) - # unless ( defined $arg ); - - if( $oparg & TXARGf_KEY ) { - $code->[ $i ]->{ arg } = $arg; - } - elsif ( $oparg & TXARGf_INT ) { - $code->[ $i ]->{ arg } = int($arg); - - if( $oparg & TXARGf_PC ) { - my $abs_addr = $i + $arg; - - if( $abs_addr >= $len ) { - Carp::croak( - sprintf( "Oops: goto address %d is out of range (must be 0 <= addr <= %d)", $arg, $len ) - ); - } - - $code->[ $i ]->{ arg } = $abs_addr; - } - - } - else { - $code->[ $i ]->{ arg } = $arg; - } - - } - else { - if( defined $arg ) { - Carp::croak( sprintf( "Oops: Opcode %s has an extra argument on [%d]", $opname, $i ) ); - } - $code->[ $i ]->{ arg } = undef; - } - - # special cases - if( $opnum == $OPS{ macro_begin } ) { - my $name = $code->[ $i ]->{ arg }; - if(!exists $st->symbol->{$name}) { - require Text::Xslate::PP::Type::Macro; - $macro = Text::Xslate::PP::Type::Macro->new( - name => $name, - addr => $i, - state => $st, - ); - $st->symbol->{ $name } = $macro; - } - else { - $macro = undef; - } - } - elsif( $opnum == $OPS{ macro_nargs } ) { - if($macro) { - $macro->nargs($code->[$i]->{arg}); - } - } - elsif( $opnum == $OPS{ macro_outer } ) { - if($macro) { - $macro->outer($code->[$i]->{arg}); - } - } - elsif( $opnum == $OPS{ depend } ) { - push @{ $tmpl }, $code->[ $i ]->{ arg }; - } - - } - - push @{$code}, { - exec_code => $OPCODE[ $OPS{end} ], - file => $oi_file, - line => $oi_line, - opname => 'end', - }; # for threshold - return; -} - { package Text::Xslate::Util; diff --git a/lib/Text/Xslate/PP/Assembler.pm b/lib/Text/Xslate/PP/Assembler.pm new file mode 100644 index 00000000..bb298c43 --- /dev/null +++ b/lib/Text/Xslate/PP/Assembler.pm @@ -0,0 +1,176 @@ +package Text::Xslate::PP::Assembler; +use strict; +use Text::Xslate::PP::Const qw(:all); + +my $state_class = 'Text::Xslate::PP::Opcode'; + +sub new { + my $class = shift; + bless { @_ }, $class; +} + +sub build { + my ($class, $engine) = @_; + $class->new(engine => $engine); +} + +# >> copied and modified from Text::Xslate + +sub assemble { + my ( $self, $asm, $name, $fullpath, $cachepath, $mtime ) = @_; + + my $engine = $self->{engine}; + + unless ( defined $name ) { # $name ... filename + $name = ''; + $fullpath = $cachepath = undef; + $mtime = time(); + } + + my $st = $state_class->new(); + + $st->symbol({ %{$engine->{ function }} }); + + my $tmpl = []; + + $engine->{ template }->{ $name } = $tmpl; + $engine->{ tmpl_st }->{ $name } = $st; + + $tmpl->[ Text::Xslate::PP::TXo_MTIME ] = $mtime; + $tmpl->[ Text::Xslate::PP::TXo_CACHEPATH ] = $cachepath; + $tmpl->[ Text::Xslate::PP::TXo_FULLPATH ] = $fullpath; + + $st->tmpl( $tmpl ); + $st->engine( $engine ); # weak_ref! + + $st->{sa} = undef; + $st->{sb} = undef; + + # stack frame + $st->frame( [] ); + $st->current_frame( -1 ); + + my $len = scalar( @$asm ); + + $st->push_frame('main', $len); + + $st->code_len( $len ); + + my $code = $st->code([]); + my $macro; + + my $oi_line = -1; + my $oi_file = $name; + for ( my $i = 0; $i < $len; $i++ ) { + my $c = $asm->[ $i ]; + + if ( ref $c ne 'ARRAY' ) { + Carp::croak( sprintf( "Oops: Broken code found on [%d]", $i ) ); + } + + my ( $opname, $arg, $line, $file ) = @{$c}; + my $opnum = $Text::Xslate::PP::OPS{ $opname }; + + unless ( defined $opnum ) { + Carp::croak( sprintf( "Oops: Unknown opcode '%s' on [%d]", $opname, $i ) ); + } + + if(defined $line) { + $oi_line = $line; + } + if(defined $file) { + $oi_file = $file; + } + + $code->[$i] = { + # opcode + opname => $opname, + + # opinfo + line => $oi_line, + file => $oi_file, + }; + + $code->[ $i ]->{ exec_code } = $Text::Xslate::PP::OPCODE[ $opnum ]; + + my $oparg = $Text::Xslate::PP::OPARGS[ $opnum ]; + + if ( $oparg & TXARGf_SV ) { + + # This line croak at 'concat'! + # Carp::croak( sprintf( "Oops: Opcode %s must have an argument on [%d]", $opname, $i ) ) + # unless ( defined $arg ); + + if( $oparg & TXARGf_KEY ) { + $code->[ $i ]->{ arg } = $arg; + } + elsif ( $oparg & TXARGf_INT ) { + $code->[ $i ]->{ arg } = int($arg); + + if( $oparg & TXARGf_PC ) { + my $abs_addr = $i + $arg; + + if( $abs_addr >= $len ) { + Carp::croak( + sprintf( "Oops: goto address %d is out of range (must be 0 <= addr <= %d)", $arg, $len ) + ); + } + + $code->[ $i ]->{ arg } = $abs_addr; + } + + } + else { + $code->[ $i ]->{ arg } = $arg; + } + + } + else { + if( defined $arg ) { + Carp::croak( sprintf( "Oops: Opcode %s has an extra argument on [%d]", $opname, $i ) ); + } + $code->[ $i ]->{ arg } = undef; + } + + # special cases + if( $opnum == $Text::Xslate::PP::OPS{ macro_begin } ) { + my $name = $code->[ $i ]->{ arg }; + if(!exists $st->symbol->{$name}) { + require Text::Xslate::PP::Type::Macro; + $macro = Text::Xslate::PP::Type::Macro->new( + name => $name, + addr => $i, + state => $st, + ); + $st->symbol->{ $name } = $macro; + } + else { + $macro = undef; + } + } + elsif( $opnum == $Text::Xslate::PP::OPS{ macro_nargs } ) { + if($macro) { + $macro->nargs($code->[$i]->{arg}); + } + } + elsif( $opnum == $Text::Xslate::PP::OPS{ macro_outer } ) { + if($macro) { + $macro->outer($code->[$i]->{arg}); + } + } + elsif( $opnum == $Text::Xslate::PP::OPS{ depend } ) { + push @{ $tmpl }, $code->[ $i ]->{ arg }; + } + + } + + push @{$code}, { + exec_code => $Text::Xslate::PP::OPCODE[ $Text::Xslate::PP::OPS{end} ], + file => $oi_file, + line => $oi_line, + opname => 'end', + }; # for threshold + return; +} + + diff --git a/src/Text-Xslate.xs b/src/Text-Xslate.xs index 4120a522..322b3b34 100644 --- a/src/Text-Xslate.xs +++ b/src/Text-Xslate.xs @@ -1326,209 +1326,6 @@ CODE: tx_register_builtin_methods(aTHX_ hv); } -void -_assemble(HV* self, AV* proto, SV* name, SV* fullpath, SV* cachepath, SV* mtime) -CODE: -{ - dMY_CXT; - dTX_optable; - MAGIC* mg; - HV* hv; - HV* const ops = get_hv("Text::Xslate::OPS", GV_ADD); - U32 const len = av_len(proto) + 1; - U32 i; - U16 oi_line; /* opinfo.line */ - SV* oi_file; - tx_state_t st; - AV* tmpl; - SV* tobj; - SV** svp; - AV* macro = NULL; - - TAINT_NOT; /* All the SVs we'll create here are safe */ - - Zero(&st, 1, tx_state_t); - - svp = hv_fetchs(self, "template", FALSE); - if(!(svp && SvROK(*svp) && SvTYPE(SvRV(*svp)) == SVt_PVHV)) { - croak("The xslate instance has no template table"); - } - hv = (HV*)SvRV(*svp); - - if(!SvOK(name)) { - croak("Undefined template name is invalid"); - } - - /* fetch the template object from $self->{template}{$name} */ - tobj = hv_iterval(hv, hv_fetch_ent(hv, name, TRUE, 0U)); - - tmpl = newAV(); - /* store the template object to $self->{template}{$name} */ - sv_setsv(tobj, sv_2mortal(newRV_noinc((SV*)tmpl))); - av_extend(tmpl, TXo_least_size - 1); - - sv_setsv(*av_fetch(tmpl, TXo_MTIME, TRUE), mtime); - sv_setsv(*av_fetch(tmpl, TXo_CACHEPATH, TRUE), cachepath); - sv_setsv(*av_fetch(tmpl, TXo_FULLPATH, TRUE), fullpath); - - /* prepare function table */ - svp = hv_fetchs(self, "function", FALSE); - TAINT_NOT; - - if(!( SvROK(*svp) && SvTYPE(SvRV(*svp)) == SVt_PVHV )) { - croak("Function table must be a HASH reference"); - } - /* $self->{function} must be copied - because it might be changed per templates */ - st.symbol = newHVhv( (HV*)SvRV(*svp) ); - - st.tmpl = tmpl; - st.engine = newRV_inc((SV*)self); - sv_rvweaken(st.engine); - - st.hint_size = TX_HINT_SIZE; - - st.sa = &PL_sv_undef; - st.sb = &PL_sv_undef; - st.targ = newSV(0); - - /* stack frame */ - st.frames = newAV(); - st.current_frame = -1; - - Newxz(st.info, len + 1, tx_info_t); - st.info[len].line = (U16)-1; /* invalid value */ - st.info[len].file = SvREFCNT_inc_simple_NN(name); - - Newxz(st.code, len, tx_code_t); - - st.code_len = len; - st.pc = &st.code[0]; - - mg = sv_magicext((SV*)tmpl, NULL, PERL_MAGIC_ext, - &xslate_vtbl, (char*)&st, sizeof(st)); - mg->mg_flags |= MGf_DUP; - - oi_line = 0; - oi_file = name; - - for(i = 0; i < len; i++) { - SV* const code = *av_fetch(proto, i, TRUE); - if(SvROK(code) && SvTYPE(SvRV(code)) == SVt_PVAV) { - AV* const av = (AV*)SvRV(code); - SV* const opname = *av_fetch(av, 0, TRUE); - SV** const arg = av_fetch(av, 1, FALSE); - SV** const line = av_fetch(av, 2, FALSE); - SV** const file = av_fetch(av, 3, FALSE); - HE* const he = hv_fetch_ent(ops, opname, FALSE, 0U); - IV opnum; - - if(!he){ - croak("Oops: Unknown opcode '%"SVf"' on [%d]", opname, (int)i); - } - - opnum = SvIVx(hv_iterval(ops, he)); - st.code[i].exec_code = tx_optable[opnum]; - if(tx_oparg[opnum] & TXARGf_SV) { - if(!arg) { - croak("Oops: Opcode %"SVf" must have an argument on [%d]", opname, (int)i); - } - - if(tx_oparg[opnum] & TXARGf_KEY) { /* shared sv */ - STRLEN len; - const char* const pv = SvPV_const(*arg, len); - st.code[i].u_arg.sv = newSVpvn_share(pv, SvUTF8(*arg) ? -len : len, 0U); - } - else if(tx_oparg[opnum] & TXARGf_INT) { /* sviv */ - SvIV_please(*arg); - st.code[i].u_arg.sv = SvIsUV(*arg) - ? newSVuv(SvUV(*arg)) - : newSViv(SvIV(*arg)); - } - else { /* normal sv */ - st.code[i].u_arg.sv = newSVsv(*arg); - } - } - else if(tx_oparg[opnum] & TXARGf_INT) { - st.code[i].u_arg.iv = SvIV(*arg); - } - else if(tx_oparg[opnum] & TXARGf_PC) { - /* calculate relational addresses to absolute addresses */ - UV const abs_pos = (UV)(i + SvIV(*arg)); - - if(abs_pos >= (UV)len) { - croak("Oops: goto address %"IVdf" is out of range (must be 0 <= addr <= %"IVdf")", - SvIV(*arg), (IV)len); - } - st.code[i].u_arg.pc = TX_POS2PC(&st, abs_pos); - } - else { - if(arg && SvOK(*arg)) { - croak("Oops: Opcode %"SVf" has an extra argument %s on [%d]", - opname, tx_neat(aTHX_ *arg), (int)i); - } - } - - /* setup opinfo */ - if(line && SvOK(*line)) { - oi_line = (U16)SvUV(*line); - } - if(file && SvOK(*file) && !sv_eq(*file, oi_file)) { - oi_file = sv_mortalcopy(*file); - } - st.info[i].optype = (U16)opnum; - st.info[i].line = oi_line; - st.info[i].file = SvREFCNT_inc_simple_NN(oi_file); - - /* special cases */ - if(opnum == TXOP_macro_begin) { - SV* const name = st.code[i].u_arg.sv; - SV* const ent = hv_iterval(st.symbol, - hv_fetch_ent(st.symbol, name, TRUE, 0U)); - - if(!sv_true(ent)) { - SV* mref; - macro = newAV(); - mref = sv_2mortal(newRV_noinc((SV*)macro)); - sv_bless(mref, MY_CXT.macro_stash); - sv_setsv(ent, mref); - - (void)av_store(macro, TXm_OUTER, newSViv(0)); - (void)av_store(macro, TXm_NARGS, newSViv(0)); - (void)av_store(macro, TXm_ADDR, newSVuv(PTR2UV(TX_POS2PC(&st, i)))); - (void)av_store(macro, TXm_NAME, name); - st.code[i].u_arg.sv = NULL; - } - else { /* already defined */ - macro = NULL; - } - } - else if(opnum == TXOP_macro_nargs) { - if(macro) { - /* the number of outer lexical variables */ - (void)av_store(macro, TXm_NARGS, st.code[i].u_arg.sv); - st.code[i].u_arg.sv = NULL; - } - } - else if(opnum == TXOP_macro_outer) { - if(macro) { - /* the number of outer lexical variables */ - (void)av_store(macro, TXm_OUTER, st.code[i].u_arg.sv); - st.code[i].u_arg.sv = NULL; - } - } - else if(opnum == TXOP_depend) { - /* add a dependent file to the tmpl object */ - av_push(tmpl, st.code[i].u_arg.sv); - st.code[i].u_arg.sv = NULL; - } - } - else { - croak("Oops: Broken code found on [%d]", (int)i); - } - } /* end for each code */ -} - void render(SV* self, SV* source, SV* vars = &PL_sv_undef) ALIAS: @@ -1794,6 +1591,211 @@ CODE: } } +MODULE = Text::Xslate PACKAGE = Text::Xslate::Assembler + +void +_assemble(SV* self, HV* engine, AV* proto, SV* name, SV* fullpath, SV* cachepath, SV* mtime) +CODE: +{ + dMY_CXT; + dTX_optable; + MAGIC* mg; + HV* hv; + HV* const ops = get_hv("Text::Xslate::OPS", GV_ADD); + U32 const len = av_len(proto) + 1; + U32 i; + U16 oi_line; /* opinfo.line */ + SV* oi_file; + tx_state_t st; + AV* tmpl; + SV* tobj; + SV** svp; + AV* macro = NULL; + + TAINT_NOT; /* All the SVs we'll create here are safe */ + + Zero(&st, 1, tx_state_t); + + svp = hv_fetchs(engine, "template", FALSE); + if(!(svp && SvROK(*svp) && SvTYPE(SvRV(*svp)) == SVt_PVHV)) { + croak("The xslate instance has no template table"); + } + hv = (HV*)SvRV(*svp); + + if(!SvOK(name)) { + croak("Undefined template name is invalid"); + } + + /* fetch the template object from $engine->{template}{$name} */ + tobj = hv_iterval(hv, hv_fetch_ent(hv, name, TRUE, 0U)); + + tmpl = newAV(); + /* store the template object to $engine->{template}{$name} */ + sv_setsv(tobj, sv_2mortal(newRV_noinc((SV*)tmpl))); + av_extend(tmpl, TXo_least_size - 1); + + sv_setsv(*av_fetch(tmpl, TXo_MTIME, TRUE), mtime); + sv_setsv(*av_fetch(tmpl, TXo_CACHEPATH, TRUE), cachepath); + sv_setsv(*av_fetch(tmpl, TXo_FULLPATH, TRUE), fullpath); + + /* prepare function table */ + svp = hv_fetchs(engine, "function", FALSE); + TAINT_NOT; + + if(!( SvROK(*svp) && SvTYPE(SvRV(*svp)) == SVt_PVHV )) { + croak("Function table must be a HASH reference"); + } + /* $engine->{function} must be copied + because it might be changed per templates */ + st.symbol = newHVhv( (HV*)SvRV(*svp) ); + + st.tmpl = tmpl; + st.engine = newRV_inc((SV*)engine); + sv_rvweaken(st.engine); + + st.hint_size = TX_HINT_SIZE; + + st.sa = &PL_sv_undef; + st.sb = &PL_sv_undef; + st.targ = newSV(0); + + /* stack frame */ + st.frames = newAV(); + st.current_frame = -1; + + Newxz(st.info, len + 1, tx_info_t); + st.info[len].line = (U16)-1; /* invalid value */ + st.info[len].file = SvREFCNT_inc_simple_NN(name); + + Newxz(st.code, len, tx_code_t); + + st.code_len = len; + st.pc = &st.code[0]; + + mg = sv_magicext((SV*)tmpl, NULL, PERL_MAGIC_ext, + &xslate_vtbl, (char*)&st, sizeof(st)); + mg->mg_flags |= MGf_DUP; + + oi_line = 0; + oi_file = name; + + for(i = 0; i < len; i++) { + SV* const code = *av_fetch(proto, i, TRUE); + if(SvROK(code) && SvTYPE(SvRV(code)) == SVt_PVAV) { + AV* const av = (AV*)SvRV(code); + SV* const opname = *av_fetch(av, 0, TRUE); + SV** const arg = av_fetch(av, 1, FALSE); + SV** const line = av_fetch(av, 2, FALSE); + SV** const file = av_fetch(av, 3, FALSE); + HE* const he = hv_fetch_ent(ops, opname, FALSE, 0U); + IV opnum; + + if(!he){ + croak("Oops: Unknown opcode '%"SVf"' on [%d]", opname, (int)i); + } + + opnum = SvIVx(hv_iterval(ops, he)); + st.code[i].exec_code = tx_optable[opnum]; + if(tx_oparg[opnum] & TXARGf_SV) { + if(!arg) { + croak("Oops: Opcode %"SVf" must have an argument on [%d]", opname, (int)i); + } + + if(tx_oparg[opnum] & TXARGf_KEY) { /* shared sv */ + STRLEN len; + const char* const pv = SvPV_const(*arg, len); + st.code[i].u_arg.sv = newSVpvn_share(pv, SvUTF8(*arg) ? -len : len, 0U); + } + else if(tx_oparg[opnum] & TXARGf_INT) { /* sviv */ + SvIV_please(*arg); + st.code[i].u_arg.sv = SvIsUV(*arg) + ? newSVuv(SvUV(*arg)) + : newSViv(SvIV(*arg)); + } + else { /* normal sv */ + st.code[i].u_arg.sv = newSVsv(*arg); + } + } + else if(tx_oparg[opnum] & TXARGf_INT) { + st.code[i].u_arg.iv = SvIV(*arg); + } + else if(tx_oparg[opnum] & TXARGf_PC) { + /* calculate relational addresses to absolute addresses */ + UV const abs_pos = (UV)(i + SvIV(*arg)); + + if(abs_pos >= (UV)len) { + croak("Oops: goto address %"IVdf" is out of range (must be 0 <= addr <= %"IVdf")", + SvIV(*arg), (IV)len); + } + st.code[i].u_arg.pc = TX_POS2PC(&st, abs_pos); + } + else { + if(arg && SvOK(*arg)) { + croak("Oops: Opcode %"SVf" has an extra argument %s on [%d]", + opname, tx_neat(aTHX_ *arg), (int)i); + } + } + + /* setup opinfo */ + if(line && SvOK(*line)) { + oi_line = (U16)SvUV(*line); + } + if(file && SvOK(*file) && !sv_eq(*file, oi_file)) { + oi_file = sv_mortalcopy(*file); + } + st.info[i].optype = (U16)opnum; + st.info[i].line = oi_line; + st.info[i].file = SvREFCNT_inc_simple_NN(oi_file); + + /* special cases */ + if(opnum == TXOP_macro_begin) { + SV* const name = st.code[i].u_arg.sv; + SV* const ent = hv_iterval(st.symbol, + hv_fetch_ent(st.symbol, name, TRUE, 0U)); + + if(!sv_true(ent)) { + SV* mref; + macro = newAV(); + mref = sv_2mortal(newRV_noinc((SV*)macro)); + sv_bless(mref, MY_CXT.macro_stash); + sv_setsv(ent, mref); + + (void)av_store(macro, TXm_OUTER, newSViv(0)); + (void)av_store(macro, TXm_NARGS, newSViv(0)); + (void)av_store(macro, TXm_ADDR, newSVuv(PTR2UV(TX_POS2PC(&st, i)))); + (void)av_store(macro, TXm_NAME, name); + st.code[i].u_arg.sv = NULL; + } + else { /* already defined */ + macro = NULL; + } + } + else if(opnum == TXOP_macro_nargs) { + if(macro) { + /* the number of outer lexical variables */ + (void)av_store(macro, TXm_NARGS, st.code[i].u_arg.sv); + st.code[i].u_arg.sv = NULL; + } + } + else if(opnum == TXOP_macro_outer) { + if(macro) { + /* the number of outer lexical variables */ + (void)av_store(macro, TXm_OUTER, st.code[i].u_arg.sv); + st.code[i].u_arg.sv = NULL; + } + } + else if(opnum == TXOP_depend) { + /* add a dependent file to the tmpl object */ + av_push(tmpl, st.code[i].u_arg.sv); + st.code[i].u_arg.sv = NULL; + } + } + else { + croak("Oops: Broken code found on [%d]", (int)i); + } + } /* end for each code */ +} + MODULE = Text::Xslate PACKAGE = Text::Xslate::Util void From dc802432f6f4d289fba05a5af9c3186fbefad967 Mon Sep 17 00:00:00 2001 From: Daisuke Maki Date: Fri, 22 Nov 2013 07:23:30 +0900 Subject: [PATCH 16/39] I shall declare SAVE_SRC deprecated --- lib/Text/Xslate.pm | 4 ---- t/010_internals/031_save_src.t | 32 -------------------------------- 2 files changed, 36 deletions(-) delete mode 100644 t/010_internals/031_save_src.t diff --git a/lib/Text/Xslate.pm b/lib/Text/Xslate.pm index 7e7a6eac..bf02639b 100644 --- a/lib/Text/Xslate.pm +++ b/lib/Text/Xslate.pm @@ -66,9 +66,6 @@ BEGIN { my $dump_load = scalar($Text::Xslate::Util::DEBUG =~ /\b dump=load \b/xms); *_DUMP_LOAD = sub(){ $dump_load }; - my $save_src = scalar($Text::Xslate::Util::DEBUG =~ /\b save_src \b/xms); - *_SAVE_SRC = sub() { $save_src }; - *_ST_MTIME = sub() { 9 }; # see perldoc -f stat my $temp_base = $ENV{TEMPDIR} || File::Spec->tmpdir; @@ -286,7 +283,6 @@ sub load_string { # called in render_string() } $self->note(' _load_string: %s', join '\n', split /\n/, $string) if _DUMP_LOAD; - $self->{source}{''} = $string if _SAVE_SRC; $self->{string_buffer} = $string; my $asm = $self->compile($string); $self->_assembler->assemble($asm, '', \$string, undef, undef); diff --git a/t/010_internals/031_save_src.t b/t/010_internals/031_save_src.t deleted file mode 100644 index dacbebfe..00000000 --- a/t/010_internals/031_save_src.t +++ /dev/null @@ -1,32 +0,0 @@ -#!perl -w -use strict; -use if exists $INC{'Text/Xslate.pm'}, - 'Test::More', skip_all => 'Text::Xslate has been loaded'; -use Test::More; -BEGIN{ $ENV{XSLATE} ||= ''; $ENV{XSLATE} .= ':save_src' } - -use Text::Xslate; -use File::Spec; -use t::lib::Util; - -my $tx = Text::Xslate->new( - path => [{ foo => 'Hello, <: "" :>world!' }, path], - cache => 0, -); - -note 'from file'; -is $tx->render('hello.tx', { lang => 'Xslate' } ), "Hello, Xslate world!\n"; -is $tx->{provider}{source}{File::Spec->catfile(path, 'hello.tx')}, - "Hello, <:= \$lang :> world!\n" - or diag(explain($tx->{source})); - -note 'from hash'; -is $tx->render('foo'), 'Hello, world!'; -is $tx->{provider}{source}{foo}, 'Hello, <: "" :>world!' - or diag(explain($tx->{source})); - -note 'from '; -is $tx->render_string('<: 1 + 41 :>'), 42; -is $tx->{source}{''}, '<: 1 + 41 :>'; - -done_testing; From 6b70a24ed0d9a3234e315a572125a0e18d2cd618 Mon Sep 17 00:00:00 2001 From: Daisuke Maki Date: Fri, 22 Nov 2013 07:27:59 +0900 Subject: [PATCH 17/39] Remove my notes in POD which was causing POD tests to fail --- lib/Text/Xslate/Loader/File.pm | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/lib/Text/Xslate/Loader/File.pm b/lib/Text/Xslate/Loader/File.pm index 41708314..ae4b31fd 100644 --- a/lib/Text/Xslate/Loader/File.pm +++ b/lib/Text/Xslate/Loader/File.pm @@ -544,5 +544,10 @@ __END__ my $asm = $loader->load($file); } +=head1 NOTES -$loader は必ず $tx->byte_code_version() を考慮したキャッシュ等の保存先を担保すべき +Loaders must save data (i.e. cache) in locations that are uniquely +distinguishable by the bytecode version used. This allows you to seamlessly +upgrade between versions of Text::Xslate with differing bytecode versions + +=cut From f27551f1087a775aa00a83d1a0f4a65049abdd12 Mon Sep 17 00:00:00 2001 From: Daisuke Maki Date: Fri, 22 Nov 2013 10:36:39 +0900 Subject: [PATCH 18/39] Super silly, crude example of a loader using SQLite and memcached --- example/loader.pl | 128 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 128 insertions(+) create mode 100644 example/loader.pl diff --git a/example/loader.pl b/example/loader.pl new file mode 100644 index 00000000..b6d8bb23 --- /dev/null +++ b/example/loader.pl @@ -0,0 +1,128 @@ +package + Example::Xslate::Loader::SQLiteMemcached; +use Mouse; +use Cache::Memcached; +use DBI; +use DBD::SQLite; # Just making it explicit, because this is an example +use File::Spec; +use File::Temp (); + +has tempdir => ( + is => 'ro', + lazy => 1, + builder => 'build_tempdir' +); + +has db => ( + is => 'ro', + lazy => 1, + builder => 'build_db' +); + +has memd => ( + is => 'ro', + lazy => 1, + builder => 'build_memd' +); + +has engine => ( + is => 'ro', + required => 1, +); + +has assembler => ( + is => 'ro', + required => 1, +); + +sub build { + my ($class, $engine) = @_; + + $class->new( + engine => $engine, + assembler => $engine->_assembler, + ); +} + +sub build_tempdir { File::Temp->newdir() } +sub build_db { + my $self = shift; + my $tempfile = File::Spec->catfile($self->tempdir, "templates.db"); + my $dbh = DBI->connect("dbi:SQLite:dbname=$tempfile", undef, undef, { + RaiseError => 1, + AutoCommit => 1, + }); + + $dbh->do(< 'Hello, <: $lang :> world!', + hello_foo => 'Hello, <: $foo :> world!', + ); + foreach my $name (keys %templates) { + $dbh->do(<new({ + servers => [ '127.0.0.1:11211' ], + namespace => join(".", "templates", $self->engine->bytecode_version, ""), + }); +} + +sub compile { shift->engine->compile(@_) } +sub assemble { shift->assembler->assemble(@_) } + +sub load { + my ($self, $name) = @_; + + my ($body, $asm, $updated_on); + my $cached = $self->memd->get($name); + if ($cached) { + $asm = $cached->[0]; + $updated_on = $cached->[1]; + goto ASSEMBLE; + } + + my $sth = $self->db->prepare_cached(<execute($name)) { + return; + } + + ($body, $updated_on) = $sth->fetchrow_array(); + $asm = $self->compile($body); + $self->memd->set($name, [$asm, $updated_on]); + +ASSEMBLE: + $self->assemble($asm, $name, $name, undef, int($updated_on)); + return $asm; +} + +package main; +use strict; +use Text::Xslate; + +my $xslate = Text::Xslate->new(); +my $loader = Example::Xslate::Loader::SQLiteMemcached->new( + engine => $xslate, + assembler => $xslate->_assembler, +); +$xslate->{loader} = $loader; + +foreach (1..10) { + print $xslate->render('hello' => { lang => "Xslate" }), "\n"; +} From ab708c0bd034ba9dc21103a3485efb7b3b59a1de Mon Sep 17 00:00:00 2001 From: Daisuke Maki Date: Fri, 22 Nov 2013 10:45:19 +0900 Subject: [PATCH 19/39] Add include example --- example/loader.pl | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/example/loader.pl b/example/loader.pl index b6d8bb23..df54cc99 100644 --- a/example/loader.pl +++ b/example/loader.pl @@ -44,6 +44,12 @@ sub build { ); } +sub BUILD { + my $self = shift; + $self->memd->flush_all(); + return $self; +} + sub build_tempdir { File::Temp->newdir() } sub build_db { my $self = shift; @@ -65,8 +71,13 @@ sub build_db { my %templates = ( hello => 'Hello, <: $lang :> world!', - hello_foo => 'Hello, <: $foo :> world!', + hello_include => <<'EOM', +Hello, <: $lang :> world! +:include "footer" +EOM + footer => "\n-- Created with Xslate", ); + foreach my $name (keys %templates) { $dbh->do(<render('hello' => { lang => "Xslate" }), "\n"; + print $xslate->render('hello_include' => { lang => "Xslate" }), "\n"; } From a2ea9f23791b3107ec81c22bfa43fb60c967993a Mon Sep 17 00:00:00 2001 From: Daisuke Maki Date: Fri, 22 Nov 2013 12:28:42 +0900 Subject: [PATCH 20/39] Create a base Loader class so that stuff like initialization can be handwaved --- example/loader.pl | 74 +++++++++++------------------ lib/Text/Xslate.pm | 43 +++++++---------- lib/Text/Xslate/Assembler.pm | 13 +++-- lib/Text/Xslate/Loader.pm | 69 +++++++++++++++++++++++++++ lib/Text/Xslate/Loader/File.pm | 44 +++++------------ lib/Text/Xslate/Runner.pm | 5 +- t/020_interface/001_parser_option.t | 3 ++ 7 files changed, 143 insertions(+), 108 deletions(-) create mode 100644 lib/Text/Xslate/Loader.pm diff --git a/example/loader.pl b/example/loader.pl index df54cc99..5d59faa2 100644 --- a/example/loader.pl +++ b/example/loader.pl @@ -3,14 +3,12 @@ package use Mouse; use Cache::Memcached; use DBI; -use DBD::SQLite; # Just making it explicit, because this is an example -use File::Spec; -use File::Temp (); -has tempdir => ( +extends 'Text::Xslate::Loader'; + +has connect_info => ( is => 'ro', - lazy => 1, - builder => 'build_tempdir' + required => 1, ); has db => ( @@ -20,45 +18,19 @@ package ); has memd => ( - is => 'ro', - lazy => 1, - builder => 'build_memd' -); - -has engine => ( is => 'ro', required => 1, ); -has assembler => ( - is => 'ro', - required => 1, -); - -sub build { - my ($class, $engine) = @_; - - $class->new( - engine => $engine, - assembler => $engine->_assembler, - ); -} - -sub BUILD { +after configure => sub { my $self = shift; - $self->memd->flush_all(); - return $self; -} + $self->memd->flush_all; +}; -sub build_tempdir { File::Temp->newdir() } +sub build_tempdir { } sub build_db { my $self = shift; - my $tempfile = File::Spec->catfile($self->tempdir, "templates.db"); - my $dbh = DBI->connect("dbi:SQLite:dbname=$tempfile", undef, undef, { - RaiseError => 1, - AutoCommit => 1, - }); - + my $dbh = DBI->connect(@{$self->connect_info}); $dbh->do(<engine->compile(@_) } -sub assemble { shift->assembler->assemble(@_) } - sub load { my ($self, $name) = @_; @@ -111,10 +80,12 @@ sub load { SELECT body, updated_on FROM template WHERE name = ? EOSQL if (! $sth->execute($name)) { + $sth->finish; return; } ($body, $updated_on) = $sth->fetchrow_array(); + $sth->finish; $asm = $self->compile($body); $self->memd->set($name, [$asm, $updated_on]); @@ -126,13 +97,26 @@ sub load { package main; use strict; use Text::Xslate; +use DBD::SQLite; # Just making it explicit, because this is an example +use File::Spec; +use File::Temp (); -my $xslate = Text::Xslate->new(); -my $loader = Example::Xslate::Loader::SQLiteMemcached->new( - engine => $xslate, - assembler => $xslate->_assembler, +my $tempdir = File::Temp->newdir(); +my $cache = Cache::Memcached->new({ + servers => [ '127.0.0.1:11211' ], + namespace => "xslate.loader.example." +}); +my $xslate = Text::Xslate->new( + loader => Example::Xslate::Loader::SQLiteMemcached->new( + memd => $cache, + connect_info => [ + sprintf("dbi:SQLite:dbname=%s", File::Spec->catfile($tempdir, "template.db")), + undef, + undef, + { RaiseError => 1, AutoCommit => 1} + ], + ), ); -$xslate->{loader} = $loader; foreach (1..10) { print $xslate->render('hello' => { lang => "Xslate" }), "\n"; diff --git a/lib/Text/Xslate.pm b/lib/Text/Xslate.pm index bf02639b..1b0641e9 100644 --- a/lib/Text/Xslate.pm +++ b/lib/Text/Xslate.pm @@ -238,6 +238,7 @@ sub new { $self->_resolve_function_aliases(\%funcs); + $self->_setup_components(); return $self; } @@ -299,7 +300,8 @@ sub find_file { sub load_file { my($self, $file, $mtime, $omit_augment) = @_; local $self->{omit_augment} = $omit_augment; - my $asm = $self->_loader->load($file, $mtime); + my $loader = $self->_loader; + my $asm = $loader->load($file, $mtime); return $asm; } @@ -352,36 +354,25 @@ sub _filter_options_for_magic_token { @filterd_options; } -sub _loader { +sub _setup_components { my $self = shift; - my $loader = $self->{loader}; - if (!ref $loader) { - require Mouse; - Mouse::load_class($loader); - $loader = $loader->build($self); - $self->{loader} = $loader; - } - return $loader; -} -sub _assembler { - my $self = shift; - my $assembler = $self->{assembler}; - if (!ref $assembler) { - # XXX ::Assembler has a function defined in XS, which gets loaded - # along with the main Xslate functions. So when the code gets here - # ::Assembler seems as if it's loaded already, and it's no XS - # methods don't get loaded properly. To prevent this, although we - # check to see if we need to load it here, ::Assembler is already - # "use"d at the top of ::Engine - require Mouse; - Mouse::load_class($assembler); - $assembler = $assembler->build($self); - $self->{assembler} = $assembler; + # MUST BE IN THIS ORDER, b.c. loader uses assembler + foreach my $name (qw(assembler loader)) { + my $component = $self->{$name}; + if (ref $component) { + $component->configure($self); + } else { + require Mouse; + Mouse::load_class($component); + $component = $component->build($self); + } + $self->{$name} = $component; } - return $assembler; } +sub _loader { $_[0]->{loader} } +sub _assembler { $_[0]->{assembler} } sub _compiler { my($self) = @_; my $compiler = $self->{compiler}; diff --git a/lib/Text/Xslate/Assembler.pm b/lib/Text/Xslate/Assembler.pm index ebf890d3..ba032087 100644 --- a/lib/Text/Xslate/Assembler.pm +++ b/lib/Text/Xslate/Assembler.pm @@ -2,13 +2,20 @@ package Text::Xslate::Assembler; use Mouse; has engine => ( - is => 'ro', - required => 1, + is => 'rw', ); sub build { my ($class, $engine) = @_; - $class->new(engine => $engine); + my $self = $class->new(); + $self->configure($engine); + return $self; +} + +sub configure { + my ($self, $engine) = @_; + $self->engine($engine); + return $self; } sub assemble { diff --git a/lib/Text/Xslate/Loader.pm b/lib/Text/Xslate/Loader.pm new file mode 100644 index 00000000..ed39c8ad --- /dev/null +++ b/lib/Text/Xslate/Loader.pm @@ -0,0 +1,69 @@ +package Text::Xslate::Loader; +use Mouse; + +has assembler => ( + is => 'rw', +); + +has engine => ( # XXX Should try to remove dep on engine + is => 'rw', +); + +has magic_template => ( + is => 'rw', +); + +has pre_process_handler => ( + is => 'rw', +); + +sub extract_config_from_engine { + my ($class, $engine) = @_; + return ( + engine => $engine, + assembler => $engine->_assembler, + magic_template => $engine->magic_template, + pre_process_handler => $engine->{pre_process_handler}, + ); +} + +sub build { + my ($class, $engine) = @_; + my $self = $class->new(); + $self->configure($engine); + return $self; +} + +sub configure { + my ($self, $engine) = @_; + my %vars = $self->extract_config_from_engine($engine); + foreach my $var (keys %vars) { + $self->$var($vars{$var}); + } + return $self; +} + +sub compile { shift->engine->compile(@_) } +sub assemble { shift->assembler->assemble(@_) } +sub load { + require Carp; + Carp::confess("$_[0]->compile() not declared"); +} + +1; + +__END__ + +=head1 NAME + +Text::Xslate::Loader - Loader Base Class + +=head1 DESCRIPTION + +Text::Xslate::Loader is a base class for Loader classes, but it doesn't do +much by itself. You are also NOT REQUIRED to inherit from this class to make +your own Loader: You just need to provide a "load()" method for it to work. +This class is here because there are a few initialization-related methods +that are mostly common for Loaders. + +=cut \ No newline at end of file diff --git a/lib/Text/Xslate/Loader/File.pm b/lib/Text/Xslate/Loader/File.pm index ae4b31fd..6d6090c4 100644 --- a/lib/Text/Xslate/Loader/File.pm +++ b/lib/Text/Xslate/Loader/File.pm @@ -10,62 +10,42 @@ use Text::Xslate::Util (); use constant ST_MTIME => 9; use constant TRACE_LOAD => Text::Xslate::Engine::_DUMP_LOAD(); -has assembler => ( - is => 'ro', - required => 1, -); +extends 'Text::Xslate::Loader'; has cache_dir => ( - is => 'ro', - required => 1, + is => 'rw', ); has cache_strategy => ( - is => 'ro', - default => 1, -); - -has engine => ( - is => 'ro', - required => 1, + is => 'rw', ); has include_dirs => ( - is => 'ro', - required => 1, + is => 'rw', ); has input_layer => ( - is => 'ro', - required => 1, + is => 'rw', default => ':utf8', ); -has pre_process_handler => ( - is => 'ro', -); - has magic_template => ( - is => 'ro', - required => 1, + is => 'rw', ); -sub build { - my ($class, $engine) = @_; - my $self = $class->new( - assembler => $engine->_assembler, +around extract_config_from_engine => sub { + my ($next, $class, $engine) = @_; + my @config = $class->$next($engine); + return ( + @config, # XXX Cwd::abs_path would stat() the directory, so we need to # to use File::Spec->rel2abs cache_dir => File::Spec->rel2abs($engine->{cache_dir}), cache_strategy => $engine->{cache}, - engine => $engine, include_dirs => $engine->{path}, input_layer => $engine->input_layer, - magic_template => $engine->magic_template, - pre_process_handler => $engine->{pre_process_handler}, ); - return $self; -} +}; use Scope::Guard; my $INDENT_LEVEL = 0; diff --git a/lib/Text/Xslate/Runner.pm b/lib/Text/Xslate/Runner.pm index 1e30f8b3..f476db03 100644 --- a/lib/Text/Xslate/Runner.pm +++ b/lib/Text/Xslate/Runner.pm @@ -312,12 +312,13 @@ sub run { # path in the xslate object if ( -d $target ) { local $self->{__process_base} = scalar(File::Spec->splitdir($target)); - local $xslate->{path} = [ $target, @{ $xslate->{path} || [] } ]; + # XXX modifying {path} like this is way too icky + local $xslate->{loader}->{include_dirs} = [ $target, @{ $xslate->{path} || [] } ]; $self->process_tree( $xslate, $target ); } else { my $dirname = File::Basename::dirname($target); local $self->{__process_base} = scalar(File::Spec->splitdir($dirname)); - local $xslate->{path} = [ $dirname, @{ $xslate->{path} || [] } ]; + local $xslate->{loader}->{include_dirs} = [ $dirname, @{ $xslate->{path} || [] } ]; $self->process_file( $xslate, $target ); } } diff --git a/t/020_interface/001_parser_option.t b/t/020_interface/001_parser_option.t index 396ae061..60812c61 100644 --- a/t/020_interface/001_parser_option.t +++ b/t/020_interface/001_parser_option.t @@ -58,6 +58,8 @@ $tx = Text::Xslate->new( is $tx->render_string('Hello, {lang} world!', { lang => 'Xslate' }), 'Hello, Xslate world!'; +TODO: { + todo_skip("objects on syntax make magic_token() fail", 6); my $myparser = Text::Xslate::Parser->new( line_start => undef, tag_start => '[%', @@ -89,5 +91,6 @@ foreach my $pair(@data) { is $tx->render_string($in, \%vars), $out or diag $in; } +} done_testing; From ed424aac1b92970829786de5f662573d0cb9563d Mon Sep 17 00:00:00 2001 From: Daisuke Maki Date: Fri, 22 Nov 2013 12:54:20 +0900 Subject: [PATCH 21/39] Move error handling to MakeError Note: COMPLETELY BREAKS Xslate::PP. I repeat: COMPLETELY BREAKS Xslate::PP! --- lib/Text/Xslate.pm | 15 +++--- lib/Text/Xslate/Compiler.pm | 63 +++++++++++------------ lib/Text/Xslate/Loader.pm | 2 + lib/Text/Xslate/Loader/File.pm | 10 ++-- lib/Text/Xslate/MakeError.pm | 85 ++++++++++++++++++++++++++++++++ lib/Text/Xslate/Parser.pm | 45 ++++++++--------- lib/Text/Xslate/Syntax/TTerse.pm | 4 +- lib/Text/Xslate/Util.pm | 71 -------------------------- src/Text-Xslate.xs | 7 +-- t/020_interface/005_util.t | 12 ++--- 10 files changed, 162 insertions(+), 152 deletions(-) create mode 100644 lib/Text/Xslate/MakeError.pm diff --git a/lib/Text/Xslate.pm b/lib/Text/Xslate.pm index 1b0641e9..ed50bf94 100644 --- a/lib/Text/Xslate.pm +++ b/lib/Text/Xslate.pm @@ -55,10 +55,11 @@ use File::Spec; use Text::Xslate::Assembler; use Text::Xslate::Util qw( - make_error dump ); +with 'Text::Xslate::MakeError'; + # constants BEGIN { our @ISA = qw(Exporter); @@ -256,7 +257,7 @@ sub _resolve_function_aliases { foreach my $f(values %{$funcs}) { my %seen; # to avoid infinite loops while(!( ref($f) or Scalar::Util::looks_like_number($f) )) { - my $v = $funcs->{$f} or $self->_error( + my $v = $funcs->{$f} or $self->throw_error( "Cannot resolve a function alias '$f'," . " which refers nothing", ); @@ -266,7 +267,7 @@ sub _resolve_function_aliases { last; } else { - $seen{$v}++ and $self->_error( + $seen{$v}++ and $self->throw_error( "Cannot resolve a function alias '$f'," . " which makes circular references", ); @@ -280,7 +281,7 @@ sub _resolve_function_aliases { sub load_string { # called in render_string() my($self, $string) = @_; if(not defined $string) { - $self->_error("LoadError: Template string is not given"); + $self->throw_error("LoadError: Template string is not given"); } $self->note(' _load_string: %s', join '\n', split /\n/, $string) if _DUMP_LOAD; @@ -317,7 +318,7 @@ $input_layer, $fullpath); return $$fullpath; } else { open my($source), '<' . $input_layer, $fullpath - or $self->_error("LoadError: Cannot open $fullpath for reading: $!"); + or $self->throw_error("LoadError: Cannot open $fullpath for reading: $!"); local $/; return scalar <$source>; } @@ -406,10 +407,6 @@ sub compile { omit_augment => $self->{omit_augment}); } -sub _error { - die make_error(@_); -} - sub _magic_token_arguments { my $self = shift; return ( diff --git a/lib/Text/Xslate/Compiler.pm b/lib/Text/Xslate/Compiler.pm index dd300f10..94e43a48 100644 --- a/lib/Text/Xslate/Compiler.pm +++ b/lib/Text/Xslate/Compiler.pm @@ -2,6 +2,8 @@ package Text::Xslate::Compiler; use Mouse; use Mouse::Util::TypeConstraints; +with 'Text::Xslate::MakeError'; + use Scalar::Util (); use Carp (); @@ -10,7 +12,6 @@ use Text::Xslate::Util qw( $DEBUG value_to_literal is_int any_in - make_error p ); @@ -386,7 +387,7 @@ sub push_expr { sub _cat_files { my($self, $files) = @_; - my $engine = $self->engine || $self->_error("No Xslate engine which header/footer requires"); + my $engine = $self->engine || $self->throw_error("No Xslate engine which header/footer requires"); my $s = ''; foreach my $file(@{$files}) { my $fullpath = $engine->find_file($file)->{fullpath}; @@ -423,7 +424,7 @@ sub _process_cascade { my($self, $cascade, $args, $main_code) = @_; printf STDERR "# cascade %s %s", $self->file, $cascade->dump if _DUMP_CAS; my $engine = $self->engine - || $self->_error("Cannot cascade templates without Xslate engine", $cascade); + || $self->throw_error("Cannot cascade templates without Xslate engine", $cascade); my($base_file, $base_code); my $base = $cascade->first; @@ -536,11 +537,11 @@ sub _process_cascade_file { if(exists $mtable->{$name}) { my $m = $mtable->{$name}; if(ref($m) ne 'HASH') { - $self->_error('[BUG] Unexpected macro structure: ' + $self->throw_error('[BUG] Unexpected macro structure: ' . p($m) ); } - $self->_error( + $self->throw_error( "Redefinition of macro/block $name in " . $file . " (you must use block modifiers to override macros/blocks)", $m->{line} @@ -638,7 +639,7 @@ sub _generate_operator { my($self, $node) = @_; # This method is called when an operators is used as an expression, # e.g. <: + :>, so simply throws the error - $self->_error("Invalid expression", $node); + $self->throw_error("Invalid expression", $node); } sub _can_optimize_print { @@ -705,7 +706,7 @@ sub _generate_print { } if(!@code) { - $self->_error("$node requires at least one argument", $node); + $self->throw_error("$node requires at least one argument", $node); } return @code; } @@ -740,14 +741,14 @@ sub _bare_to_file { return $file->value; } else { - $self->_error("Expected a name or string literal", $file); + $self->throw_error("Expected a name or string literal", $file); } } sub _generate_cascade { my($self, $node) = @_; if(defined $self->cascade) { - $self->_error("Cannot cascade twice in a template", $node); + $self->throw_error("Cannot cascade twice in a template", $node); } $self->cascade( $node ); return; @@ -790,7 +791,7 @@ sub _generate_for { my $block = $node->third; if(@{$vars} != 1) { - $self->_error("A for-loop requires single variable for each item", $node); + $self->throw_error("A for-loop requires single variable for each item", $node); } local $self->{lvar} = { %{$self->lvar} }; # new scope local $self->{const} = [ @{$self->const} ]; # new scope @@ -847,7 +848,7 @@ sub _generate_while { my $block = $node->third; if(@{$vars} > 1) { - $self->_error("A while-loop requires one or zero variable for each items", $node); + $self->throw_error("A while-loop requires one or zero variable for each items", $node); } (my $cond_op, undef, $expr) = $self->_prepare_cond_expr($expr); @@ -884,17 +885,17 @@ sub _generate_loop_control { my $type = $node->id; any_in($type, qw(last next)) - or $self->_error("[BUG] Unknown loop control statement '$type'"); + or $self->throw_error("[BUG] Unknown loop control statement '$type'"); if(not $self->{in_loop}) { - $self->_error("Use of loop control statement ($type) outside of loops"); + $self->throw_error("Use of loop control statement ($type) outside of loops"); } my @cleanup; if( $self->{in_loop} == _FOR_LOOP && $type eq 'last' ) { my $lvar_id = $self->lvar->{'($_)'}; defined($lvar_id) - or $self->_error('[BUG] Undefined loop iterator'); + or $self->throw_error('[BUG] Undefined loop iterator'); @cleanup = ( $self->opcode( 'nil', undef, @@ -945,7 +946,7 @@ sub _generate_proc { # definition of macro, block, before, around, after if(exists $self->macro_table->{$name}) { my $m = $self->macro_table->{$name}; if(p(\%macro) ne p($m)) { - $self->_error("Redefinition of $type $name is forbidden", $node); + $self->throw_error("Redefinition of $type $name is forbidden", $node); } } $self->macro_table->{$name} = \%macro; @@ -1061,7 +1062,7 @@ sub _generate_given { my $block = $node->third; if(@{$vars} > 1) { - $self->_error("A given block requires one or zero variables", $node); + $self->throw_error("A given block requires one or zero variables", $node); } local $self->{lvar} = { %{$self->lvar} }; # new scope local $self->{const} = [ @{$self->const} ]; # new scope @@ -1090,7 +1091,7 @@ sub _generate_variable { else { my $name = $self->_variable_to_value($node); if($name =~ /~/) { - $self->_error("Undefined iterator variable $node", $node); + $self->throw_error("Undefined iterator variable $node", $node); } return $self->opcode( fetch_s => $name, line => $node->line ); } @@ -1146,7 +1147,7 @@ sub _generate_unary { return @code; } else { - $self->_error("Unary operator $id is not implemented", $node); + $self->throw_error("Unary operator $id is not implemented", $node); } } @@ -1226,14 +1227,14 @@ sub _generate_binary { @rhs; } - $self->_error("Binary operator $id is not implemented", $node); + $self->throw_error("Binary operator $id is not implemented", $node); } sub _generate_range { my($self, $node) = @_; $self->can_be_in_list_context - or $self->_error("Range operator must be in list context"); + or $self->throw_error("Range operator must be in list context"); my @lhs = $self->compile_ast($node->first); @@ -1268,7 +1269,7 @@ sub _generate_call { if(my $intern = $builtin{$callable->id} and !$self->overridden_builtin->{$callable->id}) { if(@{$args} != 1) { - $self->_error("Wrong number of arguments for $callable", $node); + $self->throw_error("Wrong number of arguments for $callable", $node); } return $self->compile_ast($args->[0]), @@ -1290,7 +1291,7 @@ sub _generate_iterator { my $item_var = $node->first; my $lvar_id = $self->lvar->{$item_var}; if(!defined($lvar_id)) { - $self->_error("Refer to iterator $node, but $item_var is not defined", + $self->throw_error("Refer to iterator $node, but $item_var is not defined", $node); } @@ -1307,7 +1308,7 @@ sub _generate_iterator_body { my $item_var = $node->first; my $lvar_id = $self->lvar->{$item_var}; if(!defined($lvar_id)) { - $self->_error("Refer to iterator $node.body, but $item_var is not defined", + $self->throw_error("Refer to iterator $node.body, but $item_var is not defined", $node); } @@ -1327,7 +1328,7 @@ sub _generate_assign { my $lvar_name = $lhs->id; if($node->id ne "=") { - $self->_error("Assignment ($node) is not supported", $node); + $self->throw_error("Assignment ($node) is not supported", $node); } my @expr = $self->compile_ast($rhs); @@ -1338,7 +1339,7 @@ sub _generate_assign { } if(!exists $lvar->{$lvar_name} or $lhs->arity ne "variable") { - $self->_error("Cannot modify $lhs, which is not a lexical variable", $node); + $self->throw_error("Cannot modify $lhs, which is not a lexical variable", $node); } return @@ -1384,13 +1385,13 @@ sub _localize_vars { $self->opcode( 'localize_vars' ); } else { - $self->_error("You must pass pairs of expressions to include"); + $self->throw_error("You must pass pairs of expressions to include"); } } while(my($key, $expr) = splice @pairs, 0, 2) { if(!any_in($key->arity, qw(literal variable))) { - $self->_error("You must pass a simple name to localize variables", $key); + $self->throw_error("You must pass a simple name to localize variables", $key); } push @localize, $self->compile_ast($expr), @@ -1601,12 +1602,12 @@ sub as_assembly { return $asm; } -sub _error { - my($self, $message, $node) = @_; +around throw_error => sub { + my($next, $self, $message, $node) = @_; my $line = ref($node) ? $node->line : $node; - die $self->make_error($message, $self->file, $line); -} + $self->$next($message, $self->file, $line); +}; no Mouse; no Mouse::Util::TypeConstraints; diff --git a/lib/Text/Xslate/Loader.pm b/lib/Text/Xslate/Loader.pm index ed39c8ad..9d0f3f4b 100644 --- a/lib/Text/Xslate/Loader.pm +++ b/lib/Text/Xslate/Loader.pm @@ -1,6 +1,8 @@ package Text::Xslate::Loader; use Mouse; +with 'Text::Xslate::MakeError'; + has assembler => ( is => 'rw', ); diff --git a/lib/Text/Xslate/Loader/File.pm b/lib/Text/Xslate/Loader/File.pm index 6d6090c4..fee72bfe 100644 --- a/lib/Text/Xslate/Loader/File.pm +++ b/lib/Text/Xslate/Loader/File.pm @@ -181,7 +181,7 @@ sub locate_file { } if($name =~ /\Q$updir\E/xmso) { - die("LoadError: Forbidden component (updir: '$updir') found in file name '$name'"); + $self->throw_error("LoadError: Forbidden component (updir: '$updir') found in file name '$name'"); } my $dirs = $self->include_dirs; @@ -238,8 +238,7 @@ sub locate_file { ); } -# $engine->_error("LoadError: Cannot find '$file' (path: @{$self->{path}})"); - die "LoadError: Cannot find '$name' (include dirs: @$dirs)"; + $self->throw_error("LoadError: Cannot find '$name' (include dirs: @$dirs)"); } # Loads the compiled code from cache. Requires the full path @@ -345,7 +344,7 @@ sub store_cache { }; if (my $e = $@) { $temp->unlink_on_destroy(1); - die $e; + $self->throw_error("LoadError: Failed to create file cache: $e"); } if (! File::Copy::move($temp->filename, $path)) { @@ -421,8 +420,7 @@ sub build_asm { my $filename = $self->filename; open my($in), '<:raw', $filename - or die "LoadError: Cannot open $filename for reading: $!"; -# or $engine->_error("LoadError: Cannot open $filename for reading: $!"); + or $self->throw_error("LoadError: Cannot open $filename for reading: $!"); my $data; diff --git a/lib/Text/Xslate/MakeError.pm b/lib/Text/Xslate/MakeError.pm new file mode 100644 index 00000000..b9f4f494 --- /dev/null +++ b/lib/Text/Xslate/MakeError.pm @@ -0,0 +1,85 @@ +package Text::Xslate::MakeError; +use Mouse::Role; +use Carp (); + +our $DEBUG; +defined($DEBUG) or $DEBUG = $ENV{XSLATE} || ''; + +our $DisplayWidth = 76; +if($DEBUG =~ /display_width=(\d+)/) { + $DisplayWidth = $1; +} + +sub throw_error { + die shift->make_error(@_); +} + +sub read_around { # for error messages + my($file, $line, $around, $input_layer) = @_; + + defined($file) && defined($line) or return ''; + + if (ref $file) { # if $file is a scalar ref, it must contain text strings + my $content = $$file; + utf8::encode($content); + $file = \$content; + } + + $around = 1 if not defined $around; + $input_layer = '' if not defined $input_layer; + + open my $in, '<' . $input_layer, $file or return ''; + local $/ = "\n"; + local $. = 0; + + my $s = ''; + while(defined(my $l = <$in>)) { + if($. >= ($line - $around)) { + $s .= $l; + } + if($. >= ($line + $around)) { + last; + } + } + return $s; +} + +sub make_error { + my($self, $message, $file, $line, @extra) = @_; + if(ref $message eq 'SCALAR') { # re-thrown form virtual machines + return ${$message}; + } + + my $lines = read_around($file, $line, 1, $self->input_layer); + if($lines) { + $lines .= "\n" if $lines !~ /\n\z/xms; + $lines = '-' x $DisplayWidth . "\n" + . $lines + . '-' x $DisplayWidth . "\n"; + } + + local $Carp::CarpLevel = $Carp::CarpLevel + 1; + my $class = ref($self) || $self; + $message =~ s/\A \Q$class: \E//xms and $message .= "\t..."; + + if(defined $file) { + if(defined $line) { + unshift @extra, $line; + } + unshift @extra, ref($file) ? '' : $file; + } + + if(@extra) { + $message = Carp::shortmess(sprintf '%s (%s)', + $message, join(':', @extra)); + } + else { + $message = Carp::shortmess($message); + } + return sprintf "%s: %s%s", + $class, $message, $lines; +} + +no Mouse::Role; + +1; diff --git a/lib/Text/Xslate/Parser.pm b/lib/Text/Xslate/Parser.pm index 3c5df6b5..dad63a1f 100644 --- a/lib/Text/Xslate/Parser.pm +++ b/lib/Text/Xslate/Parser.pm @@ -10,10 +10,11 @@ use Text::Xslate::Util qw( is_int any_in neat literal_to_value - make_error p ); +with 'Text::Xslate::MakeError'; + use constant _DUMP_PROTO => scalar($DEBUG =~ /\b dump=proto \b/xmsi); use constant _DUMP_TOKEN => scalar($DEBUG =~ /\b dump=token \b/xmsi); @@ -220,7 +221,7 @@ sub parse { if(my $input_pos = pos $parser->{input}) { if($input_pos != length($parser->{input})) { - $parser->_error("Syntax error", $parser->token); + $parser->throw_error("Syntax error", $parser->token); } } @@ -368,7 +369,7 @@ sub split :method { my $orig_src = $_[0]; substr $orig_src, -length($_), length($_), ''; my $line = ($orig_src =~ tr/\n/\n/); - $parser->_error("Malformed templates detected", + $parser->throw_error("Malformed templates detected", neat((split /\n/, $_)[0]), ++$line, ); } @@ -426,7 +427,7 @@ sub preprocess { # noop, just a marker } else { - $parser->_error("Oops: Unknown token: $s ($type)"); + $parser->throw_error("Oops: Unknown token: $s ($type)"); } } print STDOUT $code, "\n" if _DUMP_PROTO; @@ -727,7 +728,7 @@ sub advance { elsif($arity eq "operator") { $symbol = $stash->{$id}; if(not defined $symbol) { - $parser->_error("Unknown operator '$id'"); + $parser->throw_error("Unknown operator '$id'"); } $symbol = $symbol->clone( arity => $arity, # to make error messages clearer @@ -775,7 +776,7 @@ sub default_nud { sub default_led { my($parser, $symbol) = @_; $parser->near_token($parser->token); - $parser->_error( + $parser->throw_error( sprintf 'Missing operator (%s): %s', $symbol->arity, $symbol->id); } @@ -783,7 +784,7 @@ sub default_led { sub default_std { my($parser, $symbol) = @_; $parser->near_token($parser->token); - $parser->_error( + $parser->throw_error( sprintf 'Not a statement (%s): %s', $symbol->arity, $symbol->id); } @@ -872,7 +873,7 @@ sub nud_prefix { sub led_assignment { my($parser, $symbol, $left) = @_; - $parser->_error("Assignment ($symbol) is forbidden", $left); + $parser->throw_error("Assignment ($symbol) is forbidden", $left); } sub assignment { @@ -1075,7 +1076,7 @@ sub reserve { # reserve a name to the scope return $symbol; } if($t->arity eq "name") { - $parser->_error("Already defined: $symbol"); + $parser->throw_error("Already defined: $symbol"); } } $top->{$symbol->id} = $symbol; @@ -1090,7 +1091,7 @@ sub define { # define a name to the scope my $t = $top->{$symbol->id}; if(defined $t) { - $parser->_error($t->is_reserved ? "Already is_reserved: $t" : "Already defined: $t"); + $parser->throw_error($t->is_reserved ? "Already is_reserved: $t" : "Already defined: $t"); } $top->{$symbol->id} = $symbol; @@ -1261,7 +1262,7 @@ sub nud_iterator { my $generator = $parser->iterator_element->{$t->value}; if(!$generator) { - $parser->_error("Undefined iterator element: $t"); + $parser->throw_error("Undefined iterator element: $t"); } $parser->advance(); # element name @@ -1386,7 +1387,7 @@ sub nud_current_vars { sub nud_separator { my($self, $symbol) = @_; - $self->_error("Invalid expression found", $symbol); + $self->throw_error("Invalid expression found", $symbol); } # -> VARS { STATEMENTS } @@ -1622,7 +1623,7 @@ sub std_when { my($parser, $symbol) = @_; if(!$parser->in_given) { - $parser->_error("You cannot use $symbol blocks outside given blocks"); + $parser->throw_error("You cannot use $symbol blocks outside given blocks"); } my $proc = $symbol->clone(arity => 'when'); if($symbol->id eq "when") { @@ -1816,7 +1817,7 @@ sub std_last { sub bad_iterator_args { my($parser, $iterator) = @_; - $parser->_error("Wrong number of arguments for $iterator." . $iterator->second); + $parser->throw_error("Wrong number of arguments for $iterator." . $iterator->second); } sub iterator_index { @@ -1966,34 +1967,34 @@ sub make_alias { # alas(from => to) sub not_supported { my($parser, $symbol) = @_; - $parser->_error("'$symbol' is not supported"); + $parser->throw_error("'$symbol' is not supported"); } sub _unexpected { my($parser, $expected, $got) = @_; if(defined($got) && $got ne ";") { if($got eq '(end)') { - $parser->_error("Expected $expected, but reached EOF"); + $parser->throw_error("Expected $expected, but reached EOF"); } else { - $parser->_error("Expected $expected, but got " . neat("$got")); + $parser->throw_error("Expected $expected, but got " . neat("$got")); } } else { - $parser->_error("Expected $expected"); + $parser->throw_error("Expected $expected"); } } -sub _error { - my($parser, $message, $near, $line) = @_; +around throw_error => sub { + my($next, $parser, $message, $near, $line) = @_; $near ||= $parser->near_token || ";"; if($near ne ";" && $message !~ /\b \Q$near\E \b/xms) { $message .= ", near $near"; } - die $parser->make_error($message . ", while parsing templates", + $parser->$next($message . ", while parsing templates", $parser->file, $line || $parser->line); -} +}; no Mouse; __PACKAGE__->meta->make_immutable; diff --git a/lib/Text/Xslate/Syntax/TTerse.pm b/lib/Text/Xslate/Syntax/TTerse.pm index 2488e308..be98e603 100644 --- a/lib/Text/Xslate/Syntax/TTerse.pm +++ b/lib/Text/Xslate/Syntax/TTerse.pm @@ -305,7 +305,7 @@ sub std_switch { sub std_case { my($parser, $symbol) = @_; if(!$parser->in_given) { - $parser->_error("You cannot use $symbol statements outside switch statements"); + $parser->throw_error("You cannot use $symbol statements outside switch statements"); } my $case = $symbol->clone(arity => "case"); @@ -475,7 +475,7 @@ sub std_macro { my $name = $parser->token; if($name->arity ne "variable") { - $parser->_error("a name", $name); + $parser->throw_error("a name", $name); } $parser->define_function($name->id); diff --git a/lib/Text/Xslate/Util.pm b/lib/Text/Xslate/Util.pm index a1bc8659..297a3f54 100644 --- a/lib/Text/Xslate/Util.pm +++ b/lib/Text/Xslate/Util.pm @@ -26,11 +26,6 @@ our @EXPORT_OK = qw( our $DEBUG; defined($DEBUG) or $DEBUG = $ENV{XSLATE} || ''; -our $DisplayWidth = 76; -if($DEBUG =~ /display_width=(\d+)/) { - $DisplayWidth = $1; -} - # cf. http://swtch.com/~rsc/regexp/regexp1.html my $dquoted = qr/" [^"\\]* (?: \\. [^"\\]* )* "/xms; my $squoted = qr/' [^'\\]* (?: \\. [^'\\]* )* '/xms; @@ -226,72 +221,6 @@ END_IMPORT return {@funcs}; } -sub make_error { - my($self, $message, $file, $line, @extra) = @_; - if(ref $message eq 'SCALAR') { # re-thrown form virtual machines - return ${$message}; - } - - my $lines = read_around($file, $line, 1, $self->input_layer); - if($lines) { - $lines .= "\n" if $lines !~ /\n\z/xms; - $lines = '-' x $DisplayWidth . "\n" - . $lines - . '-' x $DisplayWidth . "\n"; - } - - local $Carp::CarpLevel = $Carp::CarpLevel + 1; - my $class = ref($self) || $self; - $message =~ s/\A \Q$class: \E//xms and $message .= "\t..."; - - if(defined $file) { - if(defined $line) { - unshift @extra, $line; - } - unshift @extra, ref($file) ? '' : $file; - } - - if(@extra) { - $message = Carp::shortmess(sprintf '%s (%s)', - $message, join(':', @extra)); - } - else { - $message = Carp::shortmess($message); - } - return sprintf "%s: %s%s", - $class, $message, $lines; -} - -sub read_around { # for error messages - my($file, $line, $around, $input_layer) = @_; - - defined($file) && defined($line) or return ''; - - if (ref $file) { # if $file is a scalar ref, it must contain text strings - my $content = $$file; - utf8::encode($content); - $file = \$content; - } - - $around = 1 if not defined $around; - $input_layer = '' if not defined $input_layer; - - open my $in, '<' . $input_layer, $file or return ''; - local $/ = "\n"; - local $. = 0; - - my $s = ''; - while(defined(my $l = <$in>)) { - if($. >= ($line - $around)) { - $s .= $l; - } - if($. >= ($line + $around)) { - last; - } - } - return $s; -} - sub p { # for debugging, the guts of dump() require 'Data/Dumper.pm'; # we don't want to create its namespace my $dd = Data::Dumper->new(\@_); diff --git a/src/Text-Xslate.xs b/src/Text-Xslate.xs index 322b3b34..2e98f510 100644 --- a/src/Text-Xslate.xs +++ b/src/Text-Xslate.xs @@ -89,7 +89,6 @@ typedef struct { /* original error handlers */ SV* orig_warn_handler; SV* orig_die_handler; - SV* make_error; } my_cxt_t; START_MY_CXT @@ -1272,8 +1271,6 @@ tx_my_cxt_init(pTHX_ pMY_CXT_ bool const cloning PERL_UNUSED_DECL) { (SV*)get_cv("Text::Xslate::Engine::_warn", GV_ADD)); MY_CXT.die_handler = SvREFCNT_inc_NN( (SV*)get_cv("Text::Xslate::Engine::_die", GV_ADD)); - MY_CXT.make_error = SvREFCNT_inc_NN( - (SV*)get_cv("Text::Xslate::Engine::make_error", GV_ADD)); } /* Because overloading stuff of old xsubpp didn't work, @@ -1546,7 +1543,7 @@ CODE: } } /* TODO: append the stack info to msg */ - /* $full_message = make_error(engine, msg, file, line, vm_pos) */ + /* $full_message = engine->make_error(msg, file, line, vm_pos) */ PUSHMARK(SP); EXTEND(SP, 6); PUSHs(sv_mortalcopy(engine)); /* XXX: avoid premature free */ @@ -1560,7 +1557,7 @@ CODE: mPUSHs(newSVpvf("&%"SVf"[%"UVuf"]", name, pc_pos)); } PUTBACK; - call_sv(MY_CXT.make_error, G_SCALAR); + call_method("make_error", G_SCALAR); SPAGAIN; full_message = POPs; PUTBACK; diff --git a/t/020_interface/005_util.t b/t/020_interface/005_util.t index dd81aed8..1dac756e 100644 --- a/t/020_interface/005_util.t +++ b/t/020_interface/005_util.t @@ -2,10 +2,10 @@ use strict; use Test::More; +use Text::Xslate::MakeError; use Text::Xslate::Util qw( literal_to_value value_to_literal - read_around html_builder uri_escape html_escape @@ -74,12 +74,12 @@ is value_to_literal('01'), q{"01"}; # other utils -is read_around(__FILE__, 1), <<'X', 'read_around'; +is(Text::Xslate::MakeError::read_around(__FILE__, 1), <<'X', 'Text::Xslate::MakeError::read_around'); #!perl -w use strict; X -is read_around(__FILE__, 1, 2), <<'X', 'read_around'; +is(Text::Xslate::MakeError::read_around(__FILE__, 1, 2), <<'X', 'Text::Xslate::MakeError::read_around'); #!perl -w use strict; use Test::More; @@ -88,16 +88,16 @@ X #foo #bar #baz -is read_around(__FILE__, __LINE__ - 2), <<'X', 'read_around'; +is(Text::Xslate::MakeError::read_around(__FILE__, __LINE__ - 2), <<'X', 'Text::Xslate::MakeError::read_around'); #foo #bar #baz X -is read_around(__FILE__ . "__unlikely__", 1), <<'X', 'read_around'; +is(Text::Xslate::MakeError::read_around(__FILE__ . "__unlikely__", 1), <<'X', 'Text::Xslate::MakeError::read_around'); X -is read_around(undef, undef), <<'X', 'read_around'; +is(Text::Xslate::MakeError::read_around(undef, undef), <<'X', 'Text::Xslate::MakeError::read_around'); X # html escaping From 19f98d772cd111ae78432a089793f2b8993ab2f0 Mon Sep 17 00:00:00 2001 From: Daisuke Maki Date: Fri, 22 Nov 2013 13:10:36 +0900 Subject: [PATCH 22/39] Cleanup the goto stuff --- lib/Text/Xslate/Loader/File.pm | 124 +++++++++++++++++---------------- 1 file changed, 63 insertions(+), 61 deletions(-) diff --git a/lib/Text/Xslate/Loader/File.pm b/lib/Text/Xslate/Loader/File.pm index fee72bfe..4d018829 100644 --- a/lib/Text/Xslate/Loader/File.pm +++ b/lib/Text/Xslate/Loader/File.pm @@ -62,26 +62,8 @@ sub note { $self->engine->note($fmt, @_, "\n"); } -sub load { - my ($self, $name) = @_; - - my $note_guard; - if (TRACE_LOAD) { - $self->note("load: Loading %s", $name); - $note_guard = $self->indent_note(); - } - - # On a file system, we need to check for - # 1) does the file exist in fs? - # 2) if so, keep it's mtime - # 3) check against mtime of the cache - - # XXX if the file cannot be located in the filesystem, - # then we go kapot, so no check for defined $fi - my $fi = $self->locate_file($name); - if (TRACE_LOAD) { - $self->note("load: Located file %s", $fi->fullpath); - } +sub load_cached_asm { + my ($self, $fi) = @_; # Okay, the source exists. Now consider the cache. # $cache_strategy >= 2, use cache w/o checking for freshness @@ -105,61 +87,81 @@ sub load { } } - my $asm; - if ($cached_ent) { - if ($cache_strategy > 1) { - # We're careless! We just want to use the cached - # version! Go! Go! Go! - if (TRACE_LOAD) { - $self->note("Freshness check disabled, and cache exists. Just use it"); - } - - # $cache_strategy > 1 is wicked. It claims to only - # consider the cache, and yet it still checks for - # the cache validity. - if ($asm = $cached_ent->asm) { - goto ASSEMBLE_AND_RETURN; - } + if (! $cached_ent) { + return; + } - if (TRACE_LOAD) { - $self->note("Cached template's validation failed (probably a magic mismatch)"); - } - goto LOAD_FROM_SOURCE; + if ($cache_strategy > 1) { + # We're careless! We just want to use the cached + # version! Go! Go! Go! + if (TRACE_LOAD) { + $self->note("Freshness check disabled, and cache exists. Just use it"); } - # Otherwise check for freshness - if ($cached_ent->is_fresher_than($fi)) { - # Hooray, our cached version is newer than the - # source file! cheers! jubilations! - if (TRACE_LOAD) { - $self->note("Freshness check passed, returning asm"); - } + # $cache_strategy > 1 is wicked. It claims to only + # consider the cache, and yet it still checks for + # the cache validity. + if (my $asm = $cached_ent->asm) { + return $asm; + } - $asm = $cached_ent->asm; - goto ASSEMBLE_AND_RETURN; + if (TRACE_LOAD) { + $self->note("Cached template's validation failed (probably a magic mismatch)"); } + return; + } + # Otherwise check for freshness + if ($cached_ent->is_fresher_than($fi)) { + # Hooray, our cached version is newer than the + # source file! cheers! jubilations! if (TRACE_LOAD) { - $self->note("Freshness check failed."); + $self->note("Freshness check passed, returning asm"); } - # if you got here, too bad: cache is invalid. - # it doesn't mean anything, but we say bye-bye - # to the cached entity just to console our broken hearts - undef $cached_ent; + + return $cached_ent->asm; + } + + if (TRACE_LOAD) { + $self->note("Freshness check failed."); + } + # if you got here, too bad: cache is invalid. +} + +sub load { + my ($self, $name) = @_; + + my $note_guard; + if (TRACE_LOAD) { + $self->note("load: Loading %s", $name); + $note_guard = $self->indent_note(); } -LOAD_FROM_SOURCE: - # If you got here, either the cache_strategy was 0 or the cache - # was invalid. load from source - $asm = $self->load_file($fi); + # On a file system, we need to check for + # 1) does the file exist in fs? + # 2) if so, keep it's mtime + # 3) check against mtime of the cache + + # XXX if the file cannot be located in the filesystem, + # then we go kapot, so no check for defined $fi + my $fi = $self->locate_file($name); + if (TRACE_LOAD) { + $self->note("load: Located file %s", $fi->fullpath); + } - # store cache, if necessary my $cache_mtime; # XXX Should this be here? - if ($cache_strategy > 0) { - $cache_mtime = $self->store_cache($fi, $asm); + my $asm = $self->load_cached_asm($fi); + if (! $asm) { + # If you got here, either the cache_strategy was 0 or the cache + # was invalid. load from source + $asm = $self->load_file($fi); + + # store cache, if necessary + if ($self->cache_strategy > 0) { + $cache_mtime = $self->store_cache($fi, $asm); + } } -ASSEMBLE_AND_RETURN: $self->assemble($asm, $name, $fi->fullpath, $fi->cachepath, $cache_mtime); return $asm; } From 175bbe733abf6445deb51c4547f91de1834fbdd3 Mon Sep 17 00:00:00 2001 From: Daisuke Maki Date: Fri, 22 Nov 2013 13:37:28 +0900 Subject: [PATCH 23/39] Change cache dir strategy Add $> in the default cache dir, always prepend a bytecode version in the path --- lib/Text/Xslate.pm | 2 +- lib/Text/Xslate/Loader/File.pm | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/Text/Xslate.pm b/lib/Text/Xslate.pm index ed50bf94..671d4d36 100644 --- a/lib/Text/Xslate.pm +++ b/lib/Text/Xslate.pm @@ -70,7 +70,7 @@ BEGIN { *_ST_MTIME = sub() { 9 }; # see perldoc -f stat my $temp_base = $ENV{TEMPDIR} || File::Spec->tmpdir; - my $cache_dir = File::Spec->catdir($temp_base, 'xslate_cache'); + my $cache_dir = File::Spec->catdir($temp_base, 'xslate_cache', $>); *_DEFAULT_CACHE_DIR = sub() { $cache_dir }; } diff --git a/lib/Text/Xslate/Loader/File.pm b/lib/Text/Xslate/Loader/File.pm index 4d018829..ecc84680 100644 --- a/lib/Text/Xslate/Loader/File.pm +++ b/lib/Text/Xslate/Loader/File.pm @@ -232,6 +232,7 @@ sub locate_file { fullpath => $fullpath, cachepath => File::Spec->catfile( $self->cache_dir, + $self->engine->bytecode_version, $cache_prefix, $name . 'c', ), From 69ed49d702a61eb6310b93d782ab4433dbdf0d51 Mon Sep 17 00:00:00 2001 From: Daisuke Maki Date: Fri, 22 Nov 2013 15:43:53 +0900 Subject: [PATCH 24/39] Refactor constants to ::Constants * I hate Perl --- lib/Text/Xslate.pm | 78 ++++++++-------- lib/Text/Xslate/Compiler.pm | 157 +++++++++++++++------------------ lib/Text/Xslate/Constants.pm | 67 ++++++++++++++ lib/Text/Xslate/Loader/File.pm | 45 +++++----- lib/Text/Xslate/MakeError.pm | 11 +-- lib/Text/Xslate/PP.pm | 14 ++- lib/Text/Xslate/PP/Method.pm | 3 +- lib/Text/Xslate/PP/Opcode.pm | 12 ++- lib/Text/Xslate/PP/State.pm | 4 +- lib/Text/Xslate/Parser.pm | 9 +- lib/Text/Xslate/Symbol.pm | 11 ++- lib/Text/Xslate/Util.pm | 9 +- src/Text-Xslate.xs | 2 +- 13 files changed, 225 insertions(+), 197 deletions(-) create mode 100644 lib/Text/Xslate/Constants.pm diff --git a/lib/Text/Xslate.pm b/lib/Text/Xslate.pm index 671d4d36..e798f395 100644 --- a/lib/Text/Xslate.pm +++ b/lib/Text/Xslate.pm @@ -4,18 +4,19 @@ use 5.008_001; use strict; use warnings; -our $VERSION = '3.1.0'; - use Carp (); use File::Spec (); -use Exporter (); use Data::MessagePack (); use Scalar::Util (); -use Text::Xslate::Util (); +our ($VERSION, @ISA, @EXPORT_OK); + +use Text::Xslate::Constants(); BEGIN { + $VERSION = '3.1.0'; + # all the exportable functions are defined in ::Util - our @EXPORT_OK = qw( + @EXPORT_OK = qw( mark_raw unmark_raw escaped_string @@ -23,28 +24,33 @@ BEGIN { uri_escape html_builder ); - Text::Xslate::Util->import(@EXPORT_OK); -} -our @ISA = qw(Text::Xslate::Engine); - -# load backend (XS or PP) -my $use_xs = 0; -if(!exists $INC{'Text/Xslate/PP.pm'}) { - my $pp = ($Text::Xslate::Util::DEBUG =~ /\b pp \b/xms or $ENV{PERL_ONLY}); - if(!$pp) { - eval { - require XSLoader; - XSLoader::load(__PACKAGE__, $VERSION); - $use_xs = 1; - }; - die $@ if $@ && $Text::Xslate::Util::DEBUG =~ /\b xs \b/xms; # force XS - } - if(!__PACKAGE__->can('render')) { - require 'Text/Xslate/PP.pm'; + my $use_xs = 0; + + # load backend (XS or PP) + my $DEBUG = $ENV{XSLATE} || ''; + if(!exists $INC{'Text/Xslate/PP.pm'}) { + my $pp = ($DEBUG =~ /\b pp \b/xms or $ENV{PERL_ONLY}); + if(!$pp) { + eval { + require XSLoader; + XSLoader::load(__PACKAGE__, $VERSION); + $use_xs = 1; + }; + die $@ if $@ && $DEBUG =~ /\b xs \b/xms; # force XS + } + + @ISA = qw(Text::Xslate::Engine); + + if(!__PACKAGE__->can('render')) { + require 'Text/Xslate/PP.pm'; + } } + eval 'sub USE_XS() { $use_xs }'; + die if $@; } -sub USE_XS() { $use_xs } +use Text::Xslate::Util @EXPORT_OK; + # for error messages (see T::X::Util) sub input_layer { ref($_[0]) ? $_[0]->{input_layer} : ':utf8' } @@ -52,27 +58,15 @@ sub input_layer { ref($_[0]) ? $_[0]->{input_layer} : ':utf8' } package Text::Xslate::Engine; # XS/PP common base class use Mouse; use File::Spec; - +use Text::Xslate::Constants qw(DUMP_LOAD DEFAULT_CACHE_DIR); use Text::Xslate::Assembler; use Text::Xslate::Util qw( dump ); -with 'Text::Xslate::MakeError'; - -# constants -BEGIN { - our @ISA = qw(Exporter); +extends 'Exporter'; - my $dump_load = scalar($Text::Xslate::Util::DEBUG =~ /\b dump=load \b/xms); - *_DUMP_LOAD = sub(){ $dump_load }; - - *_ST_MTIME = sub() { 9 }; # see perldoc -f stat - - my $temp_base = $ENV{TEMPDIR} || File::Spec->tmpdir; - my $cache_dir = File::Spec->catdir($temp_base, 'xslate_cache', $>); - *_DEFAULT_CACHE_DIR = sub() { $cache_dir }; -} +with 'Text::Xslate::MakeError'; # the real defaults are defined in the parser my %parser_option = ( @@ -155,7 +149,7 @@ sub options { # overridable path => ['.'], input_layer => $self->input_layer, cache => 1, # 0: not cached, 1: checks mtime, 2: always cached - cache_dir => _DEFAULT_CACHE_DIR, + cache_dir => DEFAULT_CACHE_DIR, module => undef, function => undef, html_builder_module => undef, @@ -284,7 +278,7 @@ sub load_string { # called in render_string() $self->throw_error("LoadError: Template string is not given"); } $self->note(' _load_string: %s', join '\n', split /\n/, $string) - if _DUMP_LOAD; + if DUMP_LOAD; $self->{string_buffer} = $string; my $asm = $self->compile($string); $self->_assembler->assemble($asm, '', \$string, undef, undef); @@ -309,7 +303,7 @@ sub load_file { sub slurp_template { my($self, $input_layer, $fullpath) = @_; - if (_DUMP_LOAD) { + if (DUMP_LOAD) { $self->note("slurp_template: input_layer(%s), fullpath(%s)\n", $input_layer, $fullpath); } diff --git a/lib/Text/Xslate/Compiler.pm b/lib/Text/Xslate/Compiler.pm index 94e43a48..24f03eaf 100644 --- a/lib/Text/Xslate/Compiler.pm +++ b/lib/Text/Xslate/Compiler.pm @@ -7,38 +7,19 @@ with 'Text::Xslate::MakeError'; use Scalar::Util (); use Carp (); +use Text::Xslate::Constants qw( + DUMP_ADDIX DUMP_ASM DUMP_AST DUMP_GEN DUMP_CAS + OP_NAME OP_ARG OP_LINE OP_FILE OP_LABEL OP_COMMENT + OPTIMIZE + FOR_LOOP WHILE_LOOP +); use Text::Xslate::Parser; use Text::Xslate::Util qw( - $DEBUG value_to_literal is_int any_in p ); -#use constant _VERBOSE => scalar($DEBUG =~ /\b verbose \b/xms); -use constant { - _DUMP_ASM => scalar($DEBUG =~ /\b dump=asm \b/xms), - _DUMP_AST => scalar($DEBUG =~ /\b dump=ast \b/xms), - _DUMP_GEN => scalar($DEBUG =~ /\b dump=gen \b/xms), - _DUMP_CAS => scalar($DEBUG =~ /\b dump=cascade \b/xms), - - _OP_NAME => 0, - _OP_ARG => 1, - _OP_LINE => 2, - _OP_FILE => 3, - _OP_LABEL => 4, - _OP_COMMENT => 5, - - _FOR_LOOP => 1, - _WHILE_LOOP => 2, -}; - - -our $OPTIMIZE = scalar(($DEBUG =~ /\b optimize=(\d+) \b/xms)[0]); -if(not defined $OPTIMIZE) { - $OPTIMIZE = 1; # enable optimization by default -} - our @CARP_NOT = qw(Text::Xslate Text::Xslate::Parser); { @@ -313,7 +294,7 @@ sub compile { my @code; # main code { my $ast = $parser->parse($input, %args); - print STDERR p($ast) if _DUMP_AST; + print STDERR p($ast) if DUMP_AST; @code = ( $self->opcode(set_opinfo => undef, file => $self->current_file, line => 1), $self->compile_ast($ast), @@ -328,13 +309,13 @@ sub compile { push @code, $self->_flush_macro_table() if $self->has_macro_table; - if($OPTIMIZE) { + if(OPTIMIZE) { $self->_optimize_vmcode(\@code) for 1 .. 3; } print STDERR "// ", $self->filename, "\n", - $self->as_assembly(\@code, scalar($DEBUG =~ /\b ix \b/xms)) - if _DUMP_ASM; + $self->as_assembly(\@code, DUMP_ADDIX) + if DUMP_ASM; { my %uniq; @@ -403,13 +384,13 @@ sub compile_ast { my($self, $ast) = @_; return if not defined $ast; - local $_lv = $_lv + 1 if _DUMP_GEN; + local $_lv = $_lv + 1 if DUMP_GEN; my @code; foreach my $node(ref($ast) eq 'ARRAY' ? @{$ast} : $ast) { Scalar::Util::blessed($node) or Carp::confess("[BUG] Not a node object: " . p($node)); - printf STDERR "%s"."generate %s (%s)\n", "." x $_lv, $node->arity, $node->id if _DUMP_GEN; + printf STDERR "%s"."generate %s (%s)\n", "." x $_lv, $node->arity, $node->id if DUMP_GEN; my $generator = $self->can('_generate_' . $node->arity) || Carp::confess("[BUG] Unexpected node: " . p($node)); @@ -422,7 +403,7 @@ sub compile_ast { sub _process_cascade { my($self, $cascade, $args, $main_code) = @_; - printf STDERR "# cascade %s %s", $self->file, $cascade->dump if _DUMP_CAS; + printf STDERR "# cascade %s %s", $self->file, $cascade->dump if DUMP_CAS; my $engine = $self->engine || $self->throw_error("Cannot cascade templates without Xslate engine", $cascade); @@ -459,32 +440,32 @@ sub _process_cascade { # $c = [name, arg, line, file, symbol ] # retrieve macros from assembly code - if($c->[_OP_NAME] eq 'macro_begin' .. $c->[_OP_NAME] eq 'macro_end') { - if($c->[_OP_NAME] eq 'macro_begin') { + if($c->[OP_NAME] eq 'macro_begin' .. $c->[OP_NAME] eq 'macro_end') { + if($c->[OP_NAME] eq 'macro_begin') { $macro = []; $macro = { - name => $c->[_OP_ARG], - line => $c->[_OP_LINE], - file => $c->[_OP_FILE], + name => $c->[OP_ARG], + line => $c->[OP_LINE], + file => $c->[OP_FILE], body => [], }; - push @{ $mtable->{$c->[_OP_ARG]} ||= [] }, $macro; + push @{ $mtable->{$c->[OP_ARG]} ||= [] }, $macro; } - elsif($c->[_OP_NAME] eq 'macro_nargs') { - $macro->{nargs} = $c->[_OP_ARG]; + elsif($c->[OP_NAME] eq 'macro_nargs') { + $macro->{nargs} = $c->[OP_ARG]; } - elsif($c->[_OP_NAME] eq 'macro_outer') { - $macro->{outer} = $c->[_OP_ARG]; + elsif($c->[OP_NAME] eq 'macro_outer') { + $macro->{outer} = $c->[OP_ARG]; } - elsif($c->[_OP_NAME] eq 'macro_end') { + elsif($c->[OP_NAME] eq 'macro_end') { # noop } else { push @{$macro->{body}}, $c; } } - elsif($c->[_OP_NAME] eq 'depend') { - $self->requires($c->[_OP_ARG]); + elsif($c->[OP_NAME] eq 'depend') { + $self->requires($c->[OP_ARG]); } } $self->requires($fullpath); @@ -506,8 +487,8 @@ sub _process_cascade { } foreach my $c(@{$main_code}) { - if($c->[_OP_NAME] eq 'print_raw_s' - && $c->[_OP_ARG] =~ m{ [^ \t\r\n] }xms) { + if($c->[OP_NAME] eq 'print_raw_s' + && $c->[OP_ARG] =~ m{ [^ \t\r\n] }xms) { Carp::carp("Xslate: Useless use of text '$c->[1]'"); } } @@ -520,19 +501,19 @@ sub _process_cascade { sub _process_cascade_file { my($self, $file, $base_code) = @_; - printf STDERR "# cascade file %s\n", p($file) if _DUMP_CAS; + printf STDERR "# cascade file %s\n", p($file) if DUMP_CAS; my $mtable = $self->macro_table; for(my $i = 0; $i < @{$base_code}; $i++) { my $c = $base_code->[$i]; - if($c->[_OP_NAME] ne 'macro_begin') { + if($c->[OP_NAME] ne 'macro_begin') { next; } # macro - my $name = $c->[_OP_ARG]; + my $name = $c->[OP_ARG]; $name =~ s/\@.+$//; - printf STDERR "# macro %s\n", $name if _DUMP_CAS; + printf STDERR "# macro %s\n", $name if DUMP_CAS; if(exists $mtable->{$name}) { my $m = $mtable->{$name}; @@ -561,7 +542,7 @@ sub _process_cascade_file { } my $macro_start = $i+1; - $i++ while($base_code->[$i][_OP_NAME] ne 'macro_end'); # move to the end + $i++ while($base_code->[$i][OP_NAME] ne 'macro_end'); # move to the end if(defined $around) { my @original = splice @{$base_code}, $macro_start, ($i - $macro_start); @@ -572,7 +553,7 @@ sub _process_cascade_file { push @body, @{$m->{body}}; } for(my $j = 0; $j < @body; $j++) { - if($body[$j][_OP_NAME] eq 'super') { + if($body[$j][OP_NAME] eq 'super') { splice @body, $j, 1, @original; } } @@ -645,7 +626,7 @@ sub _generate_operator { sub _can_optimize_print { my($self, $name, $node) = @_; - return 0 if !$OPTIMIZE; + return 0 if !OPTIMIZE; return 0 if !($name eq 'print' or $name eq 'print_raw'); my $maybe_name = $node->first; @@ -760,7 +741,7 @@ sub _compile_loop_block { my @block_code = $self->compile_ast($block); foreach my $op(@block_code) { - if(any_in( $op->[_OP_NAME], qw(pushmark loop_control))) { + if(any_in( $op->[OP_NAME], qw(pushmark loop_control))) { # pushmark ... funcall (or something) may create mortal SVs # so surround the block with ENTER and LEAVE unshift @block_code, $self->opcode('enter'); @@ -771,13 +752,13 @@ sub _compile_loop_block { foreach my $i(1 .. (@block_code-1)) { my $op = $block_code[$i]; - if($op->[_OP_NAME] eq 'loop_control') { - my $type = $op->[_OP_ARG]; - $op->[_OP_NAME] = 'goto'; + if($op->[OP_NAME] eq 'loop_control') { + my $type = $op->[OP_ARG]; + $op->[OP_NAME] = 'goto'; - $op->[_OP_ARG] = (@block_code - $i); + $op->[OP_ARG] = (@block_code - $i); - $op->[_OP_ARG] += 1 if $type eq 'last'; + $op->[OP_ARG] += 1 if $type eq 'last'; } } @@ -795,7 +776,7 @@ sub _generate_for { } local $self->{lvar} = { %{$self->lvar} }; # new scope local $self->{const} = [ @{$self->const} ]; # new scope - local $self->{in_loop} = _FOR_LOOP; + local $self->{in_loop} = FOR_LOOP; my @code = $self->compile_ast($expr); @@ -856,7 +837,7 @@ sub _generate_while { # TODO: combine all the loop contexts into single one local $self->{lvar} = { %{$self->lvar} }; # new scope local $self->{const} = [ @{$self->const} ]; # new scope - local $self->{in_loop} = _WHILE_LOOP; + local $self->{in_loop} = WHILE_LOOP; my @code = $self->compile_ast($expr); @@ -892,7 +873,7 @@ sub _generate_loop_control { } my @cleanup; - if( $self->{in_loop} == _FOR_LOOP && $type eq 'last' ) { + if( $self->{in_loop} == FOR_LOOP && $type eq 'last' ) { my $lvar_id = $self->lvar->{'($_)'}; defined($lvar_id) or $self->throw_error('[BUG] Undefined loop iterator'); @@ -1018,9 +999,9 @@ sub _generate_if { $self->compile_ast($third); }; - if($OPTIMIZE) { + if(OPTIMIZE) { if($self->_code_is_literal(@cond)) { - my $value = $cond[0][_OP_ARG]; + my $value = $cond[0][OP_ARG]; if($cond_true eq 'and' ? $value : !$value) { return @then; } @@ -1030,7 +1011,7 @@ sub _generate_if { } } - if( (@then and @else) or !$OPTIMIZE) { + if( (@then and @else) or !OPTIMIZE) { return( @cond, $self->opcode( $cond_true => scalar(@then) + 2, comment => $node->id . ' (then)' ), @@ -1141,7 +1122,7 @@ sub _generate_unary { @operand, $self->opcode( $unary{$id} ) ); - if( $OPTIMIZE and $self->_code_is_literal(@operand) ) { + if( OPTIMIZE and $self->_code_is_literal(@operand) ) { $self->_fold_constants(\@code); } return @code; @@ -1168,7 +1149,7 @@ sub _generate_field { else { local $self->{lvar_id} = $self->lvar_use(1); my @rhs = $self->compile_ast($field); - if($OPTIMIZE and $self->_code_is_literal(@rhs)) { + if(OPTIMIZE and $self->_code_is_literal(@rhs)) { return @lhs, $self->opcode( fetch_field_s => $rhs[0][1] ); @@ -1212,7 +1193,7 @@ sub _generate_binary { $self->opcode( 'move_from_sb' ), # on false } - if($OPTIMIZE) { + if(OPTIMIZE) { if( $self->_code_is_literal(@lhs) and $self->_code_is_literal(@rhs) ){ $self->_fold_constants(\@code); } @@ -1360,10 +1341,10 @@ sub _generate_constant { $lvar->{$lvar_name} = $lvar_id; $self->{lvar_id} = $self->lvar_use(1); # don't use local() - if($OPTIMIZE) { + if(OPTIMIZE) { if(@expr == 1 - && any_in($expr[0][_OP_NAME], qw(literal load_lvar))) { - $expr[0][_OP_COMMENT] = "constant $lvar_name"; + && any_in($expr[0][OP_NAME], qw(literal load_lvar))) { + $expr[0][OP_COMMENT] = "constant $lvar_name"; $self->const->[$lvar_id] = \@expr; return @expr; # no real definition } @@ -1434,8 +1415,8 @@ sub can_be_in_list_context { sub _code_is_literal { my($self, @code) = @_; return @code == 1 - && ( $code[0][_OP_NAME] eq 'literal' - || $code[0][_OP_NAME] eq 'literal_i'); + && ( $code[0][OP_NAME] eq 'literal' + || $code[0][OP_NAME] eq 'literal_i'); } sub _fold_constants { @@ -1486,8 +1467,8 @@ sub _optimize_vmcode { my @goto_addr; for(my $i = 0; $i < @{$c}; $i++) { - if(exists $goto_family{ $c->[$i][_OP_NAME] }) { - my $addr = $c->[$i][_OP_ARG]; # relational addr + if(exists $goto_family{ $c->[$i][OP_NAME] }) { + my $addr = $c->[$i][OP_ARG]; # relational addr # mark ragens that goto family have its effects my @range = $addr > 0 @@ -1501,15 +1482,15 @@ sub _optimize_vmcode { } for(my $i = 0; $i < @{$c}; $i++) { - my $name = $c->[$i][_OP_NAME]; + my $name = $c->[$i][OP_NAME]; if($name eq 'print_raw_s') { # merge a chain of print_raw_s into single command my $j = $i + 1; # from the next op while($j < @{$c} - && $c->[$j][_OP_NAME] eq 'print_raw_s' + && $c->[$j][OP_NAME] eq 'print_raw_s' && "@{$goto_addr[$i] || []}" eq "@{$goto_addr[$j] || []}") { - $c->[$i][_OP_ARG] .= $c->[$j][_OP_ARG]; + $c->[$i][OP_ARG] .= $c->[$j][OP_ARG]; $self->_noop($c->[$j]); $j++; @@ -1528,24 +1509,24 @@ sub _optimize_vmcode { my $it = $c->[$i]; my $nn = $c->[$i+2]; # next next if(defined($nn) - && $nn->[_OP_NAME] eq 'load_lvar_to_sb' - && $nn->[_OP_ARG] == $it->[_OP_ARG]) { + && $nn->[OP_NAME] eq 'load_lvar_to_sb' + && $nn->[OP_ARG] == $it->[OP_ARG]) { @{$it} = @{$self->opcode( move_to_sb => undef, comment => "ex-$it->[0]" )}; $self->_noop($nn); } } elsif($name eq 'literal') { - if(is_int($c->[$i][_OP_ARG])) { - $c->[$i][_OP_NAME] = 'literal_i'; - $c->[$i][_OP_ARG] = int($c->[$i][_OP_ARG]); # force int + if(is_int($c->[$i][OP_ARG])) { + $c->[$i][OP_NAME] = 'literal_i'; + $c->[$i][OP_ARG] = int($c->[$i][OP_ARG]); # force int } } elsif($name eq 'fetch_field') { my $prev = $c->[$i-1]; - if($prev->[_OP_NAME] =~ /^literal/) { # literal or literal_i - $c->[$i][_OP_NAME] = 'fetch_field_s'; - $c->[$i][_OP_ARG] = $prev->[_OP_ARG]; # arg + if($prev->[OP_NAME] =~ /^literal/) { # literal or literal_i + $c->[$i][OP_NAME] = 'fetch_field_s'; + $c->[$i][OP_ARG] = $prev->[OP_ARG]; # arg $self->_noop($prev); } @@ -1554,7 +1535,7 @@ sub _optimize_vmcode { # remove noop for(my $i = 0; $i < @{$c}; $i++) { - if($c->[$i][_OP_NAME] eq 'noop') { + if($c->[$i][OP_NAME] eq 'noop') { if(defined $goto_addr[$i]) { foreach my $goto(@{ $goto_addr[$i] }) { # reduce its absolute value diff --git a/lib/Text/Xslate/Constants.pm b/lib/Text/Xslate/Constants.pm new file mode 100644 index 00000000..e4aade9f --- /dev/null +++ b/lib/Text/Xslate/Constants.pm @@ -0,0 +1,67 @@ +package Text::Xslate::Constants; +use strict; +use parent qw(Exporter); +use File::Spec; + +our $DEBUG; +our @EXPORT_OK; + +BEGIN { + if (! defined($DEBUG)) { + $DEBUG = $ENV{XSLATE} || ''; + } + + my $optimize = scalar(($DEBUG =~ /\b optimize=(\d+) \b/xms)[0]); + if(not defined $optimize) { + $optimize = 1; # enable optimization by default + } + my $default_display_width = 76; + if($DEBUG =~ /display_width=(\d+)/) { + $default_display_width = $1; + } + my %constants = ( + DEBUG => $DEBUG ? 1 : 0, + + DEFAULT_DISPLAY_WIDTH => $default_display_width, + + DUMP_ADDIX => scalar($DEBUG =~ /\b ix \b/xms), + DUMP_ASM => scalar($DEBUG =~ /\b dump=asm \b/xms), + DUMP_AST => scalar($DEBUG =~ /\b dump=ast \b/xms), + DUMP_CAS => scalar($DEBUG =~ /\b dump=cascade \b/xms), + DUMP_DENOTE => scalar($DEBUG =~ /\b dump=denote \b/xmsi), + DUMP_GEN => scalar($DEBUG =~ /\b dump=gen \b/xms), + DUMP_LOAD => scalar($DEBUG =~ /\b dump=load \b/xms), + DUMP_PP => scalar($DEBUG =~ /\b dump=pp \b/xms), + DUMP_LOAD => scalar($DEBUG =~ /\b dump=load \b/xms), + + PP_ERROR_VERBOSE => scalar($DEBUG =~ /\b pp=verbose \b/xms), + + DUMP_PROTO => scalar($DEBUG =~ /\b dump=proto \b/xmsi), + DUMP_TOKEN => scalar($DEBUG =~ /\b dump=token \b/xmsi), + OPTIMIZE => $optimize, + ST_MTIME => 9, + + DEFAULT_CACHE_DIR => File::Spec->catdir( + $ENV{TEMPDIR} || File::Spec->tmpdir, + 'xslate_cache', + $> + ), + + OP_NAME => 0, + OP_ARG => 1, + OP_LINE => 2, + OP_FILE => 3, + OP_LABEL => 4, + OP_COMMENT => 5, + + FOR_LOOP => 1, + WHILE_LOOP => 2, + ); + + require constant; + constant->import(\%constants); + + @EXPORT_OK = keys %constants; +} + +1; \ No newline at end of file diff --git a/lib/Text/Xslate/Loader/File.pm b/lib/Text/Xslate/Loader/File.pm index ecc84680..ec8ca6a0 100644 --- a/lib/Text/Xslate/Loader/File.pm +++ b/lib/Text/Xslate/Loader/File.pm @@ -7,8 +7,7 @@ use File::Spec; use File::Temp (); use Text::Xslate (); use Text::Xslate::Util (); -use constant ST_MTIME => 9; -use constant TRACE_LOAD => Text::Xslate::Engine::_DUMP_LOAD(); +use Text::Xslate::Constants qw(ST_MTIME DUMP_LOAD); extends 'Text::Xslate::Loader'; @@ -73,7 +72,7 @@ sub load_cached_asm { # $cached_ent is an object with mtime and asm my $cached_ent; my $cache_strategy = $self->cache_strategy; - if (TRACE_LOAD) { + if (DUMP_LOAD) { $self->note("load: Cache strategy is %d", $cache_strategy); } if ($cache_strategy > 0) { @@ -94,7 +93,7 @@ sub load_cached_asm { if ($cache_strategy > 1) { # We're careless! We just want to use the cached # version! Go! Go! Go! - if (TRACE_LOAD) { + if (DUMP_LOAD) { $self->note("Freshness check disabled, and cache exists. Just use it"); } @@ -105,7 +104,7 @@ sub load_cached_asm { return $asm; } - if (TRACE_LOAD) { + if (DUMP_LOAD) { $self->note("Cached template's validation failed (probably a magic mismatch)"); } return; @@ -115,14 +114,14 @@ sub load_cached_asm { if ($cached_ent->is_fresher_than($fi)) { # Hooray, our cached version is newer than the # source file! cheers! jubilations! - if (TRACE_LOAD) { + if (DUMP_LOAD) { $self->note("Freshness check passed, returning asm"); } return $cached_ent->asm; } - if (TRACE_LOAD) { + if (DUMP_LOAD) { $self->note("Freshness check failed."); } # if you got here, too bad: cache is invalid. @@ -132,7 +131,7 @@ sub load { my ($self, $name) = @_; my $note_guard; - if (TRACE_LOAD) { + if (DUMP_LOAD) { $self->note("load: Loading %s", $name); $note_guard = $self->indent_note(); } @@ -145,7 +144,7 @@ sub load { # XXX if the file cannot be located in the filesystem, # then we go kapot, so no check for defined $fi my $fi = $self->locate_file($name); - if (TRACE_LOAD) { + if (DUMP_LOAD) { $self->note("load: Located file %s", $fi->fullpath); } @@ -177,7 +176,7 @@ sub locate_file { my ($self, $name) = @_; my $note_guard; - if (TRACE_LOAD) { + if (DUMP_LOAD) { $self->note("locate_file: looking for '%s'", $name); $note_guard = $self->indent_note(); } @@ -189,7 +188,7 @@ sub locate_file { my $dirs = $self->include_dirs; my ($fullpath, $mtime, $cache_prefix); foreach my $dir (@$dirs) { - if (TRACE_LOAD) { + if (DUMP_LOAD) { $self->note("locate_file: checking in %s", $dir); } if (ref $dir eq 'HASH') { @@ -221,7 +220,7 @@ sub locate_file { } } - if (TRACE_LOAD) { + if (DUMP_LOAD) { $self->note("Found source in %s", ref($fullpath) ? $name : $fullpath); } @@ -252,14 +251,14 @@ sub load_cached { my $filename = $fi->cachepath; my $note_guard; - if (TRACE_LOAD) { + if (DUMP_LOAD) { $self->note("load_cached: %s", $filename); $note_guard = $self->indent_note(); } my $mtime = (stat($filename))[ST_MTIME()]; if (! defined $mtime) { # stat failed. cache isn't there. sayonara - if (TRACE_LOAD) { + if (DUMP_LOAD) { $self->note("load_cached: file %s does not exist", $filename); } return; @@ -285,13 +284,13 @@ sub load_file { my $filename = $fi->fullpath; my $note_guard; - if (TRACE_LOAD) { + if (DUMP_LOAD) { $self->note("load_file: Loading %s", $filename); $note_guard = $self->indent_note(); } my $data = $self->slurp_template($self->input_layer, $filename); if (my $cb = $self->pre_process_handler) { - if (TRACE_LOAD) { + if (DUMP_LOAD) { $self->note("Preprocess handler called"); } $data = $cb->($data); @@ -306,7 +305,7 @@ sub store_cache { my $path = $fi->cachepath; my $note_guard; - if (TRACE_LOAD) { + if (DUMP_LOAD) { $self->note("store_cache: Storing cache in %s (%s)", $path, $fi->fullpath); $note_guard = $self->indent_note(); } @@ -354,7 +353,7 @@ sub store_cache { Carp::carp("Xslate: Cannot rename cache file $path (ignored): $!"); } - if (TRACE_LOAD) { + if (DUMP_LOAD) { $self->note("stored cache in %s", $path); } return $newest_mtime; @@ -399,7 +398,7 @@ sub is_fresher_than { my ($self, $fi) = @_; if ($self->mtime <= $fi->mtime) { - if (Text::Xslate::Loader::File::TRACE_LOAD) { + if (Text::Xslate::Loader::File::DUMP_LOAD) { $self->note("is_fresher_than: mtime (%s) <= threshold (%s)", $self->mtime, $fi->mtime); } @@ -414,7 +413,7 @@ sub is_fresher_than { sub build_asm { my ($self, %args) = @_; - if (Text::Xslate::Loader::File::TRACE_LOAD) { + if (Text::Xslate::Loader::File::DUMP_LOAD) { $self->note("build_asm: fullpath = %s", $self->filename); } @@ -431,7 +430,7 @@ sub build_asm { my $magic = $self->magic; read $in, $data, length($magic); if (! defined $data || $data ne $magic) { - if (Text::Xslate::Loader::File::TRACE_LOAD) { + if (Text::Xslate::Loader::File::DUMP_LOAD) { $self->note("build_asm: magic mismatch ('%s' != '%s')", $data, $magic); } return; @@ -470,13 +469,13 @@ sub build_asm { # XXX if this is a vpath, if($c->[0] eq 'depend') { - my $dep_mtime = (stat $c->[1])[Text::Xslate::Engine::_ST_MTIME()]; + my $dep_mtime = (stat $c->[1])[Text::Xslate::Constants::ST_MTIME()]; if(!defined $dep_mtime) { Carp::carp("Xslate: Failed to stat $c->[1] (ignored): $!"); return undef; # purge the cache } if($check_freshness && $dep_mtime > $threshold){ - if (Text::Xslate::Loader::File::TRACE_LOAD) { + if (Text::Xslate::Loader::File::DUMP_LOAD) { $self->note(" _load_compiled: %s(%s) is newer than %s(%s)\n", $c->[1], scalar localtime($dep_mtime), $filename, scalar localtime($threshold) ) diff --git a/lib/Text/Xslate/MakeError.pm b/lib/Text/Xslate/MakeError.pm index b9f4f494..7b78085d 100644 --- a/lib/Text/Xslate/MakeError.pm +++ b/lib/Text/Xslate/MakeError.pm @@ -1,14 +1,9 @@ package Text::Xslate::MakeError; use Mouse::Role; use Carp (); +use Text::Xslate::Constants qw(DEFAULT_DISPLAY_WIDTH); -our $DEBUG; -defined($DEBUG) or $DEBUG = $ENV{XSLATE} || ''; - -our $DisplayWidth = 76; -if($DEBUG =~ /display_width=(\d+)/) { - $DisplayWidth = $1; -} +our $DisplayWidth; sub throw_error { die shift->make_error(@_); @@ -46,6 +41,8 @@ sub read_around { # for error messages sub make_error { my($self, $message, $file, $line, @extra) = @_; + + $DisplayWidth ||= DEFAULT_DISPLAY_WIDTH; if(ref $message eq 'SCALAR') { # re-thrown form virtual machines return ${$message}; } diff --git a/lib/Text/Xslate/PP.pm b/lib/Text/Xslate/PP.pm index cd3e04d1..360053b6 100644 --- a/lib/Text/Xslate/PP.pm +++ b/lib/Text/Xslate/PP.pm @@ -9,15 +9,11 @@ BEGIN{ $ENV{XSLATE} = ($ENV{XSLATE} || '') . '[pp]'; } +use Text::Xslate::Constants qw(DUMP_LOAD PP_ERROR_VERBOSE); use Text::Xslate::Util qw( - $DEBUG p ); -use constant _PP_ERROR_VERBOSE => scalar($DEBUG =~ /\b pp=verbose \b/xms); - -use constant _DUMP_LOAD => scalar($DEBUG =~ /\b dump=load \b/xms); - use Text::Xslate::PP::Const qw(:all); use Text::Xslate::PP::State; use Text::Xslate::PP::Type::Raw; @@ -33,7 +29,7 @@ require Text::Xslate; $VERSION =~ s/_//; # for developers versions -if(_PP_ERROR_VERBOSE) { +if(PP_ERROR_VERBOSE) { Carp->import('verbose'); } @@ -402,7 +398,7 @@ sub tx_all_deps_are_fresh { if ( $i != TXo_FULLPATH and $main_cache ) { unlink $main_cache or warn $!; } - if(_DUMP_LOAD) { + if(DUMP_LOAD) { printf STDERR " tx_all_depth_are_fresh: %s is too old (%d > %d)\n", $deppath, $mtime, $cache_mtime; } @@ -421,7 +417,7 @@ sub tx_execute { if ( $_depth > 100 ) { Carp::croak("Execution is too deep (> 100)"); } - if(_PP_ERROR_VERBOSE and ref $st->{code}->[0]->{ exec_code } ne 'CODE') { + if(PP_ERROR_VERBOSE and ref $st->{code}->[0]->{ exec_code } ne 'CODE') { Carp::croak("Oops: Not a CODE reference: " . Text::Xslate::Util::neat($st->{code}->[0]->{ exec_code })); } @@ -462,7 +458,7 @@ sub _error_handler { local $SIG{__WARN__} = $_orig_warn_handler; local $SIG{__DIE__} = $_orig_die_handler; - if(!_PP_ERROR_VERBOSE && $str =~ s/at .+Text.Xslate.PP.+ line \d+\.\n$//) { + if(!PP_ERROR_VERBOSE && $str =~ s/at .+Text.Xslate.PP.+ line \d+\.\n$//) { $str = Carp::shortmess($str); } diff --git a/lib/Text/Xslate/PP/Method.pm b/lib/Text/Xslate/PP/Method.pm index dbb3a157..a69810e1 100644 --- a/lib/Text/Xslate/PP/Method.pm +++ b/lib/Text/Xslate/PP/Method.pm @@ -7,11 +7,12 @@ use warnings; use Scalar::Util (); use Carp (); +use Text::Xslate::Constants qw(PP_ERROR_VERBOSE); require Text::Xslate::PP; require Text::Xslate::PP::State; require Text::Xslate::PP::Type::Pair; -if(!Text::Xslate::PP::_PP_ERROR_VERBOSE()) { +if(!PP_ERROR_VERBOSE()) { our @CARP_NOT = qw( Text::Xslate::PP::Opcode ); diff --git a/lib/Text/Xslate/PP/Opcode.pm b/lib/Text/Xslate/PP/Opcode.pm index 0cb8d199..85370e14 100644 --- a/lib/Text/Xslate/PP/Opcode.pm +++ b/lib/Text/Xslate/PP/Opcode.pm @@ -7,20 +7,18 @@ our $VERSION = '3.1.0'; use Carp (); use Scalar::Util (); +use Text::Xslate::Constants qw(DUMP_PP PP_ERROR_VERBOSE); use Text::Xslate::PP; use Text::Xslate::PP::Const; use Text::Xslate::PP::Method; use Text::Xslate::Util qw( p neat mark_raw unmark_raw html_escape uri_escape - $DEBUG ); -use constant _DUMP_PP => scalar($DEBUG =~ /\b dump=pp \b/xms); - no warnings 'recursion'; -if(!Text::Xslate::PP::_PP_ERROR_VERBOSE()) { +if(!PP_ERROR_VERBOSE()) { our @CARP_NOT = qw( Text::Xslate ); @@ -471,7 +469,7 @@ sub tx_macro_enter { my $outer = $macro->outer; my $args = pop @{ $st->{SP} }; - print STDERR " " x $st->current_frame, "tx_macro_enter($name) to $retaddr\n" if _DUMP_PP; + print STDERR " " x $st->current_frame, "tx_macro_enter($name) to $retaddr\n" if DUMP_PP; if(@{$args} != $nargs) { $st->error(undef, "Wrong number of arguments for %s (%d %s %d)", @@ -512,7 +510,7 @@ sub op_macro_end { my($st) = @_; my $top = $st->frame->[ $st->current_frame ]; - printf STDERR "%stx_macro_end(%s)]\n", ' ' x $st->current_frame - 1, $top->[ TXframe_NAME ] if _DUMP_PP; + printf STDERR "%stx_macro_end(%s)]\n", ' ' x $st->current_frame - 1, $top->[ TXframe_NAME ] if DUMP_PP; $st->{sa} = mark_raw( $st->{ output } ); $st->pop_frame(1); @@ -578,7 +576,7 @@ sub op_goto { sub op_end { my($st) = @_; - printf STDERR "op_end at %d\n", $st->{pc} if _DUMP_PP; + printf STDERR "op_end at %d\n", $st->{pc} if DUMP_PP; $st->{ pc } = $st->code_len; if($st->current_frame != 0) { diff --git a/lib/Text/Xslate/PP/State.pm b/lib/Text/Xslate/PP/State.pm index 43d4b609..47d42a86 100644 --- a/lib/Text/Xslate/PP/State.pm +++ b/lib/Text/Xslate/PP/State.pm @@ -1,13 +1,13 @@ package Text::Xslate::PP::State; # implement tx_state_t use Mouse; -use Text::Xslate::Util qw(neat p $DEBUG); +use Text::Xslate::Util qw(neat p); use Text::Xslate::PP; use Text::Xslate::PP::Const qw( TXframe_NAME TXframe_RETADDR TXframe_OUTPUT TX_VERBOSE_DEFAULT); -if(!Text::Xslate::PP::_PP_ERROR_VERBOSE()) { +if(!Text::Xslate::PP::PP_ERROR_VERBOSE()) { our @CARP_NOT = qw( Text::Xslate::PP::Opcode Text::Xslate::PP::Booter diff --git a/lib/Text/Xslate/Parser.pm b/lib/Text/Xslate/Parser.pm index dad63a1f..b94f231f 100644 --- a/lib/Text/Xslate/Parser.pm +++ b/lib/Text/Xslate/Parser.pm @@ -3,9 +3,9 @@ use Mouse; use Scalar::Util (); +use Text::Xslate::Constants qw(DUMP_PROTO DUMP_TOKEN); use Text::Xslate::Symbol; use Text::Xslate::Util qw( - $DEBUG $STRING $NUMBER is_int any_in neat @@ -15,9 +15,6 @@ use Text::Xslate::Util qw( with 'Text::Xslate::MakeError'; -use constant _DUMP_PROTO => scalar($DEBUG =~ /\b dump=proto \b/xmsi); -use constant _DUMP_TOKEN => scalar($DEBUG =~ /\b dump=token \b/xmsi); - our @CARP_NOT = qw(Text::Xslate::Compiler Text::Xslate::Symbol); my $CODE = qr/ (?: $STRING | [^'"] ) /xms; @@ -430,7 +427,7 @@ sub preprocess { $parser->throw_error("Oops: Unknown token: $s ($type)"); } } - print STDOUT $code, "\n" if _DUMP_PROTO; + print STDOUT $code, "\n" if DUMP_PROTO; return $code; } @@ -716,7 +713,7 @@ sub advance { $arity = "literal"; } - print STDOUT "[$arity => $id] #$line\n" if _DUMP_TOKEN; + print STDOUT "[$arity => $id] #$line\n" if DUMP_TOKEN; my $symbol; if($arity eq "literal") { diff --git a/lib/Text/Xslate/Symbol.pm b/lib/Text/Xslate/Symbol.pm index effd264b..0cef5314 100644 --- a/lib/Text/Xslate/Symbol.pm +++ b/lib/Text/Xslate/Symbol.pm @@ -1,7 +1,8 @@ package Text::Xslate::Symbol; use Mouse; -use Text::Xslate::Util qw(p $DEBUG); +use Text::Xslate::Constants qw(DUMP_DENOTE); +use Text::Xslate::Util qw(p); use overload bool => sub() { 1 }, @@ -11,8 +12,6 @@ use overload our @CARP_NOT = qw(Text::Xslate::Parser); -use constant _DUMP_DENOTE => scalar($DEBUG =~ /\b dump=denote \b/xmsi); - has id => ( is => 'rw', isa => 'Str', @@ -202,19 +201,19 @@ sub _std_default { sub nud { my($self, $parser, @args) = @_; - $self->_dump_denote('nud', $parser) if _DUMP_DENOTE; + $self->_dump_denote('nud', $parser) if DUMP_DENOTE; return $self->get_nud()->($parser, $self, @args); } sub led { my($self, $parser, @args) = @_; - $self->_dump_denote('led', $parser) if _DUMP_DENOTE; + $self->_dump_denote('led', $parser) if DUMP_DENOTE; return $self->get_led()->($parser, $self, @args); } sub std { my($self, $parser, @args) = @_; - $self->_dump_denote('std', $parser) if _DUMP_DENOTE; + $self->_dump_denote('std', $parser) if DUMP_DENOTE; return $self->get_std()->($parser, $self, @args); } diff --git a/lib/Text/Xslate/Util.pm b/lib/Text/Xslate/Util.pm index 297a3f54..ed9e1fba 100644 --- a/lib/Text/Xslate/Util.pm +++ b/lib/Text/Xslate/Util.pm @@ -5,7 +5,9 @@ use warnings; use Carp (); use parent qw(Exporter); -our @EXPORT_OK = qw( +our @EXPORT_OK; +BEGIN { + @EXPORT_OK = qw( mark_raw unmark_raw html_escape escaped_string uri_escape @@ -19,12 +21,9 @@ our @EXPORT_OK = qw( is_int any_in read_around make_error - $DEBUG $STRING $NUMBER ); - -our $DEBUG; -defined($DEBUG) or $DEBUG = $ENV{XSLATE} || ''; +} # cf. http://swtch.com/~rsc/regexp/regexp1.html my $dquoted = qr/" [^"\\]* (?: \\. [^"\\]* )* "/xms; diff --git a/src/Text-Xslate.xs b/src/Text-Xslate.xs index 2e98f510..4b5a7d6d 100644 --- a/src/Text-Xslate.xs +++ b/src/Text-Xslate.xs @@ -1919,7 +1919,7 @@ BOOT: code_ref); // debug flag - code_ref = sv_2mortal(newRV_inc((SV*)get_cv( "Text::Xslate::Engine::_DUMP_LOAD", GV_ADD))); + code_ref = sv_2mortal(newRV_inc((SV*)get_cv( "Text::Xslate::Constants::DUMP_LOAD", GV_ADD))); { dSP; PUSHMARK(SP); From fc1dbd041434f275feef8c4044c141ef1e63a1ab Mon Sep 17 00:00:00 2001 From: tokuhirom Date: Fri, 22 Nov 2013 17:26:39 +0900 Subject: [PATCH 25/39] Remove pure perl implementation. --- MANIFEST | 9 - Makefile.PL | 8 - lib/Text/Xslate.pm | 31 +- lib/Text/Xslate/PP.pm | 563 ------------------------- lib/Text/Xslate/PP/Assembler.pm | 176 -------- lib/Text/Xslate/PP/Method.pm | 194 --------- lib/Text/Xslate/PP/Opcode.pm | 670 ------------------------------ lib/Text/Xslate/PP/State.pm | 270 ------------ lib/Text/Xslate/PP/Type/Macro.pm | 74 ---- lib/Text/Xslate/PP/Type/Pair.pm | 27 -- lib/Text/Xslate/PP/Type/Raw.pm | 73 ---- t/000_load.t | 6 - t/010_internals/300_explicit_pp.t | 25 -- 13 files changed, 6 insertions(+), 2120 deletions(-) delete mode 100644 lib/Text/Xslate/PP.pm delete mode 100644 lib/Text/Xslate/PP/Assembler.pm delete mode 100644 lib/Text/Xslate/PP/Method.pm delete mode 100644 lib/Text/Xslate/PP/Opcode.pm delete mode 100644 lib/Text/Xslate/PP/State.pm delete mode 100644 lib/Text/Xslate/PP/Type/Macro.pm delete mode 100644 lib/Text/Xslate/PP/Type/Pair.pm delete mode 100644 lib/Text/Xslate/PP/Type/Raw.pm delete mode 100644 t/010_internals/300_explicit_pp.t diff --git a/MANIFEST b/MANIFEST index 41be5a8a..85bfe6ad 100644 --- a/MANIFEST +++ b/MANIFEST @@ -94,14 +94,6 @@ lib/Text/Xslate/Manual/Cookbook.pod lib/Text/Xslate/Manual/Debugging.pod lib/Text/Xslate/Manual/FAQ.pod lib/Text/Xslate/Parser.pm -lib/Text/Xslate/PP.pm -lib/Text/Xslate/PP/Const.pm -lib/Text/Xslate/PP/Method.pm -lib/Text/Xslate/PP/Opcode.pm -lib/Text/Xslate/PP/State.pm -lib/Text/Xslate/PP/Type/Macro.pm -lib/Text/Xslate/PP/Type/Pair.pm -lib/Text/Xslate/PP/Type/Raw.pm lib/Text/Xslate/Runner.pm lib/Text/Xslate/Symbol.pm lib/Text/Xslate/Syntax/Kolon.pm @@ -159,7 +151,6 @@ t/010_internals/038_suffix.t t/010_internals/039_taint_issue84.t t/010_internals/100_threads.t t/010_internals/200_leaktrace.t -t/010_internals/300_explicit_pp.t t/020_interface/001_parser_option.t t/020_interface/002_myparser.t t/020_interface/003_encoding.t diff --git a/Makefile.PL b/Makefile.PL index 451370d2..08f36021 100644 --- a/Makefile.PL +++ b/Makefile.PL @@ -9,7 +9,6 @@ BEGIN { my @devmods = qw( inc::Module::Install 1.06 Module::Install::XSUtil 0.44 - Module::Install::TestTarget 0.19 Module::Install::AuthorTests 0.002 ); my @not_available; @@ -80,13 +79,6 @@ if($Module::Install::AUTHOR) { @testall = (alias => 'test'); } -if($want_xs) { - test_target test_pp => ( - env => { PERL_ONLY => 1 }, - @testall, - ); -} - clean_files qw( Text-Xslate-* nytprof *.out diff --git a/lib/Text/Xslate.pm b/lib/Text/Xslate.pm index e798f395..ec981a04 100644 --- a/lib/Text/Xslate.pm +++ b/lib/Text/Xslate.pm @@ -12,6 +12,7 @@ use Scalar::Util (); our ($VERSION, @ISA, @EXPORT_OK); use Text::Xslate::Constants(); +use XSLoader; BEGIN { $VERSION = '3.1.0'; @@ -25,37 +26,18 @@ BEGIN { html_builder ); - my $use_xs = 0; - - # load backend (XS or PP) - my $DEBUG = $ENV{XSLATE} || ''; - if(!exists $INC{'Text/Xslate/PP.pm'}) { - my $pp = ($DEBUG =~ /\b pp \b/xms or $ENV{PERL_ONLY}); - if(!$pp) { - eval { - require XSLoader; - XSLoader::load(__PACKAGE__, $VERSION); - $use_xs = 1; - }; - die $@ if $@ && $DEBUG =~ /\b xs \b/xms; # force XS - } - - @ISA = qw(Text::Xslate::Engine); + XSLoader::load(__PACKAGE__, $VERSION); - if(!__PACKAGE__->can('render')) { - require 'Text/Xslate/PP.pm'; - } - } - eval 'sub USE_XS() { $use_xs }'; - die if $@; + @ISA = qw(Text::Xslate::Engine); } use Text::Xslate::Util @EXPORT_OK; +sub USE_XS { 1 } # deprecated. # for error messages (see T::X::Util) sub input_layer { ref($_[0]) ? $_[0]->{input_layer} : ':utf8' } -package Text::Xslate::Engine; # XS/PP common base class +package Text::Xslate::Engine; # base class use Mouse; use File::Spec; use Text::Xslate::Constants qw(DUMP_LOAD DEFAULT_CACHE_DIR); @@ -934,8 +916,7 @@ C<< $var == nil >> returns true if and only if I<$var> is nil. Perl 5.8.1 or later. -If you have a C compiler, the XS backend will be used. Otherwise the pure Perl -backend will be used. +C compiler =head1 TODO diff --git a/lib/Text/Xslate/PP.pm b/lib/Text/Xslate/PP.pm deleted file mode 100644 index 360053b6..00000000 --- a/lib/Text/Xslate/PP.pm +++ /dev/null @@ -1,563 +0,0 @@ -package Text::Xslate::PP; -# Text::Xslate in pure Perl -use 5.008_001; -use strict; - -our $VERSION = '3.1.0'; - -BEGIN{ - $ENV{XSLATE} = ($ENV{XSLATE} || '') . '[pp]'; -} - -use Text::Xslate::Constants qw(DUMP_LOAD PP_ERROR_VERBOSE); -use Text::Xslate::Util qw( - p -); - -use Text::Xslate::PP::Const qw(:all); -use Text::Xslate::PP::State; -use Text::Xslate::PP::Type::Raw; -use Text::Xslate::PP::Opcode; -use Text::Xslate::PP::Method; - -use Scalar::Util (); -use overload (); -use Carp (); - -# it must be loaded dynamically -require Text::Xslate; - -$VERSION =~ s/_//; # for developers versions - -if(PP_ERROR_VERBOSE) { - Carp->import('verbose'); -} - -# fix up @ISA -{ - package - Text::Xslate; - if(!our %OPS) { - # the compiler use %Text::Xslate::OPS in order to optimize the code - *OPS = \%Text::Xslate::PP::OPS; - } - our @ISA = qw(Text::Xslate::PP); - package - Text::Xslate::PP; - our @ISA = qw(Text::Xslate::Engine); -} - -# fix up default parameters (icky!) -sub options { - my $self = shift; - my $options = $self->SUPER::options(); - $options->{assembler} = 'Text::Xslate::PP::Assembler'; - return $options; -} - -our $_depth = 0; -our $_current_st; - -our($_orig_die_handler, $_orig_warn_handler); - -our %html_escape = ( - '&' => '&', - '<' => '<', - '>' => '>', - '"' => '"', - "'" => ''', # IE8 doesn't support ' in title -); -our $html_metachars = sprintf '[%s]', join '', map { quotemeta } keys %html_escape; - -sub _register_builtin_methods { - my($self, $funcs) = @_; - Text::Xslate::PP::Method::tx_register_builtin_methods($funcs); -} - -# -# public APIs -# - -sub render_string { - my($self, $string, $vars) = @_; - $self->load_string($string); - return $self->render('', $vars); -} - -sub render { - my ( $self, $name, $vars ) = @_; - - Carp::croak("Usage: Text::Xslate::render(self, name, vars)") - if !( @_ == 2 or @_ == 3 ); - unless ( ref $self ) { - Carp::croak( "Invalid xslate instance" ); - } - - if(!defined $vars) { - $vars = {}; - } - - if ( !defined $name ) { - Carp::croak("Xslate: Template name is not given"); - } - - unless ( ref $vars eq 'HASH' ) { - Carp::croak( sprintf("Xslate: Template variables must be a HASH reference, not %s", $vars ) ); - } - - my $st = $self->tx_load_template( $name, 0 ); - - local $_orig_die_handler = $SIG{__DIE__}; - local $_orig_warn_handler = $SIG{__WARN__}; - local $SIG{__DIE__} = \&_die; - local $SIG{__WARN__} = \&_warn; - - return tx_execute( $st, $vars ); -} - -sub validate { - my ( $self, $name ) = @_; - - Carp::croak("Usage: Text::Xslate::render(self, name)") - if !( @_ == 2 ); - unless ( ref $self ) { - Carp::croak( "Invalid xslate instance" ); - } - - if ( !defined $name ) { - Carp::croak("Xslate: Template name is not given"); - } - - local $self->{cache} = 0; # do not touch the cache - $self->tx_load_template( $name, 0 ); - return; -} - -sub current_engine { - return defined($_current_st) ? $_current_st->engine : undef; -} - -sub current_vars { - return defined($_current_st) ? $_current_st->vars : undef; -} - -sub current_file { - return defined($_current_st) - ? $_current_st->code->[ $_current_st->{ pc } ]->{file} - : undef; -} - -sub current_line { - return defined($_current_st) - ? $_current_st->code->[ $_current_st->{ pc } ]->{line} - : undef; -} - -sub print { - shift; - if(defined $_current_st) { - foreach my $s(@_) { - $_current_st->print($s); - } - } - else { - Carp::croak('You cannot call print() method outside render()'); - } - return ''; -} - -{ - package - Text::Xslate::Util; - - sub escaped_string; *escaped_string = \&mark_raw; - sub mark_raw { - my($str) = @_; - if(defined $str) { - return ref($str) eq Text::Xslate::PP::TXt_RAW() - ? $str - : bless \$str, Text::Xslate::PP::TXt_RAW(); - } - return $str; # undef - } - sub unmark_raw { - my($str) = @_; - return ref($str) eq Text::Xslate::PP::TXt_RAW() - ? ${$str} - : $str; - } - - sub html_escape { - my($s) = @_; - return $s if - ref($s) eq Text::Xslate::PP::TXt_RAW() - or !defined($s); - - $s =~ s/($html_metachars)/$html_escape{$1}/xmsgeo; - return bless \$s, Text::Xslate::PP::TXt_RAW(); - } - - my $uri_unsafe_rfc3986 = qr/[^A-Za-z0-9\-\._~]/; - sub uri_escape { - my($s) = @_; - return $s if not defined $s; - # XXX: This must be the same as uri_escape() in XS. - # See also tx_uri_escape() in xs/Text-Xslate.xs. - utf8::encode($s) if utf8::is_utf8($s); - $s =~ s/($uri_unsafe_rfc3986)/sprintf '%%' . '%02X', ord $1/xmsgeo; - return $s; - } - - sub is_array_ref { ref($_[0]) eq 'ARRAY' } - sub is_hash_ref { ref($_[0]) eq 'HASH' } - sub is_code_ref { ref($_[0]) eq 'CODE' } - - sub merge_hash { +{ %{ $_[0] }, %{ $_[1] } } } -} - -# -# INTERNAL -# - -sub tx_check_itr_ar { - my ( $st, $ar, $frame, $line ) = @_; - return $ar if ref($ar) eq 'ARRAY'; - - if ( defined $ar ) { - if(my $x = Text::Xslate::PP::sv_is_ref($ar, 'ARRAY', '@{}')) { - return $x; - } - - $st->error( [$frame, $line], - "Iterator variables must be an ARRAY reference, not %s", - Text::Xslate::Util::neat( $ar ) ); - } - return []; -} - -sub sv_is_ref { - my($sv, $t, $ov) = @_; - return $sv if ref($sv) eq $t; - - if(Scalar::Util::blessed($sv) - && (my $m = overload::Method($sv, $ov))) { - $sv = $sv->$m(undef, undef); - return $sv if ref($sv) eq $t; - } - return undef; -} - -sub tx_sv_eq { - my($x, $y) = @_; - if ( defined $x ) { - return defined $y && $x eq $y; - } - else { - return !defined $y; - } -} - -sub tx_match { # simple smart matching - my($x, $y) = @_; - - if(ref($y) eq 'ARRAY') { - foreach my $item(@{$y}) { - if(defined($item)) { - if(defined($x) && $x eq $item) { - return 1; - } - } - else { - if(not defined($x)) { - return 1; - } - } - } - return ''; - } - elsif(ref($y) eq 'HASH') { - return defined($x) && exists $y->{$x}; - } - elsif(defined($y)) { - return defined($x) && $x eq $y; - } - else { - return !defined($x); - } -} - -sub tx_concat { - my($lhs, $rhs) = @_; - if(ref($lhs) eq TXt_RAW) { - if(ref($rhs) eq TXt_RAW) { - return Text::Xslate::Util::mark_raw(${ $lhs } . ${ $rhs }); - } - else { - return Text::Xslate::Util::mark_raw(${ $lhs } . Text::Xslate::Util::html_escape($rhs)); - } - } - else { - if(ref($rhs) eq TXt_RAW) { - return Text::Xslate::Util::mark_raw(Text::Xslate::Util::html_escape($lhs) . ${ $rhs }); - } - else { - return $lhs . $rhs; - } - } -} - -sub tx_repeat { - my($lhs, $rhs) = @_; - if(!defined($lhs)) { - $_current_st->warn(undef, "Use of nil for repeat operator"); - } - elsif(!Scalar::Util::looks_like_number($rhs)) { - $_current_st->error(undef, "Repeat count must be a number, not %s", - Text::Xslate::Util::neat($rhs)); - } - else { - if( ref( $lhs ) eq TXt_RAW ) { - return Text::Xslate::Util::mark_raw( Text::Xslate::Util::unmark_raw($lhs) x $rhs ); - } - else { - return $lhs x $rhs; - } - } - return ''; -} - - -sub tx_load_template { - my ( $self, $name, $from_include ) = @_; - - unless ( ref $self ) { - Carp::croak( "Invalid xslate instance" ); - } - - my $ttable = $self->{ template }; - my $retried = 0; - - if(ref $ttable ne 'HASH' ) { - Carp::croak( - sprintf( "Xslate: Cannot load template '%s': %s", $name, "template table is not a HASH reference" ) - ); - } - - RETRY: - if( $retried > 1 ) { - Carp::croak( - sprintf( "Xslate: Cannot load template '%s': %s", $name, "retried reloading, but failed" ) - ); - } - - if ( not exists $ttable->{ $name } ) { - $self->load_file( $name, undef, $from_include ); - $retried++; - goto RETRY; - } - - my $tmpl = $ttable->{ $name }; - - if(ref($tmpl) ne 'ARRAY' or not exists $self->{tmpl_st}{$name}) { - Carp::croak( - sprintf( "Xslate: Cannot load template '%s': template entry is invalid", $name ), - ); - } - - my $cache_mtime = $tmpl->[ TXo_MTIME ]; - - if(not defined $cache_mtime) { # cache => 2 (release mode) - return $self->{ tmpl_st }->{ $name }; - } - - if( $retried > 0 or tx_all_deps_are_fresh( $tmpl, $cache_mtime ) ) { - return $self->{ tmpl_st }->{ $name }; - } - else{ - $self->load_file( $name, $cache_mtime, $from_include ); - $retried++; - goto RETRY; - } - - Carp::croak("Oops: Not reached"); -} - - -sub tx_all_deps_are_fresh { - my ( $tmpl, $cache_mtime ) = @_; - my $len = scalar @{$tmpl}; - - for ( my $i = TXo_FULLPATH; $i < $len; $i++ ) { - my $deppath = $tmpl->[ $i ]; - - next if ref $deppath; - - my $mtime = ( stat( $deppath ) )[9]; - if ( defined($mtime) and $mtime > $cache_mtime ) { - my $main_cache = $tmpl->[ TXo_CACHEPATH ]; - if ( $i != TXo_FULLPATH and $main_cache ) { - unlink $main_cache or warn $!; - } - if(DUMP_LOAD) { - printf STDERR " tx_all_depth_are_fresh: %s is too old (%d > %d)\n", - $deppath, $mtime, $cache_mtime; - } - return 0; - } - - } - - return 1; -} - -sub tx_execute { - my ( $st, $vars ) = @_; - no warnings 'recursion'; - - if ( $_depth > 100 ) { - Carp::croak("Execution is too deep (> 100)"); - } - if(PP_ERROR_VERBOSE and ref $st->{code}->[0]->{ exec_code } ne 'CODE') { - Carp::croak("Oops: Not a CODE reference: " - . Text::Xslate::Util::neat($st->{code}->[0]->{ exec_code })); - } - - local $st->{pc} = 0; - local $st->{vars} = $vars; - - local $_depth = $_depth + 1; - local $_current_st = $st; - - local $st->{local_stack}; - local $st->{SP} = []; - - local $st->{sa}; - local $st->{sb}; - local $st->{output} = ''; - - local $st->{current_frame} = $st->{current_frame}; - - eval { - $st->{code}->[0]->{ exec_code }->( $st ); - }; - - @{$st->{frame}->[-1]} = Text::Xslate::PP::TXframe_START_LVAR - 1; - - if ($@) { - my $e = $@; - die $e; - } - return $st->{output}; -} - - -sub _error_handler { - my ( $str, $die ) = @_; - my $st = $_current_st; - - local $SIG{__WARN__} = $_orig_warn_handler; - local $SIG{__DIE__} = $_orig_die_handler; - - if(!PP_ERROR_VERBOSE && $str =~ s/at .+Text.Xslate.PP.+ line \d+\.\n$//) { - $str = Carp::shortmess($str); - } - - Carp::croak( $str ) unless defined $st; - - my $engine = $st->engine; - - my $cframe = $st->frame->[ $st->current_frame ]; - my $name = $cframe->[ Text::Xslate::PP::TXframe_NAME ]; - - my $opcode = $st->code->[ $st->{ pc } ]; - my $file = $opcode->{file}; - if($file eq '' && exists $engine->{string_buffer}) { - $file = \$engine->{string_buffer}; - } - - my $mess = Text::Xslate::Util::make_error($engine, $str, $file, $opcode->{line}, - sprintf( "&%s[%d]", $name, $st->{pc} )); - - if ( !$die ) { - # $h can ignore warnings - if ( my $h = $engine->{ warn_handler } ) { - $h->( $mess ); - } - else { - warn $mess; - } - } - else { - # $h cannot ignore errors - if(my $h = $engine->{ die_handler } ) { - $h->( $mess ); - } - die $mess; # MUST DIE! - } - return; -} - -sub _warn { - _error_handler( $_[0], 0 ); -} - -sub _die { - _error_handler( $_[0], 1 ); -} - -{ - package - Text::Xslate::PP::Guard; - - sub DESTROY { $_[0]->() } -} - -1; -__END__ - -=head1 NAME - -Text::Xslate::PP - Yet another Text::Xslate runtime in pure Perl - -=head1 VERSION - -This document describes Text::Xslate::PP version 3.1.0. - -=head1 DESCRIPTION - -This module implements a Text::Xslate runtime engine in pure Perl. -Normally it will be loaded if it fails to load XS. So you do not need -to use this module explicitly. - - # Text::Xslate loads PP if needed - use Text::Xslate; - my $tx = Text::Xslate->new(); - -If you want to use Text::Xslate::PP, however, you can use it. - - use Text::Xslate::PP; - my $tx = Text::Xslate->new(); - -XS/PP mode might be switched with C<< $ENV{XSLATE} = 'pp' or 'xs' >>. - -C<< $ENV{XSLATE} = 'pp=verbose' } >> may be useful for debugging. - -=head1 SEE ALSO - -L - -L - -=head1 AUTHOR - -Text::Xslate::PP stuff is originally written by Makamaka Hannyaharamitu -Emakamaka at cpan.orgE, and also maintained by Fuji, Goro (gfx). - -=head1 LICENSE AND COPYRIGHT - -Copyright (c) 2010 by Makamaka Hannyaharamitu (makamaka). - -This library is free software; you can redistribute it and/or modify -it under the same terms as Perl itself. - -=cut diff --git a/lib/Text/Xslate/PP/Assembler.pm b/lib/Text/Xslate/PP/Assembler.pm deleted file mode 100644 index bb298c43..00000000 --- a/lib/Text/Xslate/PP/Assembler.pm +++ /dev/null @@ -1,176 +0,0 @@ -package Text::Xslate::PP::Assembler; -use strict; -use Text::Xslate::PP::Const qw(:all); - -my $state_class = 'Text::Xslate::PP::Opcode'; - -sub new { - my $class = shift; - bless { @_ }, $class; -} - -sub build { - my ($class, $engine) = @_; - $class->new(engine => $engine); -} - -# >> copied and modified from Text::Xslate - -sub assemble { - my ( $self, $asm, $name, $fullpath, $cachepath, $mtime ) = @_; - - my $engine = $self->{engine}; - - unless ( defined $name ) { # $name ... filename - $name = ''; - $fullpath = $cachepath = undef; - $mtime = time(); - } - - my $st = $state_class->new(); - - $st->symbol({ %{$engine->{ function }} }); - - my $tmpl = []; - - $engine->{ template }->{ $name } = $tmpl; - $engine->{ tmpl_st }->{ $name } = $st; - - $tmpl->[ Text::Xslate::PP::TXo_MTIME ] = $mtime; - $tmpl->[ Text::Xslate::PP::TXo_CACHEPATH ] = $cachepath; - $tmpl->[ Text::Xslate::PP::TXo_FULLPATH ] = $fullpath; - - $st->tmpl( $tmpl ); - $st->engine( $engine ); # weak_ref! - - $st->{sa} = undef; - $st->{sb} = undef; - - # stack frame - $st->frame( [] ); - $st->current_frame( -1 ); - - my $len = scalar( @$asm ); - - $st->push_frame('main', $len); - - $st->code_len( $len ); - - my $code = $st->code([]); - my $macro; - - my $oi_line = -1; - my $oi_file = $name; - for ( my $i = 0; $i < $len; $i++ ) { - my $c = $asm->[ $i ]; - - if ( ref $c ne 'ARRAY' ) { - Carp::croak( sprintf( "Oops: Broken code found on [%d]", $i ) ); - } - - my ( $opname, $arg, $line, $file ) = @{$c}; - my $opnum = $Text::Xslate::PP::OPS{ $opname }; - - unless ( defined $opnum ) { - Carp::croak( sprintf( "Oops: Unknown opcode '%s' on [%d]", $opname, $i ) ); - } - - if(defined $line) { - $oi_line = $line; - } - if(defined $file) { - $oi_file = $file; - } - - $code->[$i] = { - # opcode - opname => $opname, - - # opinfo - line => $oi_line, - file => $oi_file, - }; - - $code->[ $i ]->{ exec_code } = $Text::Xslate::PP::OPCODE[ $opnum ]; - - my $oparg = $Text::Xslate::PP::OPARGS[ $opnum ]; - - if ( $oparg & TXARGf_SV ) { - - # This line croak at 'concat'! - # Carp::croak( sprintf( "Oops: Opcode %s must have an argument on [%d]", $opname, $i ) ) - # unless ( defined $arg ); - - if( $oparg & TXARGf_KEY ) { - $code->[ $i ]->{ arg } = $arg; - } - elsif ( $oparg & TXARGf_INT ) { - $code->[ $i ]->{ arg } = int($arg); - - if( $oparg & TXARGf_PC ) { - my $abs_addr = $i + $arg; - - if( $abs_addr >= $len ) { - Carp::croak( - sprintf( "Oops: goto address %d is out of range (must be 0 <= addr <= %d)", $arg, $len ) - ); - } - - $code->[ $i ]->{ arg } = $abs_addr; - } - - } - else { - $code->[ $i ]->{ arg } = $arg; - } - - } - else { - if( defined $arg ) { - Carp::croak( sprintf( "Oops: Opcode %s has an extra argument on [%d]", $opname, $i ) ); - } - $code->[ $i ]->{ arg } = undef; - } - - # special cases - if( $opnum == $Text::Xslate::PP::OPS{ macro_begin } ) { - my $name = $code->[ $i ]->{ arg }; - if(!exists $st->symbol->{$name}) { - require Text::Xslate::PP::Type::Macro; - $macro = Text::Xslate::PP::Type::Macro->new( - name => $name, - addr => $i, - state => $st, - ); - $st->symbol->{ $name } = $macro; - } - else { - $macro = undef; - } - } - elsif( $opnum == $Text::Xslate::PP::OPS{ macro_nargs } ) { - if($macro) { - $macro->nargs($code->[$i]->{arg}); - } - } - elsif( $opnum == $Text::Xslate::PP::OPS{ macro_outer } ) { - if($macro) { - $macro->outer($code->[$i]->{arg}); - } - } - elsif( $opnum == $Text::Xslate::PP::OPS{ depend } ) { - push @{ $tmpl }, $code->[ $i ]->{ arg }; - } - - } - - push @{$code}, { - exec_code => $Text::Xslate::PP::OPCODE[ $Text::Xslate::PP::OPS{end} ], - file => $oi_file, - line => $oi_line, - opname => 'end', - }; # for threshold - return; -} - - diff --git a/lib/Text/Xslate/PP/Method.pm b/lib/Text/Xslate/PP/Method.pm deleted file mode 100644 index a69810e1..00000000 --- a/lib/Text/Xslate/PP/Method.pm +++ /dev/null @@ -1,194 +0,0 @@ -package Text::Xslate::PP::Method; -# xs/xslate-methods.xs in pure Perl -use strict; -use warnings; - - -use Scalar::Util (); -use Carp (); - -use Text::Xslate::Constants qw(PP_ERROR_VERBOSE); -require Text::Xslate::PP; -require Text::Xslate::PP::State; -require Text::Xslate::PP::Type::Pair; - -if(!PP_ERROR_VERBOSE()) { - our @CARP_NOT = qw( - Text::Xslate::PP::Opcode - ); -} - -our $_st; -*_st = *Text::Xslate::PP::_current_st; - -our $_context; - -sub _array_size { - my($array_ref) = @_; - return $_st->bad_arg('size') if @_ != 1; - return scalar @{$array_ref}; -} - -sub _array_join { - my($array_ref, $sep) = @_; - return $_st->bad_arg('join') if @_ != 2; - return join $sep, @{$array_ref}; -} - -sub _array_reverse { - my($array_ref) = @_; - return $_st->bad_arg('reverse') if @_ != 1; - return [ reverse @{$array_ref} ]; -} - -sub _array_sort { - my($array_ref, $callback) = @_; - return $_st->bad_arg('sort') if !(@_ == 1 or @_ == 2); - if(@_ == 1) { - return [ sort @{$array_ref} ]; - } - else { - return [ sort { - push @{ $_st->{ SP } }, [ $a, $b ]; - $_st->proccall($callback, $_context) + 0; # need to numify - } @{$array_ref} ]; - } -} - -sub _array_map { - my($array_ref, $callback) = @_; - return $_st->bad_arg('map') if @_ != 2; - return [ map { - push @{ $_st->{ SP } }, [ $_ ]; - $_st->proccall($callback, $_context); - } @{$array_ref} ]; -} - -sub _array_reduce { - my($array_ref, $callback) = @_; - return $_st->bad_arg('reduce') if @_ != 2; - return $array_ref->[0] if @{$array_ref} < 2; - - my $x = $array_ref->[0]; - for(my $i = 1; $i < @{$array_ref}; $i++) { - push @{ $_st->{ SP } }, [ $x, $array_ref->[$i] ]; - $x = $_st->proccall($callback, $_context); - } - return $x; -} - -sub _array_merge { - my($array_ref, $value) = @_; - return $_st->bad_arg('merge') if @_ != 2; - return [ @{$array_ref}, ref($value) eq 'ARRAY' ? @{$value} : $value ]; -} - -sub _hash_size { - my($hash_ref) = @_; - return $_st->bad_arg('size') if @_ != 1; - return scalar keys %{$hash_ref}; -} - -sub _hash_keys { - my($hash_ref) = @_; - return $_st->bad_arg('keys') if @_ != 1; - return [sort { $a cmp $b } keys %{$hash_ref}]; -} - -sub _hash_values { - my($hash_ref) = @_; - return $_st->bad_arg('values') if @_ != 1; - return [map { $hash_ref->{$_} } @{ _hash_keys($hash_ref) } ]; -} - -sub _hash_kv { - my($hash_ref) = @_; - $_st->bad_arg('kv') if @_ != 1; - return [ - map { Text::Xslate::PP::Type::Pair->new(key => $_, value => $hash_ref->{$_}) } - @{ _hash_keys($hash_ref) } - ]; -} - -sub _hash_merge { - my($hash_ref, $other_hash_ref) = @_; - $_st->bad_arg('merge') if @_ != 2; - - return { %{$hash_ref}, %{$other_hash_ref} }; -} - -BEGIN { - our %builtin_method = ( - 'array::size' => \&_array_size, - 'array::join' => \&_array_join, - 'array::reverse' => \&_array_reverse, - 'array::sort' => \&_array_sort, - 'array::map' => \&_array_map, - 'array::reduce' => \&_array_reduce, - 'array::merge' => \&_array_merge, - - 'hash::size' => \&_hash_size, - 'hash::keys' => \&_hash_keys, - 'hash::values' => \&_hash_values, - 'hash::kv' => \&_hash_kv, - 'hash::merge' => \&_hash_merge, - ); -} - -sub tx_register_builtin_methods { - my($hv) = @_; - our %builtin_method; - foreach my $name(keys %builtin_method) { - $hv->{$name} = $builtin_method{$name}; - } -} - -sub tx_methodcall { - my($st, $context, $method, $invocant, @args) = @_; - - if(Scalar::Util::blessed($invocant)) { - my $retval = eval { $invocant->$method(@args) }; - $st->error($context, "%s", $@) if $@; - return $retval; - } - - my $type = ref($invocant) eq 'ARRAY' ? 'array::' - : ref($invocant) eq 'HASH' ? 'hash::' - : defined($invocant) ? 'scalar::' - : 'nil::'; - my $fq_name = $type . $method; - - if(my $body = $st->symbol->{$fq_name}){ - push @{ $st->{ SP } }, [ $invocant, @args ]; # re-pushmark - local $_context = $context; - return $st->proccall($body, $context); - } - if(!defined $invocant) { - $st->warn($context, "Use of nil to invoke method %s", $method); - return undef; - } - - $st->error($context, "Undefined method %s called for %s", - $method, $invocant); - - return undef; -} - -1; -__END__ - -=head1 NAME - -Text::Xslate::PP::Method - Text::Xslate builtin method call in pure Perl - -=head1 DESCRIPTION - -This module is used by Text::Xslate::PP internally. - -=head1 SEE ALSO - -L - -L - -=cut diff --git a/lib/Text/Xslate/PP/Opcode.pm b/lib/Text/Xslate/PP/Opcode.pm deleted file mode 100644 index 85370e14..00000000 --- a/lib/Text/Xslate/PP/Opcode.pm +++ /dev/null @@ -1,670 +0,0 @@ -package Text::Xslate::PP::Opcode; -use Mouse; -extends qw(Text::Xslate::PP::State); - -our $VERSION = '3.1.0'; - -use Carp (); -use Scalar::Util (); - -use Text::Xslate::Constants qw(DUMP_PP PP_ERROR_VERBOSE); -use Text::Xslate::PP; -use Text::Xslate::PP::Const; -use Text::Xslate::PP::Method; -use Text::Xslate::Util qw( - p neat - mark_raw unmark_raw html_escape uri_escape -); - -no warnings 'recursion'; - -if(!PP_ERROR_VERBOSE()) { - our @CARP_NOT = qw( - Text::Xslate - ); -} -our $_current_frame; - - -# -# -# - -sub op_noop { - goto $_[0]->{ code }->[ ++$_[0]->{ pc } ]->{ exec_code }; -} - -*op_meta = \&op_noop; - -sub op_move_to_sb { - $_[0]->{sb} = $_[0]->{sa}; - goto $_[0]->{ code }->[ ++$_[0]->{ pc } ]->{ exec_code }; -} - -sub op_move_from_sb { - $_[0]->{sa} = $_[0]->{sb}; - goto $_[0]->{ code }->[ ++$_[0]->{ pc } ]->{ exec_code }; -} - -sub op_save_to_lvar { - tx_access_lvar( $_[0], $_[0]->op_arg, $_[0]->{sa} ); - goto $_[0]->{ code }->[ ++$_[0]->{ pc } ]->{ exec_code }; -} - -sub op_load_lvar { - $_[0]->{sa} = tx_access_lvar( $_[0], $_[0]->op_arg ); - goto $_[0]->{ code }->[ ++$_[0]->{ pc } ]->{ exec_code }; -} - -sub op_load_lvar_to_sb { - $_[0]->{sb} = tx_access_lvar( $_[0], $_[0]->op_arg ); - goto $_[0]->{ code }->[ ++$_[0]->{ pc } ]->{ exec_code }; -} - -sub op_localize_s { - my($st) = @_; - my $key = $st->op_arg; - my $newval = $st->{sa}; - $st->localize($key, $newval); - - goto $st->{ code }->[ ++$st->{ pc } ]->{ exec_code }; -} - -sub op_localize_vars { - my($st) = @_; - my $new_vars = $st->{sa}; - my $old_vars = $st->vars; - - if(ref($new_vars) ne 'HASH') { - $st->warn(undef, "Variable map must be a HASH reference"); - } - - push @{ $st->{local_stack} }, bless sub { - $st->vars($old_vars); - return; - }, 'Text::Xslate::PP::Guard'; - - $st->vars($new_vars); - - goto $st->{ code }->[ ++$st->{ pc } ]->{ exec_code }; -} - -sub op_push { - push @{ $_[0]->{ SP }->[ -1 ] }, $_[0]->{sa}; - goto $_[0]->{ code }->[ ++$_[0]->{ pc } ]->{ exec_code }; -} - - -sub op_pushmark { - push @{ $_[0]->{ SP } }, []; - goto $_[0]->{ code }->[ ++$_[0]->{ pc } ]->{ exec_code }; -} - - -sub op_nil { - $_[0]->{sa} = undef; - goto $_[0]->{ code }->[ ++$_[0]->{ pc } ]->{ exec_code }; -} - -sub op_vars { - $_[0]->{sa} = $_[0]->{vars}; - - goto $_[0]->{ code }->[ ++$_[0]->{ pc } ]->{ exec_code }; -} - -sub op_literal { - $_[0]->{sa} = $_[0]->op_arg; - goto $_[0]->{ code }->[ ++$_[0]->{ pc } ]->{ exec_code }; -} - - -sub op_literal_i { - $_[0]->{sa} = $_[0]->op_arg; - goto $_[0]->{ code }->[ ++$_[0]->{ pc } ]->{ exec_code }; -} - - -sub op_fetch_s { - $_[0]->{sa} = $_[0]->{vars}->{ $_[0]->op_arg }; - goto $_[0]->{ code }->[ ++$_[0]->{ pc } ]->{ exec_code }; -} - -sub op_fetch_field { - my($st) = @_; - my $var = $st->{sb}; - my $key = $st->{sa}; - $st->{sa} = $st->fetch($var, $key); - goto $st->{ code }->[ ++$st->{ pc } ]->{ exec_code }; -} - - -sub op_fetch_field_s { - my($st) = @_; - my $var = $st->{sa}; - my $key = $st->op_arg; - $st->{sa} = $st->fetch($var, $key); - goto $st->{ code }->[ ++$st->{ pc } ]->{ exec_code }; -} - - -sub op_print { - my($st) = @_; - $st->print($st->{sa}); - goto $st->{ code }->[ ++$st->{ pc } ]->{ exec_code }; -} - - -sub op_print_raw { - my($st) = @_; - if(defined $st->{sa}) { - $st->{ output } .= $st->{sa}; - } - else { - $st->warn( undef, "Use of nil to print" ); - } - goto $st->{ code }->[ ++$st->{ pc } ]->{ exec_code }; -} - - -sub op_print_raw_s { - $_[0]->{ output } .= $_[0]->op_arg; - goto $_[0]->{ code }->[ ++$_[0]->{ pc } ]->{ exec_code }; -} - - -sub op_include { - my($st) = @_; - my $child = Text::Xslate::PP::tx_load_template( $st->engine, $st->{sa}, 1 ); - $st->push_frame('include', undef); - my $output = Text::Xslate::PP::tx_execute( $child, $st->{vars} ); - $st->pop_frame(0); - $st->{output} .= $output; - goto $st->{ code }->[ ++$st->{ pc } ]->{ exec_code }; -} - -sub op_find_file { - $_[0]->{sa} = eval { $_[0]->engine->find_file($_[0]->{sa}); 1 }; - goto $_[0]->{ code }->[ ++$_[0]->{ pc } ]->{ exec_code }; -} - -sub op_suffix { - $_[0]->{sa} = $_[0]->engine->{suffix}; - goto $_[0]->{ code }->[ ++$_[0]->{ pc } ]->{ exec_code }; -} - -sub op_for_start { - my($st) = @_; - my $id = $st->op_arg; - my $ar = Text::Xslate::PP::tx_check_itr_ar($st, $st->{sa}); - - #tx_access_lvar( $st, $id + TXfor_ITEM, undef ); - tx_access_lvar( $st, $id + TXfor_ITER, -1 ); - tx_access_lvar( $st, $id + TXfor_ARRAY, $ar ); - - goto $st->{ code }->[ ++$st->{ pc } ]->{ exec_code }; -} - - -sub op_for_iter { - my($st) = @_; - my $id = $st->{sa}; - my $av = tx_access_lvar( $st, $id + TXfor_ARRAY ); - - if(defined $av) { - my $i = tx_access_lvar( $st, $id + TXfor_ITER ); - $av = [ $av ] unless ref $av; - if ( ++$i < scalar(@{ $av }) ) { - tx_access_lvar( $st, $id + TXfor_ITEM, $av->[ $i ] ); - tx_access_lvar( $st, $id + TXfor_ITER, $i ); - goto $st->{ code }->[ ++$st->{ pc } ]->{ exec_code }; - } - else { - # finish the loop - $st->{sa} = ( $i > 0 ); # for 'for-else' block - tx_access_lvar( $st, $id + TXfor_ITEM, undef ); - tx_access_lvar( $st, $id + TXfor_ITER, undef ); - tx_access_lvar( $st, $id + TXfor_ARRAY, undef ); - } - } - - # finish - $st->{ pc } = $st->op_arg; - goto $st->{ code }->[ $st->{ pc } ]->{ exec_code }; -} - - -sub op_add { - $_[0]->{sa} = $_[0]->{sb} + $_[0]->{sa}; - goto $_[0]->{ code }->[ ++$_[0]->{ pc } ]->{ exec_code }; -} - - -sub op_sub { - $_[0]->{sa} = $_[0]->{sb} - $_[0]->{sa}; - goto $_[0]->{ code }->[ ++$_[0]->{ pc } ]->{ exec_code }; -} - - -sub op_mul { - $_[0]->{sa} = $_[0]->{sb} * $_[0]->{sa}; - goto $_[0]->{ code }->[ ++$_[0]->{ pc } ]->{ exec_code }; -} - - -sub op_div { - $_[0]->{sa} = $_[0]->{sb} / $_[0]->{sa}; - goto $_[0]->{ code }->[ ++$_[0]->{ pc } ]->{ exec_code }; -} - - -sub op_mod { - my($st) = @_; - my $lhs = int $st->{sb}; - my $rhs = int $st->{sa}; - if($rhs == 0) { - $st->error(undef, "Illegal modulus zero"); - $st->{sa} = 'NaN'; - } - else { - $st->{sa} = $lhs % $rhs; - } - goto $st->{ code }->[ ++$st->{ pc } ]->{ exec_code }; -} - - -sub op_concat { - my($st) = @_; - $st->{sa} = Text::Xslate::PP::tx_concat($st->{sb}, $st->{sa}); - goto $st->{ code }->[ ++$st->{ pc } ]->{ exec_code }; -} - -sub op_repeat { - my($st) = @_; - $st->{sa} = Text::Xslate::PP::tx_repeat($st->{sb}, $st->{sa}); - goto $st->{ code }->[ ++$st->{ pc } ]->{ exec_code }; -} - -sub op_bitor { - $_[0]->{sa} = int($_[0]->{sb}) | int($_[0]->{sa}); - goto $_[0]->{ code }->[ ++$_[0]->{ pc } ]->{ exec_code }; -} - -sub op_bitand { - $_[0]->{sa} = int($_[0]->{sb}) & int($_[0]->{sa}); - goto $_[0]->{ code }->[ ++$_[0]->{ pc } ]->{ exec_code }; -} - -sub op_bitxor { - $_[0]->{sa} = int($_[0]->{sb}) ^ int($_[0]->{sa}); - goto $_[0]->{ code }->[ ++$_[0]->{ pc } ]->{ exec_code }; -} - -sub op_bitneg { - $_[0]->{sa} = ~int($_[0]->{sa}); - goto $_[0]->{ code }->[ ++$_[0]->{ pc } ]->{ exec_code }; -} - - - -sub op_and { - if ( $_[0]->{sa} ) { - goto $_[0]->{ code }->[ ++$_[0]->{ pc } ]->{ exec_code }; - } - else { - $_[0]->{ pc } = $_[0]->op_arg; - goto $_[0]->{ code }->[ $_[0]->{ pc } ]->{ exec_code }; - } -} - - -sub op_dand { - if ( defined $_[0]->{sa} ) { - goto $_[0]->{ code }->[ ++$_[0]->{ pc } ]->{ exec_code }; - } - else { - $_[0]->{ pc } = $_[0]->op_arg; - goto $_[0]->{ code }->[ $_[0]->{ pc } ]->{ exec_code }; - } -} - - -sub op_or { - if ( ! $_[0]->{sa} ) { - goto $_[0]->{ code }->[ ++$_[0]->{ pc } ]->{ exec_code }; - } - else { - $_[0]->{ pc } = $_[0]->op_arg; - goto $_[0]->{ code }->[ $_[0]->{ pc } ]->{ exec_code }; - } -} - - -sub op_dor { - my $sv = $_[0]->{sa}; - if ( defined $sv ) { - $_[0]->{ pc } = $_[0]->op_arg; - goto $_[0]->{ code }->[ $_[0]->{ pc } ]->{ exec_code }; - } - else { - goto $_[0]->{ code }->[ ++$_[0]->{ pc } ]->{ exec_code }; - } - -} - -sub op_not { - $_[0]->{sa} = ! $_[0]->{sa}; - goto $_[0]->{ code }->[ ++$_[0]->{ pc } ]->{ exec_code }; -} - -sub op_minus { - $_[0]->{sa} = -$_[0]->{sa}; - goto $_[0]->{ code }->[ ++$_[0]->{ pc } ]->{ exec_code }; -} - -sub op_max_index { - $_[0]->{sa} = scalar(@{ $_[0]->{sa} }) - 1; - goto $_[0]->{ code }->[ ++$_[0]->{ pc } ]->{ exec_code }; -} - -sub op_builtin_mark_raw { - $_[0]->{sa} = mark_raw($_[0]->{sa}); - goto $_[0]->{ code }->[ ++$_[0]->{ pc } ]->{ exec_code }; -} - -sub op_builtin_unmark_raw { - $_[0]->{sa} = unmark_raw($_[0]->{sa}); - goto $_[0]->{ code }->[ ++$_[0]->{ pc } ]->{ exec_code }; -} - -sub op_builtin_html_escape { - $_[0]->{sa} = html_escape($_[0]->{sa}); - goto $_[0]->{ code }->[ ++$_[0]->{ pc } ]->{ exec_code }; -} - -sub op_builtin_uri_escape { - $_[0]->{sa} = uri_escape($_[0]->{sa}); - goto $_[0]->{ code }->[ ++$_[0]->{ pc } ]->{ exec_code }; -} - -sub op_builtin_is_array_ref { - $_[0]->{sa} = Text::Xslate::Util::is_array_ref($_[0]->{sa}); - goto $_[0]->{ code }->[ ++$_[0]->{ pc } ]->{ exec_code }; -} - -sub op_builtin_is_hash_ref { - $_[0]->{sa} = Text::Xslate::Util::is_hash_ref($_[0]->{sa}); - goto $_[0]->{ code }->[ ++$_[0]->{ pc } ]->{ exec_code }; -} - -sub op_is_code_ref { - $_[0]->{sa} = Text::Xslate::Util::is_code_ref($_[0]->{sa}); - goto $_[0]->{ code }->[ ++$_[0]->{ pc } ]->{ exec_code }; -} - -sub op_match { - $_[0]->{sa} = Text::Xslate::PP::tx_match($_[0]->{sb}, $_[0]->{sa}); - goto $_[0]->{ code }->[ ++$_[0]->{ pc } ]->{ exec_code }; -} - -sub op_eq { - $_[0]->{sa} = Text::Xslate::PP::tx_sv_eq($_[0]->{sb}, $_[0]->{sa}); - goto $_[0]->{ code }->[ ++$_[0]->{ pc } ]->{ exec_code }; -} - - -sub op_ne { - $_[0]->{sa} = !Text::Xslate::PP::tx_sv_eq($_[0]->{sb}, $_[0]->{sa}); - goto $_[0]->{ code }->[ ++$_[0]->{ pc } ]->{ exec_code }; -} - - -sub op_lt { - $_[0]->{sa} = $_[0]->{sb} < $_[0]->{sa}; - goto $_[0]->{ code }->[ ++$_[0]->{ pc } ]->{ exec_code }; -} - -sub op_le { - $_[0]->{sa} = $_[0]->{sb} <= $_[0]->{sa}; - goto $_[0]->{ code }->[ ++$_[0]->{ pc } ]->{ exec_code }; -} - -sub op_gt { - $_[0]->{sa} = $_[0]->{sb} > $_[0]->{sa}; - goto $_[0]->{ code }->[ ++$_[0]->{ pc } ]->{ exec_code }; -} - -sub op_ge { - $_[0]->{sa} = $_[0]->{sb} >= $_[0]->{sa}; - goto $_[0]->{ code }->[ ++$_[0]->{ pc } ]->{ exec_code }; -} - -sub op_ncmp { - $_[0]->{sa} = $_[0]->{sb} <=> $_[0]->{sa}; - goto $_[0]->{ code }->[ ++$_[0]->{ pc } ]->{ exec_code }; -} -sub op_scmp { - $_[0]->{sa} = $_[0]->{sb} cmp $_[0]->{sa}; - goto $_[0]->{ code }->[ ++$_[0]->{ pc } ]->{ exec_code }; -} - -sub op_range { - my($self) = @_; - push @{ $self->{ SP }->[ -1 ] }, ($self->{sb} .. $self->{sa}); - goto $self->{ code }->[ ++$self->{ pc } ]->{ exec_code }; -} - -sub op_fetch_symbol { - my($st) = @_; - my $name = $st->op_arg; - $st->{sa} = $st->fetch_symbol($name); - - goto $st->{ code }->[ ++$st->{ pc } ]->{ exec_code }; -} - -sub tx_macro_enter { - my($st, $macro, $retaddr) = @_; - my $name = $macro->name; - my $addr = $macro->addr; - my $nargs = $macro->nargs; - my $outer = $macro->outer; - my $args = pop @{ $st->{SP} }; - - print STDERR " " x $st->current_frame, "tx_macro_enter($name) to $retaddr\n" if DUMP_PP; - - if(@{$args} != $nargs) { - $st->error(undef, "Wrong number of arguments for %s (%d %s %d)", - $name, scalar(@{$args}), scalar(@{$args}) > $nargs ? '>' : '<', $nargs); - $st->{ sa } = undef; - $st->{ pc }++; - return; - } - - my $cframe = $st->push_frame($name, $retaddr); - - $cframe->[ TXframe_OUTPUT ] = $st->{ output }; - - $st->{ output } = ''; - - my $i = 0; - if($outer > 0) { - # copies lexical variables from the old frame to the new one - my $oframe = $st->frame->[ $st->current_frame - 1 ]; - for(; $i < $outer; $i++) { - my $real_ix = $i + TXframe_START_LVAR; - $cframe->[$real_ix] = $oframe->[$real_ix]; - } - } - - for my $val (@{$args}) { - tx_access_lvar( $st, $i++, $val ); - } - - $st->{ pc } = $addr; - if($st->{code}->[$addr]->{opname} ne 'macro_begin') { - Carp::croak("Oops: entering non-macros: ", p($st->{code}->[$addr])); - } - return; -} - -sub op_macro_end { - my($st) = @_; - - my $top = $st->frame->[ $st->current_frame ]; - printf STDERR "%stx_macro_end(%s)]\n", ' ' x $st->current_frame - 1, $top->[ TXframe_NAME ] if DUMP_PP; - - $st->{sa} = mark_raw( $st->{ output } ); - $st->pop_frame(1); - - $st->{ pc } = $top->[ TXframe_RETADDR ]; - goto $st->{ code }->[ $st->{ pc } ]->{ exec_code }; -} - -sub op_funcall { - my($st) = @_; - my $func = $st->{sa}; - if(ref $func eq TXt_MACRO) { - tx_macro_enter($st, $func, $st->{ pc } + 1); - goto $st->{ code }->[ $st->{ pc } ]->{ exec_code }; - } - else { - $st->{sa} = tx_funcall( $st, $func ); - goto $st->{ code }->[ ++$st->{ pc } ]->{ exec_code }; - } -} - -sub op_methodcall_s { - my($st) = @_; - $st->{sa} = Text::Xslate::PP::Method::tx_methodcall( - $st, undef, $st->op_arg, @{ pop @{ $st->{SP} } }); - goto $st->{ code }->[ ++$st->{ pc } ]->{ exec_code }; -} - -sub op_make_array { - my $args = pop @{ $_[0]->{SP} }; - $_[0]->{sa} = $args; - goto $_[0]->{ code }->[ ++$_[0]->{ pc } ]->{ exec_code }; -} - -sub op_make_hash { - my $args = pop @{ $_[0]->{SP} }; - $_[0]->{sa} = { @{$args} }; - goto $_[0]->{ code }->[ ++$_[0]->{ pc } ]->{ exec_code }; -} - -sub op_merge_hash { - $_[0]->{sa} = Text::Xslate::Util::merge_hash($_[0]->{sa}, $_[0]->{sb}); - goto $_[0]->{ code }->[ ++$_[0]->{ pc } ]->{ exec_code }; -} - - -sub op_enter { - push @{$_[0]->{save_local_stack} ||= []}, delete $_[0]->{local_stack}; - - goto $_[0]->{ code }->[ ++$_[0]->{ pc } ]->{ exec_code }; -} - -sub op_leave { - $_[0]->{local_stack} = pop @{$_[0]->{save_local_stack}}; - - goto $_[0]->{ code }->[ ++$_[0]->{ pc } ]->{ exec_code }; -} - -sub op_goto { - $_[0]->{ pc } = $_[0]->op_arg; - goto $_[0]->{ code }->[ $_[0]->{ pc } ]->{ exec_code }; -} - -sub op_end { - my($st) = @_; - printf STDERR "op_end at %d\n", $st->{pc} if DUMP_PP; - $st->{ pc } = $st->code_len; - - if($st->current_frame != 0) { - #Carp::croak("Oops: broken stack frame:" . p($st->frame)); - } - return; -} - -sub op_depend; *op_depend = \&op_noop; -sub op_macro_begin; *op_macro_begin = \&op_noop; -sub op_macro_nargs; *op_macro_nargs = \&op_noop; -sub op_macro_outer; *op_macro_outer = \&op_noop; -sub op_set_opinfo; *op_set_opinfo = \&op_noop; -sub op_super; *op_super = \&op_noop; - -# -# INTERNAL COMMON FUNCTIONS -# - -sub tx_access_lvar { - return $_[0]->pad->[ $_[1] + TXframe_START_LVAR ] if @_ == 2; - $_[0]->pad->[ $_[1] + TXframe_START_LVAR ] = $_[2]; -} - - -sub tx_funcall { - my ( $st, $proc ) = @_; - my ( @args ) = @{ pop @{ $st->{ SP } } }; - my $ret; - - if(!defined $proc) { - my $c = $st->{code}->[ $st->{pc} - 1 ]; - $st->error( undef, "Undefined function%s is called", - $c->{ opname } eq 'fetch_s' ? " $c->{arg}()" : "" - ); - } - else { - $ret = eval { $proc->( @args ) }; - $st->error( undef, "%s", $@) if $@; - } - - return $ret; -} - -sub proccall { - my($st, $proc) = @_; - if(ref $proc eq TXt_MACRO) { - local $st->{pc} = $st->{pc}; - tx_macro_enter($st, $proc, $st->{code_len}); - $st->{code}->[ $st->{pc} ]->{ exec_code }->( $st ); - return $st->{sa}; - } - else { - return tx_funcall($st, $proc); - } -} - -no Mouse; -__PACKAGE__->meta->make_immutable(); -__END__ - -=head1 NAME - -Text::Xslate::PP::Opcode - Text::Xslate opcode implementation in pure Perl - -=head1 DESCRIPTION - -This module is a pure Perl implementation of the Xslate opcodes. - -The is enabled with C<< $ENV{ENV}='pp=opcode' >>. - -=head1 SEE ALSO - -L - -L - -=head1 AUTHOR - -Makamaka Hannyaharamitu Emakamaka at cpan.orgE - -Text::Xslate was written by Fuji, Goro (gfx). - -=head1 LICENSE AND COPYRIGHT - -Copyright (c) 2010 by Makamaka Hannyaharamitu (makamaka). - -This library is free software; you can redistribute it and/or modify -it under the same terms as Perl itself. - -=cut diff --git a/lib/Text/Xslate/PP/State.pm b/lib/Text/Xslate/PP/State.pm deleted file mode 100644 index 47d42a86..00000000 --- a/lib/Text/Xslate/PP/State.pm +++ /dev/null @@ -1,270 +0,0 @@ -package Text::Xslate::PP::State; # implement tx_state_t -use Mouse; - -use Text::Xslate::Util qw(neat p); -use Text::Xslate::PP; -use Text::Xslate::PP::Const qw( - TXframe_NAME TXframe_RETADDR TXframe_OUTPUT - TX_VERBOSE_DEFAULT); - -if(!Text::Xslate::PP::PP_ERROR_VERBOSE()) { - our @CARP_NOT = qw( - Text::Xslate::PP::Opcode - Text::Xslate::PP::Booter - Text::Xslate::PP::Method - ); -} - -has vars => ( - is => 'rw', -); - -has tmpl => ( - is => 'rw', -); - -has engine => ( - is => 'rw', - weak_ref => 1, -); - -has frame => ( - is => 'rw', -); - -has current_frame => ( - is => 'rw', -); - -# opinfo is integrated into code -#has info => ( -# is => 'rw', -#); - -has code => ( - is => 'rw', -); - -has code_len => ( - is => 'rw', -); - -has symbol => ( - is => 'rw', -); - -has local_stack => ( - is => 'rw', -); - -has encoding => ( - is => 'ro', - init_arg => undef, - lazy => 1, - default => sub { - require Encode; - return Encode::find_encoding('UTF-8'); - }, -); - -sub fetch { - # my ( $st, $var, $key, $frame, $line ) = @_; - my $ret; - - if ( Scalar::Util::blessed($_[1]) ) { - my $key = $_[2]; - $ret = eval { $_[1]->$key() }; - $_[0]->error( [ $_[3], $_[4] ], "%s", $@ ) if $@; - } - elsif ( ref $_[1] eq 'HASH' ) { - if ( defined $_[2] ) { - $ret = $_[1]->{ $_[2] }; - } - else { - $_[0]->warn( [ $_[3], $_[4] ], "Use of nil as a field key" ); - } - } - elsif ( ref $_[1] eq 'ARRAY' ) { - if ( Scalar::Util::looks_like_number($_[2]) ) { - $ret = $_[1]->[ $_[2] ]; - } - else { - $_[0]->warn( [ $_[3], $_[4] ], "Use of %s as an array index", neat( $_[2] ) ); - } - } - elsif ( $_[1] ) { - $_[0]->error( [ $_[3], $_[4] ], "Cannot access %s (%s is not a container)", neat($_[2]), neat($_[1]) ); - } - else { - $_[0]->warn( [ $_[3], $_[4] ], "Use of nil to access %s", neat( $_[2] ) ); - } - - return $ret; -} - -sub fetch_symbol { - my ( $st, $name, $context ) = @_; - - my $symbol_table = $st->symbol; - if ( !exists $symbol_table->{ $name } ) { - if(defined $context) { - my($frame, $line) = @{$context}; - if ( defined $line ) { - $st->{ pc } = $line; - $st->frame->[ $st->current_frame ]->[ TXframe_NAME ] = $frame; - } - } - Carp::croak( sprintf( "Undefined symbol %s", $name ) ); - } - - return $symbol_table->{ $name }; -} - -sub localize { - my($st, $key, $newval) = @_; - my $vars = $st->vars; - my $preeminent = exists $vars->{$key}; - my $oldval = delete $vars->{$key}; - - my $cleanup = $preeminent - ? sub { $vars->{$key} = $oldval; return } - : sub { delete $vars->{$key}; return }; - - push @{ $st->{local_stack} ||= [] }, - bless($cleanup, 'Text::Xslate::PP::Guard'); - - $vars->{$key} = $newval; - return; -} - -sub push_frame { - my ( $st, $name, $retaddr ) = @_; - - if ( $st->current_frame > 100 ) { - Carp::croak("Macro call is too deep (> 100)"); - } - - my $new = $st->frame->[ $st->current_frame( $st->current_frame + 1 ) ] - ||= []; - $new->[ TXframe_NAME ] = $name; - $new->[ TXframe_RETADDR ] = $retaddr; - return $new; -} - -sub pop_frame { - my( $st, $replace_output ) = @_; - $st->current_frame( $st->current_frame - 1 ); - if($replace_output) { - my $top = $st->frame->[ $st->current_frame + 1]; - ($st->{output}, $top->[ TXframe_OUTPUT ]) - = ($top->[ TXframe_OUTPUT ], $st->{output}); - } - - return; -} - -sub pad { - return $_[0]->{frame}->[ $_[0]->{current_frame} ]; -} - -sub op_arg { - $_[0]->{ code }->[ $_[0]->{ pc } ]->{ arg }; -} - -sub print { - my($st, $sv, $frame_and_line) = @_; - if ( ref( $sv ) eq Text::Xslate::PP::TXt_RAW ) { - if(defined ${$sv}) { - $st->{output} .= - (utf8::is_utf8($st->{output}) && !utf8::is_utf8(${$sv})) - ? $st->encoding->decode(${$sv}) - : ${$sv}; - } - else { - $st->warn($frame_and_line, "Use of nil to print" ); - } - } - elsif ( defined $sv ) { - $sv =~ s/($Text::Xslate::PP::html_metachars)/$Text::Xslate::PP::html_escape{$1}/xmsgeo; - $st->{output} .= - (utf8::is_utf8($st->{output}) && !utf8::is_utf8($sv)) - ? $st->encoding->decode($sv) - : $sv; - } - else { - $st->warn( $frame_and_line, "Use of nil to print" ); - } - return; -} - -sub _doerror { - my ( $st, $context, $fmt, @args ) = @_; - if(defined $context) { # hack to share it with PP::Booster and PP::Opcode - my($frame, $line) = @{$context}; - if ( defined $line ) { - $st->{ pc } = $line; - $st->frame->[ $st->current_frame ]->[ TXframe_NAME ] = $frame; - } - } - Carp::carp( sprintf( $fmt, @args ) ); - return; -} - -sub warn :method { - my $st = shift; - if( $st->engine->{verbose} > TX_VERBOSE_DEFAULT ) { - $st->_doerror(@_); - } - return; -} - - -sub error :method { - my $st = shift; - if( $st->engine->{verbose} >= TX_VERBOSE_DEFAULT ) { - $st->_doerror(@_); - } - return; -} - -sub bad_arg { - my $st = shift; - unshift @_, undef if @_ == 1; # hack to share it with PP::Booster and PP::Opcode - my($context, $name) = @_; - return $st->error($context, "Wrong number of arguments for %s", $name); -} - -no Mouse; -__PACKAGE__->meta->make_immutable; -1; -__END__ - - -=head1 NAME - -Text::Xslate::PP::State - Text::Xslate pure-Perl virtual machine state - -=head1 DESCRIPTION - -This module is used by Text::Xslate::PP internally. - -=head1 SEE ALSO - -L - -L - -=head1 AUTHOR - -Makamaka Hannyaharamitu Emakamaka at cpan.orgE - -Text::Xslate was written by Fuji, Goro (gfx). - -=head1 LICENSE AND COPYRIGHT - -Copyright (c) 2010 by Makamaka Hannyaharamitu (makamaka). - -This library is free software; you can redistribute it and/or modify -it under the same terms as Perl itself. - -=cut diff --git a/lib/Text/Xslate/PP/Type/Macro.pm b/lib/Text/Xslate/PP/Type/Macro.pm deleted file mode 100644 index 5c42d7b5..00000000 --- a/lib/Text/Xslate/PP/Type/Macro.pm +++ /dev/null @@ -1,74 +0,0 @@ -package Text::Xslate::PP::Type::Macro; -use Mouse; -use warnings FATAL => 'recursion'; - -use overload - '&{}' => \&as_code_ref, - fallback => 1, -; - -has name => ( - is => 'ro', - isa => 'Str', - - required => 1, -); - -has addr => ( - is => 'ro', - isa => 'Int', - - required => 1, -); - -has nargs => ( - is => 'rw', - isa => 'Int', - - default => 0, -); - -has outer => ( - is => 'rw', - isa => 'Int', - - default => 0, -); - -has state => ( - is => 'rw', - isa => 'Object', - - required => 1, - weak_ref => 1, -); - -sub as_code_ref { - my($self) = @_; - - return sub { - my $st = $self->state; - push @{$st->{SP}}, [@_]; - $st->proccall($self); - }; -} - -no Mouse; -__PACKAGE__->meta->make_immutable; -__END__ - -=head1 NAME - -Text::Xslate::PP::Type::Macro - Text::Xslate macro object in pure Perl - -=head1 DESCRIPTION - -This module is used by Text::Xslate::PP internally. - -=head1 SEE ALSO - -L - -L - -=cut diff --git a/lib/Text/Xslate/PP/Type/Pair.pm b/lib/Text/Xslate/PP/Type/Pair.pm deleted file mode 100644 index 04856737..00000000 --- a/lib/Text/Xslate/PP/Type/Pair.pm +++ /dev/null @@ -1,27 +0,0 @@ -package Text::Xslate::PP::Type::Pair; -use Mouse; - -has [qw(key value)] => ( - is => 'rw', - required => 1, -); - -no Mouse; -__PACKAGE__->meta->make_immutable(); -__END__ - -=head1 NAME - -Text::Xslate::PP::Type::Pair - Text::Xslate builtin pair type in pure Perl - -=head1 DESCRIPTION - -This module is used by Text::Xslate::PP internally. - -=head1 SEE ALSO - -L - -L - -=cut diff --git a/lib/Text/Xslate/PP/Type/Raw.pm b/lib/Text/Xslate/PP/Type/Raw.pm deleted file mode 100644 index a1b8aa65..00000000 --- a/lib/Text/Xslate/PP/Type/Raw.pm +++ /dev/null @@ -1,73 +0,0 @@ -package Text::Xslate::PP::Type::Raw; - -use strict; -use warnings; - -use Carp (); -use Text::Xslate::PP::Const qw(TXt_RAW); - -use overload ( - '""' => 'as_string', - fallback => 1, -); - -my $the_class = TXt_RAW; - -sub new { - my ( $class, $str ) = @_; - - Carp::croak("Usage: $the_class->new(str)") if ( @_ != 2 ); - - if ( ref $class ) { - Carp::croak("You cannot call $the_class->new() as an instance method"); - } - elsif ( $class ne $the_class ) { - Carp::croak("You cannot extend $the_class ($class)"); - } - $str = ${$str} if ref($str) eq $the_class; # unmark - return bless \$str, $the_class; -} - -sub as_string { - unless ( ref $_[0] ) { - Carp::croak("You cannot call $the_class->as_string() as a class method"); - } - return ${ $_[0] }; -} - -sub defined { 1 } - -package - Text::Xslate::Type::Raw; -our @ISA = qw(Text::Xslate::PP::Type::Raw); -1; -__END__ - -=head1 NAME - -Text::Xslate::PP::Type::Raw - Text::Xslate raw string type in pure Perl - -=head1 DESCRIPTION - -This module is used by Text::Xslate::PP internally. - -=head1 SEE ALSO - -L - -L - -=head1 AUTHOR - -Makamaka Hannyaharamitu Emakamaka at cpan.orgE - -Text::Xslate was written by Fuji, Goro (gfx). - -=head1 LICENSE AND COPYRIGHT - -Copyright (c) 2010 by Makamaka Hannyaharamitu (makamaka). - -This library is free software; you can redistribute it and/or modify -it under the same terms as Perl itself. - -=cut diff --git a/t/000_load.t b/t/000_load.t index d80bab20..47a6bdfa 100644 --- a/t/000_load.t +++ b/t/000_load.t @@ -12,11 +12,5 @@ BEGIN { use_ok 'Text::Xslate::Syntax::TTerse' } BEGIN { use_ok 'Text::Xslate::Type::Raw' } diag "Testing Text::Xslate/$Text::Xslate::VERSION"; -if(Text::Xslate->isa('Text::Xslate::PP')) { - diag "Backend: PP"; -} -else { - diag "Backend: XS"; -} diag '$ENV{XSLATE}=', $ENV{XSLATE} || ''; diff --git a/t/010_internals/300_explicit_pp.t b/t/010_internals/300_explicit_pp.t deleted file mode 100644 index 5d318cb6..00000000 --- a/t/010_internals/300_explicit_pp.t +++ /dev/null @@ -1,25 +0,0 @@ -#!perl -# Explicit use of PP version - -use strict; -use Test::More tests => 5; - -BEGIN { - use_ok 'Text::Xslate::PP'; - use_ok 'Text::Xslate', qw(escaped_string); -} - - -use B; - -ok( !B::svref_2object(Text::Xslate->can('render'))->XSUB, 'render() is not an xsub' ); - -eval { - my $tx = Text::Xslate->new(); - - is $tx->render_string('Hello, <: $lang :> world!', { lang => escaped_string('') }), - "Hello, world!"; -}; -is $@, ''; - -done_testing; From cd75a6acde9b5a219b3a6548ecd6ce5d8502adb4 Mon Sep 17 00:00:00 2001 From: tokuhirom Date: Sat, 23 Nov 2013 06:01:02 +0900 Subject: [PATCH 26/39] minil migrate --- .gitignore | 9 + .shipit | 7 - Build.PL | 72 +++++ Changes | 2 + LICENSE | 378 ++++++++++++++++++++++++++ MANIFEST | 358 ------------------------ MANIFEST.SKIP | 77 ------ META.json | 105 +++++++ Makefile.PL | 89 ------ README.md | 635 ++++++++++++++++++++++++++++++++++++++++--- builder/MyBuilder.pm | 43 +++ cpanfile | 19 ++ minil.toml | 2 + xt/01_podspell.t | 112 -------- 14 files changed, 1232 insertions(+), 676 deletions(-) delete mode 100644 .shipit create mode 100644 Build.PL create mode 100644 LICENSE delete mode 100644 MANIFEST delete mode 100644 MANIFEST.SKIP create mode 100644 META.json delete mode 100644 Makefile.PL create mode 100644 builder/MyBuilder.pm create mode 100644 cpanfile create mode 100644 minil.toml delete mode 100644 xt/01_podspell.t diff --git a/.gitignore b/.gitignore index b2a0693b..7ce6940d 100644 --- a/.gitignore +++ b/.gitignore @@ -31,3 +31,12 @@ cover_db* *.cache xslate_ops.h lib/Text/Xslate/PP/Const.pm +/Text-Xslate-* +/.build +/_build_params +/Build +/Build.bat +!Build/ +!META.json +!LICENSE +/lib/Text/Xslate.xs diff --git a/.shipit b/.shipit deleted file mode 100644 index 989226e3..00000000 --- a/.shipit +++ /dev/null @@ -1,7 +0,0 @@ -# auto-generated shipit config file. -steps = FindVersion, ChangeAllVersions, CheckChangeLog, DistTest, Commit, Tag, MakeDist, UploadCPAN - -git.tagpattern = %v -git.push_to = origin - -CheckChangeLog.files = Changes diff --git a/Build.PL b/Build.PL new file mode 100644 index 00000000..3a3e4662 --- /dev/null +++ b/Build.PL @@ -0,0 +1,72 @@ +# ========================================================================= +# THIS FILE IS AUTOMATICALLY GENERATED BY MINILLA. +# DO NOT EDIT DIRECTLY. +# ========================================================================= + +use 5.008_001; + +use strict; +use warnings; +use utf8; + +use builder::MyBuilder; +use File::Basename; +use File::Spec; +use CPAN::Meta; +use CPAN::Meta::Prereqs; + +my %args = ( + license => 'perl', + dynamic_config => 0, + + configure_requires => { + 'Module::Build' => 0.38, + }, + + name => 'Text-Xslate', + module_name => 'Text::Xslate', + allow_pureperl => 0, + + script_files => [glob('script/*'), glob('bin/*')], + c_source => [qw()], + PL_files => {}, + + test_files => ((-d '.git' || $ENV{RELEASE_TESTING}) && -d 'xt') ? 't/ xt/' : 't/', + recursive_test_files => 1, + + +); +if (-d 'share') { + $args{share_dir} = 'share'; +} + +my $builder = builder::MyBuilder->subclass( + class => 'MyBuilder', + code => q{ + sub ACTION_distmeta { + die "Do not run distmeta. Install Minilla and `minil install` instead.\n"; + } + sub ACTION_installdeps { + die "Do not run installdeps. Run `cpanm --installdeps .` instead.\n"; + } + } +)->new(%args); +$builder->create_build_script(); + +my $mbmeta = CPAN::Meta->load_file('MYMETA.json'); +my $meta = CPAN::Meta->load_file('META.json'); +my $prereqs_hash = CPAN::Meta::Prereqs->new( + $meta->prereqs +)->with_merged_prereqs( + CPAN::Meta::Prereqs->new($mbmeta->prereqs) +)->as_string_hash; +my $mymeta = CPAN::Meta->new( + { + %{$meta->as_struct}, + prereqs => $prereqs_hash + } +); +print "Merging cpanfile prereqs to MYMETA.yml\n"; +$mymeta->save('MYMETA.yml', { version => 1.4 }); +print "Merging cpanfile prereqs to MYMETA.json\n"; +$mymeta->save('MYMETA.json', { version => 2 }); diff --git a/Changes b/Changes index 2c3f0e2e..2fd5a0d5 100644 --- a/Changes +++ b/Changes @@ -1,5 +1,7 @@ Revision history for Perl extension Text::Xslate +{{$NEXT}} + 3.1.0 2013-11-16 16:46:35+0900 [BUG FIXES] - Close #95; $/ affected the parse() method diff --git a/LICENSE b/LICENSE new file mode 100644 index 00000000..3ac18de7 --- /dev/null +++ b/LICENSE @@ -0,0 +1,378 @@ +This software is copyright (c) 2013 by Fuji, Goro (gfx) .. + +This is free software; you can redistribute it and/or modify it under +the same terms as the Perl 5 programming language system itself. + +Terms of the Perl programming language system itself + +a) the GNU General Public License as published by the Free + Software Foundation; either version 1, or (at your option) any + later version, or +b) the "Artistic License" + +--- The GNU General Public License, Version 1, February 1989 --- + +This software is Copyright (c) 2013 by Fuji, Goro (gfx) .. + +This is free software, licensed under: + + The GNU General Public License, Version 1, February 1989 + + GNU GENERAL PUBLIC LICENSE + Version 1, February 1989 + + Copyright (C) 1989 Free Software Foundation, Inc. + 51 Franklin St, Suite 500, Boston, MA 02110-1335 USA + + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The license agreements of most software companies try to keep users +at the mercy of those companies. By contrast, our General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. The +General Public License applies to the Free Software Foundation's +software and to any other program whose authors commit to using it. +You can use it for your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Specifically, the General Public License is designed to make +sure that you have the freedom to give away or sell copies of free +software, that you receive source code or can get it if you want it, +that you can change the software or use pieces of it in new free +programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of a such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. You must make sure that they, too, receive or can get the +source code. And you must tell them their rights. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + The precise terms and conditions for copying, distribution and +modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License Agreement applies to any program or other work which +contains a notice placed by the copyright holder saying it may be +distributed under the terms of this General Public License. The +"Program", below, refers to any such program or work, and a "work based +on the Program" means either the Program or any work containing the +Program or a portion of it, either verbatim or with modifications. Each +licensee is addressed as "you". + + 1. You may copy and distribute verbatim copies of the Program's source +code as you receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice and +disclaimer of warranty; keep intact all the notices that refer to this +General Public License and to the absence of any warranty; and give any +other recipients of the Program a copy of this General Public License +along with the Program. You may charge a fee for the physical act of +transferring a copy. + + 2. You may modify your copy or copies of the Program or any portion of +it, and copy and distribute such modifications under the terms of Paragraph +1 above, provided that you also do the following: + + a) cause the modified files to carry prominent notices stating that + you changed the files and the date of any change; and + + b) cause the whole of any work that you distribute or publish, that + in whole or in part contains the Program or any part thereof, either + with or without modifications, to be licensed at no charge to all + third parties under the terms of this General Public License (except + that you may choose to grant warranty protection to some or all + third parties, at your option). + + c) If the modified program normally reads commands interactively when + run, you must cause it, when started running for such interactive use + in the simplest and most usual way, to print or display an + announcement including an appropriate copyright notice and a notice + that there is no warranty (or else, saying that you provide a + warranty) and that users may redistribute the program under these + conditions, and telling the user how to view a copy of this General + Public License. + + d) You may charge a fee for the physical act of transferring a + copy, and you may at your option offer warranty protection in + exchange for a fee. + +Mere aggregation of another independent work with the Program (or its +derivative) on a volume of a storage or distribution medium does not bring +the other work under the scope of these terms. + + 3. You may copy and distribute the Program (or a portion or derivative of +it, under Paragraph 2) in object code or executable form under the terms of +Paragraphs 1 and 2 above provided that you also do one of the following: + + a) accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of + Paragraphs 1 and 2 above; or, + + b) accompany it with a written offer, valid for at least three + years, to give any third party free (except for a nominal charge + for the cost of distribution) a complete machine-readable copy of the + corresponding source code, to be distributed under the terms of + Paragraphs 1 and 2 above; or, + + c) accompany it with the information you received as to where the + corresponding source code may be obtained. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form alone.) + +Source code for a work means the preferred form of the work for making +modifications to it. For an executable file, complete source code means +all the source code for all modules it contains; but, as a special +exception, it need not include source code for modules which are standard +libraries that accompany the operating system on which the executable +file runs, or for standard header files or definitions files that +accompany that operating system. + + 4. You may not copy, modify, sublicense, distribute or transfer the +Program except as expressly provided under this General Public License. +Any attempt otherwise to copy, modify, sublicense, distribute or transfer +the Program is void, and will automatically terminate your rights to use +the Program under this License. However, parties who have received +copies, or rights to use copies, from you under this General Public +License will not have their licenses terminated so long as such parties +remain in full compliance. + + 5. By copying, distributing or modifying the Program (or any work based +on the Program) you indicate your acceptance of this license to do so, +and all its terms and conditions. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the original +licensor to copy, distribute or modify the Program subject to these +terms and conditions. You may not impose any further restrictions on the +recipients' exercise of the rights granted herein. + + 7. The Free Software Foundation may publish revised and/or new versions +of the General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + +Each version is given a distinguishing version number. If the Program +specifies a version number of the license which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +the license, you may choose any version ever published by the Free Software +Foundation. + + 8. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 9. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY +FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN +OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES +PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED +OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS +TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE +PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, +REPAIR OR CORRECTION. + + 10. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING +OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED +TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY +YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER +PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. + + END OF TERMS AND CONDITIONS + + Appendix: How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to humanity, the best way to achieve this is to make it +free software which everyone can redistribute and change under these +terms. + + To do so, attach the following notices to the program. It is safest to +attach them to the start of each source file to most effectively convey +the exclusion of warranty; and each file should have at least the +"copyright" line and a pointer to where the full notice is found. + + + Copyright (C) 19yy + + This program 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 1, or (at your option) + any later version. + + This program 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 this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301 USA + + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this +when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) 19xx name of author + Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the +appropriate parts of the General Public License. Of course, the +commands you use may be called something other than `show w' and `show +c'; they could even be mouse-clicks or menu items--whatever suits your +program. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. Here a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the + program `Gnomovision' (a program to direct compilers to make passes + at assemblers) written by James Hacker. + + , 1 April 1989 + Ty Coon, President of Vice + +That's all there is to it! + + +--- The Artistic License 1.0 --- + +This software is Copyright (c) 2013 by Fuji, Goro (gfx) .. + +This is free software, licensed under: + + The Artistic License 1.0 + +The Artistic License + +Preamble + +The intent of this document is to state the conditions under which a Package +may be copied, such that the Copyright Holder maintains some semblance of +artistic control over the development of the package, while giving the users of +the package the right to use and distribute the Package in a more-or-less +customary fashion, plus the right to make reasonable modifications. + +Definitions: + + - "Package" refers to the collection of files distributed by the Copyright + Holder, and derivatives of that collection of files created through + textual modification. + - "Standard Version" refers to such a Package if it has not been modified, + or has been modified in accordance with the wishes of the Copyright + Holder. + - "Copyright Holder" is whoever is named in the copyright or copyrights for + the package. + - "You" is you, if you're thinking about copying or distributing this Package. + - "Reasonable copying fee" is whatever you can justify on the basis of media + cost, duplication charges, time of people involved, and so on. (You will + not be required to justify it to the Copyright Holder, but only to the + computing community at large as a market that must bear the fee.) + - "Freely Available" means that no fee is charged for the item itself, though + there may be fees involved in handling the item. It also means that + recipients of the item may redistribute it under the same conditions they + received it. + +1. You may make and give away verbatim copies of the source form of the +Standard Version of this Package without restriction, provided that you +duplicate all of the original copyright notices and associated disclaimers. + +2. You may apply bug fixes, portability fixes and other modifications derived +from the Public Domain or from the Copyright Holder. A Package modified in such +a way shall still be considered the Standard Version. + +3. You may otherwise modify your copy of this Package in any way, provided that +you insert a prominent notice in each changed file stating how and when you +changed that file, and provided that you do at least ONE of the following: + + a) place your modifications in the Public Domain or otherwise make them + Freely Available, such as by posting said modifications to Usenet or an + equivalent medium, or placing the modifications on a major archive site + such as ftp.uu.net, or by allowing the Copyright Holder to include your + modifications in the Standard Version of the Package. + + b) use the modified Package only within your corporation or organization. + + c) rename any non-standard executables so the names do not conflict with + standard executables, which must also be provided, and provide a separate + manual page for each non-standard executable that clearly documents how it + differs from the Standard Version. + + d) make other distribution arrangements with the Copyright Holder. + +4. You may distribute the programs of this Package in object code or executable +form, provided that you do at least ONE of the following: + + a) distribute a Standard Version of the executables and library files, + together with instructions (in the manual page or equivalent) on where to + get the Standard Version. + + b) accompany the distribution with the machine-readable source of the Package + with your modifications. + + c) accompany any non-standard executables with their corresponding Standard + Version executables, giving the non-standard executables non-standard + names, and clearly documenting the differences in manual pages (or + equivalent), together with instructions on where to get the Standard + Version. + + d) make other distribution arrangements with the Copyright Holder. + +5. You may charge a reasonable copying fee for any distribution of this +Package. You may charge any fee you choose for support of this Package. You +may not charge a fee for this Package itself. However, you may distribute this +Package in aggregate with other (possibly commercial) programs as part of a +larger (possibly commercial) software distribution provided that you do not +advertise this Package as a product of your own. + +6. The scripts and library files supplied as input to or produced as output +from the programs of this Package do not automatically fall under the copyright +of this Package, but belong to whomever generated them, and may be sold +commercially, and may be aggregated with this Package. + +7. C or perl subroutines supplied by you and linked into this Package shall not +be considered part of this Package. + +8. The name of the Copyright Holder may not be used to endorse or promote +products derived from this software without specific prior written permission. + +9. THIS PACKAGE IS PROVIDED "AS IS" AND WITHOUT ANY EXPRESS OR IMPLIED +WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF +MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE. + +The End diff --git a/MANIFEST b/MANIFEST deleted file mode 100644 index 85bfe6ad..00000000 --- a/MANIFEST +++ /dev/null @@ -1,358 +0,0 @@ -benchmark/cascade.pl -benchmark/data_section.pl -benchmark/demo-mt.pl -benchmark/demo-tt.pl -benchmark/expr.pl -benchmark/expr_eq.pl -benchmark/for.pl -benchmark/include.pl -benchmark/interpolate.pl -benchmark/json.pl -benchmark/procs/fib.pl -benchmark/procs/function.pl -benchmark/procs/keys.pl -benchmark/procs/map.pl -benchmark/procs/method.pl -benchmark/procs/sort.pl -benchmark/procs/uri_escape.pl -benchmark/procs/values.pl -benchmark/template/base.mt -benchmark/template/base.tx -benchmark/template/child.mt -benchmark/template/child.tx -benchmark/template/include.cs -benchmark/template/include.ht -benchmark/template/include.mt -benchmark/template/include.tt -benchmark/template/include.tx -benchmark/template/list.cs -benchmark/template/list.ht -benchmark/template/list.mst -benchmark/template/list.mt -benchmark/template/list.tj -benchmark/template/list.tt -benchmark/template/list.tx -benchmark/x-poor-env.pl -benchmark/x-rich-env.pl -Changes -example/amon2.psgi -example/amon2.psgi.gold -example/autolink.pl -example/autolink.pl.gold -example/base.tx -example/cascade.pl -example/cascade.pl.gold -example/cascade.tx -example/chained-scope.pl -example/chained-scope.pl.gold -example/data_section.pl -example/data_section.pl.gold -example/err_handler.pl -example/fillinform.pl -example/fillinform.pl.gold -example/form.psgi -example/form.psgi.gold -example/hello.pl -example/hello.pl.gold -example/hello.tmpl -example/hello.tx -example/htmltemplate.pl -example/htparser.pl -example/i18n-data-localize.pl -example/i18n-data-localize.pl.gold -example/i18n.pl -example/i18n.pl.gold -example/list.pl -example/list.pl.gold -example/list.tx -example/locale/ja.po -example/mojo.psgi -example/mojo.psgi.gold -example/put_js.pl -example/put_js.pl.gold -example/simple.psgi -example/simple.psgi.gold -HACKING -inc/Module/Install.pm -inc/Module/Install/AuthorTests.pm -inc/Module/Install/Base.pm -inc/Module/Install/Can.pm -inc/Module/Install/Makefile.pm -inc/Module/Install/Metadata.pm -inc/Module/Install/Scripts.pm -inc/Module/Install/TestTarget.pm -inc/Module/Install/WriteAll.pm -inc/Module/Install/XSUtil.pm -lib/Text/Xslate.pm -lib/Text/Xslate/Bridge.pm -lib/Text/Xslate/Bridge/Star.pm -lib/Text/Xslate/Compiler.pm -lib/Text/Xslate/HashWithDefault.pm -lib/Text/Xslate/Manual.pod -lib/Text/Xslate/Manual/Builtin.pod -lib/Text/Xslate/Manual/Cookbook.pod -lib/Text/Xslate/Manual/Debugging.pod -lib/Text/Xslate/Manual/FAQ.pod -lib/Text/Xslate/Parser.pm -lib/Text/Xslate/Runner.pm -lib/Text/Xslate/Symbol.pm -lib/Text/Xslate/Syntax/Kolon.pm -lib/Text/Xslate/Syntax/Metakolon.pm -lib/Text/Xslate/Syntax/TTerse.pm -lib/Text/Xslate/Type/Raw.pm -lib/Text/Xslate/Util.pm -Makefile.PL -MANIFEST This list of files -MANIFEST.SKIP -META.yml -README.md -script/xslate -src/Text-Xslate.xs -src/xslate_methods.xs -src/xslate_opcode.inc -t/000_load.t -t/010_internals/001_parse.t -t/010_internals/002_compile.t -t/010_internals/003_assemble.t -t/010_internals/004_magic.t -t/010_internals/005_load_file.t -t/010_internals/006_errhandler.t -t/010_internals/007_compile_errs.t -t/010_internals/008_files.t -t/010_internals/009_goto_address.t -t/010_internals/010_line_number.t -t/010_internals/011_missuse.t -t/010_internals/012_multilines.t -t/010_internals/013_deps_str.t -t/010_internals/014_deps_file.t -t/010_internals/015_deps_file2.t -t/010_internals/016_cached.t -t/010_internals/017_render.t -t/010_internals/018_opinfo.t -t/010_internals/019_runtime_error.t -t/010_internals/020_optimize.t -t/010_internals/021_edge_cases.t -t/010_internals/022_signal.t -t/010_internals/023_cachepath.t -t/010_internals/024_die_in_macros.t -t/010_internals/025_reset_hooks.t -t/010_internals/026_issue16_deep_recursion.t -t/010_internals/027_infinite_warn.t -t/010_internals/028_taint.t -t/010_internals/029_large_file.t -t/010_internals/030_die_in_funcs.t -t/010_internals/031_save_src.t -t/010_internals/032_encoding_error.t -t/010_internals/033_vpath_cache.t -t/010_internals/034_is_code_ref.t -t/010_internals/036_merge_hash.t -t/010_internals/037_find_file.t -t/010_internals/038_suffix.t -t/010_internals/039_taint_issue84.t -t/010_internals/100_threads.t -t/010_internals/200_leaktrace.t -t/020_interface/001_parser_option.t -t/020_interface/002_myparser.t -t/020_interface/003_encoding.t -t/020_interface/004_no_vars.t -t/020_interface/005_util.t -t/020_interface/006_psgi.t -t/020_interface/007_data_section.t -t/020_interface/008_type.t -t/020_interface/009_hash_with_default.t -t/020_interface/010_err_encoding.t -t/020_interface/011_hwd_w_include_vars.t -t/020_interface/012_default_functions.t -t/020_interface/013_slurp_template.t -t/020_interface/014_customize_option.t -t/020_interface/015_render_recursion.t -t/020_interface/016_pre_process_handler.t -t/020_interface/017_validate.t -t/030_kolon/001_interpolate.t -t/030_kolon/002_field.t -t/030_kolon/003_for.t -t/030_kolon/004_if.t -t/030_kolon/005_rel_ops.t -t/030_kolon/006_arith_ops.t -t/030_kolon/007_log_ops.t -t/030_kolon/008_literal.t -t/030_kolon/009_include.t -t/030_kolon/010_escaped_str.t -t/030_kolon/011_macro.t -t/030_kolon/012_cascade.t -t/030_kolon/013_more_field.t -t/030_kolon/014_while.t -t/030_kolon/015_methods.t -t/030_kolon/016_funcs.t -t/030_kolon/017_more_macro.t -t/030_kolon/018_dynamic_filters.t -t/030_kolon/019_explicit_interpolate.t -t/030_kolon/020_chomp.t -t/030_kolon/021_overlay.t -t/030_kolon/022_given.t -t/030_kolon/023_bitwise.t -t/030_kolon/024_localize_vars.t -t/030_kolon/025_objectliteral.t -t/030_kolon/026_constant.t -t/030_kolon/027_lambda.t -t/030_kolon/028_smartmatch.t -t/030_kolon/029_blockfilter.t -t/030_kolon/030_xfuncs.t -t/030_kolon/031_post_if.t -t/030_kolon/032_include_bareword.t -t/030_kolon/033_ov_forloop.t -t/030_kolon/034_print.t -t/030_kolon/035_include_w_vars.t -t/030_kolon/036_foreach_else.t -t/030_kolon/037_loop_ctl.t -t/030_kolon/038_custom_esc.t -t/030_kolon/039_root_vars.t -t/030_kolon/040_external_macro.t -t/040_tterse/001_parse.t -t/040_tterse/002_interpolate.t -t/040_tterse/003_field.t -t/040_tterse/004_for.t -t/040_tterse/005_if.t -t/040_tterse/006_include.t -t/040_tterse/007_funcs.t -t/040_tterse/008_methods.t -t/040_tterse/009_comments.t -t/040_tterse/010_expr.t -t/040_tterse/011_objectliterals.t -t/040_tterse/012_macro.t -t/040_tterse/013_wrapper.t -t/040_tterse/014_set.t -t/040_tterse/015_while.t -t/040_tterse/016_call.t -t/040_tterse/017_filter.t -t/040_tterse/018_process.t -t/040_tterse/019_switch.t -t/040_tterse/020_chomp.t -t/040_tterse/021_fake_use.t -t/040_tterse/022_post_if.t -t/040_tterse/023_loop_ctl.t -t/040_tterse/024_for_else.t -t/040_tterse/025_macro.t -t/040_tterse/100_not_supported.t -t/050_builtins/001_builtin_funcs.t -t/050_builtins/002_autobox.t -t/050_builtins/003_autobox_w_cb.t -t/050_builtins/004_copied.t -t/100_plugin/001_import_from.t -t/100_plugin/002_import.t -t/100_plugin/003_bridge.t -t/100_plugin/004_star.t -t/100_plugin/005_html_builder_module.t -t/100_plugin/100_error.t -t/200_app/001_hello.t -t/200_app/002_tree.t -t/200_app/003_runner.t -t/200_app/004_encoding.t -t/200_app/simple/dont_touch.tx -t/200_app/simple/goodbye.tx -t/200_app/simple/hello.tx -t/300_examples/001_basic.t -t/300_examples/002_cascade.t -t/300_examples/003_metakolon.t -t/300_examples/004_tterse.t -t/900_bugs/001_super.t -t/900_bugs/002_addfunc.t -t/900_bugs/003_frame_access.t -t/900_bugs/004_errorhandling.t -t/900_bugs/005_rec_include.t -t/900_bugs/006_complex_lex.t -t/900_bugs/007_uuv.t -t/900_bugs/008_include_var.t -t/900_bugs/009_quote_in_comments.t -t/900_bugs/010_widechar_for_md5.t -t/900_bugs/011_reserved_words.t -t/900_bugs/012_ltgt_in_comments.t -t/900_bugs/013_comments_lineno.t -t/900_bugs/014_too_large_int.t -t/900_bugs/015_my_scope.t -t/900_bugs/016_x_in_tterse.t -t/900_bugs/017_unallocated.t -t/900_bugs/018_lvar_in_macro.pl -t/900_bugs/019_nested_mm.t -t/900_bugs/020_switch_foo.t -t/900_bugs/021_cached_enc.t -t/900_bugs/022_empty_if_block.t -t/900_bugs/023_deploy_problem.t -t/900_bugs/024_use_cache.t -t/900_bugs/025_clobber-macro-args.t -t/900_bugs/026_issue61.t -t/900_bugs/027_issue65.t -t/900_bugs/028_issue68.t -t/900_bugs/029_fork_and_cache.t -t/900_bugs/030_issue71.t -t/900_bugs/031_yappo.t -t/900_bugs/032_issue79.t -t/900_bugs/033_ex_safe_render.t -t/900_bugs/034_hash_key_utf8.t -t/900_bugs/035_issue81_tiedhash.t -t/900_bugs/036_vpath_utf8.t -t/900_bugs/037_text_str_key.t -t/900_bugs/038_conbine_flaged_utf8_and_other.t -t/900_bugs/039_issue95.t -t/900_bugs/issue79/tmpl/contentA.tt -t/900_bugs/issue79/tmpl/contentB.tt -t/900_bugs/issue79/tmpl/wrapperA.tt -t/900_bugs/issue79/tmpl/wrapperB.tt -t/900_bugs/issue79/xslate.pl -t/lib/MyBridge2.pm -t/lib/Text/Xslate/Syntax/Foo.pm -t/lib/TTSimple.pm -t/lib/Util.pm -t/lib/UtilNoleak.pm -t/template/common.tx -t/template/config.tt -t/template/eg/base.tx -t/template/eg/child.tx -t/template/error/bad_include.tx -t/template/error/bad_method.tx -t/template/error/bad_syntax.tx -t/template/error/bad_tags.tx -t/template/footer1.tt -t/template/footer2.tt -t/template/for.tx -t/template/func.tx -t/template/header1.tt -t/template/header2.tt -t/template/hello.tt -t/template/hello.tx -t/template/hello.tx.mod -t/template/hello_sjis.tx -t/template/hello_utf8.tx -t/template/include.tt -t/template/include.tx -t/template/include2.tt -t/template/include2.tx -t/template/macro.tt -t/template/myapp/bad_redefine.tx -t/template/myapp/base.tx -t/template/myapp/base.tx.mod -t/template/myapp/cbar.tx -t/template/myapp/cfoo.tx -t/template/myapp/derived.tx -t/template/oi/bad_base.tx -t/template/oi/bad_component.tx -t/template/other/hello.tx -t/template/taint.tx -t/template/wrapper.tt -t/template/wrapper_div.tt -tool/disasm.pl -tool/opcode.PL -tool/opcode_for_pp.PL -tool/uri_unsafe.PL -uri_unsafe.h -xshelper.h -xslate.h -xslate_ops.h -xt/01_podspell.t -xt/02_pod.t -xt/03_pod-coverage.t -xt/04_synopsis.t -xt/05_vars.t -xt/100_eg_pl.t -xt/101_eg_psgi.t -xt/200_depended.t diff --git a/MANIFEST.SKIP b/MANIFEST.SKIP deleted file mode 100644 index a2557202..00000000 --- a/MANIFEST.SKIP +++ /dev/null @@ -1,77 +0,0 @@ - -#!start included /usr/local/lib/perl5/5.10.0/ExtUtils/MANIFEST.SKIP -# Avoid version control files. -\bRCS\b -\bCVS\b -\bSCCS\b -,v$ -\B\.svn\b -\B\.git\b -\B\.gitignore\b -\b_darcs\b - -# Avoid Makemaker generated and utility files. -\bMANIFEST\.bak -\bMakefile$ -\bblib/ -\bMakeMaker-\d -\bpm_to_blib\.ts$ -\bpm_to_blib$ -\bblibdirs\.ts$ # 6.18 through 6.25 generated this - -# Avoid Module::Build generated and utility files. -\bBuild$ -\b_build/ - -# Avoid temp and backup files. -~$ -\.old$ -\#$ -\b\.# -\.bak$ -\.swp$ - -# Avoid Devel::Cover files. -\bcover_db\b -#!end included /usr/local/lib/perl5/5.10.0/ExtUtils/MANIFEST.SKIP - - -# skip dot files -^\. - -# skip author's files -\bauthor\b - -# skip object files -Xslate\.c$ -\.o(?:bj)?$ -\.bs$ -\.def$ - -\.out$ - -# skip devel-cover stuff -\.gcda$ -\.gcno$ -\.gcov$ -cover_db/ - -# skip nytprof stuff -nytprof/ -\.out$ - -Text-Xslate- -ppport\.h$ - -\.txc$ -\.ttc$ -\.cache$ - -\.c$ - -\.stackdump$ -\.testenv\b - -t/600_app/out - -MYMETA\.(?:json|yml)$ diff --git a/META.json b/META.json new file mode 100644 index 00000000..8d1f1f20 --- /dev/null +++ b/META.json @@ -0,0 +1,105 @@ +{ + "abstract" : "Scalable template engine for Perl5", + "author" : [ + "Fuji, Goro (gfx) ." + ], + "dynamic_config" : 0, + "generated_by" : "Minilla/v0.8.4, CPAN::Meta::Converter version 2.132830", + "license" : [ + "perl_5" + ], + "meta-spec" : { + "url" : "http://search.cpan.org/perldoc?CPAN::Meta::Spec", + "version" : "2" + }, + "name" : "Text-Xslate", + "no_index" : { + "directory" : [ + "t", + "xt", + "inc", + "share", + "eg", + "examples", + "author", + "builder" + ] + }, + "prereqs" : { + "configure" : { + "requires" : { + "CPAN::Meta" : "0", + "CPAN::Meta::Prereqs" : "0", + "Devel::PPPort" : "3.19", + "ExtUtils::MakeMaker" : "6.59", + "ExtUtils::ParseXS" : "2.21", + "File::Copy::Recursive" : "0", + "Module::Build" : "0.38", + "Module::Build::XSUtil" : "0" + } + }, + "develop" : { + "requires" : { + "Test::CPAN::Meta" : "0", + "Test::MinimumVersion" : "0.10108", + "Test::Pod" : "1.41", + "Test::Spellunker" : "v0.2.7" + } + }, + "runtime" : { + "requires" : { + "Data::MessagePack" : "0.38", + "Mouse" : "0.61", + "Scalar::Util" : "1.14", + "XSLoader" : "0.02", + "parent" : "0.221", + "perl" : "5.008001" + } + }, + "test" : { + "requires" : { + "Test::More" : "0.88", + "Test::Requires" : "0" + } + } + }, + "release_status" : "unstable", + "resources" : { + "bugtracker" : { + "web" : "https://github.com/xslate/p5-Text-Xslate/issues" + }, + "homepage" : "https://github.com/xslate/p5-Text-Xslate", + "repository" : { + "type" : "git", + "url" : "git://github.com/xslate/p5-Text-Xslate.git", + "web" : "https://github.com/xslate/p5-Text-Xslate" + } + }, + "version" : "3.1.0", + "x_contributors" : [ + "Yoshiki Kurihara ", + "makamaka ", + "Mons Anderson ", + "hiratara ", + "c9s ", + "Shigeki Morimoto ", + "Olaf Alders ", + "Christian Walde ", + "punytan ", + "Ueda Satoshi ", + "Ueda Satoshi ", + "André Walker ", + "Masahiro Chiba ", + "Fuji, Goro ", + "Kenichi Ishigaki ", + "Tasuku SUENAGA a.k.a. gunyarakun ", + "Mariano Wahlmann ", + "Jesse Luehrs ", + "Fuji, Goro ", + "David Steinbrunner ", + "Shigeki Morimoto ", + "Fuji, Goro ", + "Daisuke Maki ", + "tokuhirom " + ] +} diff --git a/Makefile.PL b/Makefile.PL deleted file mode 100644 index 08f36021..00000000 --- a/Makefile.PL +++ /dev/null @@ -1,89 +0,0 @@ -# for developers: -# "cpanm < author/requires.cpanm" will install all the modules required -# "make test_with_env" does all the extra tests (with pure Perl, Moose, etc.) -use strict; -use warnings; -BEGIN { - unshift @INC, 'inc'; - # author requires, or bundled modules - my @devmods = qw( - inc::Module::Install 1.06 - Module::Install::XSUtil 0.44 - Module::Install::AuthorTests 0.002 - ); - my @not_available; - while(my($mod, $ver) = splice @devmods, 0, 2) { - eval qq{use $mod $ver (); 1} or push @not_available, $mod; - } - if(@not_available) { - print qq{# The following modules are not available.\n}; - print qq{# `perl $0 | cpanm` will install them:\n}; - print $_, "\n" for @not_available; - exit 1; - } - - $INC{'Module/Install/ExtraTests.pm'} = 1;# considered harmful! -} -use inc::Module::Install; - -all_from 'lib/Text/Xslate.pm'; - -requires 'Mouse' => '0.61'; -requires 'Data::MessagePack' => '0.38'; -requires 'parent' => '0.221'; -requires 'Scalar::Util' => '1.14'; - -test_requires 'Test::More' => 0.88; # done_testing() -test_requires 'Test::Requires'; -test_requires 'File::Copy::Recursive'; -#test_requires 'Test::LeakTrace'; - -install_script 'xslate'; - -tests_recursive; -author_tests 'xt'; - -my $want_xs = want_xs(); -if($want_xs) { - use_xshelper; - cc_warnings; - - cc_src_paths qw(src); -} - -resources - homepage => 'http://xslate.org/', - bugtracker => 'https://github.com/xslate/p5-Text-Xslate/issues', - repository => 'https://github.com/xslate/p5-Text-Xslate', - ProjectHome => 'https://github.com/xslate', - MailingList => 'http://groups.google.com/group/xslate', -; - - -system "$^X tool/opcode.PL src/xslate_opcode.inc >xslate_ops.h"; -system "$^X tool/opcode_for_pp.PL src/xslate_opcode.inc > lib/Text/Xslate/PP/Const.pm"; - -postamble <<'M'; -src/Text-Xslate.xs :: src/xslate_opcode.inc - -# xslate_ops.h is automatically generated by src/Text-Xslate.xs -xslate_ops.h :: src/xslate_opcode.inc tool/opcode.PL - $(PERLRUNINST) tool/opcode.PL src/xslate_opcode.inc >xslate_ops.h - -lib/Text/Xslate/PP/Const.pm :: src/xslate_opcode.inc tool/opcode_for_pp.PL - $(PERLRUNINST) tool/opcode_for_pp.PL src/xslate_opcode.inc > lib/Text/Xslate/PP/Const.pm -M - -my @testall; -if($Module::Install::AUTHOR) { - @testall = (alias => 'test'); -} - -clean_files qw( - Text-Xslate-* - nytprof *.out - cover_db - .xslate_cache -); - -WriteAll(check_nmake => 0); diff --git a/README.md b/README.md index 2be626f7..0e314c44 100644 --- a/README.md +++ b/README.md @@ -1,10 +1,14 @@ -# NAME [![Build Status](https://secure.travis-ci.org/xslate/p5-Text-Xslate.png)](http://travis-ci.org/xslate/p5-Text-Xslate) +# NAME Text::Xslate - Scalable template engine for Perl5 +# VERSION + +This document describes Text::Xslate version 3.1.0. + # SYNOPSIS - use Text::Xslate; + use Text::Xslate qw(mark_raw); my $tx = Text::Xslate->new(); @@ -15,61 +19,626 @@ Text::Xslate - Scalable template engine for Perl5 { title => 'Programming Perl' }, # ... ], + + # mark HTML components as raw not to escape its HTML tags + gadget => mark_raw('
...
'), ); + # for files + print $tx->render('hello.tx', \%vars); + + # for strings (easy but slow) my $template = q{ -

<: $title :>

-
    - : for $books -> $book { -
  • <: $book.title :>
  • - : } # for -
+

<: $title :>

+
    + : for $books -> $book { +
  • <: $book.title :>
  • + : } # for +
}; print $tx->render_string($template, \%vars); -# INSTALLATION +# DESCRIPTION + +__Xslate__ is a template engine, tuned for persistent applications, +safe as an HTML generator, and with rich features. + +There are a lot of template engines in CPAN, for example Template-Toolkit, +Text::MicroTemplate, HTML::Template, and so on, but all of them have +some weak points: a full-featured template engine may be slow, +while a fast template engine may be too simple to use. This is why Xslate is +developed, which is the best template engine for web applications. + +The concept of Xslate is strongly influenced by Text::MicroTemplate +and Template-Toolkit 2, but the central philosophy of Xslate is different +from them. That is, the philosophy is __sandboxing__ that the template logic +should not have no access outside the template beyond your permission. + +Other remarkable features are as follows: + +## Features + +### High performance + +This engine introduces the virtual machine paradigm. Templates are +compiled into intermediate code, and then executed by the virtual machine, +which is highly optimized for rendering templates. Thus, Xslate is +much faster than any other template engines. + +The template roundup project by Sam Graham shows Text::Xslate got +amazingly high scores in _instance\_reuse_ condition +(i.e. for persistent applications). + +- The template roundup project + + [http://illusori.co.uk/projects/Template-Roundup/](http://illusori.co.uk/projects/Template-Roundup/) + +- Perl Template Roundup October 2010 Performance vs Variant Report: instance\_reuse + + [http://illusori.co.uk/projects/Template-Roundup/201010/performance\_vs\_variant\_by\_feature\_for\_instance\_reuse.html](http://illusori.co.uk/projects/Template-Roundup/201010/performance\_vs\_variant\_by\_feature\_for\_instance\_reuse.html) + +There are also benchmarks in `benchmark/` directory in the Xslate distribution. + +### Smart escaping for HTML metacharacters + +Xslate employs the __smart escaping strategy__, where a template engine +escapes all the HTML metacharacters in template expressions unless users +mark values as __raw__. +That is, the output is unlikely to prone to XSS. + +### Template cascading + +Xslate supports the __template cascading__, which allows you to extend +templates with block modifiers. It is like a traditional template inclusion, +but is more powerful. + +This mechanism is also called as template inheritance. + +### Easiness to enhance + +Xslate is ready to enhance. You can add functions and methods to the template +engine and even add a new syntax via extending the parser. + +# INTERFACE + +## Methods + +### __Text::Xslate->new(%options)__ + +Creates a new Xslate template engine with options. You can reuse the instance +for multiple call of `render()`. + +Possible options are: + +- `path => \@path // ['.']` + + Specifies the include paths, which may be directory names or virtual paths, + i.e. HASH references which contain `$file_name => $content` pairs. + + Note that if you use taint mode (`-T`), you have to give absolute paths + to `path` and `cache_dir`. Otherwise you'll get errors because they + depend on the current working directory which might not be secure. + +- `cache => $level // 1` + + Sets the cache level. + + If `$level == 1` (default), Xslate caches compiled templates on the disk, and + checks the freshness of the original templates every time. + + If `$level >= 2`, caches will be created but the freshness + will not be checked. + + `$level == 0` uses no caches, which is provided for testing. + +- `cache_dir => $dir // "$ENV{HOME}/.xslate_cache"` + + Specifies the directory used for caches. If `$ENV{HOME}` doesn't exist, + `File::Spec->tmpdir` will be used. + + You __should__ specify this option for productions to avoid conflicts of + template names. + +- `function => \%functions` + + Specifies a function map which contains name-coderef pairs. + A function `f` may be called as `f($arg)` or `$arg | f` in templates. + + Note that those registered function have to return a __text string__, + not a binary string unless you want to handle bytes in whole templates. + Make sure what you want to use returns whether text string or binary + strings. + + For example, some methods of `Time::Piece` might return a binary string + which is encoded in UTF-8, so you'd like to decode their values. + + # under LANG=ja_JP.UTF-8 on MacOSX (Darwin 11.2.0) + use Time::Piece; + use Encode qw(decode); + + sub ctime { + my $ctime = Time::Piece->new->strftime; # UTF-8 encoded bytes + return decode "UTF-8", $ctime; + } + + my $tx = Text::Xslate->new( + function => { + ctime => \&ctime, + }, + ..., + ); + + Built-in functions are described in [Text::Xslate::Manual::Builtin](http://search.cpan.org/perldoc?Text::Xslate::Manual::Builtin). + +- `module => [$module => ?\@import_args, ...]` + + Imports functions from _$module_, which may be a function-based or bridge module. + Optional _@import\_args_ are passed to `import` as `$module->import(@import_args)`. + + For example: + + # for function-based modules + my $tx = Text::Xslate->new( + module => ['Digest::SHA1' => [qw(sha1_hex)]], + ); + print $tx->render_string( + '<: sha1_hex($x).substr(0, 6) :>', + { x => foo() }, + ); # => 0beec7 + + # for bridge modules + my $tx = Text::Xslate->new( + module => ['Text::Xslate::Bridge::Star'], + ); + print $tx->render_string( + '<: $x.uc() :>', + { x => 'foo' }, + ); # => 'FOO' + + Because you can use function-based modules with the `module` option, and + also can invoke any object methods in templates, Xslate doesn't require + specific namespaces for plugins. + +- `html_builder_module => [$module => ?\@import_args, ...]` + + Imports functions from _$module_, wrapping each function with `html_builder()`. + +- `input_layer => $perliolayers // ':utf8'` + + Specifies PerlIO layers to open template files. + +- `verbose => $level // 1` + + Specifies the verbose level. + + If `$level == 0`, all the possible errors will be ignored. + + If `$level >= 1` (default), trivial errors (e.g. to print nil) will be ignored, + but severe errors (e.g. for a method to throw the error) will be warned. + + If `$level >= 2`, all the possible errors will be warned. + +- `suffix => $ext // '.tx'` + + Specify the template suffix, which is used for `cascade` and `include` + in Kolon. + + Note that this is used for static name resolution. That is, the compiler + uses it but the runtime engine doesn't. + +- `syntax => $name // 'Kolon'` + + Specifies the template syntax you want to use. + + _$name_ may be a short name (e.g. `Kolon`), or a fully qualified name + (e.g. `Text::Xslate::Syntax::Kolon`). + + This option is passed to the compiler directly. + +- `type => $type // 'html'` + + Specifies the output content type. If _$type_ is `html` or `xml`, + smart escaping is applied to template expressions. That is, + they are interpolated via the `html_escape` filter. + If _$type_ is `text` smart escaping is not applied so that it is + suitable for plain texts like e-mails. + + _$type_ may be __html__, __xml__ (identical to `html`), and __text__. + + This option is passed to the compiler directly. + +- `line_start => $token // $parser_defined_str` + + Specify the token to start line code as a string, which `quotemeta` will be applied to. If you give `undef`, the line code style is disabled. + + This option is passed to the parser via the compiler. + +- `tag_start => $str // $parser_defined_str` + + Specify the token to start inline code as a string, which `quotemeta` will be applied to. + + This option is passed to the parser via the compiler. + +- `tag_end => $str // $parser_defined_str` + + Specify the token to end inline code as a string, which `quotemeta` will be applied to. + + This option is passed to the parser via the compiler. + +- `header => \@template_files` + + Specify the header template files, which are inserted to the head of each template. + + This option is passed to the compiler. + +- `footer => \@template_files` + + Specify the footer template files, which are inserted to the foot of each template. + + This option is passed to the compiler. + +- `warn_handler => \&cb` + + Specify the callback _&cb_ which is called on warnings. + +- `die_handler => \&cb` + + Specify the callback _&cb_ which is called on fatal errors. + +- `pre_process_handler => \&cb` + + Specify the callback _&cb_ which is called after templates are loaded from the disk + in order to pre-process template. + + For example: + + # Remove withespace from templates + my $tx = Text::Xslate->new( + pre_process_handler => sub { + my $text = shift; + $text=~s/\s+//g; + return $text; + } + ); + + The first argument is the template text string, which can be both __text strings__ and `byte strings`. + + This filter is applied only to files, not a string template for `render_string`. + +### __$tx->render($file, \\%vars) :Str__ + +Renders a template file with given variables, and returns the result. +_\\%vars_ is optional. + +Note that _$file_ may be cached according to the cache level. + +### __$tx->render\_string($string, \\%vars) :Str__ + +Renders a template string with given variables, and returns the result. +_\\%vars_ is optional. + +Note that _$string_ is never cached, so this method should be avoided in +production environment. If you want in-memory templates, consider the _path_ +option for HASH references which are cached as you expect: + + my %vpath = ( + 'hello.tx' => 'Hello, <: $lang :> world!', + ); + + my $tx = Text::Xslate->new( path => \%vpath ); + print $tx->render('hello.tx', { lang => 'Xslate' }); + +Note that _$string_ must be a text string, not a binary string. + +### __$tx->load\_file($file) :Void__ + +Loads _$file_ into memory for following `render()`. +Compiles and saves it as disk caches if needed. + +### __Text::Xslate->current\_engine :XslateEngine__ + +Returns the current Xslate engine while executing. Otherwise returns `undef`. +This method is significant when it is called by template functions and methods. + +### __Text::Xslate->current\_vars :HashRef__ -Install cpanm (App::cpanminus) and then run the following command to install -Xslate: +Returns the current variable table, namely the second argument of +`render()` while executing. Otherwise returns `undef`. - $ cpanm Text::Xslate +### __Text::Xslate->current\_file :Str__ -If you get the distribution, unpack it and build it as per the usual: +Returns the current file name while executing. Otherwise returns `undef`. +This method is significant when it is called by template functions and methods. - $ tar xzf Text-Xslate-{version}.tar.gz - $ cd Text-Xslate-{version} - $ perl Makefile.PL - $ make && make test +### __Text::Xslate->current\_line :Int__ -Then install it: +Returns the current line number while executing. Otherwise returns `undef`. +This method is significant when it is called by template functions and methods. - $ make install +### __Text::Xslate->print(...) :Void__ -If you want to install it from the repository, you must install authoring -tools. +Adds the argument into the output buffer. This method is available on executing. - $ cpanm < author/requires.cpanm +## Exportable functions -# DOCUMENTATION +### `mark_raw($str :Str) :RawStr` -Text::Xslate documentation is available as in POD. So you can do: +Marks _$str_ as raw, so that the content of _$str_ will be rendered as is, +so you have to escape these strings by yourself. - $ perldoc Text::Xslate +For example: -to read the documentation online with your favorite pager. + my $tx = Text::Xslate->new(); + my $tmpl = 'Mailaddress: <: $email :>'; + my %vars = ( + email => mark_raw('Foo <foo at example.com>'), + ); + print $tx->render_string($tmpl, \%email); + # => Mailaddress: Foo <foo@example.com> + +This function is available in templates as the `mark_raw` filter, although +the use of it is strongly discouraged. + +### `unmark_raw($str :Str) :Str` + +Clears the raw marker from _$str_, so that the content of _$str_ will +be escaped before rendered. + +This function is available in templates as the `unmark_raw` filter. + +### `html_escape($str :Str) :RawStr` + +Escapes HTML meta characters in _$str_, and returns it as a raw string (see above). +If _$str_ is already a raw string, it returns _$str_ as is. + +By default, this function will be automatically applied to all the template +expressions. + +This function is available in templates as the `html` filter, but you'd better +to use `unmark_raw` to ensure expressions to be html-escaped. + +### `uri_escape($str :Str) :Str` + +Escapes URI unsafe characters in _$str_, and returns it. + +This function is available in templates as the `uri` filter. + +### `html_builder { block } | \&function :CodeRef` + +Wraps a block or _&function_ with `mark_raw` so that the new subroutine +will return a raw string. + +This function is used to tell the xslate engine that _&function_ is an +HTML builder that returns HTML sources. For example: + + sub some_html_builder { + my @args = @_; + my $html; + # build HTML ... + return $html; + } + + my $tx = Text::Xslate->new( + function => { + some_html_builder => html_builder(\&some_html_builder), + }, + ); + +See also [Text::Xslate::Manual::Cookbook](http://search.cpan.org/perldoc?Text::Xslate::Manual::Cookbook). + +## Command line interface + +The `xslate(1)` command is provided as a CLI to the Text::Xslate module, +which is used to process directory trees or to evaluate one liners. +For example: + + $ xslate -Dname=value -o dest_path src_path + + $ xslate -e 'Hello, <: $ARGV[0] :> wolrd!' Xslate + $ xslate -s TTerse -e 'Hello, [% ARGV.0 %] world!' TTerse + +See [xslate(1)](http://man.he.net/man1/xslate) for details. + +# TEMPLATE SYNTAX + +There are multiple template syntaxes available in Xslate. + +- Kolon + + __Kolon__ is the default syntax, using `<: ... :>` inline code and + `: ...` line code, which is explained in [Text::Xslate::Syntax::Kolon](http://search.cpan.org/perldoc?Text::Xslate::Syntax::Kolon). + +- Metakolon + + __Metakolon__ is the same as Kolon except for using `[% ... %]` inline code and + `%% ...` line code, instead of `<: ... :>` and `: ...`. + +- TTerse + + __TTerse__ is a syntax that is a subset of Template-Toolkit 2 (and partially TT3), + which is explained in [Text::Xslate::Syntax::TTerse](http://search.cpan.org/perldoc?Text::Xslate::Syntax::TTerse). + +- HTMLTemplate + + There's HTML::Template compatible layers in CPAN. + + [Text::Xslate::Syntax::HTMLTemplate](http://search.cpan.org/perldoc?Text::Xslate::Syntax::HTMLTemplate) is a syntax for HTML::Template. + + [HTML::Template::Parser](http://search.cpan.org/perldoc?HTML::Template::Parser) is a converter from HTML::Template to Text::Xslate. + +# NOTES + +There are common notes in Xslate. + +## Nil/undef handling + +Note that nil (i.e. `undef` in Perl) handling is different from Perl's. +Basically it does nothing, but `verbose => 2` will produce warnings on it. + +- to print + + Prints nothing. + +- to access fields + + Returns nil. That is, `nil.foo.bar.baz` produces nil. + +- to invoke methods + + Returns nil. That is, `nil.foo().bar().baz()` produces nil. + +- to iterate + + Dealt as an empty array. + +- equality + + `$var == nil` returns true if and only if _$var_ is nil. + +# DEPENDENCIES + +Perl 5.8.1 or later. + +C compiler + +# TODO + +- Context controls. e.g. `<: [ $foo->bar @list ] :>`. +- Augment modifiers. +- Default arguments and named arguments for macros. +- External macros. + + Just idea: in the new macro concept, macros and external templates will be + the same in internals: -# RESOURCE + : macro foo($lang) { "Hello, " ~ $lang ~ " world!" } + : include foo { lang => 'Xslate' } + : # => 'Hello, Xslate world!' - web site: http://xslate.org/ - repositories: http://github.com/xslate - mailing list: http://groups.google.com/group/xslate - irc : irc://irc.perl.org/#xslate + : extern bar 'my/bar.tx'; # 'extern bar $file' is ok + : bar( value => 42 ); # calls an external template + : include bar { value => 42 } # ditto + +- A "too-safe" HTML escaping filter which escape all the symbolic characters + +# RESOURCES + +WEB: [http://xslate.org/](http://xslate.org/) + +ML: [http://groups.google.com/group/xslate](http://groups.google.com/group/xslate) + +IRC: \#xslate @ irc.perl.org + +PROJECT HOME: [http://github.com/xslate/](http://github.com/xslate/) + +REPOSITORY: [http://github.com/xslate/p5-Text-Xslate/](http://github.com/xslate/p5-Text-Xslate/) + +# BUGS + +All complex software has bugs lurking in it, and this module is no +exception. If you find a bug please either email me, or add the bug +to cpan-RT. Patches are welcome :) + +# SEE ALSO + +Documents: + +[Text::Xslate::Manual](http://search.cpan.org/perldoc?Text::Xslate::Manual) + +Xslate template syntaxes: + +[Text::Xslate::Syntax::Kolon](http://search.cpan.org/perldoc?Text::Xslate::Syntax::Kolon) + +[Text::Xslate::Syntax::Metakolon](http://search.cpan.org/perldoc?Text::Xslate::Syntax::Metakolon) + +[Text::Xslate::Syntax::TTerse](http://search.cpan.org/perldoc?Text::Xslate::Syntax::TTerse) + +Xslate command: + +[xslate](http://search.cpan.org/perldoc?xslate) + +Other template modules that Xslate has been influenced by: + +[Text::MicroTemplate](http://search.cpan.org/perldoc?Text::MicroTemplate) + +[Text::MicroTemplate::Extended](http://search.cpan.org/perldoc?Text::MicroTemplate::Extended) + +[Text::ClearSilver](http://search.cpan.org/perldoc?Text::ClearSilver) + +[Template](http://search.cpan.org/perldoc?Template) (Template::Toolkit) + +[HTML::Template](http://search.cpan.org/perldoc?HTML::Template) + +[HTML::Template::Pro](http://search.cpan.org/perldoc?HTML::Template::Pro) + +[Template::Alloy](http://search.cpan.org/perldoc?Template::Alloy) + +[Template::Sandbox](http://search.cpan.org/perldoc?Template::Sandbox) + +Benchmarks: + +[Template::Benchmark](http://search.cpan.org/perldoc?Template::Benchmark) + +[http://xslate.org/benchmark.html](http://xslate.org/benchmark.html) + +Papers: + +[http://www.cs.usfca.edu/~parrt/papers/mvc.templates.pdf](http://www.cs.usfca.edu/~parrt/papers/mvc.templates.pdf) - Enforcing Strict Model-View Separation in Template Engines + +# ACKNOWLEDGEMENT + +Thanks to lestrrat for the suggestion to the interface of `render()`, +the contribution of Text::Xslate::Runner (was App::Xslate), and a lot of +suggestions. + +Thanks to tokuhirom for the ideas, feature requests, encouragement, and bug finding. + +Thanks to gardejo for the proposal to the name __template cascading__. + +Thanks to makamaka for the contribution of Text::Xslate::PP. + +Thanks to jjn1056 to the concept of template overlay (now implemented as `cascade with ...`). + +Thanks to typester for the various inspirations. + +Thanks to clouder for the patch of adding `AND` and `OR` to TTerse. + +Thanks to punytan for the documentation improvement. + +Thanks to chiba for the bug reports and patches. + +Thanks to turugina for the patch to fix Win32 problems + +Thanks to Sam Graham for the bug reports. + +Thanks to Mons Anderson for the bug reports and patches. + +Thanks to hirose31 for the feature requests and bug reports. + +Thanks to c9s for the contribution of the documents. + +Thanks to shiba\_yu36 for the bug reports. + +Thanks to kane46taka for the bug reports. + +Thanks to cho45 for the bug reports. + +Thanks to shmorimo for the bug reports. + +Thanks to ueda for the suggestions. + +# AUTHOR + +Fuji, Goro (gfx) . + +Makamaka Hannyaharamitu (makamaka) (Text::Xslate::PP) + +Maki, Daisuke (lestrrat) (Text::Xslate::Runner) # LICENSE AND COPYRIGHT -Copyright (c) 2010, Fuji, Goro (gfx). All rights reserved. +Copyright (c) 2010-2013, Fuji, Goro (gfx). All rights reserved. This library is free software; you can redistribute it and/or modify it under the same terms as Perl itself. - diff --git a/builder/MyBuilder.pm b/builder/MyBuilder.pm new file mode 100644 index 00000000..11d18524 --- /dev/null +++ b/builder/MyBuilder.pm @@ -0,0 +1,43 @@ +package builder::MyBuilder; +use strict; +use warnings; +use utf8; +use parent qw(Module::Build::XSUtil); + +sub new { + my ( $self, %args ) = @_; + $self->SUPER::new( + %args, + include_dirs => [qw(src/ .)], + xs_files => { + 'src/Text-Xslate.xs' => 'lib/Text/Xslate.xs', + }, + c_source => ['src/xslate_methods.c'], + generate_xshelper_h => 'xshelper.h', + meta_merge => { + resources => { + homepage => 'http://xslate.org/', + bugtracker => 'https://github.com/xslate/p5-Text-Xslate/issues', + repository => 'https://github.com/xslate/p5-Text-Xslate', + ProjectHome => 'https://github.com/xslate', + MailingList => 'http://groups.google.com/group/xslate', + } + }, + ); +} + +sub ACTION_code { + my $self = shift; + + system "$^X tool/opcode.PL src/xslate_opcode.inc >xslate_ops.h"; + + require ExtUtils::ParseXS; + ExtUtils::ParseXS->new->process_file( + filename => 'src/xslate_methods.xs', + output => 'src/xslate_methods.c' + ); + + $self->SUPER::ACTION_code(@_); +} + +1; diff --git a/cpanfile b/cpanfile new file mode 100644 index 00000000..c79a6cf0 --- /dev/null +++ b/cpanfile @@ -0,0 +1,19 @@ +requires 'Data::MessagePack', '0.38'; +requires 'Mouse', '0.61'; +requires 'Scalar::Util', '1.14'; +requires 'XSLoader', '0.02'; +requires 'parent', '0.221'; +requires 'perl', '5.008001'; + +on configure => sub { + requires 'Devel::PPPort', '3.19'; + requires 'ExtUtils::MakeMaker', '6.59'; + requires 'ExtUtils::ParseXS', '2.21'; + requires 'File::Copy::Recursive'; + requires 'Module::Build::XSUtil'; +}; + +on test => sub { + requires 'Test::More', '0.88'; + requires 'Test::Requires'; +}; diff --git a/minil.toml b/minil.toml new file mode 100644 index 00000000..5a886a60 --- /dev/null +++ b/minil.toml @@ -0,0 +1,2 @@ +[build] +build_class="builder::MyBuilder" diff --git a/xt/01_podspell.t b/xt/01_podspell.t deleted file mode 100644 index ca1128a9..00000000 --- a/xt/01_podspell.t +++ /dev/null @@ -1,112 +0,0 @@ -#!perl -w - -use strict; -use Test::More; - -eval q{ use Test::Spellunker }; - -plan skip_all => q{Test::Spellunker are not available.} - if $@; - -add_stopwords(map { split /[\s\:\-]/ } ); -all_pod_files_spelling_ok('lib'); - -__DATA__ -Text::Xslate -xslate -todo -str -Opcode -cpan -render -Kolon -Metakolon -TTerse -syntaxes -pre -namespaces -plugins -html -acknowledgement -iff -EscapedString -sandboxing -APIs -runtime -autoboxing -backend -TT -adaptor -overridable -inline -Toolkit's -FillInForm -uri -CLI -PSGI -XSS -Mojo -SCALARs -namespace -CGI -xml -RT -XS -Exportable -Misc -callback -callbacks -RFC -colorize -Pre -IRC -irc -org -WAF -WAFs -JavaScript -fallbacks -UTF -preforking -github -Mojolicious -HTMLTemplate -blog - -# personal name -lestrrat -tokuhirom -gardejo -jjn -Goro Fuji -gfx -Douglas Crockford -makamaka -Hannyaharamitu -Maki -Daisuke -typester -clouder -punytan -chiba -turugina -hirose -kane -taka -cho -shmorimo -ueda -parens -opcodes -ing -vs -metacharacters -metacharacters -expressionsi -name-coderef -cb -cb -render -render_string -tx -newlines From ccf31cc6e9b01ff96e11546453e7aa5e994ac25b Mon Sep 17 00:00:00 2001 From: tokuhirom Date: Sat, 23 Nov 2013 12:41:47 +0900 Subject: [PATCH 27/39] builder/MyBuilder.pm uses OO-ish EU::ParseXS interface. --- cpanfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cpanfile b/cpanfile index c79a6cf0..f660e6bf 100644 --- a/cpanfile +++ b/cpanfile @@ -8,7 +8,7 @@ requires 'perl', '5.008001'; on configure => sub { requires 'Devel::PPPort', '3.19'; requires 'ExtUtils::MakeMaker', '6.59'; - requires 'ExtUtils::ParseXS', '2.21'; + requires 'ExtUtils::ParseXS', '3.21'; requires 'File::Copy::Recursive'; requires 'Module::Build::XSUtil'; }; From 66f357240aa810863ef310ec149ce3309e0c4a84 Mon Sep 17 00:00:00 2001 From: Daisuke Maki Date: Mon, 25 Nov 2013 13:06:02 +0900 Subject: [PATCH 28/39] Declare variable as unsued --- src/Text-Xslate.xs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/Text-Xslate.xs b/src/Text-Xslate.xs index 4b5a7d6d..67f8237d 100644 --- a/src/Text-Xslate.xs +++ b/src/Text-Xslate.xs @@ -1609,6 +1609,8 @@ CODE: SV** svp; AV* macro = NULL; + PERL_UNUSED_VAR(self); + TAINT_NOT; /* All the SVs we'll create here are safe */ Zero(&st, 1, tx_state_t); From a11fab86edd615939c1305cda80ce98f8136015a Mon Sep 17 00:00:00 2001 From: Daisuke Maki Date: Mon, 25 Nov 2013 13:10:11 +0900 Subject: [PATCH 29/39] update META.json --- META.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/META.json b/META.json index 8d1f1f20..a28ffbd5 100644 --- a/META.json +++ b/META.json @@ -32,7 +32,7 @@ "CPAN::Meta::Prereqs" : "0", "Devel::PPPort" : "3.19", "ExtUtils::MakeMaker" : "6.59", - "ExtUtils::ParseXS" : "2.21", + "ExtUtils::ParseXS" : "3.21", "File::Copy::Recursive" : "0", "Module::Build" : "0.38", "Module::Build::XSUtil" : "0" From 2c21fc5476d066c15ad8864b327b89c643d83471 Mon Sep 17 00:00:00 2001 From: Daisuke Maki Date: Mon, 25 Nov 2013 13:29:05 +0900 Subject: [PATCH 30/39] Silence author tests --- lib/Text/Xslate/Loader.pm | 4 +++- xt/05_vars.t | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/lib/Text/Xslate/Loader.pm b/lib/Text/Xslate/Loader.pm index 9d0f3f4b..b88f59ee 100644 --- a/lib/Text/Xslate/Loader.pm +++ b/lib/Text/Xslate/Loader.pm @@ -20,7 +20,9 @@ has pre_process_handler => ( ); sub extract_config_from_engine { - my ($class, $engine) = @_; + # XXX Test::Vars complains if we do ($class, $engine) and not use it + my (undef, $engine) = @_; + return ( engine => $engine, assembler => $engine->_assembler, diff --git a/xt/05_vars.t b/xt/05_vars.t index c6781428..51653453 100644 --- a/xt/05_vars.t +++ b/xt/05_vars.t @@ -6,7 +6,7 @@ use Test::More; use Test::Requires qw(Test::Vars); all_vars_ok( - ignore_vars => [qw($parser $symbol)], + ignore_vars => [qw($parser $symbol $note_guard)], ); done_testing; From 22549a341a782abedd47d91c03aae197c0cede17 Mon Sep 17 00:00:00 2001 From: Daisuke Maki Date: Mon, 25 Nov 2013 13:29:29 +0900 Subject: [PATCH 31/39] one more file to silence --- lib/Text/Xslate/Loader/File.pm | 24 +++--------------------- 1 file changed, 3 insertions(+), 21 deletions(-) diff --git a/lib/Text/Xslate/Loader/File.pm b/lib/Text/Xslate/Loader/File.pm index ec8ca6a0..d1194606 100644 --- a/lib/Text/Xslate/Loader/File.pm +++ b/lib/Text/Xslate/Loader/File.pm @@ -499,31 +499,13 @@ __END__ =head1 SYNOPSIS - package Text::Xslate; - ... + use Text::Xslate; use Text::Xslate::Loader::File; - has loader => ( - is => 'ro', - lazy => 1, - builder => 'build_loader', + Text::Xslate->new( + loader => 'Text::Xslate::Loader::File', ); - sub build_loader { - my $loader = Text::Xslate::Loader::File->new( - cache_dir => "/path/to/cache", - cache_strategy => 1, - compiler => $self->compiler, - include_dirs => [ "/path/to/dir1", "/path/to/dir2" ], - input_layer => $self->input_layer, - ); - } - - sub load_file { - my ($self, $file) = @_; - my $asm = $loader->load($file); - } - =head1 NOTES Loaders must save data (i.e. cache) in locations that are uniquely From a95f0349ced4594b47537bf0deedcf3bcc3d6f55 Mon Sep 17 00:00:00 2001 From: Daisuke Maki Date: Mon, 25 Nov 2013 13:56:23 +0900 Subject: [PATCH 32/39] This change allows MakeError consumers to change the log_prefix * normally the classname is used, but this allows you to use arbitrary string --- lib/Text/Xslate/Loader/File.pm | 2 ++ lib/Text/Xslate/MakeError.pm | 4 +++- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/lib/Text/Xslate/Loader/File.pm b/lib/Text/Xslate/Loader/File.pm index d1194606..1d988e9a 100644 --- a/lib/Text/Xslate/Loader/File.pm +++ b/lib/Text/Xslate/Loader/File.pm @@ -359,6 +359,8 @@ sub store_cache { return $newest_mtime; } +around log_prefix => sub { "Xslate" }; # Fool old tools + package Text::Xslate::Loader::File::FileInfo; use Mouse; diff --git a/lib/Text/Xslate/MakeError.pm b/lib/Text/Xslate/MakeError.pm index 7b78085d..8d3d9e41 100644 --- a/lib/Text/Xslate/MakeError.pm +++ b/lib/Text/Xslate/MakeError.pm @@ -9,6 +9,8 @@ sub throw_error { die shift->make_error(@_); } +sub log_prefix { ref($_[0]) } + sub read_around { # for error messages my($file, $line, $around, $input_layer) = @_; @@ -56,7 +58,7 @@ sub make_error { } local $Carp::CarpLevel = $Carp::CarpLevel + 1; - my $class = ref($self) || $self; + my $class = ref($self) ? $self->log_prefix() : $self; $message =~ s/\A \Q$class: \E//xms and $message .= "\t..."; if(defined $file) { From 27531ca2d750efedc8297fc34eb4856cdac13922 Mon Sep 17 00:00:00 2001 From: Daisuke Maki Date: Mon, 25 Nov 2013 14:01:01 +0900 Subject: [PATCH 33/39] Add back Catalyst::View::Xslate --- xt/200_depended.t | 2 ++ 1 file changed, 2 insertions(+) diff --git a/xt/200_depended.t b/xt/200_depended.t index 555f90d3..43dbe89b 100644 --- a/xt/200_depended.t +++ b/xt/200_depended.t @@ -15,6 +15,8 @@ if(!scalar grep { $_ eq '--install' } @ARGV) { my $cpanm = which('cpanm') or plan skip_all => 'no cpanm'; my @modules = qw( + Test::Pod::Coverage + Catalyst::View::Xslate Text::Xslate::Bridge::TT2Like ); From be2016ea412eb21ef94251fb7f4298a7378c46a9 Mon Sep 17 00:00:00 2001 From: Daisuke Maki Date: Mon, 25 Nov 2013 14:01:17 +0900 Subject: [PATCH 34/39] Change bytecode_version to be an integer --- lib/Text/Xslate.pm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/Text/Xslate.pm b/lib/Text/Xslate.pm index ec981a04..ee965741 100644 --- a/lib/Text/Xslate.pm +++ b/lib/Text/Xslate.pm @@ -83,7 +83,7 @@ my %builtin = ( uri => 'uri_escape', ); -sub bytecode_version { '2.0' }; +sub bytecode_version { 2 }; sub default_functions { +{} } # overridable From f4c5bd6aa62c7e66997dad2d0cb03feb3ae0a20a Mon Sep 17 00:00:00 2001 From: Daisuke Maki Date: Mon, 25 Nov 2013 14:30:04 +0900 Subject: [PATCH 35/39] Just a little bit of documentation --- lib/Text/Xslate/Loader.pm | 22 +++++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/lib/Text/Xslate/Loader.pm b/lib/Text/Xslate/Loader.pm index b88f59ee..48596e32 100644 --- a/lib/Text/Xslate/Loader.pm +++ b/lib/Text/Xslate/Loader.pm @@ -70,4 +70,24 @@ your own Loader: You just need to provide a "load()" method for it to work. This class is here because there are a few initialization-related methods that are mostly common for Loaders. -=cut \ No newline at end of file +=head1 CREATING YOUR LOADER + +Text::Xslate by default takes a class name as loader and instantiates it +but if you are building a new loader we recommend you don't rely on this +feature. + +Instead, instantiate an object yourself and pass it to Text::Xslate: + + Text::Xslate->new( + loader => MyAwesome::Loader->new() + ); + +Your Loader class may need some configuration using the main Text::Xslate +object instance. For that, provide a method named C: + + sub configure { + my ($loader, $engine) = @_; + # Make whatever necessary change to $loader using $engine + } + +=cut From 1df7e78411812072489342535993032123edce2d Mon Sep 17 00:00:00 2001 From: Daisuke Maki Date: Mon, 25 Nov 2013 14:49:09 +0900 Subject: [PATCH 36/39] Update Changes --- Changes | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/Changes b/Changes index 2fd5a0d5..8536a05a 100644 --- a/Changes +++ b/Changes @@ -1,6 +1,28 @@ Revision history for Perl extension Text::Xslate {{$NEXT}} + [CHANGES] + - Portions of Text::Xslate guts have been ripped out of Xslate.pm and + have been moved to Text::Xslate::Loader and Text::Xslate::Loader::File. + By providing your own Loader class, you will be able to load templates + from a database, and cache them in Memcached, for example. + See example/loader.pl for an exampl using SQLite + Memcached (lestrrat) + - Some parts were moved into Text::Xslate::Assembler (lestrrat) + - File-based caches are now stored in directory names containing the + byte code version (lestrrat) + - Bump up the byte code version. The new byte code now has a "meta" + opcode that can contain arbitrary information on the generated + bytecode, such as "utf8" flag (which previously was part of the + cached byte code /file/, but not part of the bytecode) (lestrrat) + - Error generation has been refactored into a role (lestrrat) + - The default cache directory, previously located under + $ENV{HOME}/.xslate_cache + has been moved to + "$ENV{TEMPDIR}/xslate_cache/$>" + If $ENV{TEMPDIR} is empty, File::Spec->tmpdir is used. + - Pure-Perl version of Text::Xslate have been ripped out, and is no + longer supported (tokuhirom) + - The distribution is now based on Minilla (tokuhirom) 3.1.0 2013-11-16 16:46:35+0900 [BUG FIXES] From c0a733661f0a7bd07626c55b08f5b04ec34f5b57 Mon Sep 17 00:00:00 2001 From: tokuhirom Date: Mon, 25 Nov 2013 17:18:22 +0900 Subject: [PATCH 37/39] oops. fixed usage about c-source. --- META.json | 6 +++--- builder/MyBuilder.pm | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/META.json b/META.json index a28ffbd5..26bd1516 100644 --- a/META.json +++ b/META.json @@ -4,7 +4,7 @@ "Fuji, Goro (gfx) ." ], "dynamic_config" : 0, - "generated_by" : "Minilla/v0.8.4, CPAN::Meta::Converter version 2.132830", + "generated_by" : "Minilla/v0.9.0, CPAN::Meta::Converter version 2.132830", "license" : [ "perl_5" ], @@ -99,7 +99,7 @@ "David Steinbrunner ", "Shigeki Morimoto ", "Fuji, Goro ", - "Daisuke Maki ", - "tokuhirom " + "tokuhirom ", + "Daisuke Maki " ] } diff --git a/builder/MyBuilder.pm b/builder/MyBuilder.pm index 11d18524..1e6dc454 100644 --- a/builder/MyBuilder.pm +++ b/builder/MyBuilder.pm @@ -12,7 +12,7 @@ sub new { xs_files => { 'src/Text-Xslate.xs' => 'lib/Text/Xslate.xs', }, - c_source => ['src/xslate_methods.c'], + c_source => ['src'], generate_xshelper_h => 'xshelper.h', meta_merge => { resources => { From a566811cde725464043d5ba0de23cd28508e2c5a Mon Sep 17 00:00:00 2001 From: tokuhirom Date: Mon, 25 Nov 2013 17:25:32 +0900 Subject: [PATCH 38/39] Enable travis for 5.1[246] --- .travis.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.travis.yml b/.travis.yml index a64b83f1..a14edc6e 100644 --- a/.travis.yml +++ b/.travis.yml @@ -6,4 +6,7 @@ before_install: perl: - "5.8" - "5.10" + - "5.12" + - "5.14" + - "5.16" - "5.18" From cd0589a994f6f6401d5f2e68897daad1bad5e842 Mon Sep 17 00:00:00 2001 From: "Fuji, Goro" Date: Wed, 27 Nov 2013 10:41:18 +0900 Subject: [PATCH 39/39] tweaks --- cpanfile | 6 +++++- lib/Text/Xslate.pm | 1 - lib/Text/Xslate/Loader.pm | 8 ++++---- lib/Text/Xslate/Loader/File.pm | 25 +++++++++++++------------ 4 files changed, 22 insertions(+), 18 deletions(-) diff --git a/cpanfile b/cpanfile index f660e6bf..b37ae5bf 100644 --- a/cpanfile +++ b/cpanfile @@ -1,9 +1,12 @@ +#!perl + +requires 'perl', '5.008001'; requires 'Data::MessagePack', '0.38'; requires 'Mouse', '0.61'; requires 'Scalar::Util', '1.14'; requires 'XSLoader', '0.02'; requires 'parent', '0.221'; -requires 'perl', '5.008001'; +feature 'Scope::Guard', 0; # for logging on configure => sub { requires 'Devel::PPPort', '3.19'; @@ -17,3 +20,4 @@ on test => sub { requires 'Test::More', '0.88'; requires 'Test::Requires'; }; + diff --git a/lib/Text/Xslate.pm b/lib/Text/Xslate.pm index ee965741..28a8d69d 100644 --- a/lib/Text/Xslate.pm +++ b/lib/Text/Xslate.pm @@ -355,7 +355,6 @@ sub _compiler { my $compiler = $self->{compiler}; if(!ref $compiler){ - require Mouse; Mouse::load_class($compiler); my $input_layer = $self->input_layer; diff --git a/lib/Text/Xslate/Loader.pm b/lib/Text/Xslate/Loader.pm index 48596e32..c063399c 100644 --- a/lib/Text/Xslate/Loader.pm +++ b/lib/Text/Xslate/Loader.pm @@ -22,7 +22,7 @@ has pre_process_handler => ( sub extract_config_from_engine { # XXX Test::Vars complains if we do ($class, $engine) and not use it my (undef, $engine) = @_; - + return ( engine => $engine, assembler => $engine->_assembler, @@ -49,9 +49,9 @@ sub configure { sub compile { shift->engine->compile(@_) } sub assemble { shift->assembler->assemble(@_) } -sub load { - require Carp; - Carp::confess("$_[0]->compile() not declared"); +sub load { # must be override + my($self) = @_; + Carp::confess("$self->compile() not declared"); } 1; diff --git a/lib/Text/Xslate/Loader/File.pm b/lib/Text/Xslate/Loader/File.pm index 1d988e9a..61562272 100644 --- a/lib/Text/Xslate/Loader/File.pm +++ b/lib/Text/Xslate/Loader/File.pm @@ -37,8 +37,8 @@ around extract_config_from_engine => sub { my @config = $class->$next($engine); return ( @config, - # XXX Cwd::abs_path would stat() the directory, so we need to - # to use File::Spec->rel2abs + # XXX Cwd::abs_path calls stat() for the directory and raises errors if not exists, + # so we have to use File::Spec->rel2abs instead cache_dir => File::Spec->rel2abs($engine->{cache_dir}), cache_strategy => $engine->{cache}, include_dirs => $engine->{path}, @@ -46,9 +46,10 @@ around extract_config_from_engine => sub { ); }; -use Scope::Guard; my $INDENT_LEVEL = 0; -sub indent_note { +sub indent_note { # for logging + require Scope::Guard; + $INDENT_LEVEL++; return Scope::Guard->new(sub { $INDENT_LEVEL-- @@ -57,7 +58,7 @@ sub indent_note { sub note { my $self = shift; my $fmt = sprintf "%s%s\n", " " x $INDENT_LEVEL, shift; - + $self->engine->note($fmt, @_, "\n"); } @@ -66,7 +67,7 @@ sub load_cached_asm { # Okay, the source exists. Now consider the cache. # $cache_strategy >= 2, use cache w/o checking for freshness - # $cache_strategy == 1, use cache if cache is fresh + # $cache_strategy == 1, use cache if cache is fresh # $cache_strategy == 0, ignore cache # $cached_ent is an object with mtime and asm @@ -99,7 +100,7 @@ sub load_cached_asm { # $cache_strategy > 1 is wicked. It claims to only # consider the cache, and yet it still checks for - # the cache validity. + # the cache validity. if (my $asm = $cached_ent->asm) { return $asm; } @@ -110,10 +111,10 @@ sub load_cached_asm { return; } - # Otherwise check for freshness + # Otherwise check for freshness if ($cached_ent->is_fresher_than($fi)) { - # Hooray, our cached version is newer than the - # source file! cheers! jubilations! + # Hooray, our cached version is newer than the + # source file! cheers! jubilations! if (DUMP_LOAD) { $self->note("Freshness check passed, returning asm"); } @@ -385,7 +386,7 @@ sub build_magic { return sprintf $self->magic_template, $fullpath; } -package +package Text::Xslate::Loader::File::CachedEntity; use Mouse; @@ -469,7 +470,7 @@ sub build_asm { # my($name, $arg, $line, $file, $symbol) = @{$c}; - # XXX if this is a vpath, + # XXX if this is a vpath, if($c->[0] eq 'depend') { my $dep_mtime = (stat $c->[1])[Text::Xslate::Constants::ST_MTIME()]; if(!defined $dep_mtime) {