summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--lib/Text/Handlebars.pm51
-rw-r--r--lib/Text/Handlebars/Compiler.pm38
-rw-r--r--lib/Text/Xslate/Syntax/Handlebars.pm87
-rw-r--r--t/001-basic.t6
-rw-r--r--t/002-expressions.t6
-rw-r--r--t/003-safestring.t5
-rw-r--r--t/blocks.t57
7 files changed, 245 insertions, 5 deletions
diff --git a/lib/Text/Handlebars.pm b/lib/Text/Handlebars.pm
new file mode 100644
index 0000000..bad8c64
--- /dev/null
+++ b/lib/Text/Handlebars.pm
@@ -0,0 +1,51 @@
+package Text::Handlebars;
+use strict;
+use warnings;
+
+use base 'Text::Xslate';
+
+sub default_functions {
+ my $class = shift;
+ return {
+ %{ $class->SUPER::default_functions(@_) },
+ '(is_array)' => sub {
+ my ($val) = @_;
+ return ref($val) && ref($val) eq 'ARRAY';
+ },
+ '(make_array)' => sub {
+ my ($length) = @_;
+ return [(undef) x $length];
+ },
+ '(new_vars_for)' => sub {
+ my ($vars, $value, $i) = @_;
+ $i = 0 unless defined $i; # XXX
+
+ if (my $ref = ref($value)) {
+ if (defined $ref && $ref eq 'ARRAY') {
+ die "no iterator cycle provided?"
+ unless defined $i;
+ $value = $value->[$i];
+ $ref = ref($value);
+ }
+
+ die "invalid value: $value"
+ if !defined($ref) || $ref ne 'HASH';
+
+ return $value;
+ }
+ else {
+ return $vars;
+ }
+ },
+ };
+}
+
+sub options {
+ my $class = shift;
+
+ my $options = $class->SUPER::options(@_);
+ $options->{compiler} = 'Text::Handlebars::Compiler';
+ return $options;
+}
+
+1;
diff --git a/lib/Text/Handlebars/Compiler.pm b/lib/Text/Handlebars/Compiler.pm
new file mode 100644
index 0000000..2150bba
--- /dev/null
+++ b/lib/Text/Handlebars/Compiler.pm
@@ -0,0 +1,38 @@
+package Text::Handlebars::Compiler;
+use Any::Moose;
+
+extends 'Text::Xslate::Compiler';
+
+has '+syntax' => (
+ default => 'Handlebars',
+);
+
+sub _generate_block {
+ my $self = shift;
+ my ($node) = @_;
+
+ return (
+ $self->_localize_vars($node->first),
+ (map { $self->compile_ast($_) } @{ $node->second }),
+ );
+}
+
+if (0) {
+ our $_recursing;
+ around compile_ast => sub {
+ my $orig = shift;
+ my $self = shift;
+
+ my @ast = do {
+ local $_recursing = 1;
+ $self->$orig(@_);
+ };
+ use Data::Dump; ddx(\@ast) unless $_recursing;
+ return @ast;
+ };
+}
+
+__PACKAGE__->meta->make_immutable;
+no Any::Moose;
+
+1;
diff --git a/lib/Text/Xslate/Syntax/Handlebars.pm b/lib/Text/Xslate/Syntax/Handlebars.pm
index 3f63f68..0cfc1e0 100644
--- a/lib/Text/Xslate/Syntax/Handlebars.pm
+++ b/lib/Text/Xslate/Syntax/Handlebars.pm
@@ -127,8 +127,17 @@ sub init_symbols {
$name->set_led($self->can('led_name'));
$name->lbp(1);
+ my $for = $self->symbol('(for)');
+ $for->arity('for');
+
+ my $iterator = $self->symbol('(iterator)');
+ $iterator->arity('iterator');
+
$self->infix('.', 256, $self->can('led_dot'));
$self->infix('/', 256, $self->can('led_dot'));
+
+ $self->symbol('#')->set_std($self->can('std_block'));
+ $self->prefix('/', 0)->is_block_end(1);
}
sub nud_name {
@@ -166,6 +175,73 @@ sub led_dot {
return $dot;
}
+sub std_block {
+ my $self = shift;
+ my ($symbol) = @_;
+
+ if ($self->token->arity ne 'name') {
+ $self->_unexpected("block name", $self->token);
+ }
+ my $name = $self->token->nud($self);
+ $self->advance;
+ $self->advance(';');
+
+ my $body = $self->statements;
+
+ $self->advance('/');
+
+ if ($self->token->arity ne 'name') {
+ $self->_unexpected("block name", $self->token);
+ }
+ if ($self->token->id ne $name->id) {
+ $self->_unexpected('/' . $name->id, $self->token);
+ }
+
+ $self->advance;
+
+ my $iterations = $self->make_ternary(
+ $self->call('(is_array)', $name->clone),
+ $name->clone,
+ $self->make_ternary(
+ $name->clone,
+ $self->call(
+ '(make_array)',
+ $self->symbol('(literal)')->clone(id => 1),
+ ),
+ $self->call(
+ '(make_array)',
+ $self->symbol('(literal)')->clone(id => 0),
+ ),
+ ),
+ );
+
+ my $loop_var = $self->symbol('(variable)')->clone(id => '(block)');
+
+ my $body_block = [
+ $symbol->clone(
+ arity => 'block',
+ first => [
+ $self->call(
+ '(new_vars_for)',
+ $self->symbol('(vars)')->clone(arity => 'vars'),
+ $name->clone,
+ $self->symbol('(iterator)')->clone(
+ id => '$~(block)',
+ first => $loop_var,
+ ),
+ ),
+ ],
+ second => $body,
+ ),
+ ];
+
+ return $self->symbol('(for)')->clone(
+ first => $iterations,
+ second => [$loop_var],
+ third => $body_block,
+ );
+}
+
sub make_field_lookup {
my $self = shift;
my ($var, $field, $dot) = @_;
@@ -183,6 +259,17 @@ sub make_field_lookup {
);
}
+sub make_ternary {
+ my $self = shift;
+ my ($if, $then, $else) = @_;
+ return $self->symbol('?:')->clone(
+ arity => 'if',
+ first => $if,
+ second => $then,
+ third => $else,
+ );
+}
+
if (0) {
require Devel::STDERR::Indent;
my @stack;
diff --git a/t/001-basic.t b/t/001-basic.t
index 3873076..28d54c1 100644
--- a/t/001-basic.t
+++ b/t/001-basic.t
@@ -1,9 +1,11 @@
+#!/usr/bin/env perl
use strict;
use warnings;
use Test::More;
-use Text::Xslate;
-my $tx = Text::Xslate->new(syntax => 'Handlebars');
+use Text::Handlebars;
+
+my $tx = Text::Handlebars->new;
is(
$tx->render_string(
diff --git a/t/002-expressions.t b/t/002-expressions.t
index afe66d8..730196f 100644
--- a/t/002-expressions.t
+++ b/t/002-expressions.t
@@ -1,9 +1,11 @@
+#!/usr/bin/env perl
use strict;
use warnings;
use Test::More;
-use Text::Xslate;
-my $tx = Text::Xslate->new(syntax => 'Handlebars');
+use Text::Handlebars;
+
+my $tx = Text::Handlebars->new;
is(
$tx->render_string(
diff --git a/t/003-safestring.t b/t/003-safestring.t
index 8e095ea..27d9ac1 100644
--- a/t/003-safestring.t
+++ b/t/003-safestring.t
@@ -1,9 +1,12 @@
+#!/usr/bin/env perl
use strict;
use warnings;
use Test::More;
+
+use Text::Handlebars;
use Text::Xslate 'mark_raw';
-my $tx = Text::Xslate->new(syntax => 'Handlebars');
+my $tx = Text::Handlebars->new;
is(
$tx->render_string(
diff --git a/t/blocks.t b/t/blocks.t
new file mode 100644
index 0000000..bbc22b9
--- /dev/null
+++ b/t/blocks.t
@@ -0,0 +1,57 @@
+#!/usr/bin/env perl
+use strict;
+use warnings;
+use Test::More;
+
+use Text::Handlebars;
+
+my $tx = Text::Handlebars->new;
+
+is(
+ $tx->render_string(
+ 'This is {{#shown}}shown{{/shown}}',
+ { shown => 1 },
+ ),
+ 'This is shown',
+);
+
+is(
+ $tx->render_string(
+ 'This is {{#shown}}shown{{/shown}}',
+ { shown => 0 },
+ ),
+ 'This is ',
+);
+
+is(
+ $tx->render_string(
+ 'This is {{#shown}}shown{{/shown}}',
+ { shown => [({}) x 3] },
+ ),
+ 'This is shownshownshown',
+);
+
+is(
+ $tx->render_string(
+ 'This is {{#shown}}{{content}}{{/shown}}',
+ { shown => { content => 'SHOWN' } },
+ ),
+ 'This is SHOWN',
+);
+
+is(
+ $tx->render_string(
+ 'This is {{#shown}}{{content}}{{/shown}}',
+ {
+ shown => [
+ { content => '3' },
+ { content => '2' },
+ { content => '1' },
+ { content => 'Shown' },
+ ],
+ },
+ ),
+ 'This is 321Shown',
+);
+
+done_testing;