]> Dogcows Code - chaz/p5-CGI-Ex/blobdiff - lib/CGI/Ex/Template/Extra.pm
CGI::Ex 2.13
[chaz/p5-CGI-Ex] / lib / CGI / Ex / Template / Extra.pm
diff --git a/lib/CGI/Ex/Template/Extra.pm b/lib/CGI/Ex/Template/Extra.pm
new file mode 100644 (file)
index 0000000..9fb9f31
--- /dev/null
@@ -0,0 +1,705 @@
+package CGI::Ex::Template::Extra;
+
+=head1 NAME
+
+CGI::Ex::Template::Extra - load extra and advanced features that aren't as commonly used
+
+=head1 DESCRIPTION
+
+Provides for extra or extended features that may not be as commonly used.
+This module should not normally be used by itself.
+
+=head1 AUTHOR
+
+Paul Seamons <paul at seamons dot com>
+
+=head1 LICENSE
+
+This module may be distributed under the same terms as Perl itself.
+
+=cut
+
+use strict;
+use warnings;
+
+our $VERSION = '2.13';
+
+sub parse_CONFIG {
+    my ($self, $str_ref) = @_;
+
+    my %ctime = map {$_ => 1} @CGI::Ex::Template::CONFIG_COMPILETIME;
+    my %rtime = map {$_ => 1} @CGI::Ex::Template::CONFIG_RUNTIME;
+
+    my $config = $self->parse_args($str_ref, {named_at_front => 1, is_parened => 1});
+    my $ref = $config->[0]->[0];
+    for (my $i = 2; $i < @$ref; $i += 2) {
+        my $key = $ref->[$i] = uc $ref->[$i];
+        my $val = $ref->[$i + 1];
+        if ($ctime{$key}) {
+            $self->{$key} = $self->play_expr($val);
+        } elsif (! $rtime{$key}) {
+            $self->throw('parse', "Unknown CONFIG option \"$key\"", undef, pos($$str_ref));
+        }
+    }
+    for (my $i = 1; $i < @$config; $i++) {
+        my $key = $config->[$i] = uc $config->[$i]->[0];
+        if ($ctime{$key}) {
+            $config->[$i] = "CONFIG $key = ".(defined($self->{$key}) ? $self->{$key} : 'undef');
+        } elsif (! $rtime{$key}) {
+            $self->throw('parse', "Unknown CONFIG option \"$key\"", undef, pos($$str_ref));
+        }
+    }
+    return $config;
+}
+
+sub play_CONFIG {
+    my ($self, $config, $node, $out_ref) = @_;
+
+    my %rtime = map {$_ => 1} @CGI::Ex::Template::CONFIG_RUNTIME;
+
+    ### do runtime config - not many options get these
+    my ($named, @the_rest) = @$config;
+    $named = $self->play_expr($named);
+    @{ $self }{keys %$named} = @{ $named }{keys %$named};
+
+    ### show what current values are
+    $$out_ref .= join("\n", map { $rtime{$_} ? ("CONFIG $_ = ".(defined($self->{$_}) ? $self->{$_} : 'undef')) : $_ } @the_rest);
+    return;
+}
+
+sub parse_DEBUG {
+    my ($self, $str_ref) = @_;
+    $$str_ref =~ m{ \G ([Oo][Nn] | [Oo][Ff][Ff] | [Ff][Oo][Rr][Mm][Aa][Tt]) \s* }gcx
+        || $self->throw('parse', "Unknown DEBUG option", undef, pos($$str_ref));
+    my $ret = [lc($1)];
+    if ($ret->[0] eq 'format') {
+        $$str_ref =~ m{ \G ([\"\']) (|.*?[^\\]) \1 \s* }gcxs
+            || $self->throw('parse', "Missing format string", undef, pos($$str_ref));
+        $ret->[1] = $2;
+    }
+    return $ret;
+}
+
+sub play_DEBUG {
+    my ($self, $ref) = @_;
+    if ($ref->[0] eq 'on') {
+        delete $self->{'_debug_off'};
+    } elsif ($ref->[0] eq 'off') {
+        $self->{'_debug_off'} = 1;
+    } elsif ($ref->[0] eq 'format') {
+        $self->{'_debug_format'} = $ref->[1];
+    }
+    return;
+}
+
+sub play_DUMP {
+    my ($self, $dump, $node, $out_ref) = @_;
+
+    my $conf = $self->{'DUMP'};
+    return if ! $conf && defined $conf; # DUMP => 0
+    $conf = {} if ref $conf ne 'HASH';
+
+    ### allow for handler override
+    my $handler = $conf->{'handler'};
+    if (! $handler) {
+        require Data::Dumper;
+        my $obj = Data::Dumper->new([]);
+        my $meth;
+        foreach my $prop (keys %$conf) { $obj->$prop($conf->{$prop}) if $prop =~ /^\w+$/ && ($meth = $obj->can($prop)) }
+        my $sort = defined($conf->{'Sortkeys'}) ? $obj->Sortkeys : 1;
+        $obj->Sortkeys(sub { my $h = shift; [grep {$_ !~ $CGI::Ex::Template::QR_PRIVATE} ($sort ? sort keys %$h : keys %$h)] });
+        $handler = sub { $obj->Values([@_]); $obj->Dump }
+    }
+
+    my ($named, @dump) = @$dump;
+    push @dump, $named if ! $self->is_empty_named_args($named); # add named args back on at end - if there are some
+    $_ = $self->play_expr($_) foreach @dump;
+
+    ### look for the text describing what to dump
+    my $info = $self->node_info($node);
+    my $out;
+    if (@dump) {
+        $out = $handler->(@dump && @dump == 1 ? $dump[0] : \@dump);
+        my $name = $info->{'text'};
+        $name =~ s/^[+=~-]?\s*DUMP\s+//;
+        $name =~ s/\s*[+=~-]?$//;
+        $out =~ s/\$VAR1/$name/;
+    } elsif (defined($conf->{'EntireStash'}) && ! $conf->{'EntireStash'}) {
+        $out = '';
+    } else {
+        $out = $handler->($self->{'_vars'});
+        $out =~ s/\$VAR1/EntireStash/g;
+    }
+
+    if ($conf->{'html'} || (! defined($conf->{'html'}) && $ENV{'REQUEST_METHOD'})) {
+        $out = $CGI::Ex::Template::SCALAR_OPS->{'html'}->($out);
+        $out = "<pre>$out</pre>";
+        $out = "<b>DUMP: File \"$info->{file}\" line $info->{line}</b>$out" if $conf->{'header'} || ! defined $conf->{'header'};
+    } else {
+        $out = "DUMP: File \"$info->{file}\" line $info->{line}\n    $out" if $conf->{'header'} || ! defined $conf->{'header'};
+    }
+
+    $$out_ref .= $out;
+    return;
+}
+
+sub parse_FILTER {
+    my ($self, $str_ref) = @_;
+    my $name = '';
+    if ($$str_ref =~ m{ \G ([^\W\d]\w*) \s* = \s* }gcx) {
+        $name = $1;
+    }
+
+    my $filter = $self->parse_expr($str_ref);
+    $filter = '' if ! defined $filter;
+
+    return [$name, $filter];
+}
+
+sub play_FILTER {
+    my ($self, $ref, $node, $out_ref) = @_;
+    my ($name, $filter) = @$ref;
+
+    return '' if ! @$filter;
+
+    $self->{'FILTERS'}->{$name} = $filter if length $name;
+
+    my $sub_tree = $node->[4];
+
+    ### play the block
+    my $out = '';
+    eval { $self->execute_tree($sub_tree, \$out) };
+    die $@ if $@ && ref($@) !~ /Template::Exception$/;
+
+    my $var = [[undef, '~', $out], 0, '|', @$filter]; # make a temporary var out of it
+
+    return $CGI::Ex::Template::DIRECTIVES->{'GET'}->[1]->($self, $var, $node, $out_ref);
+}
+
+sub parse_LOOP {
+    my ($self, $str_ref, $node) = @_;
+    return $self->parse_expr($str_ref)
+        || $self->throw('parse', 'Missing variable on LOOP directive', undef, pos($$str_ref));
+}
+
+sub play_LOOP {
+    my ($self, $ref, $node, $out_ref) = @_;
+
+    my $var = $self->play_expr($ref);
+    my $sub_tree = $node->[4];
+
+    my $global = ! $self->{'SYNTAX'} || $self->{'SYNTAX'} ne 'ht' || $self->{'GLOBAL_VARS'};
+
+    my $items = ref($var) eq 'ARRAY' ? $var : ! defined($var) ? [] : [$var];
+
+    my $i = 0;
+    for my $ref (@$items) {
+        ### setup the loop
+        $self->throw('loop', 'Scalar value used in LOOP') if $ref && ref($ref) ne 'HASH';
+        local $self->{'_vars'} = (! $global) ? ($ref || {}) : (ref($ref) eq 'HASH') ? {%{ $self->{'_vars'} }, %$ref} : $self->{'_vars'};
+        if ($self->{'LOOP_CONTEXT_VARS'} && ! $CGI::Ex::Template::QR_PRIVATE) {
+            $self->{'_vars'}->{'__counter__'} = ++$i;
+            $self->{'_vars'}->{'__first__'} = $i == 1 ? 1 : 0;
+            $self->{'_vars'}->{'__last__'}  = $i == @$items ? 1 : 0;
+            $self->{'_vars'}->{'__inner__'} = $i == 1 || $i == @$items ? 0 : 1;
+            $self->{'_vars'}->{'__odd__'}   = ($i % 2) ? 1 : 0;
+        }
+
+        ### execute the sub tree
+        eval { $self->execute_tree($sub_tree, $out_ref) };
+        if (my $err = $@) {
+            if (UNIVERSAL::isa($err, $CGI::Ex::Template::PACKAGE_EXCEPTION)) {
+                next if $err->type eq 'next';
+                last if $err->type =~ /last|break/;
+            }
+            die $err;
+        }
+    }
+
+    return;
+}
+
+sub parse_MACRO {
+    my ($self, $str_ref, $node) = @_;
+
+    my $name = $self->parse_expr($str_ref, {auto_quote => "(\\w+\\b) (?! \\.) \\s* $CGI::Ex::Template::QR_COMMENTS"});
+    $self->throw('parse', "Missing macro name", undef, pos($$str_ref)) if ! defined $name;
+    if (! ref $name) {
+        $name = [ $name, 0 ];
+    }
+
+    my $args;
+    if ($$str_ref =~ m{ \G \( \s* }gcx) {
+        $args = $self->parse_args($str_ref, {positional_only => 1});
+        $$str_ref =~ m{ \G \) \s* }gcx || $self->throw('parse.missing', "Missing close ')'", undef, pos($$str_ref));
+    }
+
+    $node->[6] = 1;           # set a flag to keep parsing
+    return [$name, $args];
+}
+
+sub play_MACRO {
+    my ($self, $ref, $node, $out_ref) = @_;
+    my ($name, $args) = @$ref;
+
+    ### get the sub tree
+    my $sub_tree = $node->[4];
+    if (! $sub_tree || ! $sub_tree->[0]) {
+        $self->set_variable($name, undef);
+        return;
+    } elsif ($sub_tree->[0]->[0] eq 'BLOCK') {
+        $sub_tree = $sub_tree->[0]->[4];
+    }
+
+    my $self_copy = $self;
+    eval {require Scalar::Util; Scalar::Util::weaken($self_copy)};
+
+    ### install a closure in the stash that will handle the macro
+    $self->set_variable($name, sub {
+        ### macros localize
+        my $copy = $self_copy->{'_vars'};
+        local $self_copy->{'_vars'}= {%$copy};
+
+        ### prevent recursion
+        local $self_copy->{'_macro_recurse'} = $self_copy->{'_macro_recurse'} || 0;
+        my $max = $self_copy->{'MAX_MACRO_RECURSE'} || $CGI::Ex::Template::MAX_MACRO_RECURSE;
+        $self_copy->throw('macro_recurse', "MAX_MACRO_RECURSE $max reached")
+            if ++$self_copy->{'_macro_recurse'} > $max;
+
+        ### set arguments
+        my $named = pop(@_) if $_[-1] && UNIVERSAL::isa($_[-1],'HASH') && $#_ > $#$args;
+        my @positional = @_;
+        foreach my $var (@$args) {
+            $self_copy->set_variable($var, shift(@positional));
+        }
+        foreach my $name (sort keys %$named) {
+            $self_copy->set_variable([$name, 0], $named->{$name});
+        }
+
+        ### finally - run the sub tree
+        my $out = '';
+        $self_copy->execute_tree($sub_tree, \$out);
+        return $out;
+    });
+
+    return;
+}
+
+sub play_PERL {
+    my ($self, $info, $node, $out_ref) = @_;
+    $self->throw('perl', 'EVAL_PERL not set') if ! $self->{'EVAL_PERL'};
+
+    ### fill in any variables
+    my $perl = $node->[4] || return;
+    my $out  = '';
+    $self->execute_tree($perl, \$out);
+    $out = $1 if $out =~ /^(.+)$/s; # blatant untaint - shouldn't use perl anyway
+
+    ### try the code
+    my $err;
+    eval {
+        package CGI::Ex::Template::Perl;
+
+        my $context = $self->context;
+        my $stash   = $context->stash;
+
+        ### setup a fake handle
+        local *PERLOUT;
+        tie *PERLOUT, 'CGI::Ex::Template::EvalPerlHandle', $out_ref;
+        my $old_fh = select PERLOUT;
+
+        eval $out;
+        $err = $@;
+
+        ### put the handle back
+        select $old_fh;
+
+    };
+    $err ||= $@;
+
+
+    if ($err) {
+        $self->throw('undef', $err) if ref($err) !~ /Template::Exception$/;
+        die $err;
+    }
+
+    return;
+}
+
+sub play_RAWPERL {
+    my ($self, $info, $node, $out_ref) = @_;
+    $self->throw('perl', 'EVAL_PERL not set') if ! $self->{'EVAL_PERL'};
+
+    ### fill in any variables
+    my $tree = $node->[4] || return;
+    my $perl  = '';
+    $self->execute_tree($tree, \$perl);
+    $perl = $1 if $perl =~ /^(.+)$/s; # blatant untaint - shouldn't use perl anyway
+
+    ### try the code
+    my $err;
+    my $output = '';
+    eval {
+        package CGI::Ex::Template::Perl;
+
+        my $context = $self->context;
+        my $stash   = $context->stash;
+
+        eval $perl;
+        $err = $@;
+    };
+    $err ||= $@;
+
+    $$out_ref .= $output;
+
+    if ($err) {
+        $self->throw('undef', $err) if ref($err) !~ /Template::Exception$/;
+        die $err;
+    }
+
+    return;
+}
+
+sub parse_USE {
+    my ($self, $str_ref) = @_;
+
+    my $QR_COMMENTS = $CGI::Ex::Template::QR_COMMENTS;
+
+    my $var;
+    my $mark = pos $$str_ref;
+    if (defined(my $_var = $self->parse_expr($str_ref, {auto_quote => "(\\w+\\b) (?! \\.) \\s* $QR_COMMENTS"}))
+        && ($$str_ref =~ m{ \G = >? \s* $QR_COMMENTS }gcxo # make sure there is assignment
+            || ((pos($$str_ref) = $mark) && 0))               # otherwise we need to rollback
+        ) {
+        $var = $_var;
+    }
+
+    my $module = $self->parse_expr($str_ref, {auto_quote => "(\\w+\\b (?: (?:\\.|::) \\w+\\b)*) (?! \\.) \\s* $QR_COMMENTS"});
+    $self->throw('parse', "Missing plugin name while parsing $$str_ref", undef, pos($$str_ref)) if ! defined $module;
+    $module =~ s/\./::/g;
+
+    my $args;
+    my $open = $$str_ref =~ m{ \G \( \s* $QR_COMMENTS }gcxo;
+    $args = $self->parse_args($str_ref, {is_parened => $open, named_at_front => 1});
+
+    if ($open) {
+        $$str_ref =~ m{ \G \) \s* $QR_COMMENTS }gcxo || $self->throw('parse.missing', "Missing close ')'", undef, pos($$str_ref));
+    }
+
+    return [$var, $module, $args];
+}
+
+sub play_USE {
+    my ($self, $ref, $node, $out_ref) = @_;
+    my ($var, $module, $args) = @$ref;
+
+    ### get the stash storage location - default to the module
+    $var = $module if ! defined $var;
+    my @var = map {($_, 0, '.')} split /(?:\.|::)/, $var;
+    pop @var; # remove the trailing '.'
+
+    my ($named, @args) = @$args;
+    push @args, $named if ! $self->is_empty_named_args($named); # add named args back on at end - if there are some
+
+    ### look for a plugin_base
+    my $BASE = $self->{'PLUGIN_BASE'} || 'Template::Plugin'; # I'm not maintaining plugins - leave that to TT
+    my $obj;
+
+    foreach my $base (ref($BASE) eq 'ARRAY' ? @$BASE : $BASE) {
+        my $package = $self->{'PLUGINS'}->{$module} ? $self->{'PLUGINS'}->{$module}
+        : $self->{'PLUGIN_FACTORY'}->{$module} ? $self->{'PLUGIN_FACTORY'}->{$module}
+        : "${base}::${module}";
+        my $require = "$package.pm";
+        $require =~ s|::|/|g;
+
+        ### try and load the module - fall back to bare module if allowed
+        if ($self->{'PLUGIN_FACTORY'}->{$module} || eval {require $require}) {
+            my $shape   = $package->load;
+            my $context = $self->context;
+            $obj = $shape->new($context, map { $self->play_expr($_) } @args);
+        } elsif (lc($module) eq 'iterator') { # use our iterator if none found (TT's works just fine)
+            $obj = $self->iterator($args[0]);
+        } elsif (my @packages = grep {lc($package) eq lc($_)} @{ $self->list_plugins({base => $base}) }) {
+            foreach my $package (@packages) {
+                my $require = "$package.pm";
+                $require =~ s|::|/|g;
+                eval {require $require} || next;
+                my $shape   = $package->load;
+                my $context = $self->context;
+                $obj = $shape->new($context, map { $self->play_expr($_) } @args);
+            }
+        } elsif ($self->{'LOAD_PERL'}) {
+            my $require = "$module.pm";
+            $require =~ s|::|/|g;
+            if (eval {require $require}) {
+                $obj = $module->new(map { $self->play_expr($_) } @args);
+            }
+        }
+    }
+    if (! defined $obj) {
+        my $err = "$module: plugin not found";
+        $self->throw('plugin', $err);
+    }
+
+    ### all good
+    $self->set_variable(\@var, $obj);
+
+    return;
+}
+
+sub parse_VIEW {
+    my ($self, $str_ref) = @_;
+
+    my $ref = $self->parse_args($str_ref, {
+        named_at_front       => 1,
+        require_arg          => 1,
+    });
+
+    return $ref;
+}
+
+sub play_VIEW {
+    my ($self, $ref, $node, $out_ref) = @_;
+
+    my ($blocks, $args, $name) = @$ref;
+
+    ### get args ready
+    # [[undef, '{}', 'key1', 'val1', 'key2', 'val2'], 0]
+    $args = $args->[0];
+    my $hash = {};
+    foreach (my $i = 2; $i < @$args; $i+=2) {
+        my $key = $args->[$i];
+        my $val = $self->play_expr($args->[$i+1]);
+        if (ref $key) {
+            if (@$key == 2 && ! ref($key->[0]) && ! $key->[1]) {
+                $key = $key->[0];
+            } else {
+                $self->set_variable($key, $val);
+                next; # what TT does
+            }
+        }
+        $hash->{$key} = $val;
+    }
+
+    ### prepare the blocks
+    my $prefix = $hash->{'prefix'} || (ref($name) && @$name == 2 && ! $name->[1] && ! ref($name->[0])) ? "$name->[0]/" : '';
+    foreach my $key (keys %$blocks) {
+        $blocks->{$key} = {name => "${prefix}${key}", _tree => $blocks->{$key}};
+    }
+    $hash->{'blocks'} = $blocks;
+
+    ### get the view
+    if (! eval { require Template::View }) {
+        $self->throw('view', 'Could not load Template::View library');
+    }
+    my $view = Template::View->new($self->context, $hash)
+        || $self->throw('view', $Template::View::ERROR);
+
+    ### 'play it'
+    my $old_view = $self->play_expr(['view', 0]);
+    $self->set_variable($name, $view);
+    $self->set_variable(['view', 0], $view);
+
+    if ($node->[4]) {
+        my $out = '';
+        $self->execute_tree($node->[4], \$out);
+        # throw away $out
+    }
+
+    $self->set_variable(['view', 0], $old_view);
+    $view->seal;
+
+    return;
+}
+
+###----------------------------------------------------------------###
+
+sub list_plugins {
+    my $self = shift;
+    my $args = shift || {};
+    my $base = $args->{'base'} || '';
+
+    return $self->{'_plugins'}->{$base} ||= do {
+        my @plugins;
+
+        $base =~ s|::|/|g;
+        my @dirs = grep {-d $_} map {"$_/$base"} @INC;
+
+        foreach my $dir (@dirs) {
+            require File::Find;
+            File::Find::find(sub {
+                my $mod = $base .'/'. ($File::Find::name =~ m|^ $dir / (.*\w) \.pm $|x ? $1 : return);
+                $mod =~ s|/|::|g;
+                push @plugins, $mod;
+            }, $dir);
+        }
+
+        \@plugins; # return of the do
+    };
+}
+
+###----------------------------------------------------------------###
+
+package CGI::Ex::Template::Context;
+
+use vars qw($AUTOLOAD);
+
+sub new {
+    my $class = shift;
+    my $self  = shift || {};
+    die "Missing _template" if ! $self->{'_template'};
+    return bless $self, $class;
+}
+
+sub _template { shift->{'_template'} || die "Missing _template" }
+
+sub template {
+    my ($self, $name) = @_;
+    return $self->_template->{'BLOCKS'}->{$name} || $self->_template->load_parsed_tree($name);
+}
+
+sub config { shift->_template }
+
+sub stash {
+    my $self = shift;
+    return $self->{'stash'} ||= bless {_template => $self->_template}, 'CGI::Ex::Template::_Stash';
+}
+
+sub insert { shift->_template->_insert(@_) }
+
+sub eval_perl { shift->_template->{'EVAL_PERL'} }
+
+sub process {
+    my $self = shift;
+    my $ref  = shift;
+    my $args = shift || {};
+
+    $self->_template->set_variable($_, $args->{$_}) for keys %$args;
+
+    my $out  = '';
+    $self->_template->_process($ref, $self->_template->_vars, \$out);
+    return $out;
+}
+
+sub include {
+    my $self = shift;
+    my $ref  = shift;
+    my $args = shift || {};
+
+    my $t = $self->_template;
+
+    my $swap = $t->{'_vars'};
+    local $t->{'_vars'} = {%$swap};
+
+    $t->set_variable($_, $args->{$_}) for keys %$args;
+
+    my $out = ''; # have temp item to allow clear to correctly clear
+    eval { $t->_process($ref, $t->_vars, \$out) };
+    if (my $err = $@) {
+        die $err if ref($err) !~ /Template::Exception$/ || $err->type !~ /return/;
+    }
+
+    return $out;
+}
+
+sub define_filter {
+    my ($self, $name, $filter, $is_dynamic) = @_;
+    $filter = [ $filter, 1 ] if $is_dynamic;
+    $self->define_vmethod('filter', $name, $filter);
+}
+
+sub filter {
+    my ($self, $name, $args, $alias) = @_;
+    my $t = $self->_template;
+
+    my $filter;
+    if (! ref $name) {
+        $filter = $t->{'FILTERS'}->{$name} || $CGI::Ex::Template::FILTER_OPS->{$name} || $CGI::Ex::Template::SCALAR_OPS->{$name};
+        $t->throw('filter', $name) if ! $filter;
+    } elsif (UNIVERSAL::isa($name, 'CODE') || UNIVERSAL::isa($name, 'ARRAY')) {
+        $filter = $name;
+    } elsif (UNIVERSAL::can($name, 'factory')) {
+        $filter = $name->factory || $t->throw($name->error);
+    } else {
+        $t->throw('undef', "$name: filter not found");
+    }
+
+    if (UNIVERSAL::isa($filter, 'ARRAY')) {
+        $filter = ($filter->[1]) ? $filter->[0]->($t->context, @$args) : $filter->[0];
+    } elsif ($args && @$args) {
+        my $sub = $filter;
+        $filter = sub { $sub->(shift, @$args) };
+    }
+
+    $t->{'FILTERS'}->{$alias} = $filter if $alias;
+
+    return $filter;
+}
+
+sub define_vmethod { shift->_template->define_vmethod(@_) }
+
+sub throw {
+    my ($self, $type, $info) = @_;
+
+    if (UNIVERSAL::isa($type, $CGI::Ex::Template::PACKAGE_EXCEPTION)) {
+       die $type;
+    } elsif (defined $info) {
+       $self->_template->throw($type, $info);
+    } else {
+       $self->_template->throw('undef', $type);
+    }
+}
+
+sub AUTOLOAD { shift->_template->throw('not_implemented', "The method $AUTOLOAD has not been implemented") }
+
+sub DESTROY {}
+
+###----------------------------------------------------------------###
+
+package CGI::Ex::Template::_Stash;
+
+use vars qw($AUTOLOAD);
+
+sub _template { shift->{'_template'} || die "Missing _template" }
+
+sub get {
+    my ($self, $var) = @_;
+    if (! ref $var) {
+        if ($var =~ /^\w+$/) {  $var = [$var, 0] }
+        else {                  $var = $self->_template->parse_expr(\$var, {no_dots => 1}) }
+    }
+    return $self->_template->play_expr($var, {no_dots => 1});
+}
+
+sub set {
+    my ($self, $var, $val) = @_;
+    if (! ref $var) {
+        if ($var =~ /^\w+$/) {  $var = [$var, 0] }
+        else {                  $var = $self->_template->parse_expr(\$var, {no_dots => 1}) }
+    }
+    $self->_template->set_variable($var, $val, {no_dots => 1});
+    return $val;
+}
+
+sub AUTOLOAD { shift->_template->throw('not_implemented', "The method $AUTOLOAD has not been implemented") }
+
+sub DESTROY {}
+
+###----------------------------------------------------------------###
+
+package CGI::Ex::Template::EvalPerlHandle;
+
+sub TIEHANDLE {
+    my ($class, $out_ref) = @_;
+    return bless [$out_ref], $class;
+}
+
+sub PRINT {
+    my $self = shift;
+    ${ $self->[0] } .= $_ for grep {defined && length} @_;
+    return 1;
+}
+
+###----------------------------------------------------------------###
+
+1;
This page took 0.028508 seconds and 4 git commands to generate.